public SafeFreeCertContext(SafeX509Handle certificate) : base(IntPtr.Zero, true) { // In certain scenarios (e.g. server querying for a client cert), the // input certificate may be invalid and this is OK if ((null != certificate) && !certificate.IsInvalid) { bool gotRef = false; certificate.DangerousAddRef(ref gotRef); Debug.Assert(gotRef, "Unexpected failure in AddRef of certificate"); _certificate = certificate; handle = _certificate.DangerousGetHandle(); } }
public SafeFreeSslCredentials(SslStreamCertificateContext?context, SslProtocols protocols, EncryptionPolicy policy, bool isServer) : base(IntPtr.Zero, true) { Debug.Assert( context == null || context.Certificate is X509Certificate2, "Only X509Certificate2 certificates are supported at this time"); X509Certificate2?cert = context?.Certificate; if (cert != null) { Debug.Assert(cert.HasPrivateKey, "cert.HasPrivateKey"); using (RSAOpenSsl? rsa = (RSAOpenSsl?)cert.GetRSAPrivateKey()) { if (rsa != null) { _certKeyHandle = rsa.DuplicateKeyHandle(); Interop.Crypto.CheckValidOpenSslHandle(_certKeyHandle); } } if (_certKeyHandle == null) { using (ECDsaOpenSsl? ecdsa = (ECDsaOpenSsl?)cert.GetECDsaPrivateKey()) { if (ecdsa != null) { _certKeyHandle = ecdsa.DuplicateKeyHandle(); Interop.Crypto.CheckValidOpenSslHandle(_certKeyHandle); } } } if (_certKeyHandle == null) { throw new NotSupportedException(SR.net_ssl_io_no_server_cert); } _certHandle = Interop.Crypto.X509UpRef(cert.Handle); Interop.Crypto.CheckValidOpenSslHandle(_certHandle); } _protocols = protocols; _policy = policy; _context = context; }
internal static void UpdateClientCertiticate(SafeSslHandle ssl, SslAuthenticationOptions sslAuthenticationOptions) { // Disable certificate selection callback. We either got certificate or we will try to proceed without it. Interop.Ssl.SslSetClientCertCallback(ssl, 0); if (sslAuthenticationOptions.CertificateContext == null) { return; } var credential = new SafeFreeSslCredentials(sslAuthenticationOptions.CertificateContext, sslAuthenticationOptions.EnabledSslProtocols, sslAuthenticationOptions.EncryptionPolicy, sslAuthenticationOptions.IsServer); SafeX509Handle? certHandle = credential.CertHandle; SafeEvpPKeyHandle?certKeyHandle = credential.CertKeyHandle; Debug.Assert(certHandle != null); Debug.Assert(certKeyHandle != null); int retVal = Ssl.SslUseCertificate(ssl, certHandle); if (1 != retVal) { throw CreateSslException(SR.net_ssl_use_cert_failed); } retVal = Ssl.SslUsePrivateKey(ssl, certKeyHandle); if (1 != retVal) { throw CreateSslException(SR.net_ssl_use_private_key_failed); } if (sslAuthenticationOptions.CertificateContext.IntermediateCertificates.Length > 0) { if (!Ssl.AddExtraChainCertificates(ssl, sslAuthenticationOptions.CertificateContext.IntermediateCertificates)) { throw CreateSslException(SR.net_ssl_use_cert_failed); } } }
internal static SafeSslHandle AllocateSslContext(SslProtocols protocols, SafeX509Handle?certHandle, SafeEvpPKeyHandle?certKeyHandle, EncryptionPolicy policy, SslAuthenticationOptions sslAuthenticationOptions) { SafeSslHandle?context = null; // Always use SSLv23_method, regardless of protocols. It supports negotiating to the highest // mutually supported version and can thus handle any of the set protocols, and we then use // SetProtocolOptions to ensure we only allow the ones requested. using (SafeSslContextHandle innerContext = Ssl.SslCtxCreate(Ssl.SslMethods.SSLv23_method)) { if (innerContext.IsInvalid) { throw CreateSslException(SR.net_allocate_ssl_context_failed); } if (!Interop.Ssl.Tls13Supported) { if (protocols != SslProtocols.None && CipherSuitesPolicyPal.WantsTls13(protocols)) { protocols = protocols & (~SslProtocols.Tls13); } } else if (CipherSuitesPolicyPal.WantsTls13(protocols) && CipherSuitesPolicyPal.ShouldOptOutOfTls13(sslAuthenticationOptions.CipherSuitesPolicy, policy)) { if (protocols == SslProtocols.None) { // we are using default settings but cipher suites policy says that TLS 1.3 // is not compatible with our settings (i.e. we requested no encryption or disabled // all TLS 1.3 cipher suites) protocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12; } else { // user explicitly asks for TLS 1.3 but their policy is not compatible with TLS 1.3 throw new SslException( SR.Format(SR.net_ssl_encryptionpolicy_notsupported, policy)); } } if (CipherSuitesPolicyPal.ShouldOptOutOfLowerThanTls13(sslAuthenticationOptions.CipherSuitesPolicy, policy)) { if (!CipherSuitesPolicyPal.WantsTls13(protocols)) { // We cannot provide neither TLS 1.3 or non TLS 1.3, user disabled all cipher suites throw new SslException( SR.Format(SR.net_ssl_encryptionpolicy_notsupported, policy)); } protocols = SslProtocols.Tls13; } // Configure allowed protocols. It's ok to use DangerousGetHandle here without AddRef/Release as we just // create the handle, it's rooted by the using, no one else has a reference to it, etc. Ssl.SetProtocolOptions(innerContext.DangerousGetHandle(), protocols); // Sets policy and security level if (!Ssl.SetEncryptionPolicy(innerContext, policy)) { throw new SslException( SR.Format(SR.net_ssl_encryptionpolicy_notsupported, policy)); } // The logic in SafeSslHandle.Disconnect is simple because we are doing a quiet // shutdown (we aren't negotiating for session close to enable later session // restoration). // // If you find yourself wanting to remove this line to enable bidirectional // close-notify, you'll probably need to rewrite SafeSslHandle.Disconnect(). // https://www.openssl.org/docs/manmaster/ssl/SSL_shutdown.html Ssl.SslCtxSetQuietShutdown(innerContext); byte[]? cipherList = CipherSuitesPolicyPal.GetOpenSslCipherList(sslAuthenticationOptions.CipherSuitesPolicy, protocols, policy); Debug.Assert(cipherList == null || (cipherList.Length >= 1 && cipherList[cipherList.Length - 1] == 0)); byte[]? cipherSuites = CipherSuitesPolicyPal.GetOpenSslCipherSuites(sslAuthenticationOptions.CipherSuitesPolicy, protocols, policy); Debug.Assert(cipherSuites == null || (cipherSuites.Length >= 1 && cipherSuites[cipherSuites.Length - 1] == 0)); unsafe { fixed(byte *cipherListStr = cipherList) fixed(byte *cipherSuitesStr = cipherSuites) { if (!Ssl.SetCiphers(innerContext, cipherListStr, cipherSuitesStr)) { Crypto.ErrClearError(); throw new PlatformNotSupportedException(SR.Format(SR.net_ssl_encryptionpolicy_notsupported, policy)); } } } bool hasCertificateAndKey = certHandle != null && !certHandle.IsInvalid && certKeyHandle != null && !certKeyHandle.IsInvalid; if (hasCertificateAndKey) { SetSslCertificate(innerContext, certHandle !, certKeyHandle !); } if (sslAuthenticationOptions.IsServer && sslAuthenticationOptions.RemoteCertRequired) { Ssl.SslCtxSetVerify(innerContext, s_verifyClientCertificate); } GCHandle alpnHandle = default; try { if (sslAuthenticationOptions.ApplicationProtocols != null) { if (sslAuthenticationOptions.IsServer) { alpnHandle = GCHandle.Alloc(sslAuthenticationOptions.ApplicationProtocols); Interop.Ssl.SslCtxSetAlpnSelectCb(innerContext, s_alpnServerCallback, GCHandle.ToIntPtr(alpnHandle)); } else { if (Interop.Ssl.SslCtxSetAlpnProtos(innerContext, sslAuthenticationOptions.ApplicationProtocols) != 0) { throw CreateSslException(SR.net_alpn_config_failed); } } } context = SafeSslHandle.Create(innerContext, sslAuthenticationOptions.IsServer); Debug.Assert(context != null, "Expected non-null return value from SafeSslHandle.Create"); if (context.IsInvalid) { context.Dispose(); throw CreateSslException(SR.net_allocate_ssl_context_failed); } if (!sslAuthenticationOptions.IsServer) { // The IdnMapping converts unicode input into the IDNA punycode sequence. string punyCode = s_idnMapping.GetAscii(sslAuthenticationOptions.TargetHost !); // Similar to windows behavior, set SNI on openssl by default for client context, ignore errors. if (!Ssl.SslSetTlsExtHostName(context, punyCode)) { Crypto.ErrClearError(); } } if (hasCertificateAndKey) { bool hasCertReference = false; try { certHandle !.DangerousAddRef(ref hasCertReference); using (X509Certificate2 cert = new X509Certificate2(certHandle.DangerousGetHandle())) { X509Chain?chain = null; try { chain = TLSCertificateExtensions.BuildNewChain(cert, includeClientApplicationPolicy: false); if (chain != null && !Ssl.AddExtraChainCertificates(context, chain)) { throw CreateSslException(SR.net_ssl_use_cert_failed); } } finally { if (chain != null) { int elementsCount = chain.ChainElements.Count; for (int i = 0; i < elementsCount; i++) { chain.ChainElements[i].Certificate !.Dispose(); } chain.Dispose(); } } } } finally { if (hasCertReference) { certHandle !.DangerousRelease(); } } } context.AlpnHandle = alpnHandle; } catch { if (alpnHandle.IsAllocated) { alpnHandle.Free(); } throw; } } return(context); }
// This essentially wraps SSL_CTX* aka SSL_CTX_new + setting internal static SafeSslContextHandle AllocateSslContext(SafeFreeSslCredentials credential, SslAuthenticationOptions sslAuthenticationOptions, SslProtocols protocols) { SafeX509Handle? certHandle = credential.CertHandle; SafeEvpPKeyHandle?certKeyHandle = credential.CertKeyHandle; // Always use SSLv23_method, regardless of protocols. It supports negotiating to the highest // mutually supported version and can thus handle any of the set protocols, and we then use // SetProtocolOptions to ensure we only allow the ones requested. SafeSslContextHandle sslCtx = Ssl.SslCtxCreate(Ssl.SslMethods.SSLv23_method); try { if (sslCtx.IsInvalid) { throw CreateSslException(SR.net_allocate_ssl_context_failed); } Ssl.SslCtxSetProtocolOptions(sslCtx, protocols); if (sslAuthenticationOptions.EncryptionPolicy != EncryptionPolicy.RequireEncryption) { // Sets policy and security level if (!Ssl.SetEncryptionPolicy(sslCtx, sslAuthenticationOptions.EncryptionPolicy)) { throw new SslException(SR.Format(SR.net_ssl_encryptionpolicy_notsupported, sslAuthenticationOptions.EncryptionPolicy)); } } byte[]? cipherList = CipherSuitesPolicyPal.GetOpenSslCipherList(sslAuthenticationOptions.CipherSuitesPolicy, protocols, sslAuthenticationOptions.EncryptionPolicy); Debug.Assert(cipherList == null || (cipherList.Length >= 1 && cipherList[cipherList.Length - 1] == 0)); byte[]? cipherSuites = CipherSuitesPolicyPal.GetOpenSslCipherSuites(sslAuthenticationOptions.CipherSuitesPolicy, protocols, sslAuthenticationOptions.EncryptionPolicy); Debug.Assert(cipherSuites == null || (cipherSuites.Length >= 1 && cipherSuites[cipherSuites.Length - 1] == 0)); unsafe { fixed(byte *cipherListStr = cipherList) fixed(byte *cipherSuitesStr = cipherSuites) { if (!Ssl.SslCtxSetCiphers(sslCtx, cipherListStr, cipherSuitesStr)) { Crypto.ErrClearError(); throw new PlatformNotSupportedException(SR.Format(SR.net_ssl_encryptionpolicy_notsupported, sslAuthenticationOptions.EncryptionPolicy)); } } } // The logic in SafeSslHandle.Disconnect is simple because we are doing a quiet // shutdown (we aren't negotiating for session close to enable later session // restoration). // // If you find yourself wanting to remove this line to enable bidirectional // close-notify, you'll probably need to rewrite SafeSslHandle.Disconnect(). // https://www.openssl.org/docs/manmaster/ssl/SSL_shutdown.html Ssl.SslCtxSetQuietShutdown(sslCtx); if (sslAuthenticationOptions.IsServer && sslAuthenticationOptions.ApplicationProtocols != null && sslAuthenticationOptions.ApplicationProtocols.Count != 0) { unsafe { Interop.Ssl.SslCtxSetAlpnSelectCb(sslCtx, &AlpnServerSelectCallback, IntPtr.Zero); } } bool hasCertificateAndKey = certHandle != null && !certHandle.IsInvalid && certKeyHandle != null && !certKeyHandle.IsInvalid; if (hasCertificateAndKey) { SetSslCertificate(sslCtx, certHandle !, certKeyHandle !); } if (sslAuthenticationOptions.CertificateContext != null && sslAuthenticationOptions.CertificateContext.IntermediateCertificates.Length > 0) { if (!Ssl.AddExtraChainCertificates(sslCtx, sslAuthenticationOptions.CertificateContext.IntermediateCertificates)) { throw CreateSslException(SR.net_ssl_use_cert_failed); } } } catch { sslCtx.Dispose(); throw; } return(sslCtx); }