crypto.go 4.9 KB
Newer Older
Marten Seemann's avatar
Marten Seemann committed
1 2 3
package libp2ptls

import (
4
	"crypto"
Marten Seemann's avatar
Marten Seemann committed
5 6 7 8
	"crypto/rand"
	"crypto/tls"
	"crypto/x509"
	"errors"
Marten Seemann's avatar
Marten Seemann committed
9
	"fmt"
Marten Seemann's avatar
Marten Seemann committed
10
	"math/big"
11
	"net"
Marten Seemann's avatar
Marten Seemann committed
12 13 14 15 16 17 18
	"time"

	ic "github.com/libp2p/go-libp2p-crypto"
	pb "github.com/libp2p/go-libp2p-crypto/pb"
	peer "github.com/libp2p/go-libp2p-peer"
)

19 20
const certValidityPeriod = 180 * 24 * time.Hour

Marten Seemann's avatar
Marten Seemann committed
21 22 23 24 25 26
// Identity is used to secure connections
type Identity struct {
	*tls.Config
}

// NewIdentity creates a new identity
27 28 29 30 31
func NewIdentity(
	privKey ic.PrivKey,
	verifiedPeerCallback func(net.Conn, ic.PubKey),
) (*Identity, error) {
	key, cert, err := keyToCertificate(privKey)
Marten Seemann's avatar
Marten Seemann committed
32 33 34
	if err != nil {
		return nil, err
	}
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
	conf := &tls.Config{
		MinVersion:         tls.VersionTLS13,
		InsecureSkipVerify: true, // This is not insecure here. We will verify the cert chain ourselves.
		ClientAuth:         tls.RequireAnyClientCert,
		Certificates: []tls.Certificate{{
			Certificate: [][]byte{cert.Raw},
			PrivateKey:  key,
		}},
	}
	// When receiving the ClientHello, create a new tls.Config.
	// This new config has a VerifyPeerCertificate set, which calls the verifiedPeerCallback
	// when we derived the remote's public key from its certificate chain.
	conf.GetConfigForClient = func(ch *tls.ClientHelloInfo) (*tls.Config, error) {
		c := conf.Clone()
		c.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
			chain := make([]*x509.Certificate, len(rawCerts))
			for i := 0; i < len(rawCerts); i++ {
				cert, err := x509.ParseCertificate(rawCerts[i])
				if err != nil {
					return err
				}
				chain[i] = cert
			}
			pubKey, err := getRemotePubKey(chain)
			if err != nil {
				return err
			}
			verifiedPeerCallback(ch.Conn, pubKey)
			return nil
		}
		return c, nil
	}
Marten Seemann's avatar
Marten Seemann committed
67 68 69 70 71
	return &Identity{conf}, nil
}

// ConfigForPeer creates a new tls.Config that verifies the peers certificate chain.
// It should be used to create a new tls.Config before dialing.
72 73 74 75
func (i *Identity) ConfigForPeer(
	remote peer.ID,
	verifiedPeerCallback func(ic.PubKey),
) *tls.Config {
Marten Seemann's avatar
Marten Seemann committed
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
	// We need to check the peer ID in the VerifyPeerCertificate callback.
	// The tls.Config it is also used for listening, and we might also have concurrent dials.
	// Clone it so we can check for the specific peer ID we're dialing here.
	conf := i.Config.Clone()
	// We're using InsecureSkipVerify, so the verifiedChains parameter will always be empty.
	// We need to parse the certificates ourselves from the raw certs.
	conf.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
		chain := make([]*x509.Certificate, len(rawCerts))
		for i := 0; i < len(rawCerts); i++ {
			cert, err := x509.ParseCertificate(rawCerts[i])
			if err != nil {
				return err
			}
			chain[i] = cert
		}
		pubKey, err := getRemotePubKey(chain)
		if err != nil {
			return err
		}
		if !remote.MatchesPublicKey(pubKey) {
			return errors.New("peer IDs don't match")
		}
98
		verifiedPeerCallback(pubKey)
Marten Seemann's avatar
Marten Seemann committed
99 100 101 102 103
		return nil
	}
	return conf
}

104
// getRemotePubKey derives the remote's public key from the certificate chain.
Marten Seemann's avatar
Marten Seemann committed
105 106 107 108 109 110 111
func getRemotePubKey(chain []*x509.Certificate) (ic.PubKey, error) {
	if len(chain) != 1 {
		return nil, errors.New("expected one certificates in the chain")
	}
	pool := x509.NewCertPool()
	pool.AddCert(chain[0])
	if _, err := chain[0].Verify(x509.VerifyOptions{Roots: pool}); err != nil {
112 113 114
		// If we return an x509 error here, it will be sent on the wire.
		// Wrap the error to avoid that.
		return nil, fmt.Errorf("certificate verification failed: %s", err)
Marten Seemann's avatar
Marten Seemann committed
115 116 117 118 119
	}
	remotePubKey, err := x509.MarshalPKIXPublicKey(chain[0].PublicKey)
	if err != nil {
		return nil, err
	}
Marten Seemann's avatar
Marten Seemann committed
120 121 122 123 124 125 126 127
	switch chain[0].PublicKeyAlgorithm {
	case x509.RSA:
		return ic.UnmarshalRsaPublicKey(remotePubKey)
	case x509.ECDSA:
		return ic.UnmarshalECDSAPublicKey(remotePubKey)
	default:
		return nil, fmt.Errorf("unexpected public key algorithm: %d", chain[0].PublicKeyAlgorithm)
	}
Marten Seemann's avatar
Marten Seemann committed
128 129
}

130
func keyToCertificate(sk ic.PrivKey) (crypto.PrivateKey, *x509.Certificate, error) {
Marten Seemann's avatar
Marten Seemann committed
131 132 133 134 135 136 137 138 139 140
	sn, err := rand.Int(rand.Reader, big.NewInt(1<<62))
	if err != nil {
		return nil, nil, err
	}
	tmpl := &x509.Certificate{
		SerialNumber: sn,
		NotBefore:    time.Now().Add(-24 * time.Hour),
		NotAfter:     time.Now().Add(certValidityPeriod),
	}

141 142
	var privateKey crypto.PrivateKey
	var publicKey crypto.PublicKey
143
	raw, err := sk.Raw()
Marten Seemann's avatar
Marten Seemann committed
144 145 146
	if err != nil {
		return nil, nil, err
	}
147
	switch sk.Type() {
Marten Seemann's avatar
Marten Seemann committed
148
	case pb.KeyType_RSA:
149
		k, err := x509.ParsePKCS1PrivateKey(raw)
Marten Seemann's avatar
Marten Seemann committed
150 151 152 153 154
		if err != nil {
			return nil, nil, err
		}
		publicKey = &k.PublicKey
		privateKey = k
Marten Seemann's avatar
Marten Seemann committed
155
	case pb.KeyType_ECDSA:
156
		k, err := x509.ParseECPrivateKey(raw)
Marten Seemann's avatar
Marten Seemann committed
157 158 159 160 161 162
		if err != nil {
			return nil, nil, err
		}
		publicKey = &k.PublicKey
		privateKey = k
	// TODO: add support for Ed25519
Marten Seemann's avatar
Marten Seemann committed
163 164 165 166 167 168 169 170 171 172 173 174 175
	default:
		return nil, nil, errors.New("unsupported key type for TLS")
	}
	certDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, publicKey, privateKey)
	if err != nil {
		return nil, nil, err
	}
	cert, err := x509.ParseCertificate(certDER)
	if err != nil {
		return nil, nil, err
	}
	return privateKey, cert, nil
}