From 2cabe0d087f0c9db3672c773b633207c9975cab9 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Mon, 17 Oct 2022 19:39:11 +0100 Subject: [PATCH 01/18] Use deterministic TLS certificates for webtransport --- p2p/transport/websocket/websocket_test.go | 2 +- p2p/transport/webtransport/cert_manager.go | 43 ++++++++---- .../webtransport/cert_manager_test.go | 55 ++++++++++++++- p2p/transport/webtransport/crypto.go | 61 +++++++++++++++-- p2p/transport/webtransport/crypto_test.go | 68 +++++++++++++++++++ p2p/transport/webtransport/transport.go | 2 +- 6 files changed, 208 insertions(+), 23 deletions(-) diff --git a/p2p/transport/websocket/websocket_test.go b/p2p/transport/websocket/websocket_test.go index 645dc82452..c167d42538 100644 --- a/p2p/transport/websocket/websocket_test.go +++ b/p2p/transport/websocket/websocket_test.go @@ -73,7 +73,7 @@ func newInsecureMuxer(t *testing.T) (peer.ID, sec.SecureMuxer) { func newSecureMuxer(t *testing.T) (peer.ID, sec.SecureMuxer) { t.Helper() - priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 256) + priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 32) if err != nil { t.Fatal(err) } diff --git a/p2p/transport/webtransport/cert_manager.go b/p2p/transport/webtransport/cert_manager.go index da2c69f464..81221d6f15 100644 --- a/p2p/transport/webtransport/cert_manager.go +++ b/p2p/transport/webtransport/cert_manager.go @@ -9,6 +9,7 @@ import ( "time" "github.com/benbjohnson/clock" + ic "github.com/libp2p/go-libp2p/core/crypto" ma "github.com/multiformats/go-multiaddr" "github.com/multiformats/go-multihash" ) @@ -26,8 +27,8 @@ type certConfig struct { func (c *certConfig) Start() time.Time { return c.tlsConf.Certificates[0].Leaf.NotBefore } func (c *certConfig) End() time.Time { return c.tlsConf.Certificates[0].Leaf.NotAfter } -func newCertConfig(start, end time.Time) (*certConfig, error) { - conf, err := getTLSConf(start, end) +func newCertConfig(key ic.PrivKey, start, end time.Time) (*certConfig, error) { + conf, err := getTLSConf(key, start, end) if err != nil { return nil, err } @@ -57,32 +58,50 @@ type certManager struct { serializedCertHashes [][]byte } -func newCertManager(clock clock.Clock) (*certManager, error) { +func newCertManager(hostKey ic.PrivKey, clock clock.Clock) (*certManager, error) { m := &certManager{clock: clock} m.ctx, m.ctxCancel = context.WithCancel(context.Background()) - if err := m.init(); err != nil { + if err := m.init(hostKey); err != nil { return nil, err } - m.background() + m.background(hostKey) return m, nil } -func (m *certManager) init() error { +type timeBuckets struct { + current time.Time + next time.Time +} + +// getTimeBuckets returns a canonical set of times that are bucketed in ranges +// of certValidity since unix epoch. This lets you get the same time ranges +// across reboots without having to persist state. +func getTimeBuckets(now time.Time) timeBuckets { + currentBucket := now.UnixMilli() / certValidity.Milliseconds() + return timeBuckets{ + current: time.UnixMilli((currentBucket) * certValidity.Milliseconds()), + next: time.UnixMilli((currentBucket + 1) * certValidity.Milliseconds()), + } + +} + +func (m *certManager) init(hostKey ic.PrivKey) error { start := m.clock.Now().Add(-clockSkewAllowance) + timeBuckets := getTimeBuckets(start) var err error - m.nextConfig, err = newCertConfig(start, start.Add(certValidity)) + m.nextConfig, err = newCertConfig(hostKey, timeBuckets.current, timeBuckets.next) if err != nil { return err } - return m.rollConfig() + return m.rollConfig(hostKey) } -func (m *certManager) rollConfig() error { +func (m *certManager) rollConfig(hostKey ic.PrivKey) error { // We stop using the current certificate clockSkewAllowance before its expiry time. // At this point, the next certificate needs to be valid for one clockSkewAllowance. nextStart := m.nextConfig.End().Add(-2 * clockSkewAllowance) - c, err := newCertConfig(nextStart, nextStart.Add(certValidity)) + c, err := newCertConfig(hostKey, nextStart, nextStart.Add(certValidity)) if err != nil { return err } @@ -95,7 +114,7 @@ func (m *certManager) rollConfig() error { return m.cacheAddrComponent() } -func (m *certManager) background() { +func (m *certManager) background(hostKey ic.PrivKey) { d := m.currentConfig.End().Add(-clockSkewAllowance).Sub(m.clock.Now()) log.Debugw("setting timer", "duration", d.String()) t := m.clock.Timer(d) @@ -111,7 +130,7 @@ func (m *certManager) background() { return case now := <-t.C: m.mx.Lock() - if err := m.rollConfig(); err != nil { + if err := m.rollConfig(hostKey); err != nil { log.Errorw("rolling config failed", "error", err) } d := m.currentConfig.End().Add(-clockSkewAllowance).Sub(now) diff --git a/p2p/transport/webtransport/cert_manager_test.go b/p2p/transport/webtransport/cert_manager_test.go index 3f2328fbb7..ec2267255a 100644 --- a/p2p/transport/webtransport/cert_manager_test.go +++ b/p2p/transport/webtransport/cert_manager_test.go @@ -3,10 +3,13 @@ package libp2pwebtransport import ( "crypto/sha256" "crypto/tls" + "fmt" "testing" "time" "github.com/benbjohnson/clock" + "github.com/libp2p/go-libp2p/core/crypto" + "github.com/libp2p/go-libp2p/core/test" ma "github.com/multiformats/go-multiaddr" "github.com/multiformats/go-multibase" "github.com/multiformats/go-multihash" @@ -39,7 +42,9 @@ func certHashFromComponent(t *testing.T, comp ma.Component) []byte { func TestInitialCert(t *testing.T) { cl := clock.NewMock() cl.Add(1234567 * time.Hour) - m, err := newCertManager(cl) + priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 32) + require.NoError(t, err) + m, err := newCertManager(priv, cl) require.NoError(t, err) defer m.Close() @@ -59,7 +64,9 @@ func TestInitialCert(t *testing.T) { func TestCertRenewal(t *testing.T) { cl := clock.NewMock() - m, err := newCertManager(cl) + priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 32) + require.NoError(t, err) + m, err := newCertManager(priv, cl) require.NoError(t, err) defer m.Close() @@ -100,3 +107,47 @@ func TestCertRenewal(t *testing.T) { // check that the 2nd certificate from the beginning was rolled over to be the 1st certificate require.Equal(t, second[1].Value(), third[0].Value()) } + +func TestDeterministicCertsAcrossReboots(t *testing.T) { + // Run this test 100 times to make sure it's deterministic + runs := 100 + for i := 0; i < runs; i++ { + t.Run(fmt.Sprintf("Run=%d", i), func(t *testing.T) { + cl := clock.NewMock() + priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 32) + require.NoError(t, err) + m, err := newCertManager(priv, cl) + require.NoError(t, err) + defer m.Close() + + conf := m.GetConfig() + require.Len(t, conf.Certificates, 1) + oldCerts := m.serializedCertHashes + + m.Close() + + cl.Add(time.Hour) + // reboot + m, err = newCertManager(priv, cl) + require.NoError(t, err) + defer m.Close() + + newCerts := m.serializedCertHashes + + require.Equal(t, oldCerts, newCerts) + }) + } +} + +func TestDeterministicTimeBuckets(t *testing.T) { + bucketsA := getTimeBuckets((time.Time{})) + bucketsB := getTimeBuckets((time.Time{}.Add(time.Hour * 24))) + require.Equal(t, bucketsA.current, bucketsB.current) + require.Equal(t, bucketsA.next, bucketsB.next) + + // 15 Days later + bucketsC := getTimeBuckets((time.Time{}.Add(time.Hour * 24 * 15))) + require.Equal(t, bucketsC.current, bucketsB.next) + require.NotEqual(t, bucketsC.next, bucketsB.next) + require.NotEqual(t, bucketsC.current, bucketsB.current) +} diff --git a/p2p/transport/webtransport/crypto.go b/p2p/transport/webtransport/crypto.go index 0ea71323af..bef6fff392 100644 --- a/p2p/transport/webtransport/crypto.go +++ b/p2p/transport/webtransport/crypto.go @@ -4,7 +4,6 @@ import ( "bytes" "crypto/ecdsa" "crypto/elliptic" - "crypto/rand" "crypto/sha256" "crypto/tls" "crypto/x509" @@ -12,14 +11,21 @@ import ( "encoding/binary" "errors" "fmt" + "io" "math/big" "time" + ic "github.com/libp2p/go-libp2p/core/crypto" + "github.com/multiformats/go-multihash" + "golang.org/x/crypto/hkdf" ) -func getTLSConf(start, end time.Time) (*tls.Config, error) { - cert, priv, err := generateCert(start, end) +const certSerialInfo = "certificate serial number" +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 } @@ -32,9 +38,22 @@ func getTLSConf(start, end time.Time) (*tls.Config, error) { }, nil } -func generateCert(start, end time.Time) (*x509.Certificate, *ecdsa.PrivateKey, error) { +// 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 + } + + serialReader := hkdf.New(sha256.New, keyBytes, nil, []byte(certSerialInfo)) + + startTimeSalt := make([]byte, 8) + binary.LittleEndian.PutUint64(startTimeSalt, uint64(start.UnixNano())) + deterministicHKDFReader := newDeterministicReader(keyBytes, startTimeSalt, deterministicCertInfo) + b := make([]byte, 8) - if _, err := rand.Read(b); err != nil { + if _, err := serialReader.Read(b); err != nil { return nil, nil, err } serial := int64(binary.BigEndian.Uint64(b)) @@ -51,11 +70,12 @@ func generateCert(start, end time.Time) (*x509.Certificate, *ecdsa.PrivateKey, e KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, BasicConstraintsValid: true, } - caPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + + caPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), deterministicHKDFReader) if err != nil { return nil, nil, err } - caBytes, err := x509.CreateCertificate(rand.Reader, certTempl, certTempl, &caPrivateKey.PublicKey, caPrivateKey) + caBytes, err := x509.CreateCertificate(deterministicHKDFReader, certTempl, certTempl, caPrivateKey.Public(), caPrivateKey) if err != nil { return nil, nil, err } @@ -106,3 +126,30 @@ 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, + } +} + +func (r *deterministicReader) Read(p []byte) (n int, err error) { + if len(p) == 1 { + return r.singleByteReader.Read(p) + } + return r.reader.Read(p) +} diff --git a/p2p/transport/webtransport/crypto_test.go b/p2p/transport/webtransport/crypto_test.go index d6d106202a..143cd6de45 100644 --- a/p2p/transport/webtransport/crypto_test.go +++ b/p2p/transport/webtransport/crypto_test.go @@ -1,6 +1,7 @@ package libp2pwebtransport import ( + "bytes" "crypto" "crypto/ecdsa" "crypto/elliptic" @@ -10,11 +11,13 @@ import ( "crypto/x509" "crypto/x509/pkix" "fmt" + "io" "math/big" mrand "math/rand" "testing" "time" + ic "github.com/libp2p/go-libp2p/core/crypto" "github.com/multiformats/go-multihash" "github.com/stretchr/testify/require" ) @@ -130,3 +133,68 @@ func TestCertificateVerification(t *testing.T) { }) } } + +func TestDeterministicCertHashes(t *testing.T) { + // Run this test 1000 times since we want to make sure the signatures are deterministic + runs := 1000 + for i := 0; i < runs; i++ { + t.Run(fmt.Sprintf("Run=%d", i), func(t *testing.T) { + zeroSeed := [32]byte{} + priv, _, err := ic.GenerateEd25519Key(bytes.NewReader(zeroSeed[:])) + require.NoError(t, err) + cert, certPriv, err := generateCert(priv, time.Time{}, time.Time{}.Add(time.Hour*24*14)) + require.NoError(t, err) + + keyBytes, err := x509.MarshalECPrivateKey(certPriv) + require.NoError(t, err) + + cert2, certPriv2, err := generateCert(priv, time.Time{}, time.Time{}.Add(time.Hour*24*14)) + require.NoError(t, err) + + require.Equal(t, cert2.Signature, cert.Signature) + require.Equal(t, cert2.Raw, cert.Raw) + keyBytes2, err := x509.MarshalECPrivateKey(certPriv2) + require.NoError(t, err) + 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 i := 0; i < runs; i++ { + t.Run(fmt.Sprintf("Run=%d", i), func(t *testing.T) { + 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) + }) + } +} diff --git a/p2p/transport/webtransport/transport.go b/p2p/transport/webtransport/transport.go index 3d902b4edc..bebee9b287 100644 --- a/p2p/transport/webtransport/transport.go +++ b/p2p/transport/webtransport/transport.go @@ -293,7 +293,7 @@ func (t *transport) Listen(laddr ma.Multiaddr) (tpt.Listener, error) { } if t.staticTLSConf == nil { t.listenOnce.Do(func() { - t.certManager, t.listenOnceErr = newCertManager(t.clock) + t.certManager, t.listenOnceErr = newCertManager(t.privKey, t.clock) }) if t.listenOnceErr != nil { return nil, t.listenOnceErr From b039a0f1f033b751e96bb7ec4dcf752a2d97a422 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Mon, 17 Oct 2022 20:39:35 +0100 Subject: [PATCH 02/18] Update test to work with buckets --- p2p/transport/webtransport/cert_manager_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/p2p/transport/webtransport/cert_manager_test.go b/p2p/transport/webtransport/cert_manager_test.go index ec2267255a..a13f4e0fea 100644 --- a/p2p/transport/webtransport/cert_manager_test.go +++ b/p2p/transport/webtransport/cert_manager_test.go @@ -51,7 +51,8 @@ func TestInitialCert(t *testing.T) { conf := m.GetConfig() require.Len(t, conf.Certificates, 1) cert := conf.Certificates[0] - require.Equal(t, cl.Now().Add(-clockSkewAllowance).UTC(), cert.Leaf.NotBefore) + timeBuckets := getTimeBuckets(cl.Now()) + require.Equal(t, timeBuckets.current.UTC(), cert.Leaf.NotBefore) require.Equal(t, cert.Leaf.NotBefore.Add(certValidity), cert.Leaf.NotAfter) addr := m.AddrComponent() components := splitMultiaddr(addr) @@ -64,6 +65,7 @@ func TestInitialCert(t *testing.T) { func TestCertRenewal(t *testing.T) { cl := clock.NewMock() + cl.Set(time.UnixMilli(0)) priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 32) require.NoError(t, err) m, err := newCertManager(priv, cl) @@ -84,7 +86,7 @@ func TestCertRenewal(t *testing.T) { } return false }, 100*time.Millisecond, 10*time.Millisecond) - cl.Add(2 * time.Second) + cl.Add(clockSkewAllowance + 2*time.Second) require.Eventually(t, func() bool { return m.GetConfig() != firstConf }, 200*time.Millisecond, 10*time.Millisecond) secondConf := m.GetConfig() From b9e79babe3e6f58a06a8027d95fd053af2f4fc0f Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Thu, 20 Oct 2022 15:37:13 +0100 Subject: [PATCH 03/18] Make sure to overlap and use a random offset --- p2p/transport/webtransport/cert_manager.go | 42 +++++++++++-------- .../webtransport/cert_manager_test.go | 20 ++++----- p2p/transport/webtransport/crypto.go | 5 +-- 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/p2p/transport/webtransport/cert_manager.go b/p2p/transport/webtransport/cert_manager.go index 81221d6f15..8a34bf53f4 100644 --- a/p2p/transport/webtransport/cert_manager.go +++ b/p2p/transport/webtransport/cert_manager.go @@ -4,6 +4,7 @@ import ( "context" "crypto/sha256" "crypto/tls" + "encoding/binary" "fmt" "sync" "time" @@ -18,6 +19,7 @@ import ( // When we generate a certificate, the NotBefore time is set to clockSkewAllowance before the current time. // Similarly, we stop using a certificate one clockSkewAllowance before its expiry time. const clockSkewAllowance = time.Hour +const validityMinusTwoSkew = certValidity - (2 * clockSkewAllowance) type certConfig struct { tlsConf *tls.Config @@ -69,28 +71,32 @@ func newCertManager(hostKey ic.PrivKey, clock clock.Clock) (*certManager, error) return m, nil } -type timeBuckets struct { - current time.Time - next time.Time -} - -// getTimeBuckets returns a canonical set of times that are bucketed in ranges -// of certValidity since unix epoch. This lets you get the same time ranges -// across reboots without having to persist state. -func getTimeBuckets(now time.Time) timeBuckets { - currentBucket := now.UnixMilli() / certValidity.Milliseconds() - return timeBuckets{ - current: time.UnixMilli((currentBucket) * certValidity.Milliseconds()), - next: time.UnixMilli((currentBucket + 1) * certValidity.Milliseconds()), - } - +// getCurrentTimeBucket returns the canonical start time of the given time are bucketed by +// ranges of certValidity since unix epoch. This lets you get the same time +// ranges across reboots without having to persist state. timeBuckets represent +// our current time bbucket and our next time bucket. +// These overlap by 2*clock skew. +// ``` +// ... |--------| |--------| ... +// ... |--------| |--------| ... +// ``` +func getCurrentBucketStartTime(now time.Time, offset time.Duration) time.Time { + currentBucket := (now.UnixMilli() - offset.Milliseconds()) / validityMinusTwoSkew.Milliseconds() + return time.UnixMilli(offset.Milliseconds() + currentBucket*validityMinusTwoSkew.Milliseconds()) } func (m *certManager) init(hostKey ic.PrivKey) error { start := m.clock.Now().Add(-clockSkewAllowance) - timeBuckets := getTimeBuckets(start) - var err error - m.nextConfig, err = newCertConfig(hostKey, timeBuckets.current, timeBuckets.next) + pubkeyBytes, err := hostKey.GetPublic().Raw() + if err != nil { + return err + } + offset := (time.Duration(binary.LittleEndian.Uint16(pubkeyBytes)) * time.Minute) % certValidity + // Subtract clock skew since the currentBucket start time can be at most our + // current time and we want to make sure that our cert is valid from 1 clock + // skew before now. + startTime := getCurrentBucketStartTime(start, offset).Add(-clockSkewAllowance) + m.nextConfig, err = newCertConfig(hostKey, startTime, startTime.Add(certValidity)) if err != nil { return err } diff --git a/p2p/transport/webtransport/cert_manager_test.go b/p2p/transport/webtransport/cert_manager_test.go index a13f4e0fea..f21239cdf9 100644 --- a/p2p/transport/webtransport/cert_manager_test.go +++ b/p2p/transport/webtransport/cert_manager_test.go @@ -51,8 +51,7 @@ func TestInitialCert(t *testing.T) { conf := m.GetConfig() require.Len(t, conf.Certificates, 1) cert := conf.Certificates[0] - timeBuckets := getTimeBuckets(cl.Now()) - require.Equal(t, timeBuckets.current.UTC(), cert.Leaf.NotBefore) + require.GreaterOrEqual(t, cl.Now(), cert.Leaf.NotBefore) require.Equal(t, cert.Leaf.NotBefore.Add(certValidity), cert.Leaf.NotAfter) addr := m.AddrComponent() components := splitMultiaddr(addr) @@ -77,7 +76,7 @@ func TestCertRenewal(t *testing.T) { require.Len(t, first, 2) require.NotEqual(t, first[0].Value(), first[1].Value(), "the hashes should differ") // wait for a new certificate to be generated - cl.Add(certValidity - 2*clockSkewAllowance - time.Second) + cl.Set(m.currentConfig.End().Add(-(2*clockSkewAllowance + time.Second))) require.Never(t, func() bool { for i, c := range splitMultiaddr(m.AddrComponent()) { if c.Value() != first[i].Value() { @@ -86,6 +85,7 @@ func TestCertRenewal(t *testing.T) { } return false }, 100*time.Millisecond, 10*time.Millisecond) + m.currentConfig.End() cl.Add(clockSkewAllowance + 2*time.Second) require.Eventually(t, func() bool { return m.GetConfig() != firstConf }, 200*time.Millisecond, 10*time.Millisecond) secondConf := m.GetConfig() @@ -142,14 +142,12 @@ func TestDeterministicCertsAcrossReboots(t *testing.T) { } func TestDeterministicTimeBuckets(t *testing.T) { - bucketsA := getTimeBuckets((time.Time{})) - bucketsB := getTimeBuckets((time.Time{}.Add(time.Hour * 24))) - require.Equal(t, bucketsA.current, bucketsB.current) - require.Equal(t, bucketsA.next, bucketsB.next) + cl := clock.NewMock() + startA := getCurrentBucketStartTime(cl.Now(), 0) + startB := getCurrentBucketStartTime(cl.Now().Add(time.Hour*24), 0) + require.Equal(t, startA, startB) // 15 Days later - bucketsC := getTimeBuckets((time.Time{}.Add(time.Hour * 24 * 15))) - require.Equal(t, bucketsC.current, bucketsB.next) - require.NotEqual(t, bucketsC.next, bucketsB.next) - require.NotEqual(t, bucketsC.current, bucketsB.current) + startC := getCurrentBucketStartTime(cl.Now().Add(time.Hour*24*15), 0) + require.NotEqual(t, startC, startB) } diff --git a/p2p/transport/webtransport/crypto.go b/p2p/transport/webtransport/crypto.go index bef6fff392..dc2c1f03a7 100644 --- a/p2p/transport/webtransport/crypto.go +++ b/p2p/transport/webtransport/crypto.go @@ -21,7 +21,6 @@ import ( "golang.org/x/crypto/hkdf" ) -const certSerialInfo = "certificate serial number" const deterministicCertInfo = "determinisitic cert" func getTLSConf(key ic.PrivKey, start, end time.Time) (*tls.Config, error) { @@ -46,14 +45,12 @@ func generateCert(key ic.PrivKey, start, end time.Time) (*x509.Certificate, *ecd return nil, nil, err } - serialReader := hkdf.New(sha256.New, keyBytes, nil, []byte(certSerialInfo)) - startTimeSalt := make([]byte, 8) binary.LittleEndian.PutUint64(startTimeSalt, uint64(start.UnixNano())) deterministicHKDFReader := newDeterministicReader(keyBytes, startTimeSalt, deterministicCertInfo) b := make([]byte, 8) - if _, err := serialReader.Read(b); err != nil { + if _, err := deterministicHKDFReader.Read(b); err != nil { return nil, nil, err } serial := int64(binary.BigEndian.Uint64(b)) From 64f3ce3b472051a4f4b6b929222396e751c81bc9 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Thu, 20 Oct 2022 18:32:39 +0100 Subject: [PATCH 04/18] Fixup mistaken change in other test --- p2p/transport/websocket/websocket_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/transport/websocket/websocket_test.go b/p2p/transport/websocket/websocket_test.go index c167d42538..645dc82452 100644 --- a/p2p/transport/websocket/websocket_test.go +++ b/p2p/transport/websocket/websocket_test.go @@ -73,7 +73,7 @@ func newInsecureMuxer(t *testing.T) (peer.ID, sec.SecureMuxer) { func newSecureMuxer(t *testing.T) (peer.ID, sec.SecureMuxer) { t.Helper() - priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 32) + priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 256) if err != nil { t.Fatal(err) } From 13b204a946a56d5e60228ed12da46ce36f84123c Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Thu, 20 Oct 2022 18:33:16 +0100 Subject: [PATCH 05/18] Add QuickCheck tests for cert behavior --- p2p/transport/webtransport/cert_manager.go | 2 +- .../webtransport/cert_manager_test.go | 28 +++++- p2p/transport/webtransport/transport_test.go | 90 +++++++++++++++++++ 3 files changed, 116 insertions(+), 4 deletions(-) diff --git a/p2p/transport/webtransport/cert_manager.go b/p2p/transport/webtransport/cert_manager.go index 8a34bf53f4..89edb2325d 100644 --- a/p2p/transport/webtransport/cert_manager.go +++ b/p2p/transport/webtransport/cert_manager.go @@ -86,7 +86,7 @@ func getCurrentBucketStartTime(now time.Time, offset time.Duration) time.Time { } func (m *certManager) init(hostKey ic.PrivKey) error { - start := m.clock.Now().Add(-clockSkewAllowance) + start := m.clock.Now() pubkeyBytes, err := hostKey.GetPublic().Raw() if err != nil { return err diff --git a/p2p/transport/webtransport/cert_manager_test.go b/p2p/transport/webtransport/cert_manager_test.go index f21239cdf9..f78c1f3817 100644 --- a/p2p/transport/webtransport/cert_manager_test.go +++ b/p2p/transport/webtransport/cert_manager_test.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "fmt" "testing" + "testing/quick" "time" "github.com/benbjohnson/clock" @@ -42,7 +43,7 @@ func certHashFromComponent(t *testing.T, comp ma.Component) []byte { func TestInitialCert(t *testing.T) { cl := clock.NewMock() cl.Add(1234567 * time.Hour) - priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 32) + priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 256) require.NoError(t, err) m, err := newCertManager(priv, cl) require.NoError(t, err) @@ -65,7 +66,7 @@ func TestInitialCert(t *testing.T) { func TestCertRenewal(t *testing.T) { cl := clock.NewMock() cl.Set(time.UnixMilli(0)) - priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 32) + priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 256) require.NoError(t, err) m, err := newCertManager(priv, cl) require.NoError(t, err) @@ -116,7 +117,7 @@ func TestDeterministicCertsAcrossReboots(t *testing.T) { for i := 0; i < runs; i++ { t.Run(fmt.Sprintf("Run=%d", i), func(t *testing.T) { cl := clock.NewMock() - priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 32) + priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 256) require.NoError(t, err) m, err := newCertManager(priv, cl) require.NoError(t, err) @@ -151,3 +152,24 @@ func TestDeterministicTimeBuckets(t *testing.T) { startC := getCurrentBucketStartTime(cl.Now().Add(time.Hour*24*15), 0) require.NotEqual(t, startC, startB) } + +func TestGetCurrentBucketStartTimeIsWithinBounds(t *testing.T) { + require.NoError(t, quick.Check(func(timeSinceUnixEpoch time.Duration, offset time.Duration) bool { + if offset < 0 { + offset = -offset + } + if timeSinceUnixEpoch < 0 { + timeSinceUnixEpoch = -timeSinceUnixEpoch + } + + offset = offset % certValidity + // Bound this to 100 years + timeSinceUnixEpoch = time.Duration(timeSinceUnixEpoch % (time.Hour * 24 * 365 * 100)) + // Start a bit further in the future to avoid edge cases around epoch + timeSinceUnixEpoch += time.Hour * 24 * 365 + start := time.UnixMilli(timeSinceUnixEpoch.Milliseconds()) + + bucketStart := getCurrentBucketStartTime(start, offset) + return !bucketStart.After(start) || bucketStart.Equal(start) + }, nil)) +} diff --git a/p2p/transport/webtransport/transport_test.go b/p2p/transport/webtransport/transport_test.go index de4cf2263c..d720928855 100644 --- a/p2p/transport/webtransport/transport_test.go +++ b/p2p/transport/webtransport/transport_test.go @@ -20,14 +20,20 @@ import ( "strings" "sync/atomic" "testing" + "testing/quick" "time" + "github.com/benbjohnson/clock" + "github.com/libp2p/go-libp2p/core/crypto" ic "github.com/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/network" mocknetwork "github.com/libp2p/go-libp2p/core/network/mocks" "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/test" tpt "github.com/libp2p/go-libp2p/core/transport" libp2pwebtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport" + "github.com/lucas-clemente/quic-go" + "github.com/lucas-clemente/quic-go/interop/utils" "github.com/golang/mock/gomock" quicproxy "github.com/lucas-clemente/quic-go/integrationtests/tools/proxy" @@ -714,3 +720,87 @@ func TestFlowControlWindowIncrease(t *testing.T) { t.Fatal("timeout") } } + +func TestServerSendsBackValidCert(t *testing.T) { + const clockSkewAllowance = time.Hour + var maxTimeoutErrors = 10 + + require.NoError(t, quick.Check(func(timeSinceUnixEpoch time.Duration, keySeed int64, randomClientSkew time.Duration) bool { + if timeSinceUnixEpoch < 0 { + timeSinceUnixEpoch = -timeSinceUnixEpoch + } + + // Bound this to 100 years + timeSinceUnixEpoch = time.Duration(timeSinceUnixEpoch % (time.Hour * 24 * 365 * 100)) + // Start a bit further in the future to avoid edge cases around epoch + timeSinceUnixEpoch += time.Hour * 24 * 365 + start := time.UnixMilli(timeSinceUnixEpoch.Milliseconds()) + + randomClientSkew = randomClientSkew % clockSkewAllowance + + cl := clock.NewMock() + cl.Set(start) + + priv, _, err := test.SeededTestKeyPair(crypto.Ed25519, 256, keySeed) + if err != nil { + return false + } + tr, err := libp2pwebtransport.New(priv, nil, network.NullResourceManager, libp2pwebtransport.WithClock(cl)) + if err != nil { + return false + } + l, err := tr.Listen(ma.StringCast("/ip4/127.0.0.1/udp/9193/quic/webtransport")) + if err != nil { + return false + } + defer l.Close() + + getLogWriter, err := utils.GetQLOGWriter() + if err != nil { + panic(err) + } + + conn, err := quic.DialAddr(l.Addr().String(), &tls.Config{ + NextProtos: []string{"h3"}, + InsecureSkipVerify: true, + VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + for _, c := range rawCerts { + cert, err := x509.ParseCertificate(c) + if err != nil { + return err + } + + for _, clientSkew := range []time.Duration{randomClientSkew, -clockSkewAllowance, clockSkewAllowance} { + clientTime := cl.Now().Add(clientSkew) + if clientTime.After(cert.NotAfter) || clientTime.Before(cert.NotBefore) { + return fmt.Errorf("Times are not valid: server_now=%v client_now=%v certstart=%v certend=%v", cl.Now().UTC(), clientTime.UTC(), cert.NotBefore.UTC(), cert.NotAfter.UTC()) + } + } + + } + return nil + }, + }, &quic.Config{MaxIdleTimeout: time.Second}) + _ = getLogWriter + + if err != nil { + if _, ok := err.(*quic.IdleTimeoutError); ok { + maxTimeoutErrors -= 1 + fmt.Println("Timeout") + if maxTimeoutErrors <= 0 { + fmt.Println("Too many timeout errors") + } + // Sporadic timeout errors on macOS + return true + } + // Print the error so we see what happened, since we only return + // true/false to quickcheck + fmt.Println("Error:", err) + return false + } + defer conn.CloseWithError(0, "") + + return true + + }, nil)) +} From f5cca71d758fff213b04040610ac3fe6dab60ebb Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Thu, 20 Oct 2022 20:06:21 +0100 Subject: [PATCH 06/18] Lint fix --- p2p/transport/webtransport/transport_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/p2p/transport/webtransport/transport_test.go b/p2p/transport/webtransport/transport_test.go index d720928855..415039db30 100644 --- a/p2p/transport/webtransport/transport_test.go +++ b/p2p/transport/webtransport/transport_test.go @@ -24,7 +24,6 @@ import ( "time" "github.com/benbjohnson/clock" - "github.com/libp2p/go-libp2p/core/crypto" ic "github.com/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/network" mocknetwork "github.com/libp2p/go-libp2p/core/network/mocks" @@ -741,7 +740,7 @@ func TestServerSendsBackValidCert(t *testing.T) { cl := clock.NewMock() cl.Set(start) - priv, _, err := test.SeededTestKeyPair(crypto.Ed25519, 256, keySeed) + priv, _, err := test.SeededTestKeyPair(ic.Ed25519, 256, keySeed) if err != nil { return false } From 7c6dce5c159fc2fa399418a2c1ac19ec735442cd Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Fri, 21 Oct 2022 13:06:35 +0100 Subject: [PATCH 07/18] Add more tests --- p2p/transport/webtransport/cert_manager.go | 14 ++- p2p/transport/webtransport/transport_test.go | 110 ++++++++++++++++++- 2 files changed, 119 insertions(+), 5 deletions(-) diff --git a/p2p/transport/webtransport/cert_manager.go b/p2p/transport/webtransport/cert_manager.go index 89edb2325d..c543c38921 100644 --- a/p2p/transport/webtransport/cert_manager.go +++ b/p2p/transport/webtransport/cert_manager.go @@ -91,11 +91,17 @@ func (m *certManager) init(hostKey ic.PrivKey) error { if err != nil { return err } + + // We want to add a random offset to each start time so that not all certs + // rotate at the same time across the network. The offset represents moving + // the bucket start time some `offset` earlier. offset := (time.Duration(binary.LittleEndian.Uint16(pubkeyBytes)) * time.Minute) % certValidity - // Subtract clock skew since the currentBucket start time can be at most our - // current time and we want to make sure that our cert is valid from 1 clock - // skew before now. - startTime := getCurrentBucketStartTime(start, offset).Add(-clockSkewAllowance) + + // We want our offset to be at least clockSkewAllowance so that the + // certificate has at least been valid for one hour. + offset = (offset + clockSkewAllowance) % certValidity + + startTime := getCurrentBucketStartTime(start, offset) m.nextConfig, err = newCertConfig(hostKey, startTime, startTime.Add(certValidity)) if err != nil { return err diff --git a/p2p/transport/webtransport/transport_test.go b/p2p/transport/webtransport/transport_test.go index 415039db30..ce073c3650 100644 --- a/p2p/transport/webtransport/transport_test.go +++ b/p2p/transport/webtransport/transport_test.go @@ -43,6 +43,9 @@ import ( "github.com/stretchr/testify/require" ) +const clockSkewAllowance = time.Hour +const certValidity = 14 * 24 * time.Hour + func newIdentity(t *testing.T) (peer.ID, ic.PrivKey) { key, _, err := ic.GenerateEd25519Key(rand.Reader) require.NoError(t, err) @@ -721,7 +724,6 @@ func TestFlowControlWindowIncrease(t *testing.T) { } func TestServerSendsBackValidCert(t *testing.T) { - const clockSkewAllowance = time.Hour var maxTimeoutErrors = 10 require.NoError(t, quick.Check(func(timeSinceUnixEpoch time.Duration, keySeed int64, randomClientSkew time.Duration) bool { @@ -803,3 +805,109 @@ func TestServerSendsBackValidCert(t *testing.T) { }, nil)) } + +func TestServerRotatesCertCorrectly(t *testing.T) { + require.NoError(t, quick.Check(func(timeSinceUnixEpoch time.Duration, keySeed int64) bool { + if timeSinceUnixEpoch < 0 { + timeSinceUnixEpoch = -timeSinceUnixEpoch + } + + // Bound this to 100 years + timeSinceUnixEpoch = time.Duration(timeSinceUnixEpoch % (time.Hour * 24 * 365 * 100)) + // Start a bit further in the future to avoid edge cases around epoch + timeSinceUnixEpoch += time.Hour * 24 * 365 + start := time.UnixMilli(timeSinceUnixEpoch.Milliseconds()) + + cl := clock.NewMock() + cl.Set(start) + + priv, _, err := test.SeededTestKeyPair(ic.Ed25519, 256, keySeed) + if err != nil { + return false + } + tr, err := libp2pwebtransport.New(priv, nil, network.NullResourceManager, libp2pwebtransport.WithClock(cl)) + if err != nil { + return false + } + + l, err := tr.Listen(ma.StringCast("/ip4/127.0.0.1/udp/9193/quic/webtransport")) + if err != nil { + return false + } + certhashes := extractCertHashes(l.Multiaddr()) + l.Close() + + // These two certificates together are valid for at most certValidity - (4*clockSkewAllowance) + cl.Add(certValidity - (4 * clockSkewAllowance) - time.Second) + tr, err = libp2pwebtransport.New(priv, nil, network.NullResourceManager, libp2pwebtransport.WithClock(cl)) + if err != nil { + return false + } + + l, err = tr.Listen(ma.StringCast("/ip4/127.0.0.1/udp/9193/quic/webtransport")) + if err != nil { + return false + } + defer l.Close() + + var found bool + ma.ForEach(l.Multiaddr(), func(c ma.Component) bool { + if c.Protocol().Code == ma.P_CERTHASH { + for _, prevCerthash := range certhashes { + if c.Value() == prevCerthash { + found = true + return false + } + } + } + return true + }) + + return found + + }, nil)) +} + +func TestServerRotatesCertCorrectlyAfterSteps(t *testing.T) { + cl := clock.NewMock() + // Move one year ahead to avoid edge cases around epoch + cl.Add(time.Hour * 24 * 365) + + priv, _, err := test.RandTestKeyPair(ic.Ed25519, 256) + require.NoError(t, err) + tr, err := libp2pwebtransport.New(priv, nil, network.NullResourceManager, libp2pwebtransport.WithClock(cl)) + require.NoError(t, err) + + l, err := tr.Listen(ma.StringCast("/ip4/127.0.0.1/udp/9193/quic/webtransport")) + require.NoError(t, err) + + certhashes := extractCertHashes(l.Multiaddr()) + l.Close() + + // Traverse various time boundaries and make sure we always keep a common certhash. + // e.g. certhash/A/certhash/B ... -> ... certhash/B/certhash/C ... -> ... certhash/C/certhash/D + for i := 0; i < 200; i++ { + cl.Add(24 * time.Hour) + tr, err := libp2pwebtransport.New(priv, nil, network.NullResourceManager, libp2pwebtransport.WithClock(cl)) + require.NoError(t, err) + l, err := tr.Listen(ma.StringCast("/ip4/127.0.0.1/udp/9193/quic/webtransport")) + require.NoError(t, err) + + var found bool + ma.ForEach(l.Multiaddr(), func(c ma.Component) bool { + if c.Protocol().Code == ma.P_CERTHASH { + for _, prevCerthash := range certhashes { + if prevCerthash == c.Value() { + found = true + return false + } + } + } + return true + }) + certhashes = extractCertHashes(l.Multiaddr()) + l.Close() + + require.True(t, found, "Failed after hour: %v", i) + } +} From fce5ca55031a2f839f7664410771d24d27b65817 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Fri, 21 Oct 2022 15:00:57 +0100 Subject: [PATCH 08/18] Add webtransport integration test --- p2p/test/webtransport/webtransport_test.go | 48 ++++++++++++++++++++ p2p/transport/webtransport/transport_test.go | 10 ++-- 2 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 p2p/test/webtransport/webtransport_test.go diff --git a/p2p/test/webtransport/webtransport_test.go b/p2p/test/webtransport/webtransport_test.go new file mode 100644 index 0000000000..3169df73a3 --- /dev/null +++ b/p2p/test/webtransport/webtransport_test.go @@ -0,0 +1,48 @@ +package webtransport_test + +import ( + "testing" + "time" + + "github.com/benbjohnson/clock" + "github.com/libp2p/go-libp2p" + libp2pwebtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport" + ma "github.com/multiformats/go-multiaddr" + "github.com/stretchr/testify/require" +) + +func extractCertHashes(addr ma.Multiaddr) []string { + var certHashesStr []string + ma.ForEach(addr, func(c ma.Component) bool { + if c.Protocol().Code == ma.P_CERTHASH { + certHashesStr = append(certHashesStr, c.Value()) + } + return true + }) + return certHashesStr +} + +func TestDeterministicCertsAfterReboot(t *testing.T) { + cl := clock.NewMock() + // Move one year ahead to avoid edge cases around epoch + cl.Add(time.Hour * 24 * 365) + h, err := libp2p.New(libp2p.NoTransports, libp2p.Transport(libp2pwebtransport.New, libp2pwebtransport.WithClock(cl))) + require.NoError(t, err) + err = h.Network().Listen(ma.StringCast("/ip4/127.0.0.1/udp/0/quic/webtransport")) + require.NoError(t, err) + + prevCerthashes := extractCertHashes(h.Addrs()[0]) + h.Close() + + h, err = libp2p.New(libp2p.NoTransports, libp2p.Transport(libp2pwebtransport.New, libp2pwebtransport.WithClock(cl))) + require.NoError(t, err) + defer h.Close() + err = h.Network().Listen(ma.StringCast("/ip4/127.0.0.1/udp/0/quic/webtransport")) + require.NoError(t, err) + + nextCertHashes := extractCertHashes(h.Addrs()[0]) + + for i := range prevCerthashes { + require.Equal(t, prevCerthashes[i], nextCertHashes[i]) + } +} diff --git a/p2p/transport/webtransport/transport_test.go b/p2p/transport/webtransport/transport_test.go index ce073c3650..45effeca69 100644 --- a/p2p/transport/webtransport/transport_test.go +++ b/p2p/transport/webtransport/transport_test.go @@ -750,7 +750,7 @@ func TestServerSendsBackValidCert(t *testing.T) { if err != nil { return false } - l, err := tr.Listen(ma.StringCast("/ip4/127.0.0.1/udp/9193/quic/webtransport")) + l, err := tr.Listen(ma.StringCast("/ip4/127.0.0.1/udp/0/quic/webtransport")) if err != nil { return false } @@ -830,7 +830,7 @@ func TestServerRotatesCertCorrectly(t *testing.T) { return false } - l, err := tr.Listen(ma.StringCast("/ip4/127.0.0.1/udp/9193/quic/webtransport")) + l, err := tr.Listen(ma.StringCast("/ip4/127.0.0.1/udp/0/quic/webtransport")) if err != nil { return false } @@ -844,7 +844,7 @@ func TestServerRotatesCertCorrectly(t *testing.T) { return false } - l, err = tr.Listen(ma.StringCast("/ip4/127.0.0.1/udp/9193/quic/webtransport")) + l, err = tr.Listen(ma.StringCast("/ip4/127.0.0.1/udp/0/quic/webtransport")) if err != nil { return false } @@ -878,7 +878,7 @@ func TestServerRotatesCertCorrectlyAfterSteps(t *testing.T) { tr, err := libp2pwebtransport.New(priv, nil, network.NullResourceManager, libp2pwebtransport.WithClock(cl)) require.NoError(t, err) - l, err := tr.Listen(ma.StringCast("/ip4/127.0.0.1/udp/9193/quic/webtransport")) + l, err := tr.Listen(ma.StringCast("/ip4/127.0.0.1/udp/0/quic/webtransport")) require.NoError(t, err) certhashes := extractCertHashes(l.Multiaddr()) @@ -890,7 +890,7 @@ func TestServerRotatesCertCorrectlyAfterSteps(t *testing.T) { cl.Add(24 * time.Hour) tr, err := libp2pwebtransport.New(priv, nil, network.NullResourceManager, libp2pwebtransport.WithClock(cl)) require.NoError(t, err) - l, err := tr.Listen(ma.StringCast("/ip4/127.0.0.1/udp/9193/quic/webtransport")) + l, err := tr.Listen(ma.StringCast("/ip4/127.0.0.1/udp/0/quic/webtransport")) require.NoError(t, err) var found bool From 949cd48fb271718b174cb56e20b81a9669305e4a Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Fri, 21 Oct 2022 15:23:23 +0100 Subject: [PATCH 09/18] Use same key --- p2p/test/webtransport/webtransport_test.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/p2p/test/webtransport/webtransport_test.go b/p2p/test/webtransport/webtransport_test.go index 3169df73a3..adc64b49a3 100644 --- a/p2p/test/webtransport/webtransport_test.go +++ b/p2p/test/webtransport/webtransport_test.go @@ -6,6 +6,8 @@ import ( "github.com/benbjohnson/clock" "github.com/libp2p/go-libp2p" + ic "github.com/libp2p/go-libp2p/core/crypto" + "github.com/libp2p/go-libp2p/core/test" libp2pwebtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport" ma "github.com/multiformats/go-multiaddr" "github.com/stretchr/testify/require" @@ -23,10 +25,13 @@ func extractCertHashes(addr ma.Multiaddr) []string { } func TestDeterministicCertsAfterReboot(t *testing.T) { + priv, _, err := test.RandTestKeyPair(ic.Ed25519, 256) + require.NoError(t, err) + cl := clock.NewMock() // Move one year ahead to avoid edge cases around epoch cl.Add(time.Hour * 24 * 365) - h, err := libp2p.New(libp2p.NoTransports, libp2p.Transport(libp2pwebtransport.New, libp2pwebtransport.WithClock(cl))) + h, err := libp2p.New(libp2p.NoTransports, libp2p.Transport(libp2pwebtransport.New, libp2pwebtransport.WithClock(cl)), libp2p.Identity(priv)) require.NoError(t, err) err = h.Network().Listen(ma.StringCast("/ip4/127.0.0.1/udp/0/quic/webtransport")) require.NoError(t, err) @@ -34,7 +39,7 @@ func TestDeterministicCertsAfterReboot(t *testing.T) { prevCerthashes := extractCertHashes(h.Addrs()[0]) h.Close() - h, err = libp2p.New(libp2p.NoTransports, libp2p.Transport(libp2pwebtransport.New, libp2pwebtransport.WithClock(cl))) + h, err = libp2p.New(libp2p.NoTransports, libp2p.Transport(libp2pwebtransport.New, libp2pwebtransport.WithClock(cl)), libp2p.Identity(priv)) require.NoError(t, err) defer h.Close() err = h.Network().Listen(ma.StringCast("/ip4/127.0.0.1/udp/0/quic/webtransport")) From 461f0d113946099a8944a8e48199e639c7192a88 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Fri, 21 Oct 2022 15:39:29 +0100 Subject: [PATCH 10/18] Actually offset by at least clockSkew --- p2p/transport/webtransport/cert_manager.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/p2p/transport/webtransport/cert_manager.go b/p2p/transport/webtransport/cert_manager.go index c543c38921..b0511499ff 100644 --- a/p2p/transport/webtransport/cert_manager.go +++ b/p2p/transport/webtransport/cert_manager.go @@ -95,11 +95,11 @@ func (m *certManager) init(hostKey ic.PrivKey) error { // We want to add a random offset to each start time so that not all certs // rotate at the same time across the network. The offset represents moving // the bucket start time some `offset` earlier. - offset := (time.Duration(binary.LittleEndian.Uint16(pubkeyBytes)) * time.Minute) % certValidity + offset := (time.Duration(binary.LittleEndian.Uint16(pubkeyBytes)) * time.Minute) % (certValidity - clockSkewAllowance) // We want our offset to be at least clockSkewAllowance so that the // certificate has at least been valid for one hour. - offset = (offset + clockSkewAllowance) % certValidity + offset = offset + clockSkewAllowance startTime := getCurrentBucketStartTime(start, offset) m.nextConfig, err = newCertConfig(hostKey, startTime, startTime.Add(certValidity)) From 1d4ba6d05e98c43c5812a72e3c32a9640c279d79 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Sat, 22 Oct 2022 09:48:43 +0100 Subject: [PATCH 11/18] Use seeded key for certs after reboot test --- p2p/transport/webtransport/cert_manager_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/p2p/transport/webtransport/cert_manager_test.go b/p2p/transport/webtransport/cert_manager_test.go index f78c1f3817..0791f3b237 100644 --- a/p2p/transport/webtransport/cert_manager_test.go +++ b/p2p/transport/webtransport/cert_manager_test.go @@ -117,7 +117,7 @@ func TestDeterministicCertsAcrossReboots(t *testing.T) { for i := 0; i < runs; i++ { t.Run(fmt.Sprintf("Run=%d", i), func(t *testing.T) { cl := clock.NewMock() - priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 256) + priv, _, err := test.SeededTestKeyPair(crypto.Ed25519, 256, 0) require.NoError(t, err) m, err := newCertManager(priv, cl) require.NoError(t, err) @@ -144,6 +144,7 @@ func TestDeterministicCertsAcrossReboots(t *testing.T) { func TestDeterministicTimeBuckets(t *testing.T) { cl := clock.NewMock() + cl.Add(time.Hour * 24 * 365) startA := getCurrentBucketStartTime(cl.Now(), 0) startB := getCurrentBucketStartTime(cl.Now().Add(time.Hour*24), 0) require.Equal(t, startA, startB) @@ -170,6 +171,6 @@ func TestGetCurrentBucketStartTimeIsWithinBounds(t *testing.T) { start := time.UnixMilli(timeSinceUnixEpoch.Milliseconds()) bucketStart := getCurrentBucketStartTime(start, offset) - return !bucketStart.After(start) || bucketStart.Equal(start) + return !bucketStart.After(start.Add(-clockSkewAllowance)) || bucketStart.Equal(start.Add(-clockSkewAllowance)) }, nil)) } From ff2525e99d66648f41ffda0e61c196eaf722458a Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Sat, 22 Oct 2022 15:36:52 +0100 Subject: [PATCH 12/18] PR comments --- p2p/transport/webtransport/cert_manager_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/p2p/transport/webtransport/cert_manager_test.go b/p2p/transport/webtransport/cert_manager_test.go index 0791f3b237..1e3ee9d577 100644 --- a/p2p/transport/webtransport/cert_manager_test.go +++ b/p2p/transport/webtransport/cert_manager_test.go @@ -52,7 +52,7 @@ func TestInitialCert(t *testing.T) { conf := m.GetConfig() require.Len(t, conf.Certificates, 1) cert := conf.Certificates[0] - require.GreaterOrEqual(t, cl.Now(), cert.Leaf.NotBefore) + require.GreaterOrEqual(t, cl.Now().Add(-clockSkewAllowance), cert.Leaf.NotBefore) require.Equal(t, cert.Leaf.NotBefore.Add(certValidity), cert.Leaf.NotAfter) addr := m.AddrComponent() components := splitMultiaddr(addr) @@ -65,8 +65,9 @@ func TestInitialCert(t *testing.T) { func TestCertRenewal(t *testing.T) { cl := clock.NewMock() - cl.Set(time.UnixMilli(0)) - priv, _, err := test.RandTestKeyPair(crypto.Ed25519, 256) + // Add a year to avoid edge cases around the epoch + cl.Add(time.Hour * 24 * 365) + priv, _, err := test.SeededTestKeyPair(crypto.Ed25519, 256, 0) require.NoError(t, err) m, err := newCertManager(priv, cl) require.NoError(t, err) @@ -77,7 +78,7 @@ func TestCertRenewal(t *testing.T) { require.Len(t, first, 2) require.NotEqual(t, first[0].Value(), first[1].Value(), "the hashes should differ") // wait for a new certificate to be generated - cl.Set(m.currentConfig.End().Add(-(2*clockSkewAllowance + time.Second))) + cl.Set(m.currentConfig.End().Add(-(clockSkewAllowance + time.Second))) require.Never(t, func() bool { for i, c := range splitMultiaddr(m.AddrComponent()) { if c.Value() != first[i].Value() { @@ -86,8 +87,7 @@ func TestCertRenewal(t *testing.T) { } return false }, 100*time.Millisecond, 10*time.Millisecond) - m.currentConfig.End() - cl.Add(clockSkewAllowance + 2*time.Second) + cl.Add(2 * time.Second) require.Eventually(t, func() bool { return m.GetConfig() != firstConf }, 200*time.Millisecond, 10*time.Millisecond) secondConf := m.GetConfig() From c4d856e2f239821e2fa823a7ca6abbc3bc13c4ea Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Sat, 22 Oct 2022 15:39:24 +0100 Subject: [PATCH 13/18] Remove debug code --- p2p/transport/webtransport/transport_test.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/p2p/transport/webtransport/transport_test.go b/p2p/transport/webtransport/transport_test.go index 45effeca69..a18bda08bc 100644 --- a/p2p/transport/webtransport/transport_test.go +++ b/p2p/transport/webtransport/transport_test.go @@ -32,7 +32,6 @@ import ( tpt "github.com/libp2p/go-libp2p/core/transport" libp2pwebtransport "github.com/libp2p/go-libp2p/p2p/transport/webtransport" "github.com/lucas-clemente/quic-go" - "github.com/lucas-clemente/quic-go/interop/utils" "github.com/golang/mock/gomock" quicproxy "github.com/lucas-clemente/quic-go/integrationtests/tools/proxy" @@ -756,11 +755,6 @@ func TestServerSendsBackValidCert(t *testing.T) { } defer l.Close() - getLogWriter, err := utils.GetQLOGWriter() - if err != nil { - panic(err) - } - conn, err := quic.DialAddr(l.Addr().String(), &tls.Config{ NextProtos: []string{"h3"}, InsecureSkipVerify: true, @@ -782,12 +776,10 @@ func TestServerSendsBackValidCert(t *testing.T) { return nil }, }, &quic.Config{MaxIdleTimeout: time.Second}) - _ = getLogWriter if err != nil { if _, ok := err.(*quic.IdleTimeoutError); ok { maxTimeoutErrors -= 1 - fmt.Println("Timeout") if maxTimeoutErrors <= 0 { fmt.Println("Too many timeout errors") } From 5e1c1df8b428769631966fe4fee01ae137fb6934 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Mon, 31 Oct 2022 21:43:19 +0000 Subject: [PATCH 14/18] Fix calculation for cert having been valid Fixes the logic that a cert has been valid for a clockSkew by subtracting the clockSkew from the start time rather than incorporating it into the offset. The offset should be used to shift the buckets. --- p2p/transport/webtransport/cert_manager.go | 8 +- p2p/transport/webtransport/transport_test.go | 146 +++++++++++-------- 2 files changed, 91 insertions(+), 63 deletions(-) diff --git a/p2p/transport/webtransport/cert_manager.go b/p2p/transport/webtransport/cert_manager.go index b0511499ff..c56c089e39 100644 --- a/p2p/transport/webtransport/cert_manager.go +++ b/p2p/transport/webtransport/cert_manager.go @@ -95,12 +95,10 @@ func (m *certManager) init(hostKey ic.PrivKey) error { // We want to add a random offset to each start time so that not all certs // rotate at the same time across the network. The offset represents moving // the bucket start time some `offset` earlier. - offset := (time.Duration(binary.LittleEndian.Uint16(pubkeyBytes)) * time.Minute) % (certValidity - clockSkewAllowance) - - // We want our offset to be at least clockSkewAllowance so that the - // certificate has at least been valid for one hour. - offset = offset + clockSkewAllowance + offset := (time.Duration(binary.LittleEndian.Uint16(pubkeyBytes)) * time.Minute) % certValidity + // We want the certificate have been valid for at least one clockSkewAllowance + start = start.Add(-clockSkewAllowance) startTime := getCurrentBucketStartTime(start, offset) m.nextConfig, err = newCertConfig(hostKey, startTime, startTime.Add(certValidity)) if err != nil { diff --git a/p2p/transport/webtransport/transport_test.go b/p2p/transport/webtransport/transport_test.go index a18bda08bc..5e67eab3d7 100644 --- a/p2p/transport/webtransport/transport_test.go +++ b/p2p/transport/webtransport/transport_test.go @@ -722,82 +722,112 @@ func TestFlowControlWindowIncrease(t *testing.T) { } } -func TestServerSendsBackValidCert(t *testing.T) { - var maxTimeoutErrors = 10 +var timeoutErr = errors.New("timeout") - require.NoError(t, quick.Check(func(timeSinceUnixEpoch time.Duration, keySeed int64, randomClientSkew time.Duration) bool { - if timeSinceUnixEpoch < 0 { - timeSinceUnixEpoch = -timeSinceUnixEpoch - } +func serverSendsBackValidCert(timeSinceUnixEpoch time.Duration, keySeed int64, randomClientSkew time.Duration) error { + if timeSinceUnixEpoch < 0 { + timeSinceUnixEpoch = -timeSinceUnixEpoch + } - // Bound this to 100 years - timeSinceUnixEpoch = time.Duration(timeSinceUnixEpoch % (time.Hour * 24 * 365 * 100)) - // Start a bit further in the future to avoid edge cases around epoch - timeSinceUnixEpoch += time.Hour * 24 * 365 - start := time.UnixMilli(timeSinceUnixEpoch.Milliseconds()) + // Bound this to 100 years + timeSinceUnixEpoch = time.Duration(timeSinceUnixEpoch % (time.Hour * 24 * 365 * 100)) + // Start a bit further in the future to avoid edge cases around epoch + timeSinceUnixEpoch += time.Hour * 24 * 365 + start := time.UnixMilli(timeSinceUnixEpoch.Milliseconds()) - randomClientSkew = randomClientSkew % clockSkewAllowance + randomClientSkew = randomClientSkew % clockSkewAllowance - cl := clock.NewMock() - cl.Set(start) + cl := clock.NewMock() + cl.Set(start) - priv, _, err := test.SeededTestKeyPair(ic.Ed25519, 256, keySeed) - if err != nil { - return false - } - tr, err := libp2pwebtransport.New(priv, nil, network.NullResourceManager, libp2pwebtransport.WithClock(cl)) - if err != nil { - return false - } - l, err := tr.Listen(ma.StringCast("/ip4/127.0.0.1/udp/0/quic/webtransport")) - if err != nil { - return false - } - defer l.Close() + priv, _, err := test.SeededTestKeyPair(ic.Ed25519, 256, keySeed) + if err != nil { + return err + } + tr, err := libp2pwebtransport.New(priv, nil, network.NullResourceManager, libp2pwebtransport.WithClock(cl)) + if err != nil { + return err + } + l, err := tr.Listen(ma.StringCast("/ip4/127.0.0.1/udp/0/quic/webtransport")) + if err != nil { + return err + } + defer l.Close() + + conn, err := quic.DialAddr(l.Addr().String(), &tls.Config{ + NextProtos: []string{"h3"}, + InsecureSkipVerify: true, + VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + for _, c := range rawCerts { + cert, err := x509.ParseCertificate(c) + if err != nil { + return err + } - conn, err := quic.DialAddr(l.Addr().String(), &tls.Config{ - NextProtos: []string{"h3"}, - InsecureSkipVerify: true, - VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { - for _, c := range rawCerts { - cert, err := x509.ParseCertificate(c) - if err != nil { - return err + for _, clientSkew := range []time.Duration{randomClientSkew, -clockSkewAllowance, clockSkewAllowance} { + clientTime := cl.Now().Add(clientSkew) + if clientTime.After(cert.NotAfter) || clientTime.Before(cert.NotBefore) { + return fmt.Errorf("Times are not valid: server_now=%v client_now=%v certstart=%v certend=%v", cl.Now().UTC(), clientTime.UTC(), cert.NotBefore.UTC(), cert.NotAfter.UTC()) } + } - for _, clientSkew := range []time.Duration{randomClientSkew, -clockSkewAllowance, clockSkewAllowance} { - clientTime := cl.Now().Add(clientSkew) - if clientTime.After(cert.NotAfter) || clientTime.Before(cert.NotBefore) { - return fmt.Errorf("Times are not valid: server_now=%v client_now=%v certstart=%v certend=%v", cl.Now().UTC(), clientTime.UTC(), cert.NotBefore.UTC(), cert.NotAfter.UTC()) - } - } + } + return nil + }, + }, &quic.Config{MaxIdleTimeout: time.Second}) - } - return nil - }, - }, &quic.Config{MaxIdleTimeout: time.Second}) + if err != nil { + if _, ok := err.(*quic.IdleTimeoutError); ok { + return timeoutErr + } + return err + } + defer conn.CloseWithError(0, "") - if err != nil { - if _, ok := err.(*quic.IdleTimeoutError); ok { - maxTimeoutErrors -= 1 - if maxTimeoutErrors <= 0 { - fmt.Println("Too many timeout errors") - } - // Sporadic timeout errors on macOS - return true + return nil +} + +func TestServerSendsBackValidCert(t *testing.T) { + var maxTimeoutErrors = 10 + require.NoError(t, quick.Check(func(timeSinceUnixEpoch time.Duration, keySeed int64, randomClientSkew time.Duration) bool { + err := serverSendsBackValidCert(timeSinceUnixEpoch, keySeed, randomClientSkew) + if err == timeoutErr { + maxTimeoutErrors -= 1 + if maxTimeoutErrors <= 0 { + fmt.Println("Too many timeout errors") + return false } - // Print the error so we see what happened, since we only return - // true/false to quickcheck - fmt.Println("Error:", err) + // Sporadic timeout errors on macOS + return true + } else if err != nil { + fmt.Println("Err:", err) return false } - defer conn.CloseWithError(0, "") return true - }, nil)) } +func TestServerSendsBackValidCertEveryHour(t *testing.T) { + var maxTimeoutErrors = 10 + // A more exhaustive kind of test + days := 30 + hours := days * 24 + for h := 0; h < hours; h++ { + err := serverSendsBackValidCert(time.Hour*time.Duration(h), 0, 0) + if err == timeoutErr { + maxTimeoutErrors -= 1 + if maxTimeoutErrors <= 0 { + t.Fatalf("Too many timeout errors") + } + // Sporadic timeout errors on macOS + continue + } + + require.NoError(t, err) + } +} + func TestServerRotatesCertCorrectly(t *testing.T) { require.NoError(t, quick.Check(func(timeSinceUnixEpoch time.Duration, keySeed int64) bool { if timeSinceUnixEpoch < 0 { From afb9c97ede0abf5e97dc0c81d12e1b95fa4cf3cf Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Mon, 31 Oct 2022 21:51:06 +0000 Subject: [PATCH 15/18] Update comment --- p2p/transport/webtransport/cert_manager.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/p2p/transport/webtransport/cert_manager.go b/p2p/transport/webtransport/cert_manager.go index c56c089e39..9d9da18835 100644 --- a/p2p/transport/webtransport/cert_manager.go +++ b/p2p/transport/webtransport/cert_manager.go @@ -71,12 +71,12 @@ func newCertManager(hostKey ic.PrivKey, clock clock.Clock) (*certManager, error) return m, nil } -// getCurrentTimeBucket returns the canonical start time of the given time are bucketed by -// ranges of certValidity since unix epoch. This lets you get the same time -// ranges across reboots without having to persist state. timeBuckets represent -// our current time bbucket and our next time bucket. -// These overlap by 2*clock skew. +// getCurrentTimeBucket returns the canonical start time of the given time as +// bucketed by ranges of certValidity since unix epoch (plus an offset). This +// lets you get the same time ranges across reboots without having to persist +// state. // ``` +// ... v--- epoch + offset // ... |--------| |--------| ... // ... |--------| |--------| ... // ``` From ec9820a60f318e6894ad63cb29da618f40bc6b79 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Mon, 31 Oct 2022 21:52:10 +0000 Subject: [PATCH 16/18] Lint fix --- p2p/transport/webtransport/transport_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/p2p/transport/webtransport/transport_test.go b/p2p/transport/webtransport/transport_test.go index 5e67eab3d7..b22dd83066 100644 --- a/p2p/transport/webtransport/transport_test.go +++ b/p2p/transport/webtransport/transport_test.go @@ -722,7 +722,7 @@ func TestFlowControlWindowIncrease(t *testing.T) { } } -var timeoutErr = errors.New("timeout") +var errTimeout = errors.New("timeout") func serverSendsBackValidCert(timeSinceUnixEpoch time.Duration, keySeed int64, randomClientSkew time.Duration) error { if timeSinceUnixEpoch < 0 { @@ -778,7 +778,7 @@ func serverSendsBackValidCert(timeSinceUnixEpoch time.Duration, keySeed int64, r if err != nil { if _, ok := err.(*quic.IdleTimeoutError); ok { - return timeoutErr + return errTimeout } return err } @@ -791,7 +791,7 @@ func TestServerSendsBackValidCert(t *testing.T) { var maxTimeoutErrors = 10 require.NoError(t, quick.Check(func(timeSinceUnixEpoch time.Duration, keySeed int64, randomClientSkew time.Duration) bool { err := serverSendsBackValidCert(timeSinceUnixEpoch, keySeed, randomClientSkew) - if err == timeoutErr { + if err == errTimeout { maxTimeoutErrors -= 1 if maxTimeoutErrors <= 0 { fmt.Println("Too many timeout errors") @@ -815,7 +815,7 @@ func TestServerSendsBackValidCertEveryHour(t *testing.T) { hours := days * 24 for h := 0; h < hours; h++ { err := serverSendsBackValidCert(time.Hour*time.Duration(h), 0, 0) - if err == timeoutErr { + if err == errTimeout { maxTimeoutErrors -= 1 if maxTimeoutErrors <= 0 { t.Fatalf("Too many timeout errors") From f40bb200156740766a4cc798db1095cccf960f48 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Mon, 31 Oct 2022 22:02:20 +0000 Subject: [PATCH 17/18] Update TestGetCurrentBucketStartTimeIsWithinBounds to include clockSkew calculation --- p2p/transport/webtransport/cert_manager_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/p2p/transport/webtransport/cert_manager_test.go b/p2p/transport/webtransport/cert_manager_test.go index 1e3ee9d577..4b4550bb9d 100644 --- a/p2p/transport/webtransport/cert_manager_test.go +++ b/p2p/transport/webtransport/cert_manager_test.go @@ -170,7 +170,7 @@ func TestGetCurrentBucketStartTimeIsWithinBounds(t *testing.T) { timeSinceUnixEpoch += time.Hour * 24 * 365 start := time.UnixMilli(timeSinceUnixEpoch.Milliseconds()) - bucketStart := getCurrentBucketStartTime(start, offset) + bucketStart := getCurrentBucketStartTime(start.Add(-clockSkewAllowance), offset) return !bucketStart.After(start.Add(-clockSkewAllowance)) || bucketStart.Equal(start.Add(-clockSkewAllowance)) }, nil)) } From 3387eb7069a522a3a0081404f8b7d82c5c4626f8 Mon Sep 17 00:00:00 2001 From: Marco Munizaga Date: Mon, 31 Oct 2022 23:13:55 +0000 Subject: [PATCH 18/18] Rebase fixes --- p2p/transport/webtransport/transport_test.go | 30 ++++---------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/p2p/transport/webtransport/transport_test.go b/p2p/transport/webtransport/transport_test.go index b22dd83066..70d4ec4b3d 100644 --- a/p2p/transport/webtransport/transport_test.go +++ b/p2p/transport/webtransport/transport_test.go @@ -744,7 +744,7 @@ func serverSendsBackValidCert(timeSinceUnixEpoch time.Duration, keySeed int64, r if err != nil { return err } - tr, err := libp2pwebtransport.New(priv, nil, network.NullResourceManager, libp2pwebtransport.WithClock(cl)) + tr, err := libp2pwebtransport.New(priv, nil, &network.NullResourceManager{}, libp2pwebtransport.WithClock(cl)) if err != nil { return err } @@ -808,26 +808,6 @@ func TestServerSendsBackValidCert(t *testing.T) { }, nil)) } -func TestServerSendsBackValidCertEveryHour(t *testing.T) { - var maxTimeoutErrors = 10 - // A more exhaustive kind of test - days := 30 - hours := days * 24 - for h := 0; h < hours; h++ { - err := serverSendsBackValidCert(time.Hour*time.Duration(h), 0, 0) - if err == errTimeout { - maxTimeoutErrors -= 1 - if maxTimeoutErrors <= 0 { - t.Fatalf("Too many timeout errors") - } - // Sporadic timeout errors on macOS - continue - } - - require.NoError(t, err) - } -} - func TestServerRotatesCertCorrectly(t *testing.T) { require.NoError(t, quick.Check(func(timeSinceUnixEpoch time.Duration, keySeed int64) bool { if timeSinceUnixEpoch < 0 { @@ -847,7 +827,7 @@ func TestServerRotatesCertCorrectly(t *testing.T) { if err != nil { return false } - tr, err := libp2pwebtransport.New(priv, nil, network.NullResourceManager, libp2pwebtransport.WithClock(cl)) + tr, err := libp2pwebtransport.New(priv, nil, &network.NullResourceManager{}, libp2pwebtransport.WithClock(cl)) if err != nil { return false } @@ -861,7 +841,7 @@ func TestServerRotatesCertCorrectly(t *testing.T) { // These two certificates together are valid for at most certValidity - (4*clockSkewAllowance) cl.Add(certValidity - (4 * clockSkewAllowance) - time.Second) - tr, err = libp2pwebtransport.New(priv, nil, network.NullResourceManager, libp2pwebtransport.WithClock(cl)) + tr, err = libp2pwebtransport.New(priv, nil, &network.NullResourceManager{}, libp2pwebtransport.WithClock(cl)) if err != nil { return false } @@ -897,7 +877,7 @@ func TestServerRotatesCertCorrectlyAfterSteps(t *testing.T) { priv, _, err := test.RandTestKeyPair(ic.Ed25519, 256) require.NoError(t, err) - tr, err := libp2pwebtransport.New(priv, nil, network.NullResourceManager, libp2pwebtransport.WithClock(cl)) + tr, err := libp2pwebtransport.New(priv, nil, &network.NullResourceManager{}, libp2pwebtransport.WithClock(cl)) require.NoError(t, err) l, err := tr.Listen(ma.StringCast("/ip4/127.0.0.1/udp/0/quic/webtransport")) @@ -910,7 +890,7 @@ func TestServerRotatesCertCorrectlyAfterSteps(t *testing.T) { // e.g. certhash/A/certhash/B ... -> ... certhash/B/certhash/C ... -> ... certhash/C/certhash/D for i := 0; i < 200; i++ { cl.Add(24 * time.Hour) - tr, err := libp2pwebtransport.New(priv, nil, network.NullResourceManager, libp2pwebtransport.WithClock(cl)) + tr, err := libp2pwebtransport.New(priv, nil, &network.NullResourceManager{}, libp2pwebtransport.WithClock(cl)) require.NoError(t, err) l, err := tr.Listen(ma.StringCast("/ip4/127.0.0.1/udp/0/quic/webtransport")) require.NoError(t, err)