1 //-----------------------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 //-----------------------------------------------------------------------------
5 namespace System.ServiceModel.Security
7 using System.ComponentModel;
8 using System.Diagnostics;
9 using System.IdentityModel;
10 using System.IdentityModel.Selectors;
11 using System.IdentityModel.Tokens;
12 using System.Runtime.InteropServices;
13 using System.Security;
14 using System.Security.Authentication.ExtendedProtection;
15 using System.Security.Cryptography;
16 using System.Security.Cryptography.X509Certificates;
17 using System.Security.Principal;
18 using System.Threading;
20 using DiagnosticUtility = System.ServiceModel.DiagnosticUtility;
21 using SR = System.ServiceModel.SR;
23 sealed class TlsSspiNegotiation : ISspiNegotiation
25 static SspiContextFlags ClientStandardFlags;
26 static SspiContextFlags ServerStandardFlags;
27 static SspiContextFlags StandardFlags;
29 SspiContextFlags attributes;
30 X509Certificate2 clientCertificate;
31 bool clientCertRequired;
32 SslConnectionInfo connectionInfo;
33 SafeFreeCredentials credentialsHandle;
38 SchProtocols protocolFlags;
39 X509Certificate2 remoteCertificate;
40 SafeDeleteContext securityContext;
42 //also used as a static lock object
43 const string SecurityPackage = "Microsoft Unified Security Protocol Provider";
45 X509Certificate2 serverCertificate;
46 StreamSizes streamSizes;
47 Object syncObject = new Object();
48 bool wasClientCertificateSent;
49 X509Certificate2Collection remoteCertificateChain;
50 string incomingValueTypeUri;
54 public TlsSspiNegotiation(
56 SchProtocols protocolFlags,
57 X509Certificate2 clientCertificate) :
58 this(destination, false, protocolFlags, null, clientCertificate, false)
64 public TlsSspiNegotiation(
65 SchProtocols protocolFlags,
66 X509Certificate2 serverCertificate,
67 bool clientCertRequired) :
68 this(null, true, protocolFlags, serverCertificate, null, clientCertRequired)
71 static TlsSspiNegotiation()
73 StandardFlags = SspiContextFlags.ReplayDetect | SspiContextFlags.Confidentiality | SspiContextFlags.AllocateMemory;
74 ServerStandardFlags = StandardFlags | SspiContextFlags.AcceptExtendedError | SspiContextFlags.AcceptStream;
75 ClientStandardFlags = StandardFlags | SspiContextFlags.InitManualCredValidation | SspiContextFlags.InitStream;
78 private TlsSspiNegotiation(
81 SchProtocols protocolFlags,
82 X509Certificate2 serverCertificate,
83 X509Certificate2 clientCertificate,
84 bool clientCertRequired)
86 SspiWrapper.GetVerifyPackageInfo(SecurityPackage);
87 this.destination = destination;
88 this.isServer = isServer;
89 this.protocolFlags = protocolFlags;
90 this.serverCertificate = serverCertificate;
91 this.clientCertificate = clientCertificate;
92 this.clientCertRequired = clientCertRequired;
93 this.securityContext = null;
96 ValidateServerCertificate();
100 ValidateClientCertificate();
104 // This retry is to address intermittent failure when accessing private key (MB56153)
107 AcquireServerCredentials();
109 catch (Win32Exception ex)
111 if (ex.NativeErrorCode != (int)SecurityStatus.UnknownCredential)
116 DiagnosticUtility.TraceHandledException(ex, TraceEventType.Information);
120 AcquireServerCredentials();
125 // delay client credentials presenting till they are asked for
126 AcquireDummyCredentials();
131 /// Local cert of client side
133 public X509Certificate2 ClientCertificate
138 return this.clientCertificate;
142 public bool ClientCertRequired
147 return this.clientCertRequired;
151 public string Destination
156 return this.destination;
160 public DateTime ExpirationTimeUtc
165 return SecurityUtils.MaxUtcDateTime;
169 public bool IsCompleted
174 return this.isCompleted;
178 public bool IsMutualAuthFlag
183 return (this.attributes & SspiContextFlags.MutualAuth) != 0;
187 public bool IsValidContext
191 return (this.securityContext != null && this.securityContext.IsInvalid == false);
195 public string KeyEncryptionAlgorithm
199 return SecurityAlgorithms.TlsSspiKeyWrap;
204 /// The cert of the remote party
206 public X509Certificate2 RemoteCertificate
213 // PreSharp Bug: Property get methods should not throw exceptions.
214 #pragma warning suppress 56503
215 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new Win32Exception((int)SecurityStatus.InvalidHandle));
217 if (this.remoteCertificate == null)
219 ExtractRemoteCertificate();
221 return this.remoteCertificate;
225 public X509Certificate2Collection RemoteCertificateChain
232 // PreSharp Bug: Property get methods should not throw exceptions.
233 #pragma warning suppress 56503
234 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new Win32Exception((int)SecurityStatus.InvalidHandle));
236 if (this.remoteCertificateChain == null)
238 ExtractRemoteCertificate();
240 return this.remoteCertificateChain;
245 /// Local cert of server side
247 public X509Certificate2 ServerCertificate
252 return this.serverCertificate;
256 public bool WasClientCertificateSent
261 return this.wasClientCertificateSent;
265 internal SslConnectionInfo ConnectionInfo
272 // PreSharp Bug: Property get methods should not throw exceptions.
273 #pragma warning suppress 56503
274 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new Win32Exception((int)SecurityStatus.InvalidHandle));
276 if (this.connectionInfo == null)
278 SslConnectionInfo tmpInfo = SspiWrapper.QueryContextAttributes(
279 this.securityContext,
280 ContextAttribute.ConnectionInfo
281 ) as SslConnectionInfo;
284 this.connectionInfo = tmpInfo;
288 return this.connectionInfo;
292 internal StreamSizes StreamSizes
297 if (this.streamSizes == null)
299 StreamSizes tmpSizes = (StreamSizes)SspiWrapper.QueryContextAttributes(this.securityContext, ContextAttribute.StreamSizes);
300 if (this.IsCompleted)
302 this.streamSizes = tmpSizes;
306 return this.streamSizes;
310 // This is for CDF1229 workaround to be able to echo incoming and outgoing ValueType
311 internal string IncomingValueTypeUri
313 get { return this.incomingValueTypeUri; }
314 set { this.incomingValueTypeUri = value; }
317 public string GetRemoteIdentityName()
319 if (!this.IsValidContext)
323 X509Certificate2 cert = this.RemoteCertificate;
328 return SecurityUtils.GetCertificateId(cert);
331 public byte[] Decrypt(byte[] encryptedContent)
334 byte[] dataBuffer = DiagnosticUtility.Utility.AllocateByteArray(encryptedContent.Length);
336 Buffer.BlockCopy(encryptedContent, 0, dataBuffer, 0, encryptedContent.Length);
338 int decryptedLen = 0;
340 this.DecryptInPlace(dataBuffer, out dataStartOffset, out decryptedLen);
341 byte[] outputBuffer = DiagnosticUtility.Utility.AllocateByteArray(decryptedLen);
343 Buffer.BlockCopy(dataBuffer, dataStartOffset, outputBuffer, 0, decryptedLen);
347 public void Dispose()
350 GC.SuppressFinalize(this);
353 public byte[] Encrypt(byte[] input)
356 byte[] buffer = DiagnosticUtility.Utility.AllocateByteArray(checked(input.Length + StreamSizes.header + StreamSizes.trailer));
358 Buffer.BlockCopy(input, 0, buffer, StreamSizes.header, input.Length);
360 int encryptedSize = 0;
362 this.EncryptInPlace(buffer, 0, input.Length, out encryptedSize);
363 if (encryptedSize == buffer.Length)
369 byte[] outputBuffer = DiagnosticUtility.Utility.AllocateByteArray(encryptedSize);
370 Buffer.BlockCopy(buffer, 0, outputBuffer, 0, encryptedSize);
375 public byte[] GetOutgoingBlob(byte[] incomingBlob, ChannelBinding channelbinding, ExtendedProtectionPolicy protectionPolicy)
378 SecurityBuffer incomingSecurity = null;
379 if (incomingBlob != null)
381 incomingSecurity = new SecurityBuffer(incomingBlob, BufferType.Token);
384 SecurityBuffer outgoingSecurity = new SecurityBuffer(null, BufferType.Token);
385 this.remoteCertificate = null;
387 if (this.isServer == true)
389 statusCode = SspiWrapper.AcceptSecurityContext(
390 this.credentialsHandle,
391 ref this.securityContext,
392 ServerStandardFlags | (this.clientCertRequired ? SspiContextFlags.MutualAuth : SspiContextFlags.Zero),
402 statusCode = SspiWrapper.InitializeSecurityContext(
403 this.credentialsHandle,
404 ref this.securityContext,
414 if ((statusCode & unchecked((int)0x80000000)) != 0)
417 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new Win32Exception(statusCode));
420 if (statusCode == (int)SecurityStatus.OK)
423 // ensure that the key negotiated is strong enough
424 if (SecurityUtils.ShouldValidateSslCipherStrength())
426 SslConnectionInfo connectionInfo = (SslConnectionInfo)SspiWrapper.QueryContextAttributes(this.securityContext, ContextAttribute.ConnectionInfo);
427 if (connectionInfo == null)
429 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityNegotiationException(SR.GetString(SR.CannotObtainSslConnectionInfo)));
431 SecurityUtils.ValidateSslCipherStrength(connectionInfo.DataKeySize);
433 this.isCompleted = true;
435 else if (statusCode == (int)SecurityStatus.CredentialsNeeded)
437 // the server requires the client to supply creds
438 // Currently we dont attempt to find the client cert to choose at runtime
439 // so just re-call the function
440 AcquireClientCredentials();
441 if (this.ClientCertificate != null)
443 this.wasClientCertificateSent = true;
445 return this.GetOutgoingBlob(incomingBlob, channelbinding, protectionPolicy);
447 else if (statusCode != (int)SecurityStatus.ContinueNeeded)
450 if (statusCode == (int)SecurityStatus.InternalError)
452 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new Win32Exception(statusCode, SR.GetString(SR.LsaAuthorityNotContacted)));
456 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new Win32Exception(statusCode));
459 return outgoingSecurity.token;
463 /// The decrypted data will start header bytes from the start of
464 /// encryptedContent array.
466 internal unsafe void DecryptInPlace(byte[] encryptedContent, out int dataStartOffset, out int dataLen)
469 dataStartOffset = StreamSizes.header;
472 byte[] emptyBuffer1 = new byte[0];
473 byte[] emptyBuffer2 = new byte[0];
474 byte[] emptyBuffer3 = new byte[0];
476 SecurityBuffer[] securityBuffer = new SecurityBuffer[4];
477 securityBuffer[0] = new SecurityBuffer(encryptedContent, 0, encryptedContent.Length, BufferType.Data);
478 securityBuffer[1] = new SecurityBuffer(emptyBuffer1, BufferType.Empty);
479 securityBuffer[2] = new SecurityBuffer(emptyBuffer2, BufferType.Empty);
480 securityBuffer[3] = new SecurityBuffer(emptyBuffer3, BufferType.Empty);
482 int errorCode = SspiWrapper.DecryptMessage(this.securityContext, securityBuffer, 0, false);
485 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new Win32Exception(errorCode));
488 for (int i = 0; i < securityBuffer.Length; ++i)
490 if (securityBuffer[i].type == BufferType.Data)
492 dataLen = securityBuffer[i].size;
501 /// Assumes that the data to encrypt is "header" bytes ahead of bufferStartOffset
503 internal unsafe void EncryptInPlace(byte[] buffer, int bufferStartOffset, int dataLen, out int encryptedDataLen)
506 encryptedDataLen = 0;
507 if (bufferStartOffset + dataLen + StreamSizes.header + StreamSizes.trailer > buffer.Length)
512 byte[] emptyBuffer = new byte[0];
513 int trailerOffset = bufferStartOffset + StreamSizes.header + dataLen;
515 SecurityBuffer[] securityBuffer = new SecurityBuffer[4];
516 securityBuffer[0] = new SecurityBuffer(buffer, bufferStartOffset, StreamSizes.header, BufferType.Header);
517 securityBuffer[1] = new SecurityBuffer(buffer, bufferStartOffset + StreamSizes.header, dataLen, BufferType.Data);
518 securityBuffer[2] = new SecurityBuffer(buffer, trailerOffset, StreamSizes.trailer, BufferType.Trailer);
519 securityBuffer[3] = new SecurityBuffer(emptyBuffer, BufferType.Empty);
521 int errorCode = SspiWrapper.EncryptMessage(this.securityContext, securityBuffer, 0);
524 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new Win32Exception(errorCode));
528 for (int i = 0; i < securityBuffer.Length; ++i)
530 if (securityBuffer[i].type == BufferType.Trailer)
532 trailerSize = securityBuffer[i].size;
533 encryptedDataLen = StreamSizes.header + dataLen + trailerSize;
541 static void ValidatePrivateKey(X509Certificate2 certificate)
543 bool hasPrivateKey = false;
546 hasPrivateKey = certificate != null && certificate.PrivateKey != null;
548 catch (SecurityException e)
550 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentException(SR.GetString(SR.SslCertMayNotDoKeyExchange, certificate.SubjectName.Name), e));
552 catch (CryptographicException e)
554 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentException(SR.GetString(SR.SslCertMayNotDoKeyExchange, certificate.SubjectName.Name), e));
558 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentException(SR.GetString(SR.SslCertMustHavePrivateKey, certificate.SubjectName.Name)));
562 void ValidateServerCertificate()
564 if (this.serverCertificate == null)
566 throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("serverCertificate");
569 ValidatePrivateKey(this.serverCertificate);
572 void ValidateClientCertificate()
574 if (this.clientCertificate != null)
576 ValidatePrivateKey(this.clientCertificate);
580 private void AcquireClientCredentials()
582 SecureCredential secureCredential = new SecureCredential(SecureCredential.CurrentVersion, this.ClientCertificate, SecureCredential.Flags.ValidateManual | SecureCredential.Flags.NoDefaultCred, this.protocolFlags);
583 this.credentialsHandle = SspiWrapper.AcquireCredentialsHandle(
585 CredentialUse.Outbound,
590 private void AcquireDummyCredentials()
592 SecureCredential secureCredential = new SecureCredential(SecureCredential.CurrentVersion, null, SecureCredential.Flags.ValidateManual | SecureCredential.Flags.NoDefaultCred, this.protocolFlags);
593 this.credentialsHandle = SspiWrapper.AcquireCredentialsHandle(SecurityPackage, CredentialUse.Outbound, secureCredential);
596 private void AcquireServerCredentials()
598 SecureCredential secureCredential = new SecureCredential(SecureCredential.CurrentVersion, this.serverCertificate, SecureCredential.Flags.Zero, this.protocolFlags);
599 this.credentialsHandle = SspiWrapper.AcquireCredentialsHandle(
601 CredentialUse.Inbound,
606 private void Dispose(bool disposing)
608 lock (this.syncObject)
610 if (this.disposed == false)
612 this.disposed = true;
615 if (this.securityContext != null)
617 this.securityContext.Close();
618 this.securityContext = null;
620 if (this.credentialsHandle != null)
622 this.credentialsHandle.Close();
623 this.credentialsHandle = null;
627 // set to null any references that aren't finalizable
628 this.connectionInfo = null;
629 this.destination = null;
630 this.streamSizes = null;
635 private SafeFreeCertContext ExtractCertificateHandle(ContextAttribute contextAttribute)
637 SafeFreeCertContext result = SspiWrapper.QueryContextAttributes(this.securityContext, contextAttribute) as SafeFreeCertContext;
641 //This method extracts a remote certificate and chain upon request.
642 private void ExtractRemoteCertificate()
644 SafeFreeCertContext remoteContext = null;
645 this.remoteCertificate = null;
646 this.remoteCertificateChain = null;
649 remoteContext = ExtractCertificateHandle(ContextAttribute.RemoteCertificate);
650 if (remoteContext != null && !remoteContext.IsInvalid)
652 this.remoteCertificateChain = UnmanagedCertificateContext.GetStore(remoteContext);
653 this.remoteCertificate = new X509Certificate2(remoteContext.DangerousGetHandle());
658 if (remoteContext != null)
660 remoteContext.Close();
665 internal bool TryGetContextIdentity(out WindowsIdentity mappedIdentity)
669 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new Win32Exception((int)SecurityStatus.InvalidHandle));
672 SafeCloseHandle token = null;
675 SecurityStatus status = (SecurityStatus)SspiWrapper.QuerySecurityContextToken(this.securityContext, out token);
676 if (status != SecurityStatus.OK)
678 mappedIdentity = null;
681 mappedIdentity = new WindowsIdentity(token.DangerousGetHandle(), SecurityUtils.AuthTypeCertMap);
695 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new MessageSecurityException(SR.GetString(SR.BadData)));
698 void ThrowIfDisposed()
700 lock (this.syncObject)
704 throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ObjectDisposedException(null));
709 unsafe static class UnmanagedCertificateContext
712 [StructLayout(LayoutKind.Sequential)]
713 private struct _CERT_CONTEXT
715 internal Int32 dwCertEncodingType;
716 internal IntPtr pbCertEncoded;
717 internal Int32 cbCertEncoded;
718 internal IntPtr pCertInfo;
719 internal IntPtr hCertStore;
722 internal static X509Certificate2Collection GetStore(SafeFreeCertContext certContext)
724 X509Certificate2Collection result = new X509Certificate2Collection();
726 if (certContext.IsInvalid)
729 _CERT_CONTEXT context = (_CERT_CONTEXT)Marshal.PtrToStructure(certContext.DangerousGetHandle(), typeof(_CERT_CONTEXT));
731 if (context.hCertStore != IntPtr.Zero)
733 X509Store store = null;
736 store = new X509Store(context.hCertStore);
737 result = store.Certificates;