// 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.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); } else // sslAuthenticationOptions.IsServer { if (sslAuthenticationOptions.RemoteCertRequired) { Ssl.SslSetVerifyPeer(sslHandle); } 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."); } } } } catch { if (alpnHandle.IsAllocated) { alpnHandle.Free(); } throw; } finally { newCtxHandle?.Dispose(); } return(sslHandle); }
// This essentially wraps SSL_CTX* aka SSL_CTX_new + setting internal static unsafe SafeSslContextHandle AllocateSslContext(SslAuthenticationOptions sslAuthenticationOptions, SslProtocols protocols, bool enableResume) { // 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)); } } ReadOnlySpan <byte> cipherList = CipherSuitesPolicyPal.GetOpenSslCipherList(sslAuthenticationOptions.CipherSuitesPolicy, protocols, sslAuthenticationOptions.EncryptionPolicy); Debug.Assert(cipherList.IsEmpty || cipherList[^ 1] == 0); byte[]? cipherSuites = CipherSuitesPolicyPal.GetOpenSslCipherSuites(sslAuthenticationOptions.CipherSuitesPolicy, protocols, sslAuthenticationOptions.EncryptionPolicy); Debug.Assert(cipherSuites == null || (cipherSuites.Length >= 1 && cipherSuites[cipherSuites.Length - 1] == 0)); 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 (enableResume) { if (sslAuthenticationOptions.IsServer) { Ssl.SslCtxSetCaching(sslCtx, 1, s_cacheSize, null, null); } else { int result = Ssl.SslCtxSetCaching(sslCtx, 1, s_cacheSize, &NewSessionCallback, &RemoveSessionCallback); Debug.Assert(result == 1); sslCtx.EnableSessionCache(); } } else { Ssl.SslCtxSetCaching(sslCtx, 0, -1, null, null); } if (sslAuthenticationOptions.IsServer && sslAuthenticationOptions.ApplicationProtocols != null && sslAuthenticationOptions.ApplicationProtocols.Count != 0) { Interop.Ssl.SslCtxSetAlpnSelectCb(sslCtx, &AlpnServerSelectCallback, IntPtr.Zero); } if (sslAuthenticationOptions.CertificateContext != null) { SetSslCertificate(sslCtx, sslAuthenticationOptions.CertificateContext.CertificateHandle, sslAuthenticationOptions.CertificateContext.KeyHandle); if (sslAuthenticationOptions.CertificateContext.IntermediateCertificates.Length > 0) { if (!Ssl.AddExtraChainCertificates(sslCtx, sslAuthenticationOptions.CertificateContext.IntermediateCertificates)) { throw CreateSslException(SR.net_ssl_use_cert_failed); } } if (sslAuthenticationOptions.CertificateContext.OcspStaplingAvailable) { Ssl.SslCtxSetDefaultOcspCallback(sslCtx); } } } 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.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(); } if (sslAuthenticationOptions.CertSelectionDelegate != null && sslAuthenticationOptions.CertificateContext == null) { // We don't have certificate but we have callback. We should wait for remote certificate and // possible trusted issuer list. Interop.Ssl.SslSetClientCertCallback(sslHandle, 1); } } if (sslAuthenticationOptions.IsServer && sslAuthenticationOptions.RemoteCertRequired) { Ssl.SslSetVerifyPeer(sslHandle); } } catch { if (alpnHandle.IsAllocated) { alpnHandle.Free(); } throw; } finally { newCtxHandle?.Dispose(); } return(sslHandle); }