aa27bbe33f
This change should simplify the pipeline's job. Reference issue: https://github.com/ooni/probe/issues/1817. I previously dismissed this possibility, but now it seems clear it is simpler to have a very tabular data format internally and to convert such a format to OONI's data format when serializing. The OONI data format is what the pipeline expects, but processing is easier with a more linear/tabular format.
125 lines
3.7 KiB
Go
125 lines
3.7 KiB
Go
package measurex
|
|
|
|
//
|
|
// TLS
|
|
//
|
|
// Wraps TLS code to write events into a WritableDB.
|
|
//
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"errors"
|
|
"net"
|
|
"time"
|
|
|
|
"github.com/ooni/probe-cli/v3/internal/netxlite"
|
|
)
|
|
|
|
// TLSHandshaker performs TLS handshakes.
|
|
type TLSHandshaker = netxlite.TLSHandshaker
|
|
|
|
// WrapTLSHandshaker wraps a netxlite.TLSHandshaker to return a new
|
|
// instance of TLSHandshaker that saves events into the DB.
|
|
func (mx *Measurer) WrapTLSHandshaker(db WritableDB, thx netxlite.TLSHandshaker) TLSHandshaker {
|
|
return &tlsHandshakerDB{TLSHandshaker: thx, db: db, begin: mx.Begin}
|
|
}
|
|
|
|
// NewTLSHandshakerStdlib creates a new TLS handshaker that
|
|
// saves results into the DB and uses the stdlib for TLS.
|
|
func (mx *Measurer) NewTLSHandshakerStdlib(db WritableDB, logger Logger) TLSHandshaker {
|
|
return mx.WrapTLSHandshaker(db, netxlite.NewTLSHandshakerStdlib(logger))
|
|
}
|
|
|
|
type tlsHandshakerDB struct {
|
|
netxlite.TLSHandshaker
|
|
begin time.Time
|
|
db WritableDB
|
|
}
|
|
|
|
// QUICTLSHandshakeEvent contains a QUIC or TLS handshake event.
|
|
type QUICTLSHandshakeEvent struct {
|
|
CipherSuite string
|
|
Failure *string
|
|
NegotiatedProto string
|
|
TLSVersion string
|
|
PeerCerts [][]byte
|
|
Finished float64
|
|
RemoteAddr string
|
|
SNI string
|
|
ALPN []string
|
|
SkipVerify bool
|
|
Oddity Oddity
|
|
Network string
|
|
Started float64
|
|
}
|
|
|
|
func (thx *tlsHandshakerDB) Handshake(ctx context.Context,
|
|
conn Conn, config *tls.Config) (net.Conn, tls.ConnectionState, error) {
|
|
network := conn.RemoteAddr().Network()
|
|
remoteAddr := conn.RemoteAddr().String()
|
|
started := time.Since(thx.begin).Seconds()
|
|
tconn, state, err := thx.TLSHandshaker.Handshake(ctx, conn, config)
|
|
finished := time.Since(thx.begin).Seconds()
|
|
thx.db.InsertIntoTLSHandshake(&QUICTLSHandshakeEvent{
|
|
Network: network,
|
|
RemoteAddr: remoteAddr,
|
|
SNI: config.ServerName,
|
|
ALPN: config.NextProtos,
|
|
SkipVerify: config.InsecureSkipVerify,
|
|
Started: started,
|
|
Finished: finished,
|
|
Failure: NewFailure(err),
|
|
Oddity: thx.computeOddity(err),
|
|
TLSVersion: netxlite.TLSVersionString(state.Version),
|
|
CipherSuite: netxlite.TLSCipherSuiteString(state.CipherSuite),
|
|
NegotiatedProto: state.NegotiatedProtocol,
|
|
PeerCerts: peerCerts(err, &state),
|
|
})
|
|
return tconn, state, err
|
|
}
|
|
|
|
func (thx *tlsHandshakerDB) computeOddity(err error) Oddity {
|
|
if err == nil {
|
|
return ""
|
|
}
|
|
switch err.Error() {
|
|
case netxlite.FailureGenericTimeoutError:
|
|
return OddityTLSHandshakeTimeout
|
|
case netxlite.FailureConnectionReset:
|
|
return OddityTLSHandshakeReset
|
|
case netxlite.FailureEOFError:
|
|
return OddityTLSHandshakeUnexpectedEOF
|
|
case netxlite.FailureSSLInvalidHostname:
|
|
return OddityTLSHandshakeInvalidHostname
|
|
case netxlite.FailureSSLUnknownAuthority:
|
|
return OddityTLSHandshakeUnknownAuthority
|
|
default:
|
|
return OddityTLSHandshakeOther
|
|
}
|
|
}
|
|
|
|
func peerCerts(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
|
|
}
|