示例#1
0
        bool InternalUserCertificateValidationCallback(object sender, X509Certificate x509Certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            #region OBSOLETE

#pragma warning disable CS0618 // Type or member is obsolete
            var certificateValidationCallback = _options?.TlsOptions?.CertificateValidationCallback;
#pragma warning restore CS0618 // Type or member is obsolete
            if (certificateValidationCallback != null)
            {
                return(certificateValidationCallback(x509Certificate, chain, sslPolicyErrors, _clientOptions));
            }

            #endregion

            var certificateValidationHandler = _options?.TlsOptions?.CertificateValidationHandler;
            if (certificateValidationHandler != null)
            {
                var context = new MqttClientCertificateValidationCallbackContext
                {
                    Certificate     = x509Certificate,
                    Chain           = chain,
                    SslPolicyErrors = sslPolicyErrors,
                    ClientOptions   = _options
                };

                return(certificateValidationHandler(context));
            }

            if (sslPolicyErrors == SslPolicyErrors.None)
            {
                return(true);
            }

            if (chain.ChainStatus.Any(c => c.Status == X509ChainStatusFlags.RevocationStatusUnknown || c.Status == X509ChainStatusFlags.Revoked || c.Status == X509ChainStatusFlags.OfflineRevocation))
            {
                if (!_options.TlsOptions.IgnoreCertificateRevocationErrors)
                {
                    return(false);
                }
            }

            if (chain.ChainStatus.Any(c => c.Status == X509ChainStatusFlags.PartialChain))
            {
                if (!_options.TlsOptions.IgnoreCertificateChainErrors)
                {
                    return(false);
                }
            }

            return(_options.TlsOptions.AllowUntrustedCertificates);
        }
示例#2
0
        void SetupClientWebSocket(ClientWebSocket clientWebSocket)
        {
            if (_options.ProxyOptions != null)
            {
                clientWebSocket.Options.Proxy = CreateProxy();
            }

            if (_options.RequestHeaders != null)
            {
                foreach (var requestHeader in _options.RequestHeaders)
                {
                    clientWebSocket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value);
                }
            }

            if (_options.SubProtocols != null)
            {
                foreach (var subProtocol in _options.SubProtocols)
                {
                    clientWebSocket.Options.AddSubProtocol(subProtocol);
                }
            }

            if (_options.CookieContainer != null)
            {
                clientWebSocket.Options.Cookies = _options.CookieContainer;
            }

            if (_options.TlsOptions?.UseTls == true && _options.TlsOptions?.Certificates != null)
            {
                clientWebSocket.Options.ClientCertificates = new X509CertificateCollection();
                foreach (var certificate in _options.TlsOptions.Certificates)
                {
#if WINDOWS_UWP
                    clientWebSocket.Options.ClientCertificates.Add(new X509Certificate(certificate));
#else
                    clientWebSocket.Options.ClientCertificates.Add(certificate);
#endif
                }
            }

            var certificateValidationHandler = _options.TlsOptions?.CertificateValidationHandler;
#if NETSTANDARD2_1
            if (certificateValidationHandler != null)
            {
                clientWebSocket.Options.RemoteCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback((sender, certificate, chain, sslPolicyErrors) =>
                {
                    // TODO: Find a way to add client options to same callback. Problem is that they have a different type.
                    var context = new MqttClientCertificateValidationCallbackContext
                    {
                        Certificate     = certificate,
                        Chain           = chain,
                        SslPolicyErrors = sslPolicyErrors,
                        ClientOptions   = _options
                    };

                    return(certificateValidationHandler(context));
                });
            }
#else
            if (certificateValidationHandler != null)
            {
                throw new NotSupportedException("The remote certificate validation callback for Web Sockets is only supported for netstandard 2.1+");
            }
#endif
        }
示例#3
0
        void SetupClientWebSocket(ClientWebSocket clientWebSocket)
        {
            if (_options.ProxyOptions != null)
            {
                clientWebSocket.Options.Proxy = CreateProxy();
            }

            if (_options.RequestHeaders != null)
            {
                foreach (var requestHeader in _options.RequestHeaders)
                {
                    clientWebSocket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value);
                }
            }

            if (_options.SubProtocols != null)
            {
                foreach (var subProtocol in _options.SubProtocols)
                {
                    clientWebSocket.Options.AddSubProtocol(subProtocol);
                }
            }

            if (_options.CookieContainer != null)
            {
                clientWebSocket.Options.Cookies = _options.CookieContainer;
            }

            if (_options.TlsOptions?.UseTls == true && _options.TlsOptions?.Certificates != null)
            {
                clientWebSocket.Options.ClientCertificates = new X509CertificateCollection();
                foreach (var certificate in _options.TlsOptions.Certificates)
                {
#if WINDOWS_UWP
                    clientWebSocket.Options.ClientCertificates.Add(new X509Certificate(certificate));
#else
                    clientWebSocket.Options.ClientCertificates.Add(certificate);
#endif
                }
            }

            var certificateValidationHandler = _options.TlsOptions?.CertificateValidationHandler;
            if (certificateValidationHandler != null)
            {
#if NETSTANDARD1_3
                throw new NotSupportedException("Remote certificate validation callback is not supported when using 'netstandard1.3'.");
#elif NETSTANDARD2_0
                throw new NotSupportedException("Remote certificate validation callback is not supported when using 'netstandard2.0'.");
#elif WINDOWS_UWP
                throw new NotSupportedException("Remote certificate validation callback is not supported when using 'uap10.0'.");
#elif NET452
                throw new NotSupportedException("Remote certificate validation callback is not supported when using 'net452'.");
#elif NET461
                throw new NotSupportedException("Remote certificate validation callback is not supported when using 'net461'.");
#else
                clientWebSocket.Options.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) =>
                {
                    // TODO: Find a way to add client options to same callback. Problem is that they have a different type.
                    var context = new MqttClientCertificateValidationCallbackContext
                    {
                        Certificate     = certificate,
                        Chain           = chain,
                        SslPolicyErrors = sslPolicyErrors,
                        ClientOptions   = _options
                    };

                    return(certificateValidationHandler(context));
                };
#endif
            }
        }
示例#4
0
        /// <summary>
        ///     According to the implementation of MQTTNet this callback is similar to the one used by the SSLStream class.
        ///     https://docs.microsoft.com/en-us/dotnet/api/system.net.security.remotecertificatevalidationcallback
        /// </summary>
        /// <param name="callbackContext">
        ///     from MQTTnet V3.0.10 a class containing the fct. argument that were separated so far:
        ///     <list type="bullet">
        ///         <item>
        ///             <term>
        ///                 <see cref="X509Certificate">Certificate</see>
        ///             </term>
        ///             <desccription>used to authenticate the remote party.</desccription>
        ///         </item>
        ///         <item>
        ///             <term>
        ///                 <see cref="X509Chain">Chain</see>
        ///             </term>
        ///             <desccription>The chain of certificate authorities associated with the remote certificate.</desccription>
        ///         </item>
        ///         <item>
        ///             <term>
        ///                 <see cref="SslPolicyErrors">SSLPolicyErrors</see>
        ///             </term>
        ///             <desccription>One or more errors associated with the remote certificate.</desccription>
        ///         </item>
        ///         <item>
        ///             <term>
        ///                 <see cref="IMqttClientChannelOptions">ClientOptions</see>
        ///             </term>
        ///             <desccription>MQTTClient options which were used to establish the connection.</desccription>
        ///         </item>
        ///     </list>
        /// </param>
        /// <returns>A Boolean value that determines whether the specified certificate is accepted for authentication.</returns>
        private bool CertificateValidationCallback(MqttClientCertificateValidationCallbackContext callbackContext)
        {
            // a broker CA certificate must exist
            if (BrokerCACert == null)
            {
                return(false);
            }

            // get the broker CA certificate for validation
            using (X509Certificate2 brokerCACert = new X509Certificate2(BrokerCACert, "", X509KeyStorageFlags.Exportable))
            {
                // the certificate received from broker during TLS handshake
                using (X509Certificate2 brokerCert = new X509Certificate2(callbackContext.Certificate))
                {
                    // the validation which was made base on the certificates stored in the certificate store
                    // was successful
                    if (callbackContext.SslPolicyErrors == SslPolicyErrors.None)
                    {
                        // that means that all CA certificates that are required for validation check are entered in the
                        // "trusted CA" part of the certificate store and match with the certtificate/chain from remote

                        // it seems that also e.g. in the docker container the cert store of the OS is used for
                        // validation; but we only want to validate against the broker CA that was selected
                        // during configuration of the device; so we only make our own validations here
                        // (see below) also if policyErrors is none
                        //return true;
                    }

                    // further validation checks:

                    // a certificate chain is received and it is valid
                    if (callbackContext.Chain != null && callbackContext.Chain.ChainElements.Count > 1)
                    {
                        // we check whether the broker CA certificate we have stored is part of this chain
                        foreach (X509ChainElement cert in callbackContext.Chain.ChainElements)
                        {
                            X509Certificate2 certInChain = cert.Certificate;
                            if (brokerCACert.Equals(certInChain))
                            {
                                // we accept as valid
                                return(true);
                            }
                        }
                    }

                    // for the next check we build a certificate chain including the received certificate from
                    // remote and the broker CA we have stored and check this chain for validitiy
                    using (X509Chain testChain = new X509Chain())
                    {
                        testChain.ChainPolicy.RevocationMode      = X509RevocationMode.NoCheck;
                        testChain.ChainPolicy.RevocationFlag      = X509RevocationFlag.ExcludeRoot;
                        testChain.ChainPolicy.VerificationFlags   = X509VerificationFlags.AllowUnknownCertificateAuthority;
                        testChain.ChainPolicy.VerificationTime    = DateTime.Now;
                        testChain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 0);

                        // the CA certificate we want to test whether it is the root from the broker cert
                        testChain.ChainPolicy.ExtraStore.Add(brokerCACert);
                        if (testChain.Build(brokerCert))
                        {
                            // the .Contains check below is added because the .AllowUnknownCertificateAuthority results all in true
                            // if the ExtraStore.Add adds nothing or empty
                            if (testChain.ChainStatus.Length == 1 &&
                                testChain.ChainStatus.First()
                                .Status
                                == X509ChainStatusFlags.UntrustedRoot &&
                                testChain.ChainPolicy.ExtraStore.Contains(testChain.ChainElements[testChain.ChainElements.Count - 1]
                                                                          .Certificate
                                                                          ))
                            {
                                // chain is valid,
                                // and we expect that root is untrusted which the status flag tells us
                                return(true);
                            }
                        }
                    }
                }
            }

            // the validation check errors shall be ignored by setting
            if (callbackContext.ClientOptions.TlsOptions.IgnoreCertificateChainErrors)
            {
                Logger.Error("Ignoring broker certificate validation errors.");
                return(true);
            }

            // not valid
            Logger.Error("Broker certificate validation failed");
            if (Logger.IsDebugEnabled)
            {
                try
                {
                    Logger.Error("Remote Certificate:");
                    using (X509Certificate2 certificate = new X509Certificate2(callbackContext.Certificate))
                    {
                        CertifacteLogging.LogCertifacte(certificate, Logger);
                    }
                    Logger.Error("Remote Certificate Chain:");
                    CertifacteLogging.LogCertificateChain(callbackContext.Chain, Logger);
                }
                catch (Exception ex)
                {
                    Logger.Error("Exception while logging certificate details.", ex);
                }
            }
            return(false);
        }