Skip to content

Commit

Permalink
Merge pull request #1737 from libp2p/merge-webtransport
Browse files Browse the repository at this point in the history
move go-libp2p-webtransport to p2p/transport/webtransport
  • Loading branch information
marten-seemann authored Sep 7, 2022
2 parents cb8c25f + 73b3d56 commit d934c56
Show file tree
Hide file tree
Showing 18 changed files with 2,343 additions and 1 deletion.
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@ require (
github.com/libp2p/zeroconf/v2 v2.2.0
github.com/lucas-clemente/quic-go v0.29.0
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd
github.com/marten-seemann/webtransport-go v0.1.0
github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b
github.com/minio/sha256-simd v1.0.0
github.com/mr-tron/base58 v1.2.0
github.com/multiformats/go-base32 v0.0.4
github.com/multiformats/go-multiaddr v0.6.0
github.com/multiformats/go-multiaddr-dns v0.3.1
github.com/multiformats/go-multiaddr-fmt v0.1.0
github.com/multiformats/go-multibase v0.1.1
github.com/multiformats/go-multicodec v0.5.0
github.com/multiformats/go-multihash v0.2.1
github.com/multiformats/go-multistream v0.3.3
Expand Down Expand Up @@ -81,6 +83,7 @@ require (
github.com/klauspost/cpuid/v2 v2.1.0 // indirect
github.com/koron/go-ssdp v0.0.3 // indirect
github.com/libp2p/go-cidranger v1.1.0 // indirect
github.com/marten-seemann/qpack v0.2.1 // indirect
github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect
github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
Expand All @@ -89,7 +92,6 @@ require (
github.com/miekg/dns v1.1.50 // indirect
github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect
github.com/multiformats/go-base36 v0.1.0 // indirect
github.com/multiformats/go-multibase v0.1.1 // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/onsi/ginkgo v1.16.5 // indirect
github.com/opencontainers/runtime-spec v1.0.2 // indirect
Expand All @@ -107,6 +109,7 @@ require (
golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/net v0.0.0-20220812174116-3211cb980234 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.12 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -321,12 +321,16 @@ github.com/lucas-clemente/quic-go v0.29.0/go.mod h1:CTcNfLYJS2UuRNB+zcNlgvkjBhxX
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs=
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
github.com/marten-seemann/qtls-go1-18 v0.1.2 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKAHMntXmIV7kM=
github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
github.com/marten-seemann/qtls-go1-19 v0.1.0 h1:rLFKD/9mp/uq1SYGYuVZhm83wkmU95pK5df3GufyYYU=
github.com/marten-seemann/qtls-go1-19 v0.1.0/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI=
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk=
github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU=
github.com/marten-seemann/webtransport-go v0.1.0 h1:tKknMOI+9fmMTPRJv3Fz7XpDq67Tg8Va8B1QEE+qaZw=
github.com/marten-seemann/webtransport-go v0.1.0/go.mod h1:CACJOFSohYRmnqHWHWZ2rpTr9MfA0pNqOSkpqb6J/+w=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
Expand Down Expand Up @@ -393,6 +397,7 @@ github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
Expand Down Expand Up @@ -695,6 +700,7 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
168 changes: 168 additions & 0 deletions p2p/transport/webtransport/cert_manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package libp2pwebtransport

import (
"bytes"
"context"
"crypto/sha256"
"crypto/tls"
"fmt"
"sync"
"time"

"github.com/benbjohnson/clock"
ma "github.com/multiformats/go-multiaddr"
"github.com/multiformats/go-multihash"
)

// Allow for a bit of clock skew.
// 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

type certConfig struct {
tlsConf *tls.Config
sha256 [32]byte // cached from the tlsConf
}

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)
if err != nil {
return nil, err
}
return &certConfig{
tlsConf: conf,
sha256: sha256.Sum256(conf.Certificates[0].Leaf.Raw),
}, nil
}

// Certificate renewal logic:
// 1. On startup, we generate one cert that is valid from now (-1h, to allow for clock skew), and another
// cert that is valid from the expiry date of the first certificate (again, with allowance for clock skew).
// 2. Once we reach 1h before expiry of the first certificate, we switch over to the second certificate.
// At the same time, we stop advertising the certhash of the first cert and generate the next cert.
type certManager struct {
clock clock.Clock
ctx context.Context
ctxCancel context.CancelFunc
refCount sync.WaitGroup

mx sync.RWMutex
lastConfig *certConfig // initially nil
currentConfig *certConfig
nextConfig *certConfig // nil until we have passed half the certValidity of the current config
addrComp ma.Multiaddr
}

func newCertManager(clock clock.Clock) (*certManager, error) {
m := &certManager{clock: clock}
m.ctx, m.ctxCancel = context.WithCancel(context.Background())
if err := m.init(); err != nil {
return nil, err
}

m.background()
return m, nil
}

func (m *certManager) init() error {
start := m.clock.Now().Add(-clockSkewAllowance)
var err error
m.nextConfig, err = newCertConfig(start, start.Add(certValidity))
if err != nil {
return err
}
return m.rollConfig()
}

func (m *certManager) rollConfig() 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))
if err != nil {
return err
}
m.lastConfig = m.currentConfig
m.currentConfig = m.nextConfig
m.nextConfig = c
return m.cacheAddrComponent()
}

func (m *certManager) background() {
d := m.currentConfig.End().Add(-clockSkewAllowance).Sub(m.clock.Now())
log.Debugw("setting timer", "duration", d.String())
t := m.clock.Timer(d)
m.refCount.Add(1)

go func() {
defer m.refCount.Done()
defer t.Stop()

for {
select {
case <-m.ctx.Done():
return
case now := <-t.C:
m.mx.Lock()
if err := m.rollConfig(); err != nil {
log.Errorw("rolling config failed", "error", err)
}
d := m.currentConfig.End().Add(-clockSkewAllowance).Sub(now)
log.Debugw("rolling certificates", "next", d.String())
t.Reset(d)
m.mx.Unlock()
}
}
}()
}

func (m *certManager) GetConfig() *tls.Config {
m.mx.RLock()
defer m.mx.RUnlock()
return m.currentConfig.tlsConf
}

func (m *certManager) AddrComponent() ma.Multiaddr {
m.mx.RLock()
defer m.mx.RUnlock()
return m.addrComp
}

func (m *certManager) Verify(hashes []multihash.DecodedMultihash) error {
for _, h := range hashes {
if h.Code != multihash.SHA2_256 {
return fmt.Errorf("expected SHA256 hash, got %d", h.Code)
}
if !bytes.Equal(h.Digest, m.currentConfig.sha256[:]) &&
(m.nextConfig == nil || !bytes.Equal(h.Digest, m.nextConfig.sha256[:])) &&
(m.lastConfig == nil || !bytes.Equal(h.Digest, m.lastConfig.sha256[:])) {
return fmt.Errorf("found unexpected hash: %+x", h.Digest)
}
}
return nil
}

func (m *certManager) cacheAddrComponent() error {
addr, err := addrComponentForCert(m.currentConfig.sha256[:])
if err != nil {
return err
}
if m.nextConfig != nil {
comp, err := addrComponentForCert(m.nextConfig.sha256[:])
if err != nil {
return err
}
addr = addr.Encapsulate(comp)
}
m.addrComp = addr
return nil
}

func (m *certManager) Close() error {
m.ctxCancel()
m.refCount.Wait()
return nil
}
102 changes: 102 additions & 0 deletions p2p/transport/webtransport/cert_manager_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package libp2pwebtransport

import (
"crypto/sha256"
"crypto/tls"
"testing"
"time"

"github.com/benbjohnson/clock"
ma "github.com/multiformats/go-multiaddr"
"github.com/multiformats/go-multibase"
"github.com/multiformats/go-multihash"
"github.com/stretchr/testify/require"
)

func certificateHashFromTLSConfig(c *tls.Config) [32]byte {
return sha256.Sum256(c.Certificates[0].Certificate[0])
}

func splitMultiaddr(addr ma.Multiaddr) []ma.Component {
var components []ma.Component
ma.ForEach(addr, func(c ma.Component) bool {
components = append(components, c)
return true
})
return components
}

func certHashFromComponent(t *testing.T, comp ma.Component) []byte {
t.Helper()
_, data, err := multibase.Decode(comp.Value())
require.NoError(t, err)
mh, err := multihash.Decode(data)
require.NoError(t, err)
require.Equal(t, uint64(multihash.SHA2_256), mh.Code)
return mh.Digest
}

func TestInitialCert(t *testing.T) {
cl := clock.NewMock()
cl.Add(1234567 * time.Hour)
m, err := newCertManager(cl)
require.NoError(t, err)
defer m.Close()

conf := m.GetConfig()
require.Len(t, conf.Certificates, 1)
cert := conf.Certificates[0]
require.Equal(t, cl.Now().Add(-clockSkewAllowance).UTC(), cert.Leaf.NotBefore)
require.Equal(t, cert.Leaf.NotBefore.Add(certValidity), cert.Leaf.NotAfter)
addr := m.AddrComponent()
components := splitMultiaddr(addr)
require.Len(t, components, 2)
require.Equal(t, ma.P_CERTHASH, components[0].Protocol().Code)
hash := certificateHashFromTLSConfig(conf)
require.Equal(t, hash[:], certHashFromComponent(t, components[0]))
require.Equal(t, ma.P_CERTHASH, components[1].Protocol().Code)
}

func TestCertRenewal(t *testing.T) {
cl := clock.NewMock()
m, err := newCertManager(cl)
require.NoError(t, err)
defer m.Close()

firstConf := m.GetConfig()
first := splitMultiaddr(m.AddrComponent())
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)
require.Never(t, func() bool {
for i, c := range splitMultiaddr(m.AddrComponent()) {
if c.Value() != first[i].Value() {
return true
}
}
return false
}, 100*time.Millisecond, 10*time.Millisecond)
cl.Add(2 * time.Second)
require.Eventually(t, func() bool { return m.GetConfig() != firstConf }, 200*time.Millisecond, 10*time.Millisecond)
secondConf := m.GetConfig()

second := splitMultiaddr(m.AddrComponent())
require.Len(t, second, 2)
for _, c := range second {
require.Equal(t, ma.P_CERTHASH, c.Protocol().Code)
}
// check that the 2nd certificate from the beginning was rolled over to be the 1st certificate
require.Equal(t, first[1].Value(), second[0].Value())
require.NotEqual(t, first[0].Value(), second[1].Value())

cl.Add(certValidity - 2*clockSkewAllowance + time.Second)
require.Eventually(t, func() bool { return m.GetConfig() != secondConf }, 200*time.Millisecond, 10*time.Millisecond)
third := splitMultiaddr(m.AddrComponent())
require.Len(t, third, 2)
for _, c := range third {
require.Equal(t, ma.P_CERTHASH, c.Protocol().Code)
}
// 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())
}
Loading

0 comments on commit d934c56

Please sign in to comment.