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 }