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); } } }
// This essentially wraps SSL* SSL_new() internal static SafeSslHandle AllocateSslHandle(SafeFreeSslCredentials credential, SslAuthenticationOptions sslAuthenticationOptions) { SafeSslHandle? sslHandle = null; SafeSslContextHandle?sslCtxHandle = null; SafeSslContextHandle?newCtxHandle = null; SslProtocols protocols = CalculateEffectiveProtocols(sslAuthenticationOptions); bool cacheSslContext = !DisableTlsResume && sslAuthenticationOptions.EncryptionPolicy == EncryptionPolicy.RequireEncryption && sslAuthenticationOptions.CertificateContext != null && sslAuthenticationOptions.CertificateContext.SslContexts != null && sslAuthenticationOptions.CipherSuitesPolicy == null && (!sslAuthenticationOptions.IsServer || (sslAuthenticationOptions.ApplicationProtocols != null && sslAuthenticationOptions.ApplicationProtocols.Count != 0)); if (cacheSslContext) { sslAuthenticationOptions.CertificateContext !.SslContexts !.TryGetValue(protocols, out sslCtxHandle); } if (sslCtxHandle == null) { // We did not get SslContext from cache sslCtxHandle = newCtxHandle = AllocateSslContext(credential, sslAuthenticationOptions, protocols); if (cacheSslContext && sslAuthenticationOptions.CertificateContext !.SslContexts !.TryAdd(protocols, newCtxHandle)) { newCtxHandle = null; } } GCHandle alpnHandle = default; try { sslHandle = SafeSslHandle.Create(sslCtxHandle, sslAuthenticationOptions.IsServer); Debug.Assert(sslHandle != null, "Expected non-null return value from SafeSslHandle.Create"); if (sslHandle.IsInvalid) { sslHandle.Dispose(); throw CreateSslException(SR.net_allocate_ssl_context_failed); } if (sslAuthenticationOptions.ApplicationProtocols != null && sslAuthenticationOptions.ApplicationProtocols.Count != 0) { if (sslAuthenticationOptions.IsServer) { alpnHandle = GCHandle.Alloc(sslAuthenticationOptions.ApplicationProtocols); Interop.Ssl.SslSetData(sslHandle, GCHandle.ToIntPtr(alpnHandle)); sslHandle.AlpnHandle = alpnHandle; } else { if (Interop.Ssl.SslSetAlpnProtos(sslHandle, sslAuthenticationOptions.ApplicationProtocols) != 0) { throw CreateSslException(SR.net_alpn_config_failed); } } } if (!sslAuthenticationOptions.IsServer) { // The IdnMapping converts unicode input into the IDNA punycode sequence. string punyCode = string.IsNullOrEmpty(sslAuthenticationOptions.TargetHost) ? string.Empty : s_idnMapping.GetAscii(sslAuthenticationOptions.TargetHost !); // Similar to windows behavior, set SNI on openssl by default for client context, ignore errors. if (!Ssl.SslSetTlsExtHostName(sslHandle, punyCode)) { Crypto.ErrClearError(); } } if (sslAuthenticationOptions.IsServer && sslAuthenticationOptions.RemoteCertRequired) { Ssl.SslSetVerifyPeer(sslHandle); } } catch { if (alpnHandle.IsAllocated) { alpnHandle.Free(); } throw; } finally { newCtxHandle?.Dispose(); } return(sslHandle); }
// 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); }
// This essentially wraps SSL* SSL_new() internal static SafeSslHandle AllocateSslHandle(SafeFreeSslCredentials credential, SslAuthenticationOptions sslAuthenticationOptions) { SafeSslHandle? sslHandle = null; SafeSslContextHandle?sslCtxHandle = null; SafeSslContextHandle?newCtxHandle = null; SslProtocols protocols = CalculateEffectiveProtocols(sslAuthenticationOptions); bool hasAlpn = sslAuthenticationOptions.ApplicationProtocols != null && sslAuthenticationOptions.ApplicationProtocols.Count != 0; bool cacheSslContext = !DisableTlsResume && sslAuthenticationOptions.EncryptionPolicy == EncryptionPolicy.RequireEncryption && sslAuthenticationOptions.CipherSuitesPolicy == null; if (cacheSslContext) { if (sslAuthenticationOptions.IsClient) { // We don't support client resume on old OpenSSL versions. // We don't want to try on empty TargetName since that is our key. // And we don't want to mess up with client authentication. It may be possible // but it seems safe to get full new session. if (!Interop.Ssl.Capabilities.Tls13Supported || string.IsNullOrEmpty(sslAuthenticationOptions.TargetHost) || sslAuthenticationOptions.CertificateContext != null || sslAuthenticationOptions.CertSelectionDelegate != null) { cacheSslContext = false; } } else { // Server should always have certificate Debug.Assert(sslAuthenticationOptions.CertificateContext != null); if (sslAuthenticationOptions.CertificateContext == null || sslAuthenticationOptions.CertificateContext.SslContexts == null) { cacheSslContext = false; } } } if (cacheSslContext) { if (sslAuthenticationOptions.IsServer) { sslAuthenticationOptions.CertificateContext !.SslContexts !.TryGetValue(protocols | (hasAlpn ? FakeAlpnSslProtocol : SslProtocols.None), out sslCtxHandle); } else { s_clientSslContexts.TryGetValue(protocols, out sslCtxHandle); } } if (sslCtxHandle == null) { // We did not get SslContext from cache sslCtxHandle = newCtxHandle = AllocateSslContext(credential, sslAuthenticationOptions, protocols, cacheSslContext); if (cacheSslContext) { bool added = sslAuthenticationOptions.IsServer ? sslAuthenticationOptions.CertificateContext !.SslContexts !.TryAdd(protocols | (SslProtocols)(hasAlpn ? 1 : 0), newCtxHandle) : s_clientSslContexts.TryAdd(protocols, newCtxHandle); if (added) { newCtxHandle = null; } } } GCHandle alpnHandle = default; try { sslHandle = SafeSslHandle.Create(sslCtxHandle, sslAuthenticationOptions.IsServer); Debug.Assert(sslHandle != null, "Expected non-null return value from SafeSslHandle.Create"); if (sslHandle.IsInvalid) { sslHandle.Dispose(); throw CreateSslException(SR.net_allocate_ssl_context_failed); } if (sslAuthenticationOptions.ApplicationProtocols != null && sslAuthenticationOptions.ApplicationProtocols.Count != 0) { if (sslAuthenticationOptions.IsServer) { Debug.Assert(Interop.Ssl.SslGetData(sslHandle) == IntPtr.Zero); alpnHandle = GCHandle.Alloc(sslAuthenticationOptions.ApplicationProtocols); Interop.Ssl.SslSetData(sslHandle, GCHandle.ToIntPtr(alpnHandle)); sslHandle.AlpnHandle = alpnHandle; } else { if (Interop.Ssl.SslSetAlpnProtos(sslHandle, sslAuthenticationOptions.ApplicationProtocols) != 0) { throw CreateSslException(SR.net_alpn_config_failed); } } } if (sslAuthenticationOptions.IsClient) { // The IdnMapping converts unicode input into the IDNA punycode sequence. string punyCode = string.IsNullOrEmpty(sslAuthenticationOptions.TargetHost) ? string.Empty : s_idnMapping.GetAscii(sslAuthenticationOptions.TargetHost !); // Similar to windows behavior, set SNI on openssl by default for client context, ignore errors. if (!Ssl.SslSetTlsExtHostName(sslHandle, punyCode)) { Crypto.ErrClearError(); } if (cacheSslContext && !string.IsNullOrEmpty(punyCode)) { sslCtxHandle.TrySetSession(sslHandle, punyCode); } // relevant to TLS 1.3 only: if user supplied a client cert or cert callback, // advertise that we are willing to send the certificate post-handshake. if (sslAuthenticationOptions.ClientCertificates?.Count > 0 || sslAuthenticationOptions.CertSelectionDelegate != null) { Ssl.SslSetPostHandshakeAuth(sslHandle, 1); } // Set client cert callback, this will interrupt the handshake with SecurityStatusPalErrorCode.CredentialsNeeded // if server actually requests a certificate. Ssl.SslSetClientCertCallback(sslHandle, 1); } else // sslAuthenticationOptions.IsServer { if (sslAuthenticationOptions.RemoteCertRequired) { Ssl.SslSetVerifyPeer(sslHandle); } if (sslAuthenticationOptions.CertificateContext != null) { if (sslAuthenticationOptions.CertificateContext.Trust?._sendTrustInHandshake == true) { SslCertificateTrust trust = sslAuthenticationOptions.CertificateContext !.Trust !; X509Certificate2Collection certList = (trust._trustList ?? trust._store !.Certificates); Debug.Assert(certList != null, "certList != null"); Span <IntPtr> handles = certList.Count <= 256 ? stackalloc IntPtr[256] : new IntPtr[certList.Count]; for (int i = 0; i < certList.Count; i++) { handles[i] = certList[i].Handle; } if (!Ssl.SslAddClientCAs(sslHandle, handles.Slice(0, certList.Count))) { // The method can fail only when the number of cert names exceeds the maximum capacity // supported by STACK_OF(X509_NAME) structure, which should not happen under normal // operation. Debug.Fail("Failed to add issuer to trusted CA list."); } } byte[]? ocspResponse = sslAuthenticationOptions.CertificateContext.GetOcspResponseNoWaiting(); if (ocspResponse != null) { Ssl.SslStapleOcsp(sslHandle, ocspResponse); } } } } catch { if (alpnHandle.IsAllocated) { alpnHandle.Free(); } throw; } finally { newCtxHandle?.Dispose(); } return(sslHandle); }
// This essentially wraps SSL* SSL_new() internal static SafeSslHandle AllocateSslHandle(SafeFreeSslCredentials credential, SslAuthenticationOptions sslAuthenticationOptions) { SafeSslHandle? sslHandle = null; SafeSslContextHandle?sslCtxHandle = null; SafeSslContextHandle?newCtxHandle = null; SslProtocols protocols = CalculateEffectiveProtocols(sslAuthenticationOptions); bool hasAlpn = sslAuthenticationOptions.ApplicationProtocols != null && sslAuthenticationOptions.ApplicationProtocols.Count != 0; bool cacheSslContext = !DisableTlsResume && sslAuthenticationOptions.EncryptionPolicy == EncryptionPolicy.RequireEncryption && sslAuthenticationOptions.IsServer && sslAuthenticationOptions.CertificateContext != null && sslAuthenticationOptions.CertificateContext.SslContexts != null && sslAuthenticationOptions.CipherSuitesPolicy == null; if (cacheSslContext) { sslAuthenticationOptions.CertificateContext !.SslContexts !.TryGetValue(protocols | (SslProtocols)(hasAlpn ? 1 : 0), out sslCtxHandle); } if (sslCtxHandle == null) { // We did not get SslContext from cache sslCtxHandle = newCtxHandle = AllocateSslContext(credential, sslAuthenticationOptions, protocols, cacheSslContext); if (cacheSslContext && sslAuthenticationOptions.CertificateContext !.SslContexts !.TryAdd(protocols | (SslProtocols)(hasAlpn ? 1 : 0), newCtxHandle)) { newCtxHandle = null; } } GCHandle alpnHandle = default; try { sslHandle = SafeSslHandle.Create(sslCtxHandle, sslAuthenticationOptions.IsServer); Debug.Assert(sslHandle != null, "Expected non-null return value from SafeSslHandle.Create"); if (sslHandle.IsInvalid) { sslHandle.Dispose(); throw CreateSslException(SR.net_allocate_ssl_context_failed); } if (sslAuthenticationOptions.ApplicationProtocols != null && sslAuthenticationOptions.ApplicationProtocols.Count != 0) { if (sslAuthenticationOptions.IsServer) { alpnHandle = GCHandle.Alloc(sslAuthenticationOptions.ApplicationProtocols); Interop.Ssl.SslSetData(sslHandle, GCHandle.ToIntPtr(alpnHandle)); sslHandle.AlpnHandle = alpnHandle; } else { if (Interop.Ssl.SslSetAlpnProtos(sslHandle, sslAuthenticationOptions.ApplicationProtocols) != 0) { throw CreateSslException(SR.net_alpn_config_failed); } } } if (!sslAuthenticationOptions.IsServer) { // The IdnMapping converts unicode input into the IDNA punycode sequence. string punyCode = string.IsNullOrEmpty(sslAuthenticationOptions.TargetHost) ? string.Empty : s_idnMapping.GetAscii(sslAuthenticationOptions.TargetHost !); // Similar to windows behavior, set SNI on openssl by default for client context, ignore errors. if (!Ssl.SslSetTlsExtHostName(sslHandle, punyCode)) { Crypto.ErrClearError(); } // relevant to TLS 1.3 only: if user supplied a client cert or cert callback, // advertise that we are willing to send the certificate post-handshake. if (sslAuthenticationOptions.ClientCertificates?.Count > 0 || sslAuthenticationOptions.CertSelectionDelegate != null) { Ssl.SslSetPostHandshakeAuth(sslHandle, 1); } // Set client cert callback, this will interrupt the handshake with SecurityStatusPalErrorCode.CredentialsNeeded // if server actually requests a certificate. Ssl.SslSetClientCertCallback(sslHandle, 1); } if (sslAuthenticationOptions.IsServer && sslAuthenticationOptions.RemoteCertRequired) { Ssl.SslSetVerifyPeer(sslHandle); } } catch { if (alpnHandle.IsAllocated) { alpnHandle.Free(); } throw; } finally { newCtxHandle?.Dispose(); } return(sslHandle); }