public void SslStream_SendCertificateTrust_ThrowsOnUnsupportedPlatform() { (X509Certificate2 certificate, X509Certificate2Collection caCerts) = TestHelper.GenerateCertificates(nameof(SslStream_SendCertificateTrust_CertificateCollection)); using X509Store store = new X509Store("Root", StoreLocation.LocalMachine); Assert.Throws <PlatformNotSupportedException>(() => SslCertificateTrust.CreateForX509Collection(caCerts, sendTrustInHandshake: true)); Assert.Throws <PlatformNotSupportedException>(() => SslCertificateTrust.CreateForX509Store(store, sendTrustInHandshake: true)); }
public async Task SslStream_SendCertificateTrust_CertificateCollection() { (X509Certificate2 certificate, X509Certificate2Collection caCerts) = TestHelper.GenerateCertificates(nameof(SslStream_SendCertificateTrust_CertificateCollection)); SslCertificateTrust trust = SslCertificateTrust.CreateForX509Collection(caCerts, sendTrustInHandshake: true); string[] acceptableIssuers = await ConnectAndGatherAcceptableIssuers(trust); Assert.Equal(caCerts.Count, acceptableIssuers.Length); Assert.Equal(caCerts.Select(c => c.Subject), acceptableIssuers); }
public async Task SslStream_SendCertificateTrust_CertificateStore() { using X509Store store = new X509Store("Root", StoreLocation.LocalMachine); SslCertificateTrust trust = SslCertificateTrust.CreateForX509Store(store, sendTrustInHandshake: true); string[] acceptableIssuers = await ConnectAndGatherAcceptableIssuers(trust); // don't assert individual ellements, just that some issuers were sent // we use Root cert store which should always contain at least some certs Assert.NotEmpty(acceptableIssuers); }
private async Task <string[]> ConnectAndGatherAcceptableIssuers(SslCertificateTrust trust) { (SslStream client, SslStream server) = TestHelper.GetConnectedSslStreams(); using (client) using (server) using (X509Certificate2 serverCertificate = Configuration.Certificates.GetServerCertificate()) using (X509Certificate2 clientCertificate = Configuration.Certificates.GetClientCertificate()) { SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions { ServerCertificate = serverCertificate, ClientCertificateRequired = true, RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true, ServerCertificateContext = SslStreamCertificateContext.Create(serverCertificate, null, false, trust) }; string[] acceptableIssuers = Array.Empty <string>(); SslClientAuthenticationOptions clientOptions = new SslClientAuthenticationOptions { TargetHost = "localhost", // Force Tls 1.2 to avoid issues with certain OpenSSL versions and Tls 1.3 // https://github.com/openssl/openssl/issues/7384 EnabledSslProtocols = SslProtocols.Tls12, RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true, LocalCertificateSelectionCallback = (sender, targetHost, localCertificates, remoteCertificate, issuers) => { if (remoteCertificate == null) { // ignore the first call that is called before handshake return(null); } acceptableIssuers = issuers; return(clientCertificate); }, }; await TestConfiguration.WhenAllOrAnyFailedWithTimeout( client.AuthenticateAsClientAsync(clientOptions), server.AuthenticateAsServerAsync(serverOptions)); return(acceptableIssuers); } }
// This essentially wraps SSL* SSL_new() internal static SafeSslHandle AllocateSslHandle(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(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); bool ignored = false; sslCtxHandle.DangerousAddRef(ref ignored); sslHandle.SslContextHandle = sslCtxHandle; } // 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); }
public SafeDeleteSslContext(SafeFreeSslCredentials credential, SslAuthenticationOptions sslAuthenticationOptions) : base(credential) { Debug.Assert((null != credential) && !credential.IsInvalid, "Invalid credential used in SafeDeleteSslContext"); try { int osStatus; _sslContext = CreateSslContext(credential, sslAuthenticationOptions.IsServer); // Make sure the class instance is associated to the session and is provided // in the Read/Write callback connection parameter SslSetConnection(_sslContext); unsafe { osStatus = Interop.AppleCrypto.SslSetIoCallbacks( _sslContext, &ReadFromConnection, &WriteToConnection); } if (osStatus != 0) { throw Interop.AppleCrypto.CreateExceptionForOSStatus(osStatus); } if (sslAuthenticationOptions.CipherSuitesPolicy != null) { uint[] tlsCipherSuites = sslAuthenticationOptions.CipherSuitesPolicy.Pal.TlsCipherSuites; unsafe { fixed(uint *cipherSuites = tlsCipherSuites) { osStatus = Interop.AppleCrypto.SslSetEnabledCipherSuites( _sslContext, cipherSuites, tlsCipherSuites.Length); if (osStatus != 0) { throw Interop.AppleCrypto.CreateExceptionForOSStatus(osStatus); } } } } if (sslAuthenticationOptions.ApplicationProtocols != null && sslAuthenticationOptions.ApplicationProtocols.Count != 0) { // On OSX coretls supports only client side. For server, we will silently ignore the option. if (!sslAuthenticationOptions.IsServer) { Interop.AppleCrypto.SslCtxSetAlpnProtos(_sslContext, sslAuthenticationOptions.ApplicationProtocols); } } } catch (Exception ex) { Debug.Write("Exception Caught. - " + ex); Dispose(); throw; } if (!string.IsNullOrEmpty(sslAuthenticationOptions.TargetHost) && !sslAuthenticationOptions.IsServer) { Interop.AppleCrypto.SslSetTargetName(_sslContext, sslAuthenticationOptions.TargetHost); } if (sslAuthenticationOptions.CertificateContext == null && sslAuthenticationOptions.CertSelectionDelegate != null) { // certificate was not provided but there is user callback. We can break handshake if server asks for certificate // and we can try to get it based on remote certificate and trusted issuers. Interop.AppleCrypto.SslBreakOnCertRequested(_sslContext, true); } if (sslAuthenticationOptions.IsServer) { if (sslAuthenticationOptions.RemoteCertRequired) { Interop.AppleCrypto.SslSetAcceptClientCert(_sslContext); } 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; } Interop.AppleCrypto.SslSetCertificateAuthorities(_sslContext, handles.Slice(0, certList.Count), true); } } }
public void SslStream_SendCertificateTrust_CertificateCollection_ThrowsOnWindows() { (X509Certificate2 certificate, X509Certificate2Collection caCerts) = TestHelper.GenerateCertificates(nameof(SslStream_SendCertificateTrust_CertificateCollection)); Assert.Throws <PlatformNotSupportedException>(() => SslCertificateTrust.CreateForX509Collection(caCerts, sendTrustInHandshake: true)); }