package archival // // Saves TLS events // import ( "context" "crypto/tls" "crypto/x509" "errors" "net" "time" "github.com/ooni/probe-cli/v3/internal/model" "github.com/ooni/probe-cli/v3/internal/netxlite" ) // QUICTLSHandshakeEvent contains a QUIC or TLS handshake event. type QUICTLSHandshakeEvent struct { ALPN []string CipherSuite string Failure error Finished time.Time NegotiatedProto string Network string PeerCerts [][]byte RemoteAddr string SNI string SkipVerify bool Started time.Time TLSVersion string } // TLSHandshake performs a TLS handshake with the given handshaker // and saves the results into the saver. func (s *Saver) TLSHandshake(ctx context.Context, thx model.TLSHandshaker, conn net.Conn, config *tls.Config) (net.Conn, tls.ConnectionState, error) { network := conn.RemoteAddr().Network() remoteAddr := conn.RemoteAddr().String() started := time.Now() tconn, state, err := thx.Handshake(ctx, conn, config) // Implementation note: state is an empty ConnectionState on failure // so it's safe to access its fields also in that case s.appendTLSHandshake(&QUICTLSHandshakeEvent{ ALPN: config.NextProtos, CipherSuite: netxlite.TLSCipherSuiteString(state.CipherSuite), Failure: err, Finished: time.Now(), NegotiatedProto: state.NegotiatedProtocol, Network: network, PeerCerts: s.tlsPeerCerts(err, &state), RemoteAddr: remoteAddr, SNI: config.ServerName, SkipVerify: config.InsecureSkipVerify, Started: started, TLSVersion: netxlite.TLSVersionString(state.Version), }) return tconn, state, err } func (s *Saver) appendTLSHandshake(ev *QUICTLSHandshakeEvent) { s.mu.Lock() s.trace.TLSHandshake = append(s.trace.TLSHandshake, ev) s.mu.Unlock() } func (s *Saver) tlsPeerCerts(err error, state *tls.ConnectionState) (out [][]byte) { var x509HostnameError x509.HostnameError if errors.As(err, &x509HostnameError) { // Test case: https://wrong.host.badssl.com/ return [][]byte{x509HostnameError.Certificate.Raw} } var x509UnknownAuthorityError x509.UnknownAuthorityError if errors.As(err, &x509UnknownAuthorityError) { // Test case: https://self-signed.badssl.com/. This error has // never been among the ones returned by MK. return [][]byte{x509UnknownAuthorityError.Cert.Raw} } var x509CertificateInvalidError x509.CertificateInvalidError if errors.As(err, &x509CertificateInvalidError) { // Test case: https://expired.badssl.com/ return [][]byte{x509CertificateInvalidError.Cert.Raw} } for _, cert := range state.PeerCertificates { out = append(out, cert.Raw) } return }