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

import (
Marten Seemann's avatar
Marten Seemann committed
4 5
	"crypto/ecdsa"
	"crypto/elliptic"
Marten Seemann's avatar
Marten Seemann committed
6 7 8
	"crypto/rand"
	"crypto/tls"
	"crypto/x509"
Marten Seemann's avatar
Marten Seemann committed
9 10
	"crypto/x509/pkix"
	"encoding/asn1"
Marten Seemann's avatar
Marten Seemann committed
11
	"errors"
Marten Seemann's avatar
Marten Seemann committed
12
	"fmt"
Marten Seemann's avatar
Marten Seemann committed
13 14 15
	"math/big"
	"time"

Marten Seemann's avatar
Marten Seemann committed
16
	crypto "github.com/libp2p/go-libp2p-crypto"
Marten Seemann's avatar
Marten Seemann committed
17 18 19 20
	ic "github.com/libp2p/go-libp2p-crypto"
	peer "github.com/libp2p/go-libp2p-peer"
)

Marten Seemann's avatar
Marten Seemann committed
21 22 23 24 25 26 27 28
const certValidityPeriod = 100 * 365 * 24 * time.Hour // ~100 years

var extensionID = getPrefixedExtensionID([]int{1, 1})

type signedKey struct {
	PubKey    []byte
	Signature []byte
}
29

Marten Seemann's avatar
Marten Seemann committed
30 31
// Identity is used to secure connections
type Identity struct {
32
	config tls.Config
Marten Seemann's avatar
Marten Seemann committed
33 34 35
}

// NewIdentity creates a new identity
36
func NewIdentity(privKey ic.PrivKey) (*Identity, error) {
Marten Seemann's avatar
Marten Seemann committed
37
	cert, err := keyToCertificate(privKey)
Marten Seemann's avatar
Marten Seemann committed
38 39 40
	if err != nil {
		return nil, err
	}
41 42 43 44 45
	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,
Marten Seemann's avatar
Marten Seemann committed
46
			Certificates:       []tls.Certificate{*cert},
47 48 49 50 51 52 53 54 55 56
			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
57 58
}

59 60 61 62 63 64
// 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.
65 66
func (i *Identity) ConfigForPeer(
	remote peer.ID,
67 68
) (*tls.Config, <-chan ic.PubKey) {
	keyCh := make(chan ic.PubKey, 1)
Marten Seemann's avatar
Marten Seemann committed
69 70 71
	// 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.
72
	conf := i.config.Clone()
Marten Seemann's avatar
Marten Seemann committed
73 74 75
	// 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 {
76 77
		defer close(keyCh)

Marten Seemann's avatar
Marten Seemann committed
78 79 80 81 82 83 84 85
		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
		}
86

Marten Seemann's avatar
Marten Seemann committed
87 88 89 90
		pubKey, err := getRemotePubKey(chain)
		if err != nil {
			return err
		}
91
		if remote != "" && !remote.MatchesPublicKey(pubKey) {
Marten Seemann's avatar
Marten Seemann committed
92 93
			return errors.New("peer IDs don't match")
		}
94
		keyCh <- pubKey
Marten Seemann's avatar
Marten Seemann committed
95 96
		return nil
	}
97
	return conf, keyCh
Marten Seemann's avatar
Marten Seemann committed
98 99
}

100
// getRemotePubKey derives the remote's public key from the certificate chain.
Marten Seemann's avatar
Marten Seemann committed
101 102 103 104
func getRemotePubKey(chain []*x509.Certificate) (ic.PubKey, error) {
	if len(chain) != 1 {
		return nil, errors.New("expected one certificates in the chain")
	}
Marten Seemann's avatar
Marten Seemann committed
105
	cert := chain[0]
Marten Seemann's avatar
Marten Seemann committed
106
	pool := x509.NewCertPool()
Marten Seemann's avatar
Marten Seemann committed
107 108
	pool.AddCert(cert)
	if _, err := cert.Verify(x509.VerifyOptions{Roots: pool}); err != nil {
109 110 111
		// 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
112
	}
Marten Seemann's avatar
Marten Seemann committed
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135

	var found bool
	var keyExt pkix.Extension
	// find the libp2p key extension, skipping all unknown extensions
	for _, ext := range cert.Extensions {
		if extensionIDEqual(ext.Id, extensionID) {
			keyExt = ext
			found = true
			break
		}
	}
	if !found {
		return nil, errors.New("expected certificate to contain the key extension")
	}
	var sk signedKey
	if _, err := asn1.Unmarshal(keyExt.Value, &sk); err != nil {
		return nil, fmt.Errorf("unmarshalling signed certificate failed: %s", err)
	}
	pubKey, err := crypto.UnmarshalPublicKey(sk.PubKey)
	if err != nil {
		return nil, fmt.Errorf("unmarshalling public key failed: %s", err)
	}
	certKeyPub, err := x509.MarshalPKIXPublicKey(cert.PublicKey)
Marten Seemann's avatar
Marten Seemann committed
136 137 138
	if err != nil {
		return nil, err
	}
Marten Seemann's avatar
Marten Seemann committed
139 140 141
	valid, err := pubKey.Verify(certKeyPub, sk.Signature)
	if err != nil {
		return nil, fmt.Errorf("signature verification failed: %s", err)
Marten Seemann's avatar
Marten Seemann committed
142
	}
Marten Seemann's avatar
Marten Seemann committed
143 144 145 146
	if !valid {
		return nil, errors.New("signature invalid")
	}
	return pubKey, nil
Marten Seemann's avatar
Marten Seemann committed
147 148
}

Marten Seemann's avatar
Marten Seemann committed
149 150
func keyToCertificate(sk ic.PrivKey) (*tls.Certificate, error) {
	certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
Marten Seemann's avatar
Marten Seemann committed
151
	if err != nil {
Marten Seemann's avatar
Marten Seemann committed
152
		return nil, err
Marten Seemann's avatar
Marten Seemann committed
153 154
	}

Marten Seemann's avatar
Marten Seemann committed
155
	keyBytes, err := crypto.MarshalPublicKey(sk.GetPublic())
Marten Seemann's avatar
Marten Seemann committed
156
	if err != nil {
Marten Seemann's avatar
Marten Seemann committed
157
		return nil, err
Marten Seemann's avatar
Marten Seemann committed
158
	}
Marten Seemann's avatar
Marten Seemann committed
159 160 161 162 163 164 165 166 167 168 169 170 171 172
	certKeyPub, err := x509.MarshalPKIXPublicKey(certKey.Public())
	if err != nil {
		return nil, err
	}
	signature, err := sk.Sign(certKeyPub)
	if err != nil {
		return nil, err
	}
	value, err := asn1.Marshal(signedKey{
		PubKey:    keyBytes,
		Signature: signature,
	})
	if err != nil {
		return nil, err
Marten Seemann's avatar
Marten Seemann committed
173
	}
Marten Seemann's avatar
Marten Seemann committed
174 175

	sn, err := rand.Int(rand.Reader, big.NewInt(1<<62))
Marten Seemann's avatar
Marten Seemann committed
176
	if err != nil {
Marten Seemann's avatar
Marten Seemann committed
177 178 179 180 181 182 183 184 185 186
		return nil, err
	}
	tmpl := &x509.Certificate{
		SerialNumber: sn,
		NotBefore:    time.Time{},
		NotAfter:     time.Now().Add(certValidityPeriod),
		// after calling CreateCertificate, these will end up in Certificate.Extensions
		ExtraExtensions: []pkix.Extension{
			{Id: extensionID, Value: value},
		},
Marten Seemann's avatar
Marten Seemann committed
187
	}
Marten Seemann's avatar
Marten Seemann committed
188
	certDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, certKey.Public(), certKey)
Marten Seemann's avatar
Marten Seemann committed
189
	if err != nil {
Marten Seemann's avatar
Marten Seemann committed
190
		return nil, err
Marten Seemann's avatar
Marten Seemann committed
191
	}
Marten Seemann's avatar
Marten Seemann committed
192 193 194 195
	return &tls.Certificate{
		Certificate: [][]byte{certDER},
		PrivateKey:  certKey,
	}, nil
Marten Seemann's avatar
Marten Seemann committed
196
}