refactor(webtransport): Use keygen package for deterministic ecdsa key

generation.
This commit is contained in:
Marco Munizaga
2025-06-20 08:34:52 -07:00
committed by Marco Munizaga
parent 93a9158968
commit 418cf8ff40
6 changed files with 49 additions and 62 deletions
+2
View File
@@ -7,6 +7,7 @@ retract v0.26.1 // Tag was applied incorrectly due to a bug in the release workf
retract v0.36.0 // Accidentally modified the tag.
require (
filippo.io/keygen v0.0.0-20260114151900-8e2790ea4c5b
github.com/benbjohnson/clock v1.3.5
github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0
@@ -66,6 +67,7 @@ require (
)
require (
filippo.io/bigmod v0.1.1-0.20260103110540-f8a47775ebe5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
+8
View File
@@ -1,7 +1,13 @@
filippo.io/bigmod v0.1.1-0.20260103110540-f8a47775ebe5 h1:JA0fFr+kxpqTdxR9LOBiTWpGNchqmkcsgmdeJZRclZ0=
filippo.io/bigmod v0.1.1-0.20260103110540-f8a47775ebe5/go.mod h1:OjOXDNlClLblvXdwgFFOQFJEocLhhtai8vGLy0JCZlI=
filippo.io/keygen v0.0.0-20260114151900-8e2790ea4c5b h1:REI1FbdW71yO56Are4XAxD+OS/e+BQsB3gE4mZRQEXY=
filippo.io/keygen v0.0.0-20260114151900-8e2790ea4c5b/go.mod h1:9nnw1SlYHYuPSo/3wjQzNjSbeHlq2NsKo5iEtfJPWP0=
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3 h1:oe6fCvaEpkhyW3qAicT0TnGtyht/UrgvOwMcEgLb7Aw=
github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3/go.mod h1:qdP0gaj0QtgX2RUZhnlVrceJ+Qln8aSlDyJwelLLFeM=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -224,6 +230,8 @@ golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+29 -24
View File
@@ -2,6 +2,7 @@ package libp2pwebtransport
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/sha256"
@@ -17,6 +18,8 @@ import (
"golang.org/x/crypto/hkdf"
"filippo.io/keygen"
ic "github.com/libp2p/go-libp2p/core/crypto"
"github.com/multiformats/go-multihash"
@@ -71,11 +74,16 @@ func generateCert(key ic.PrivKey, start, end time.Time) (*x509.Certificate, *ecd
BasicConstraintsValid: true,
}
caPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), deterministicHKDFReader)
ecdsaSeed := make([]byte, 192/8) // 192 bits of entropy required for P256
if _, err := io.ReadFull(deterministicHKDFReader, ecdsaSeed); err != nil {
return nil, nil, err
}
caPrivateKey, err := keygen.ECDSA(elliptic.P256(), ecdsaSeed)
if err != nil {
return nil, nil, err
}
caBytes, err := x509.CreateCertificate(deterministicHKDFReader, certTempl, certTempl, caPrivateKey.Public(), caPrivateKey)
caBytes, err := x509.CreateCertificate(deterministicHKDFReader, certTempl, certTempl, caPrivateKey.Public(), deterministicSigner{caPrivateKey})
if err != nil {
return nil, nil, err
}
@@ -136,29 +144,26 @@ func verifyRawCerts(rawCerts [][]byte, certHashes []multihash.DecodedMultihash)
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,
}
return hkdf.New(sha256.New, seed, salt, []byte(info))
}
func (r *deterministicReader) Read(p []byte) (n int, err error) {
if len(p) == 1 {
return r.singleByteReader.Read(p)
}
return r.reader.Read(p)
// deterministicSigner wraps an ecdsa.PrivateKey and exposes a `Sign` method
// that will produce deterministic signatures by ignoring the rand reader.
// Go 1.24 produces deterministic ecdsa signatures when passed a nil random source.
// See: https://go.dev/doc/go1.24#cryptoecdsapkgcryptoecdsa
type deterministicSigner struct {
priv *ecdsa.PrivateKey
}
var _ crypto.Signer = deterministicSigner{}
func (ds deterministicSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
// Ignore the rand reader to produce deterministic signatures.
_ = rand
return ds.priv.Sign(nil, digest, opts)
}
func (ds deterministicSigner) Public() crypto.PublicKey {
return ds.priv.Public()
}
-38
View File
@@ -11,7 +11,6 @@ import (
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"io"
"math/big"
mrand "math/rand"
"testing"
@@ -155,40 +154,3 @@ func TestDeterministicCertHashes(t *testing.T) {
require.Equal(t, keyBytes, keyBytes2)
}
}
// TestDeterministicSig tests that our hack around making ECDSA signatures
// deterministic works. If this fails, this means we need to try another
// strategy to make deterministic signatures or try something else entirely.
// See deterministicReader for more context.
func TestDeterministicSig(t *testing.T) {
// Run this test 1000 times since we want to make sure the signatures are deterministic
runs := 1000
for range runs {
zeroSeed := [32]byte{}
deterministicHKDFReader := newDeterministicReader(zeroSeed[:], nil, deterministicCertInfo)
b := [1024]byte{}
io.ReadFull(deterministicHKDFReader, b[:])
caPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), deterministicHKDFReader)
require.NoError(t, err)
sig, err := caPrivateKey.Sign(deterministicHKDFReader, b[:], crypto.SHA256)
require.NoError(t, err)
deterministicHKDFReader = newDeterministicReader(zeroSeed[:], nil, deterministicCertInfo)
b2 := [1024]byte{}
io.ReadFull(deterministicHKDFReader, b2[:])
caPrivateKey2, err := ecdsa.GenerateKey(elliptic.P256(), deterministicHKDFReader)
require.NoError(t, err)
sig2, err := caPrivateKey2.Sign(deterministicHKDFReader, b2[:], crypto.SHA256)
require.NoError(t, err)
keyBytes, err := x509.MarshalECPrivateKey(caPrivateKey)
require.NoError(t, err)
keyBytes2, err := x509.MarshalECPrivateKey(caPrivateKey2)
require.NoError(t, err)
require.Equal(t, sig, sig2)
require.Equal(t, keyBytes, keyBytes2)
}
}
+2
View File
@@ -9,6 +9,8 @@ require (
)
require (
filippo.io/bigmod v0.1.1-0.20260103110540-f8a47775ebe5 // indirect
filippo.io/keygen v0.0.0-20260114151900-8e2790ea4c5b // indirect
github.com/benbjohnson/clock v1.3.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
+8
View File
@@ -1,7 +1,13 @@
filippo.io/bigmod v0.1.1-0.20260103110540-f8a47775ebe5 h1:JA0fFr+kxpqTdxR9LOBiTWpGNchqmkcsgmdeJZRclZ0=
filippo.io/bigmod v0.1.1-0.20260103110540-f8a47775ebe5/go.mod h1:OjOXDNlClLblvXdwgFFOQFJEocLhhtai8vGLy0JCZlI=
filippo.io/keygen v0.0.0-20260114151900-8e2790ea4c5b h1:REI1FbdW71yO56Are4XAxD+OS/e+BQsB3gE4mZRQEXY=
filippo.io/keygen v0.0.0-20260114151900-8e2790ea4c5b/go.mod h1:9nnw1SlYHYuPSo/3wjQzNjSbeHlq2NsKo5iEtfJPWP0=
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3 h1:oe6fCvaEpkhyW3qAicT0TnGtyht/UrgvOwMcEgLb7Aw=
github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3/go.mod h1:qdP0gaj0QtgX2RUZhnlVrceJ+Qln8aSlDyJwelLLFeM=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -220,6 +226,8 @@ golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=