private static uint HandleEventPeerCertificateReceived(State state, ref ConnectionEvent connectionEvent) { SslPolicyErrors sslPolicyErrors = SslPolicyErrors.None; X509Chain? chain = null; X509Certificate2? certificate = null; X509Certificate2Collection?additionalCertificates = null; try { if (connectionEvent.Data.PeerCertificateReceived.PlatformCertificateHandle != IntPtr.Zero) { if (OperatingSystem.IsWindows()) { certificate = new X509Certificate2(connectionEvent.Data.PeerCertificateReceived.PlatformCertificateHandle); } else { unsafe { ReadOnlySpan <QuicBuffer> quicBuffer = new ReadOnlySpan <QuicBuffer>((void *)connectionEvent.Data.PeerCertificateReceived.PlatformCertificateHandle, sizeof(QuicBuffer)); certificate = new X509Certificate2(new ReadOnlySpan <byte>(quicBuffer[0].Buffer, (int)quicBuffer[0].Length)); if (connectionEvent.Data.PeerCertificateReceived.PlatformCertificateChainHandle != IntPtr.Zero) { quicBuffer = new ReadOnlySpan <QuicBuffer>((void *)connectionEvent.Data.PeerCertificateReceived.PlatformCertificateChainHandle, sizeof(QuicBuffer)); if (quicBuffer[0].Length != 0 && quicBuffer[0].Buffer != null) { additionalCertificates = new X509Certificate2Collection(); additionalCertificates.Import(new ReadOnlySpan <byte>(quicBuffer[0].Buffer, (int)quicBuffer[0].Length)); } } } } } if (certificate == null) { if (NetEventSource.Log.IsEnabled() && state.RemoteCertificateRequired) { NetEventSource.Error(state, $"{state.TraceId} Remote certificate required, but no remote certificate received"); } sslPolicyErrors |= SslPolicyErrors.RemoteCertificateNotAvailable; } else { chain = new X509Chain(); chain.ChainPolicy.RevocationMode = state.RevocationMode; chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot; chain.ChainPolicy.ApplicationPolicy.Add(state.IsServer ? s_clientAuthOid : s_serverAuthOid); if (additionalCertificates != null && additionalCertificates.Count > 1) { chain.ChainPolicy.ExtraStore.AddRange(additionalCertificates); } sslPolicyErrors |= CertificateValidation.BuildChainAndVerifyProperties(chain, certificate, true, state.IsServer, state.TargetHost); } if (!state.RemoteCertificateRequired) { sslPolicyErrors &= ~SslPolicyErrors.RemoteCertificateNotAvailable; } state.RemoteCertificate = certificate; if (state.RemoteCertificateValidationCallback != null) { bool success = state.RemoteCertificateValidationCallback(state, certificate, chain, sslPolicyErrors); // Unset the callback to prevent multiple invocations of the callback per a single connection. // Return the same value as the custom callback just did. state.RemoteCertificateValidationCallback = (_, _, _, _) => success; if (!success && NetEventSource.Log.IsEnabled()) { NetEventSource.Error(state, $"{state.TraceId} Remote certificate rejected by verification callback"); } if (!success) { throw new AuthenticationException(SR.net_quic_cert_custom_validation); } return(MsQuicStatusCodes.Success); } if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(state, $"{state.TraceId} Certificate validation for '${certificate?.Subject}' finished with ${sslPolicyErrors}"); } if (sslPolicyErrors != SslPolicyErrors.None) { throw new AuthenticationException(SR.Format(SR.net_quic_cert_chain_validation, sslPolicyErrors)); } return(MsQuicStatusCodes.Success); } catch (Exception ex) { if (NetEventSource.Log.IsEnabled()) { NetEventSource.Error(state, $"{state.TraceId} Certificate validation failed ${ex.Message}"); } throw; } }
private static unsafe int HandleEventPeerCertificateReceived(State state, ref QUIC_CONNECTION_EVENT connectionEvent) { SslPolicyErrors sslPolicyErrors = SslPolicyErrors.None; X509Chain? chain = null; X509Certificate2? certificate = null; X509Certificate2Collection?additionalCertificates = null; IntPtr certificateBuffer = IntPtr.Zero; int certificateLength = 0; try { IntPtr certificateHandle = (IntPtr)connectionEvent.PEER_CERTIFICATE_RECEIVED.Certificate; if (certificateHandle != IntPtr.Zero) { if (OperatingSystem.IsWindows()) { certificate = new X509Certificate2(certificateHandle); } else { unsafe { QUIC_BUFFER *certBuffer = (QUIC_BUFFER *)certificateHandle; certificate = new X509Certificate2(new ReadOnlySpan <byte>(certBuffer->Buffer, (int)certBuffer->Length)); certificateBuffer = (IntPtr)certBuffer->Buffer; certificateLength = (int)certBuffer->Length; IntPtr chainHandle = (IntPtr)connectionEvent.PEER_CERTIFICATE_RECEIVED.Chain; if (chainHandle != IntPtr.Zero) { QUIC_BUFFER *chainBuffer = (QUIC_BUFFER *)chainHandle; if (chainBuffer->Length != 0 && chainBuffer->Buffer != null) { additionalCertificates = new X509Certificate2Collection(); additionalCertificates.Import(new ReadOnlySpan <byte>(chainBuffer->Buffer, (int)chainBuffer->Length)); } } } } } if (certificate == null) { if (NetEventSource.Log.IsEnabled() && state.RemoteCertificateRequired) { NetEventSource.Error(state, $"{state.Handle} Remote certificate required, but no remote certificate received"); } sslPolicyErrors |= SslPolicyErrors.RemoteCertificateNotAvailable; } else { chain = new X509Chain(); chain.ChainPolicy.RevocationMode = state.RevocationMode; chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot; chain.ChainPolicy.ApplicationPolicy.Add(state.IsServer ? s_clientAuthOid : s_serverAuthOid); if (additionalCertificates != null && additionalCertificates.Count > 1) { chain.ChainPolicy.ExtraStore.AddRange(additionalCertificates); } sslPolicyErrors |= CertificateValidation.BuildChainAndVerifyProperties(chain, certificate, true, state.IsServer, state.TargetHost, certificateBuffer, certificateLength); } if (!state.RemoteCertificateRequired) { sslPolicyErrors &= ~SslPolicyErrors.RemoteCertificateNotAvailable; } state.RemoteCertificate = certificate; if (state.RemoteCertificateValidationCallback != null) { bool success = state.RemoteCertificateValidationCallback(state, certificate, chain, sslPolicyErrors); // Unset the callback to prevent multiple invocations of the callback per a single connection. // Return the same value as the custom callback just did. state.RemoteCertificateValidationCallback = (_, _, _, _) => success; if (!success && NetEventSource.Log.IsEnabled()) { NetEventSource.Error(state, $"{state.Handle} Remote certificate rejected by verification callback"); } if (!success) { if (state.IsServer) { return(QUIC_STATUS_USER_CANCELED); } throw new AuthenticationException(SR.net_quic_cert_custom_validation); } return(QUIC_STATUS_SUCCESS); } if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(state, $"{state.Handle} Certificate validation for '${certificate?.Subject}' finished with ${sslPolicyErrors}"); } if (sslPolicyErrors != SslPolicyErrors.None) { if (state.IsServer) { return(QUIC_STATUS_HANDSHAKE_FAILURE); } throw new AuthenticationException(SR.Format(SR.net_quic_cert_chain_validation, sslPolicyErrors)); } return(QUIC_STATUS_SUCCESS); } catch (Exception ex) { if (NetEventSource.Log.IsEnabled()) { NetEventSource.Error(state, $"{state.Handle} Certificate validation failed ${ex.Message}"); } throw; } }
private static bool VerifyCertChain(SafeX509StoreCtxHandle storeCtx, EasyRequest easy) { IntPtr leafCertPtr = Interop.Crypto.X509StoreCtxGetTargetCert(storeCtx); if (leafCertPtr == IntPtr.Zero) { EventSourceTrace("Invalid certificate pointer", easy: easy); return(false); } X509Certificate2[] otherCerts = null; int otherCertsCount = 0; var leafCert = new X509Certificate2(leafCertPtr); try { // We need to respect the user's server validation callback if there is one. If there isn't one, // we can start by first trying to use OpenSSL's verification, though only if CRL checking is disabled, // as OpenSSL doesn't do that. if (easy._handler.ServerCertificateCustomValidationCallback == null && !easy._handler.CheckCertificateRevocationList) { // Start by using the default verification provided directly by OpenSSL. // If it succeeds in verifying the cert chain, we're done. Employing this instead of // our custom implementation will need to be revisited if we ever decide to introduce a // "disallowed" store that enables users to "untrust" certs the system trusts. int sslResult = Interop.Crypto.X509VerifyCert(storeCtx); if (sslResult == 1) { return(true); } // X509_verify_cert can return < 0 in the case of programmer error Debug.Assert(sslResult == 0, "Unexpected error from X509_verify_cert: " + sslResult); } // Either OpenSSL verification failed, or there was a server validation callback // or certificate revocation checking was enabled. Either way, fall back to manual // and more expensive verification that includes checking the user's certs (not // just the system store ones as OpenSSL does). using (var chain = new X509Chain()) { chain.ChainPolicy.RevocationMode = easy._handler.CheckCertificateRevocationList ? X509RevocationMode.Online : X509RevocationMode.NoCheck; chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot; using (SafeSharedX509StackHandle extraStack = Interop.Crypto.X509StoreCtxGetSharedUntrusted(storeCtx)) { if (extraStack.IsInvalid) { otherCerts = Array.Empty <X509Certificate2>(); } else { int extraSize = Interop.Crypto.GetX509StackFieldCount(extraStack); otherCerts = new X509Certificate2[extraSize]; for (int i = 0; i < extraSize; i++) { IntPtr certPtr = Interop.Crypto.GetX509StackField(extraStack, i); if (certPtr != IntPtr.Zero) { X509Certificate2 cert = new X509Certificate2(certPtr); otherCerts[otherCertsCount++] = cert; chain.ChainPolicy.ExtraStore.Add(cert); } } } } var serverCallback = easy._handler._serverCertificateValidationCallback; if (serverCallback == null) { SslPolicyErrors errors = CertificateValidation.BuildChainAndVerifyProperties(chain, leafCert, checkCertName: false, hostName: null); // libcurl already verifies the host name return(errors == SslPolicyErrors.None); } else { // Authenticate the remote party: (e.g. when operating in client mode, authenticate the server). chain.ChainPolicy.ApplicationPolicy.Add(s_serverAuthOid); SslPolicyErrors errors = CertificateValidation.BuildChainAndVerifyProperties(chain, leafCert, checkCertName: true, hostName: easy._requestMessage.RequestUri.Host); // we disabled automatic host verification, so we do it here return(serverCallback(easy._requestMessage, leafCert, chain, errors)); } } } finally { for (int i = 0; i < otherCertsCount; i++) { otherCerts[i].Dispose(); } leafCert.Dispose(); } }
private static int VerifyCertChain(IntPtr storeCtxPtr, IntPtr curlPtr) { EasyRequest easy; if (!TryGetEasyRequest(curlPtr, out easy)) { EventSourceTrace("Could not find associated easy request: {0}", curlPtr); return(0); } using (var storeCtx = new SafeX509StoreCtxHandle(storeCtxPtr, ownsHandle: false)) { IntPtr leafCertPtr = Interop.Crypto.X509StoreCtxGetTargetCert(storeCtx); if (IntPtr.Zero == leafCertPtr) { EventSourceTrace("Invalid certificate pointer"); return(0); } using (X509Certificate2 leafCert = new X509Certificate2(leafCertPtr)) { // Set up the CBT with this certificate. easy._requestContentStream?.SetChannelBindingToken(leafCert); // We need to respect the user's server validation callback if there is one. If there isn't one, // we can start by first trying to use OpenSSL's verification, though only if CRL checking is disabled, // as OpenSSL doesn't do that. if (easy._handler.ServerCertificateValidationCallback == null && !easy._handler.CheckCertificateRevocationList) { // Start by using the default verification provided directly by OpenSSL. // If it succeeds in verifying the cert chain, we're done. Employing this instead of // our custom implementation will need to be revisited if we ever decide to introduce a // "disallowed" store that enables users to "untrust" certs the system trusts. int sslResult = Interop.Crypto.X509VerifyCert(storeCtx); if (sslResult == 1) { return(1); } // X509_verify_cert can return < 0 in the case of programmer error Debug.Assert(sslResult == 0, "Unexpected error from X509_verify_cert: " + sslResult); } // Either OpenSSL verification failed, or there was a server validation callback. // Either way, fall back to manual and more expensive verification that includes // checking the user's certs (not just the system store ones as OpenSSL does). X509Certificate2[] otherCerts; int otherCertsCount = 0; bool success; using (X509Chain chain = new X509Chain()) { chain.ChainPolicy.RevocationMode = easy._handler.CheckCertificateRevocationList ? X509RevocationMode.Online : X509RevocationMode.NoCheck; chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot; using (SafeSharedX509StackHandle extraStack = Interop.Crypto.X509StoreCtxGetSharedUntrusted(storeCtx)) { if (extraStack.IsInvalid) { otherCerts = Array.Empty <X509Certificate2>(); } else { int extraSize = Interop.Crypto.GetX509StackFieldCount(extraStack); otherCerts = new X509Certificate2[extraSize]; for (int i = 0; i < extraSize; i++) { IntPtr certPtr = Interop.Crypto.GetX509StackField(extraStack, i); if (certPtr != IntPtr.Zero) { X509Certificate2 cert = new X509Certificate2(certPtr); otherCerts[otherCertsCount++] = cert; chain.ChainPolicy.ExtraStore.Add(cert); } } } } var serverCallback = easy._handler._serverCertificateValidationCallback; if (serverCallback == null) { SslPolicyErrors errors = CertificateValidation.BuildChainAndVerifyProperties(chain, leafCert, checkCertName: false, hostName: null); // libcurl already verifies the host name success = errors == SslPolicyErrors.None; } else { SslPolicyErrors errors = CertificateValidation.BuildChainAndVerifyProperties(chain, leafCert, checkCertName: true, hostName: easy._requestMessage.RequestUri.Host); // we disabled automatic host verification, so we do it here try { success = serverCallback(easy._requestMessage, leafCert, chain, errors); } catch (Exception exc) { EventSourceTrace("Server validation callback threw exception: {0}", exc); easy.FailRequest(exc); success = false; } } } for (int i = 0; i < otherCertsCount; i++) { otherCerts[i].Dispose(); } return(success ? 1 : 0); } } }