package libp2pwebtransport
import (
"context"
"crypto/sha256"
"crypto/tls"
"encoding/binary"
"fmt"
"sync"
"time"
"github.com/benbjohnson/clock"
ic "github.com/libp2p/go-libp2p/core/crypto"
ma "github.com/multiformats/go-multiaddr"
"github.com/multiformats/go-multihash"
)
const clockSkewAllowance = time .Hour
const validityMinusTwoSkew = certValidity - (2 * clockSkewAllowance )
type certConfig struct {
tlsConf *tls .Config
sha256 [32 ]byte
}
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(key ic .PrivKey , start , end time .Time ) (*certConfig , error ) {
conf , err := getTLSConf (key , start , end )
if err != nil {
return nil , err
}
return &certConfig {
tlsConf : conf ,
sha256 : sha256 .Sum256 (conf .Certificates [0 ].Leaf .Raw ),
}, nil
}
type certManager struct {
clock clock .Clock
ctx context .Context
ctxCancel context .CancelFunc
refCount sync .WaitGroup
mx sync .RWMutex
lastConfig *certConfig
currentConfig *certConfig
nextConfig *certConfig
addrComp ma .Multiaddr
serializedCertHashes [][]byte
}
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 (hostKey ); err != nil {
return nil , err
}
m .background (hostKey )
return m , nil
}
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 ()
pubkeyBytes , err := hostKey .GetPublic ().Raw ()
if err != nil {
return err
}
offset := (time .Duration (binary .LittleEndian .Uint16 (pubkeyBytes )) * time .Minute ) % certValidity
start = start .Add (-clockSkewAllowance )
startTime := getCurrentBucketStartTime (start , offset )
m .nextConfig , err = newCertConfig (hostKey , startTime , startTime .Add (certValidity ))
if err != nil {
return err
}
return m .rollConfig (hostKey )
}
func (m *certManager ) rollConfig (hostKey ic .PrivKey ) error {
nextStart := m .nextConfig .End ().Add (-2 * clockSkewAllowance )
c , err := newCertConfig (hostKey , nextStart , nextStart .Add (certValidity ))
if err != nil {
return err
}
m .lastConfig = m .currentConfig
m .currentConfig = m .nextConfig
m .nextConfig = c
if err := m .cacheSerializedCertHashes (); err != nil {
return err
}
return m .cacheAddrComponent ()
}
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 )
m .refCount .Add (1 )
go func () {
defer m .refCount .Done ()
defer t .Stop ()
for {
select {
case <- m .ctx .Done ():
return
case <- t .C :
now := m .clock .Now ()
m .mx .Lock ()
if err := m .rollConfig (hostKey ); 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 ) SerializedCertHashes () [][]byte {
return m .serializedCertHashes
}
func (m *certManager ) cacheSerializedCertHashes () error {
hashes := make ([][32 ]byte , 0 , 3 )
if m .lastConfig != nil {
hashes = append (hashes , m .lastConfig .sha256 )
}
hashes = append (hashes , m .currentConfig .sha256 )
if m .nextConfig != nil {
hashes = append (hashes , m .nextConfig .sha256 )
}
m .serializedCertHashes = m .serializedCertHashes [:0 ]
for _ , certHash := range hashes {
h , err := multihash .Encode (certHash [:], multihash .SHA2_256 )
if err != nil {
return fmt .Errorf ("failed to encode certificate hash: %w" , err )
}
m .serializedCertHashes = append (m .serializedCertHashes , h )
}
return nil
}
func (m *certManager ) cacheAddrComponent () error {
var addr ma .Multiaddr
c , err := addrComponentForCert (m .currentConfig .sha256 [:])
if err != nil {
return err
}
addr = addr .AppendComponent (c )
if m .nextConfig != nil {
comp , err := addrComponentForCert (m .nextConfig .sha256 [:])
if err != nil {
return err
}
addr = addr .AppendComponent (comp )
}
m .addrComp = addr
return nil
}
func (m *certManager ) Close () error {
m .ctxCancel ()
m .refCount .Wait ()
return nil
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .