crypto.go 6.52 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"

16 17
	"golang.org/x/sys/cpu"

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

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

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

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

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

// NewIdentity creates a new identity
38
func NewIdentity(privKey ic.PrivKey) (*Identity, error) {
Marten Seemann's avatar
Marten Seemann committed
39
	cert, err := keyToCertificate(privKey)
Marten Seemann's avatar
Marten Seemann committed
40 41 42
	if err != nil {
		return nil, err
	}
43 44
	return &Identity{
		config: tls.Config{
45 46 47 48 49
			MinVersion:               tls.VersionTLS13,
			PreferServerCipherSuites: preferServerCipherSuites(),
			InsecureSkipVerify:       true, // This is not insecure here. We will verify the cert chain ourselves.
			ClientAuth:               tls.RequireAnyClientCert,
			Certificates:             []tls.Certificate{*cert},
50 51 52 53 54 55 56 57 58 59
			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
60 61
}

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

Marten Seemann's avatar
Marten Seemann committed
81 82 83 84 85 86 87 88
		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
		}
89

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

103
// getRemotePubKey derives the remote's public key from the certificate chain.
Marten Seemann's avatar
Marten Seemann committed
104 105 106 107
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
108
	cert := chain[0]
Marten Seemann's avatar
Marten Seemann committed
109
	pool := x509.NewCertPool()
Marten Seemann's avatar
Marten Seemann committed
110 111
	pool.AddCert(cert)
	if _, err := cert.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
	}
Marten Seemann's avatar
Marten Seemann committed
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138

	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
139 140 141
	if err != nil {
		return nil, err
	}
Marten Seemann's avatar
Marten Seemann committed
142 143 144
	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
145
	}
Marten Seemann's avatar
Marten Seemann committed
146 147 148 149
	if !valid {
		return nil, errors.New("signature invalid")
	}
	return pubKey, nil
Marten Seemann's avatar
Marten Seemann committed
150 151
}

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

Marten Seemann's avatar
Marten Seemann committed
158
	keyBytes, err := crypto.MarshalPublicKey(sk.GetPublic())
Marten Seemann's avatar
Marten Seemann committed
159
	if err != nil {
Marten Seemann's avatar
Marten Seemann committed
160
		return nil, err
Marten Seemann's avatar
Marten Seemann committed
161
	}
Marten Seemann's avatar
Marten Seemann committed
162 163 164 165 166 167 168 169 170 171 172 173 174 175
	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
176
	}
Marten Seemann's avatar
Marten Seemann committed
177 178

	sn, err := rand.Int(rand.Reader, big.NewInt(1<<62))
Marten Seemann's avatar
Marten Seemann committed
179
	if err != nil {
Marten Seemann's avatar
Marten Seemann committed
180 181 182 183 184 185 186 187 188 189
		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
190
	}
Marten Seemann's avatar
Marten Seemann committed
191
	certDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, certKey.Public(), certKey)
Marten Seemann's avatar
Marten Seemann committed
192
	if err != nil {
Marten Seemann's avatar
Marten Seemann committed
193
		return nil, err
Marten Seemann's avatar
Marten Seemann committed
194
	}
Marten Seemann's avatar
Marten Seemann committed
195 196 197 198
	return &tls.Certificate{
		Certificate: [][]byte{certDER},
		PrivateKey:  certKey,
	}, nil
Marten Seemann's avatar
Marten Seemann committed
199
}
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221

// We want nodes without AES hardware (e.g. ARM) support to always use ChaCha.
// Only if both nodes have AES hardware support (e.g. x86), AES should be used.
// x86->x86: AES, ARM->x86: ChaCha, x86->ARM: ChaCha and ARM->ARM: Chacha
// This function returns true if we don't have AES hardware support, and false otherwise.
// Thus, ARM servers will always use their own cipher suite preferences (ChaCha first),
// and x86 servers will aways use the client's cipher suite preferences.
func preferServerCipherSuites() bool {
	// Copied from the Go TLS implementation.

	// Check the cpu flags for each platform that has optimized GCM implementations.
	// Worst case, these variables will just all be false.
	var (
		hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ
		hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL
		// Keep in sync with crypto/aes/cipher_s390x.go.
		hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR && (cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)

		hasGCMAsm = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X
	)
	return !hasGCMAsm
}