github.com/hoffie/larasync@v0.0.0-20151025221940-0384d2bddcef/api/tls/fingerprintVerifier.go (about)

     1  package tls
     2  
     3  import (
     4  	"crypto/tls"
     5  	"crypto/x509"
     6  	"errors"
     7  	"net"
     8  	"time"
     9  
    10  	helpers "github.com/hoffie/larasync/helpers/x509"
    11  )
    12  
    13  // ErrFingerprintRejected is returned when the fingerprint cannot be verified.
    14  var ErrFingerprintRejected = errors.New("fingerprint rejected")
    15  
    16  type handshakeTimeoutError struct{}
    17  
    18  func (handshakeTimeoutError) Timeout() bool   { return true }
    19  func (handshakeTimeoutError) Temporary() bool { return true }
    20  func (handshakeTimeoutError) Error() string {
    21  	return "api/tls: TLS handshake timeout"
    22  }
    23  
    24  const handshakeTimeout = 10 * time.Second
    25  
    26  // VerificationFunc is the interface all callbacks have to fullfill so that they
    27  // can act as a fingerprint verifier.
    28  type VerificationFunc func(string) bool
    29  
    30  // FingerprintVerifier provides a TLS connection handler which validates connections
    31  // based on the server's fingerprint.
    32  type FingerprintVerifier struct {
    33  	AcceptFingerprint string
    34  	VerificationFunc  VerificationFunc
    35  }
    36  
    37  // DialTLS is the function which hooks into net/http.Transport and should be
    38  // passed as a function reference.
    39  func (v *FingerprintVerifier) DialTLS(network, addr string) (net.Conn, error) {
    40  	return v.dialTLS(network, addr, handshakeTimeout)
    41  }
    42  
    43  // dialTLS is a helper function which does the main work for connecting
    44  // to the system.
    45  func (v *FingerprintVerifier) dialTLS(network, addr string, tlsTimeout time.Duration) (net.Conn, error) {
    46  	// setting InsecureSkipVerify here so that net/tls does not perform any
    47  	// validations; we validate the certificate fingerprint later.
    48  	cfg := &tls.Config{
    49  		InsecureSkipVerify: true,
    50  		CipherSuites:       []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256},
    51  		MinVersion:         tls.VersionTLS12,
    52  	}
    53  	plainConn, err := net.Dial(network, addr)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  	tlsConn := tls.Client(plainConn, cfg)
    58  	errc := make(chan error, 2)
    59  
    60  	// for canceling TLS handshake
    61  	timer := time.AfterFunc(tlsTimeout, func() {
    62  		errc <- handshakeTimeoutError{}
    63  	})
    64  	go func() {
    65  		err := tlsConn.Handshake()
    66  		if timer != nil {
    67  			timer.Stop()
    68  		}
    69  		errc <- err
    70  	}()
    71  	if err := <-errc; err != nil {
    72  		plainConn.Close()
    73  		return nil, err
    74  	}
    75  	// note: this is the place where hostname verification usually occurs;
    76  	// we do not do this as we do not use a CA infrastructure;
    77  	// instead, we do fingerprint verification here
    78  	cs := tlsConn.ConnectionState()
    79  	if len(cs.PeerCertificates) < 1 {
    80  		plainConn.Close()
    81  		return nil, err
    82  	}
    83  	peerCert := cs.PeerCertificates[0]
    84  	if !v.acceptPeerCert(peerCert) {
    85  		plainConn.Close()
    86  		return nil, ErrFingerprintRejected
    87  	}
    88  	return tlsConn, err
    89  }
    90  
    91  // acceptPeerCert decides whether the given cert is accepted; it first checks
    92  // if the fingerprint is white-listed already; if it isn't, the verification
    93  // callback is invoked if non-nil.
    94  // in all other cases the certificate is rejected
    95  func (v *FingerprintVerifier) acceptPeerCert(cert *x509.Certificate) bool {
    96  	fp := helpers.CertificateFingerprint(cert)
    97  	if v.AcceptFingerprint != "" {
    98  		return v.AcceptFingerprint == fp
    99  	}
   100  	if v.VerificationFunc == nil {
   101  		return false
   102  	}
   103  	res := v.VerificationFunc(fp)
   104  	if res {
   105  		v.AcceptFingerprint = fp
   106  	}
   107  	return res
   108  }