Support certificate validation TLS alerts for Linux#115996
Support certificate validation TLS alerts for Linux#115996rzikm wants to merge 37 commits intodotnet:mainfrom
Conversation
|
Tagging subscribers to this area: @dotnet/ncl, @bartonjs, @vcsjones |
a4c4177 to
6450c19
Compare
|
Draft Pull Request was automatically closed for 30 days of inactivity. Please let us know if you'd like to reopen it. |
|
/azp run runtime-libraries-coreclr outerloop |
|
Azure Pipelines successfully started running 1 pipeline(s). |
| chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; | ||
| if (trust._store != null) | ||
| { | ||
| chain.ChainPolicy.CustomTrustStore.AddRange(trust._store.Certificates); |
There was a problem hiding this comment.
Is it possible for the same chain to get here multiple times (since it's a parameter)? If so, this list could get really, really long.
There was a problem hiding this comment.
In theory if there is TLS 1.2 renegotiation (which basically another handshake mid-connection) and the other side decides to switch certificates, but TLS implementations heavily restrict renegotiation (and enforce identity continuity) so it shouldn't happen in practice.
| { | ||
| CreateFatalHandshakeAlertToken(sslPolicyErrors, chain!, ref alertToken); | ||
| } | ||
| #pragma warning restore CS0162 // unreachable code detected (compile time const) |
There was a problem hiding this comment.
Does the restore need to be around the block, or just the if?
There was a problem hiding this comment.
It needs to go on the block
Contributes to #18837.
Summary
On Linux,
SslStreampreviously closed the transport without sending a TLS alert when certificate validation failed. This PR makesSslStreamsend appropriate TLS alerts (e.g.bad_certificate,unknown_ca,certificate_expired,certificate_required) by moving certificate validation inline into the OpenSSL handshake viaSSL_CTX_set_cert_verify_cb.How it works
Instead of validating the peer certificate after the handshake completes (post-handshake), the validation now runs inside OpenSSL's certificate verification callback during the handshake itself. This allows OpenSSL to send the appropriate TLS alert to the peer when validation fails, since the handshake is still in progress.
The
CertVerifyCallback(registered on theSSL_CTX) extracts the certificate chain from theX509_STORE_CTX, then calls the existing managedVerifyRemoteCertificatelogic — including the user'sRemoteCertificateValidationCallbackif one is registered. If validation fails, the callback maps the failure reason to anX509_V_ERR_*status code, which OpenSSL translates into the corresponding TLS alert.Connection info during the callback
Because the callback fires mid-handshake, connection properties like
SslProtocolandCipherAlgorithmwould normally not be available yet. To preserve backward compatibility with user callbacks that access these properties,_connectionInfois populated before invoking the user callback. On TLS 1.2,SSL_get_current_cipher()returns NULL at this point (the session cipher is set at ChangeCipherSpec), so the native code falls back toSSL_get_pending_cipher()which is available onceServerHellohas been processed.Propagating exceptions from validation callback
The exception that would normally be thrown from the cert validation (either AuthenticationException, or whatever user code throws) is caught and stored in
SafeSslHandlebefore returning back to OpenSSL code, and when the control flow returns fromSSL_do_handshake, the exception gets rethrown.Client certificates and
ClientCertificateRequiredOpenSSL only invokes the cert verify callback when the peer actually sends a certificate. When a server sets
ClientCertificateRequired = truebut the client sends no certificate, the callback never fires. Two cases are handled:SSL_VERIFY_FAIL_IF_NO_PEER_CERTis set alongsideSSL_VERIFY_PEER, so OpenSSL itself rejects the handshake and sends thecertificate_required(TLS 1.3) orhandshake_failure(TLS 1.2) alert.SSL_VERIFY_PEERis set. The handshake succeeds, and managedCompleteHandshakecode detects that_remoteCertificateis still null, then callsVerifyRemoteCertificatewhich invokes the user's callback withSslPolicyErrors.RemoteCertificateNotAvailable. This preserves the existing behavior where applications can accept connections without a client certificate via the callback, unfortunately, no TLS alert is sent in this case.Resumed sessions
On resumed sessions, OpenSSL skips the cert verify callback since the certificate was already validated in the original handshake.
CompleteHandshakedetects this (_remoteCertificateis null) and retrieves the peer certificate from the SSL handle viaGetRemoteCertificateand valites it post-handshake, previous behavior is preserved (no TLS alert is sent if revalidation fails, but this is not expected to happen often in practice).