Beispiel #1
0
        public HttpsConnectionMiddleware(ConnectionDelegate next, HttpsConnectionAdapterOptions options, ILoggerFactory loggerFactory)
        {
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }

            if (options.ServerCertificate == null && options.ServerCertificateSelector == null)
            {
                throw new ArgumentException(CoreStrings.ServerCertificateRequired, nameof(options));
            }

            _next             = next;
            _handshakeTimeout = options.HandshakeTimeout;
            _logger           = loggerFactory.CreateLogger <HttpsConnectionMiddleware>();

            // Something similar to the following could allow us to remove more duplicate logic, but we need https://github.com/dotnet/runtime/issues/40402 to be fixed first.
            //var sniOptionsSelector = new SniOptionsSelector("", new Dictionary<string, SniConfig> { { "*", new SniConfig() } }, new NoopCertificateConfigLoader(), options, options.HttpProtocols, _logger);
            //_httpsOptionsCallback = SniOptionsSelector.OptionsCallback;
            //_httpsOptionsCallbackState = sniOptionsSelector;
            //_sslStreamFactory = s => new SslStream(s);

            _options = options;
            _options.HttpProtocols = ValidateAndNormalizeHttpProtocols(_options.HttpProtocols, _logger);

            // capture the certificate now so it can't be switched after validation
            _serverCertificate         = options.ServerCertificate;
            _serverCertificateSelector = options.ServerCertificateSelector;

            // If a selector is provided then ignore the cert, it may be a default cert.
            if (_serverCertificateSelector != null)
            {
                // SslStream doesn't allow both.
                _serverCertificate = null;
            }
            else
            {
                Debug.Assert(_serverCertificate != null);

                EnsureCertificateIsAllowedForServerAuth(_serverCertificate);

                var certificate = _serverCertificate;
                if (!certificate.HasPrivateKey)
                {
                    // SslStream historically has logic to deal with certificate missing private keys.
                    // By resolving the SslStreamCertificateContext eagerly, we circumvent this logic so
                    // try to resolve the certificate from the store if there's no private key in the cert.
                    certificate = LocateCertificateWithPrivateKey(certificate);
                }

                // This might be do blocking IO but it'll resolve the certificate chain up front before any connections are
                // made to the server
                _serverCertificateContext = SslStreamCertificateContext.Create(certificate, additionalCertificates: null);
            }

            var remoteCertificateValidationCallback = _options.ClientCertificateMode == ClientCertificateMode.NoCertificate ?
                                                      (RemoteCertificateValidationCallback?)null : RemoteCertificateValidationCallback;

            _sslStreamFactory = s => new SslStream(s, leaveInnerStreamOpen: false, userCertificateValidationCallback: remoteCertificateValidationCallback);
        }
Beispiel #2
0
 public static SafeFreeCredentials AcquireCredentialsHandle(
     SslStreamCertificateContext?certificateContext,
     SslProtocols protocols,
     EncryptionPolicy policy,
     bool isServer)
 {
     return(new SafeFreeSslCredentials(certificateContext, protocols, policy));
 }
        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;
        }
Beispiel #4
0
        public SafeFreeSslCredentials(SslStreamCertificateContext?certificateContext, SslProtocols protocols, EncryptionPolicy policy)
            : base(IntPtr.Zero, true)
        {
            if (certificateContext != null)
            {
                // Make a defensive copy of the certificate. In some async cases the
                // certificate can have been disposed before being provided to the handshake.
                //
                // This meshes with the Unix (OpenSSL) PAL, because it extracts the private key
                // and cert handle (which get up-reffed) to match the API expectations.
                certificateContext = certificateContext.Duplicate();

                Debug.Assert(certificateContext.Certificate.HasPrivateKey, "cert clone.HasPrivateKey");
            }

            CertificateContext = certificateContext;
            Protocols          = protocols;
            Policy             = policy;
        }
Beispiel #5
0
        // TODO: this is called from MsQuicListener and when it fails it wreaks havoc in MsQuicListener finalizer.
        //       Consider moving bigger logic like this outside of constructor call chains.
        private static unsafe SafeMsQuicConfigurationHandle Create(QuicOptions options, QUIC_CREDENTIAL_FLAGS flags, X509Certificate?certificate, SslStreamCertificateContext?certificateContext, List <SslApplicationProtocol>?alpnProtocols)
        {
            // TODO: some of these checks should be done by the QuicOptions type.
            if (alpnProtocols == null || alpnProtocols.Count == 0)
            {
                throw new Exception("At least one SslApplicationProtocol value must be present in SslClientAuthenticationOptions or SslServerAuthenticationOptions.");
            }

            if (options.MaxBidirectionalStreams > ushort.MaxValue)
            {
                throw new Exception("MaxBidirectionalStreams overflow.");
            }

            if (options.MaxBidirectionalStreams > ushort.MaxValue)
            {
                throw new Exception("MaxBidirectionalStreams overflow.");
            }

            if ((flags & QUIC_CREDENTIAL_FLAGS.CLIENT) == 0)
            {
                if (certificate == null && certificateContext == null)
                {
                    throw new Exception("Server must provide certificate");
                }
            }
            else
            {
                flags |= QUIC_CREDENTIAL_FLAGS.INDICATE_CERTIFICATE_RECEIVED | QUIC_CREDENTIAL_FLAGS.NO_CERTIFICATE_VALIDATION;
            }

            if (!OperatingSystem.IsWindows())
            {
                // Use certificate handles on Windows, fall-back to ASN1 otherwise.
                flags |= QUIC_CREDENTIAL_FLAGS.USE_PORTABLE_CERTIFICATES;
            }

            Debug.Assert(!MsQuicApi.Api.Registration.IsInvalid);

            var settings = new QuicSettings
            {
                IsSetFlags = QuicSettingsIsSetFlags.PeerBidiStreamCount |
                             QuicSettingsIsSetFlags.PeerUnidiStreamCount,
                PeerBidiStreamCount  = (ushort)options.MaxBidirectionalStreams,
                PeerUnidiStreamCount = (ushort)options.MaxUnidirectionalStreams
            };

            if (options.IdleTimeout != Timeout.InfiniteTimeSpan)
            {
                if (options.IdleTimeout <= TimeSpan.Zero)
                {
                    throw new Exception("IdleTimeout must not be negative.");
                }

                ulong ms = (ulong)options.IdleTimeout.Ticks / TimeSpan.TicksPerMillisecond;
                if (ms > (1ul << 62) - 1)
                {
                    throw new Exception("IdleTimeout is too large (max 2^62-1 milliseconds)");
                }

                settings.IsSetFlags   |= QuicSettingsIsSetFlags.IdleTimeoutMs;
                settings.IdleTimeoutMs = (ulong)options.IdleTimeout.TotalMilliseconds;
            }

            uint status;
            SafeMsQuicConfigurationHandle?configurationHandle;

            X509Certificate2[]? intermediates = null;

            MemoryHandle[]? handles = null;
            QuicBuffer[]? buffers   = null;
            try
            {
                MsQuicAlpnHelper.Prepare(alpnProtocols, out handles, out buffers);
                status = MsQuicApi.Api.ConfigurationOpenDelegate(MsQuicApi.Api.Registration, (QuicBuffer *)Marshal.UnsafeAddrOfPinnedArrayElement(buffers, 0), (uint)alpnProtocols.Count, ref settings, (uint)sizeof(QuicSettings), context: IntPtr.Zero, out configurationHandle);
            }
            finally
            {
                MsQuicAlpnHelper.Return(ref handles, ref buffers);
            }

            QuicExceptionHelpers.ThrowIfFailed(status, "ConfigurationOpen failed.");

            try
            {
                CredentialConfig config = default;
                config.Flags = flags; // TODO: consider using LOAD_ASYNCHRONOUS with a callback.

                if (certificateContext != null)
                {
                    certificate   = (X509Certificate2?)_contextCertificate.GetValue(certificateContext);
                    intermediates = (X509Certificate2[]?)_contextChain.GetValue(certificateContext);

                    if (certificate == null || intermediates == null)
                    {
                        throw new ArgumentException(nameof(certificateContext));
                    }
                }

                if (certificate != null)
                {
                    if (OperatingSystem.IsWindows())
                    {
                        config.Type        = QUIC_CREDENTIAL_TYPE.CONTEXT;
                        config.Certificate = certificate.Handle;
                        status             = MsQuicApi.Api.ConfigurationLoadCredentialDelegate(configurationHandle, ref config);
                    }
                    else
                    {
                        CredentialConfigCertificatePkcs12 pkcs12Config;
                        byte[] asn1;

                        if (intermediates?.Length > 0)
                        {
                            X509Certificate2Collection collection = new X509Certificate2Collection();
                            collection.Add(certificate);
                            for (int i = 0; i < intermediates?.Length; i++)
                            {
                                collection.Add(intermediates[i]);
                            }

                            asn1 = collection.Export(X509ContentType.Pkcs12) !;
                        }
                        else
                        {
                            asn1 = certificate.Export(X509ContentType.Pkcs12);
                        }

                        fixed(void *ptr = asn1)
                        {
                            pkcs12Config.Asn1Blob           = (IntPtr)ptr;
                            pkcs12Config.Asn1BlobLength     = (uint)asn1.Length;
                            pkcs12Config.PrivateKeyPassword = IntPtr.Zero;

                            config.Type        = QUIC_CREDENTIAL_TYPE.PKCS12;
                            config.Certificate = (IntPtr)(&pkcs12Config);
                            status             = MsQuicApi.Api.ConfigurationLoadCredentialDelegate(configurationHandle, ref config);
                        }
                    }
                }
                else
                {
                    config.Type = QUIC_CREDENTIAL_TYPE.NONE;
                    status      = MsQuicApi.Api.ConfigurationLoadCredentialDelegate(configurationHandle, ref config);
                }

                QuicExceptionHelpers.ThrowIfFailed(status, "ConfigurationLoadCredential failed.");
            }
            catch
            {
                configurationHandle.Dispose();
                throw;
            }

            return(configurationHandle);
        }
        // TODO: this is called from MsQuicListener and when it fails it wreaks havoc in MsQuicListener finalizer.
        //       Consider moving bigger logic like this outside of constructor call chains.
        private static unsafe SafeMsQuicConfigurationHandle Create(QuicOptions options, QUIC_CREDENTIAL_FLAGS flags, X509Certificate?certificate, SslStreamCertificateContext?certificateContext, List <SslApplicationProtocol>?alpnProtocols, CipherSuitesPolicy?cipherSuitesPolicy)
        {
            // TODO: some of these checks should be done by the QuicOptions type.
            if (alpnProtocols == null || alpnProtocols.Count == 0)
            {
                throw new Exception("At least one SslApplicationProtocol value must be present in SslClientAuthenticationOptions or SslServerAuthenticationOptions.");
            }

            if (options.MaxBidirectionalStreams > ushort.MaxValue)
            {
                throw new Exception("MaxBidirectionalStreams overflow.");
            }

            if (options.MaxBidirectionalStreams > ushort.MaxValue)
            {
                throw new Exception("MaxBidirectionalStreams overflow.");
            }

            if ((flags & QUIC_CREDENTIAL_FLAGS.CLIENT) == 0)
            {
                if (certificate == null && certificateContext == null)
                {
                    throw new Exception("Server must provide certificate");
                }
            }
            else
            {
                flags |= QUIC_CREDENTIAL_FLAGS.INDICATE_CERTIFICATE_RECEIVED | QUIC_CREDENTIAL_FLAGS.NO_CERTIFICATE_VALIDATION;
            }

            if (!OperatingSystem.IsWindows())
            {
                // Use certificate handles on Windows, fall-back to ASN1 otherwise.
                flags |= QUIC_CREDENTIAL_FLAGS.USE_PORTABLE_CERTIFICATES;
            }

            Debug.Assert(!MsQuicApi.Api.Registration.IsInvalid);

            QUIC_SETTINGS settings = default(QUIC_SETTINGS);

            settings.IsSet.PeerUnidiStreamCount = 1;
            settings.PeerUnidiStreamCount       = (ushort)options.MaxUnidirectionalStreams;
            settings.IsSet.PeerBidiStreamCount  = 1;
            settings.PeerBidiStreamCount        = (ushort)options.MaxBidirectionalStreams;

            settings.IsSet.IdleTimeoutMs = 1;
            if (options.IdleTimeout != Timeout.InfiniteTimeSpan)
            {
                if (options.IdleTimeout <= TimeSpan.Zero)
                {
                    throw new Exception("IdleTimeout must not be negative.");
                }
                settings.IdleTimeoutMs = (ulong)options.IdleTimeout.TotalMilliseconds;
            }
            else
            {
                settings.IdleTimeoutMs = 0;
            }

            SafeMsQuicConfigurationHandle configurationHandle;

            X509Certificate2[]? intermediates = null;

            QUIC_HANDLE *handle;

            using var msquicBuffers = new MsQuicBuffers();
            msquicBuffers.Initialize(alpnProtocols, alpnProtocol => alpnProtocol.Protocol);
            ThrowIfFailure(MsQuicApi.Api.ApiTable->ConfigurationOpen(
                               MsQuicApi.Api.Registration.QuicHandle,
                               msquicBuffers.Buffers,
                               (uint)alpnProtocols.Count,
                               &settings,
                               (uint)sizeof(QUIC_SETTINGS),
                               (void *)IntPtr.Zero,
                               &handle), "ConfigurationOpen failed");
            configurationHandle = new SafeMsQuicConfigurationHandle(handle);

            try
            {
                QUIC_CREDENTIAL_CONFIG config = default;
                config.Flags = flags; // TODO: consider using LOAD_ASYNCHRONOUS with a callback.

                if (cipherSuitesPolicy != null)
                {
                    config.Flags |= QUIC_CREDENTIAL_FLAGS.SET_ALLOWED_CIPHER_SUITES;
                    config.AllowedCipherSuites = CipherSuitePolicyToFlags(cipherSuitesPolicy);
                }

                if (certificateContext != null)
                {
                    certificate   = certificateContext.Certificate;
                    intermediates = certificateContext.IntermediateCertificates;
                }

                int status;
                if (certificate != null)
                {
                    if (OperatingSystem.IsWindows())
                    {
                        config.Type = QUIC_CREDENTIAL_TYPE.CERTIFICATE_CONTEXT;
                        config.CertificateContext = (void *)certificate.Handle;
                        status = MsQuicApi.Api.ApiTable->ConfigurationLoadCredential(configurationHandle.QuicHandle, &config);
                    }
                    else
                    {
                        byte[] asn1;

                        if (intermediates?.Length > 0)
                        {
                            X509Certificate2Collection collection = new X509Certificate2Collection();
                            collection.Add(certificate);
                            for (int i = 0; i < intermediates?.Length; i++)
                            {
                                collection.Add(intermediates[i]);
                            }

                            asn1 = collection.Export(X509ContentType.Pkcs12) !;
                        }
                        else
                        {
                            asn1 = certificate.Export(X509ContentType.Pkcs12);
                        }

                        fixed(byte *ptr = asn1)
                        {
                            QUIC_CERTIFICATE_PKCS12 pkcs12Config = new QUIC_CERTIFICATE_PKCS12
                            {
                                Asn1Blob           = ptr,
                                Asn1BlobLength     = (uint)asn1.Length,
                                PrivateKeyPassword = (sbyte *)IntPtr.Zero
                            };

                            config.Type = QUIC_CREDENTIAL_TYPE.CERTIFICATE_PKCS12;
                            config.CertificatePkcs12 = &pkcs12Config;
                            status = MsQuicApi.Api.ApiTable->ConfigurationLoadCredential(configurationHandle.QuicHandle, &config);
                        }
                    }
                }
                else
                {
                    config.Type = QUIC_CREDENTIAL_TYPE.NONE;
                    status      = MsQuicApi.Api.ApiTable->ConfigurationLoadCredential(configurationHandle.QuicHandle, &config);
                }

#if TARGET_WINDOWS
                if ((Interop.SECURITY_STATUS)status == Interop.SECURITY_STATUS.AlgorithmMismatch && MsQuicApi.Tls13MayBeDisabled)
                {
                    throw new MsQuicException(status, SR.net_ssl_app_protocols_invalid);
                }
#endif

                ThrowIfFailure(status, "ConfigurationLoadCredential failed");
            }
            catch
            {
                configurationHandle.Dispose();
                throw;
            }

            return(configurationHandle);
        }