forked from libp2p/go-libp2p
/
crypto.go
155 lines (139 loc) · 4.55 KB
/
crypto.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package libp2pwebtransport
import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/binary"
"errors"
"fmt"
"io"
"math/big"
"time"
"golang.org/x/crypto/hkdf"
ic "github.com/libp2p/go-libp2p/core/crypto"
"github.com/multiformats/go-multihash"
"github.com/quic-go/quic-go/http3"
)
const deterministicCertInfo = "determinisitic cert"
func getTLSConf(key ic.PrivKey, start, end time.Time) (*tls.Config, error) {
cert, priv, err := generateCert(key, start, end)
if err != nil {
return nil, err
}
return &tls.Config{
Certificates: []tls.Certificate{{
Certificate: [][]byte{cert.Raw},
PrivateKey: priv,
Leaf: cert,
}},
NextProtos: []string{http3.NextProtoH3},
}, nil
}
// generateCert generates certs deterministically based on the `key` and start
// time passed in. Uses `golang.org/x/crypto/hkdf`.
func generateCert(key ic.PrivKey, start, end time.Time) (*x509.Certificate, *ecdsa.PrivateKey, error) {
keyBytes, err := key.Raw()
if err != nil {
return nil, nil, err
}
startTimeSalt := make([]byte, 8)
binary.LittleEndian.PutUint64(startTimeSalt, uint64(start.UnixNano()))
deterministicHKDFReader := newDeterministicReader(keyBytes, startTimeSalt, deterministicCertInfo)
b := make([]byte, 8)
if _, err := deterministicHKDFReader.Read(b); err != nil {
return nil, nil, err
}
serial := int64(binary.BigEndian.Uint64(b))
if serial < 0 {
serial = -serial
}
certTempl := &x509.Certificate{
SerialNumber: big.NewInt(serial),
Subject: pkix.Name{},
NotBefore: start,
NotAfter: end,
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}
caPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), deterministicHKDFReader)
if err != nil {
return nil, nil, err
}
caBytes, err := x509.CreateCertificate(deterministicHKDFReader, certTempl, certTempl, caPrivateKey.Public(), caPrivateKey)
if err != nil {
return nil, nil, err
}
ca, err := x509.ParseCertificate(caBytes)
if err != nil {
return nil, nil, err
}
return ca, caPrivateKey, nil
}
func verifyRawCerts(rawCerts [][]byte, certHashes []multihash.DecodedMultihash) error {
if len(rawCerts) < 1 {
return errors.New("no cert")
}
leaf := rawCerts[len(rawCerts)-1]
// The W3C WebTransport specification currently only allows SHA-256 certificates for serverCertificateHashes.
hash := sha256.Sum256(leaf)
var verified bool
for _, h := range certHashes {
if h.Code == multihash.SHA2_256 && bytes.Equal(h.Digest, hash[:]) {
verified = true
break
}
}
if !verified {
digests := make([][]byte, 0, len(certHashes))
for _, h := range certHashes {
digests = append(digests, h.Digest)
}
return fmt.Errorf("cert hash not found: %#x (expected: %#x)", hash, digests)
}
cert, err := x509.ParseCertificate(leaf)
if err != nil {
return err
}
// TODO: is this the best (and complete?) way to identify RSA certificates?
switch cert.SignatureAlgorithm {
case x509.SHA1WithRSA, x509.SHA256WithRSA, x509.SHA384WithRSA, x509.SHA512WithRSA, x509.MD2WithRSA, x509.MD5WithRSA:
return errors.New("cert uses RSA")
}
if l := cert.NotAfter.Sub(cert.NotBefore); l > 14*24*time.Hour {
return fmt.Errorf("cert must not be valid for longer than 14 days (NotBefore: %s, NotAfter: %s, Length: %s)", cert.NotBefore, cert.NotAfter, l)
}
now := time.Now()
if now.Before(cert.NotBefore) || now.After(cert.NotAfter) {
return fmt.Errorf("cert not valid (NotBefore: %s, NotAfter: %s)", cert.NotBefore, cert.NotAfter)
}
return nil
}
// deterministicReader is a hack. It counter-acts the Go library's attempt at
// making ECDSA signatures non-deterministic. Go adds non-determinism by
// randomly dropping a singly byte from the reader stream. This counteracts this
// by detecting when a read is a single byte and using a different reader
// instead.
type deterministicReader struct {
reader io.Reader
singleByteReader io.Reader
}
func newDeterministicReader(seed []byte, salt []byte, info string) io.Reader {
reader := hkdf.New(sha256.New, seed, salt, []byte(info))
singleByteReader := hkdf.New(sha256.New, seed, salt, []byte(info+" single byte"))
return &deterministicReader{
reader: reader,
singleByteReader: singleByteReader,
}
}
func (r *deterministicReader) Read(p []byte) (n int, err error) {
if len(p) == 1 {
return r.singleByteReader.Read(p)
}
return r.reader.Read(p)
}