crypto.go 4.6 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 11 12 13 14 15 16 17
	"math/big"
	"time"

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

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

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

// NewIdentity creates a new identity
26
func NewIdentity(privKey ic.PrivKey) (*Identity, error) {
27
	key, cert, err := keyToCertificate(privKey)
Marten Seemann's avatar
Marten Seemann committed
28 29 30
	if err != nil {
		return nil, err
	}
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
	return &Identity{
		config: 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,
			}},
			VerifyPeerCertificate: func(_ [][]byte, _ [][]*x509.Certificate) error {
				panic("tls config not specialized for peer")
			},
		},
	}, nil
}

// ConfigForAny is a short-hand for ConfigForPeer("").
func (i *Identity) ConfigForAny() (*tls.Config, <-chan ic.PubKey) {
	return i.ConfigForPeer("")
Marten Seemann's avatar
Marten Seemann committed
50 51
}

52 53 54 55 56 57
// ConfigForPeer creates a new single-use tls.Config that verifies the peer's
// certificate chain and returns the peer's public key via the channel. If the
// peer ID is empty, the returned config will accept any peer.
//
// It should be used to create a new tls.Config before securing either an
// incoming or outgoing connection.
58 59
func (i *Identity) ConfigForPeer(
	remote peer.ID,
60 61
) (*tls.Config, <-chan ic.PubKey) {
	keyCh := make(chan ic.PubKey, 1)
Marten Seemann's avatar
Marten Seemann committed
62 63 64
	// 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.
65
	conf := i.config.Clone()
Marten Seemann's avatar
Marten Seemann committed
66 67 68
	// 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 {
69 70
		defer close(keyCh)

Marten Seemann's avatar
Marten Seemann committed
71 72 73 74 75 76 77 78
		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
		}
79

Marten Seemann's avatar
Marten Seemann committed
80 81 82 83
		pubKey, err := getRemotePubKey(chain)
		if err != nil {
			return err
		}
84
		if remote != "" && !remote.MatchesPublicKey(pubKey) {
Marten Seemann's avatar
Marten Seemann committed
85 86
			return errors.New("peer IDs don't match")
		}
87
		keyCh <- pubKey
Marten Seemann's avatar
Marten Seemann committed
88 89
		return nil
	}
90
	return conf, keyCh
Marten Seemann's avatar
Marten Seemann committed
91 92
}

93
// getRemotePubKey derives the remote's public key from the certificate chain.
Marten Seemann's avatar
Marten Seemann committed
94 95 96 97 98 99 100
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 {
101 102 103
		// 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
104 105 106 107 108
	}
	remotePubKey, err := x509.MarshalPKIXPublicKey(chain[0].PublicKey)
	if err != nil {
		return nil, err
	}
Marten Seemann's avatar
Marten Seemann committed
109 110 111 112 113 114 115 116
	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
117 118
}

119
func keyToCertificate(sk ic.PrivKey) (crypto.PrivateKey, *x509.Certificate, error) {
Marten Seemann's avatar
Marten Seemann committed
120 121 122 123 124 125 126 127 128 129
	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),
	}

130 131
	var privateKey crypto.PrivateKey
	var publicKey crypto.PublicKey
132
	raw, err := sk.Raw()
Marten Seemann's avatar
Marten Seemann committed
133 134 135
	if err != nil {
		return nil, nil, err
	}
136
	switch sk.Type() {
Marten Seemann's avatar
Marten Seemann committed
137
	case pb.KeyType_RSA:
138
		k, err := x509.ParsePKCS1PrivateKey(raw)
Marten Seemann's avatar
Marten Seemann committed
139 140 141 142 143
		if err != nil {
			return nil, nil, err
		}
		publicKey = &k.PublicKey
		privateKey = k
Marten Seemann's avatar
Marten Seemann committed
144
	case pb.KeyType_ECDSA:
145
		k, err := x509.ParseECPrivateKey(raw)
Marten Seemann's avatar
Marten Seemann committed
146 147 148 149 150 151
		if err != nil {
			return nil, nil, err
		}
		publicKey = &k.PublicKey
		privateKey = k
	// TODO: add support for Ed25519
Marten Seemann's avatar
Marten Seemann committed
152 153 154 155 156 157 158 159 160 161 162 163 164
	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
}