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); }
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 }
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 } }
/// <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); }