-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
move go-libp2p-webtransport to p2p/transport/webtransport #1737
Merged
Merged
Changes from all commits
Commits
Show all changes
54 commits
Select commit
Hold shift + click to select a range
9c2be3c
initial commit
marten-seemann 528fa96
use a context to control listener shutdown
marten-seemann c7ebc6d
run a Noise handshake on the first stream
marten-seemann 1ea3360
generate a single certificate, not a chain
marten-seemann a077c89
implement a basic certificate manager
marten-seemann 268725c
check that addr contains at least one certhash in CanDial
marten-seemann 2d295b5
fix copying of http.Server mutex in listener constructor
marten-seemann 59fc6ef
fix staticcheck
marten-seemann 85384b7
fix flaky TestCertRenewal test on CI
marten-seemann 77cdde6
check validity of listen addresses
marten-seemann ee0e2d0
initialize a certiticate manager and pass it to the listeners
marten-seemann ae21c6c
implement a Close method for the transport
marten-seemann 323960e
chore: update webtransport-go
marten-seemann 0af48f5
run a Noise handshake on the first stream to verify peer identities
marten-seemann 52ae9b7
simplify the listener.Accept logic
marten-seemann 91e9730
don't close the HTTP response body when dialing
marten-seemann d8209e3
add more tests for failed handshakes
marten-seemann 47be412
return local and remote multiaddrs from conn
marten-seemann a0eec0f
update webtransport-go, rename webtransport.Conn to Session
marten-seemann d155f11
tighten multiaddr conversion logic
marten-seemann 106cbc3
return the listener's HTTP handler as soon as the handshake completes
marten-seemann ed5a2f5
use the resource manager when dialing
marten-seemann 468fd51
move the HTTP handler to a separate method on the listener
marten-seemann 2110e68
add a function to convert a IP:port string to a WebTransport multiaddr
marten-seemann f55a4ff
use the resource manager when accepting connections
marten-seemann 3b13c83
move extraction of certificate hashes to a separate function
marten-seemann c7149b3
pass the connection scope to the connection (#11)
marten-seemann 69341f6
properly implement conn.IsClosed (#13)
marten-seemann ecc1eff
refactor the conn constructor
marten-seemann 2c6fc83
implement connection gating for dials
marten-seemann a508e94
implement InterceptAccept connection gating
marten-seemann 7e8ca3a
implement InterceptSecured for accepted connections
marten-seemann 851e6ba
verify the hash of the server's certificate (#16)
marten-seemann f0dbd3e
fix flaky resource manager test (#19)
marten-seemann d626e80
move connection interface to conn.go
marten-seemann ebcb513
use a mock clock in cert manager tests (#20)
marten-seemann 9f2e830
remove member variable for certificate validity from cert manager
marten-seemann ff5aa30
simplify certificate generation
marten-seemann 2823159
optimize expiry periods of certificates (#21)
marten-seemann d74921d
make it possible to use a custom tls.Config for listening and dialing…
marten-seemann 60a4071
chore: update CI to Go 1.18 / 1.19, update webtransport-go to v0.1.0
marten-seemann 4ce4e4f
only use positive numbers for x509.Certificate serial numbers
marten-seemann 3521b4f
chore: update go-multiaddr to v0.6.0
marten-seemann 97e739f
update to the current master of go-libp2p (#23)
marten-seemann 0295df0
remove Hello World HTTP endpoint
marten-seemann c783884
add a link to the certificate hashes section of the w3c WebTransport …
marten-seemann ef657d6
make addrComponentForCert a pure function
marten-seemann d2b47dd
remove the unneeded connSecurityMultiaddrs interface
marten-seemann 86a058d
close the scope when the connection gater intercepts
marten-seemann f49dd4f
clarify comment about certificate hash verification
marten-seemann 7714b04
drop incoming sessions when the accept queue fills up (#24)
marten-seemann 98ff0c6
fix CanDial for addresses that don't contain any certificate hashes (…
marten-seemann 48341e4
move go-libp2p-webtransport here
marten-seemann 73b3d56
webtransport: adjust import paths
marten-seemann File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this is an anti-pattern. https://go.dev/blog/context-and-structs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, this is also the pattern we've been using all across go-libp2p, in every service that spawns Go routines. It's super convenient since you can call the
CancelFunc
multiple times (which you can't do if you do the naive implementation with acloseChan chan struct{}
, as you can only close a channel once).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I know we do it a lot. Just worth pointing out that I don’t think it’s idiomatic go, and maybe we should stop adding new code like this.
Not a blocking change of course