mirror of
https://source.quilibrium.com/quilibrium/ceremonyclient.git
synced 2024-12-29 18:05:18 +00:00
156 lines
4.6 KiB
Go
156 lines
4.6 KiB
Go
|
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)
|
||
|
}
|