|
|
@ -15,11 +15,19 @@ import ( |
|
|
|
"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 { |
|
|
@ -32,22 +40,17 @@ func newCertConfig(start, end time.Time) (*certConfig, error) { |
|
|
|
} |
|
|
|
|
|
|
|
// Certificate renewal logic:
|
|
|
|
// 0. To simplify the math, assume the certificate is valid for 10 days (in real life: 14 days).
|
|
|
|
// 1. On startup, we generate the first certificate (1).
|
|
|
|
// 2. After 4 days, we generate a second certificate (2).
|
|
|
|
// We don't use that certificate yet, but we advertise the hashes of (1) and (2).
|
|
|
|
// That allows clients to connect to us using addresses that are 4 days old.
|
|
|
|
// 3. After another 4 days, we now actually start using (2).
|
|
|
|
// We also generate a third certificate (3), and start advertising the hashes of (2) and (3).
|
|
|
|
// We continue to remember the hash of (1) for validation during the Noise handshake for another 4 days,
|
|
|
|
// as the client might be connecting with a cached address.
|
|
|
|
// 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.Mutex |
|
|
|
mx sync.RWMutex |
|
|
|
lastConfig *certConfig // initially nil
|
|
|
|
currentConfig *certConfig |
|
|
|
nextConfig *certConfig // nil until we have passed half the certValidity of the current config
|
|
|
@ -61,64 +64,71 @@ func newCertManager(clock clock.Clock) (*certManager, error) { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
|
|
|
|
t := m.clock.Ticker(certValidity * 4 / 9) // make sure we're a bit faster than 1/2
|
|
|
|
m.refCount.Add(1) |
|
|
|
go func() { |
|
|
|
defer m.refCount.Done() |
|
|
|
defer t.Stop() |
|
|
|
if err := m.background(t); err != nil { |
|
|
|
log.Fatal(err) |
|
|
|
} |
|
|
|
}() |
|
|
|
m.background() |
|
|
|
return m, nil |
|
|
|
} |
|
|
|
|
|
|
|
func (m *certManager) init() error { |
|
|
|
start := m.clock.Now() |
|
|
|
end := start.Add(certValidity) |
|
|
|
cc, err := newCertConfig(start, end) |
|
|
|
start := m.clock.Now().Add(-clockSkewAllowance) |
|
|
|
var err error |
|
|
|
m.nextConfig, err = newCertConfig(start, start.Add(certValidity)) |
|
|
|
if err != nil { |
|
|
|
return err |
|
|
|
} |
|
|
|
m.currentConfig = cc |
|
|
|
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(t *clock.Ticker) error { |
|
|
|
for { |
|
|
|
select { |
|
|
|
case <-m.ctx.Done(): |
|
|
|
return nil |
|
|
|
case start := <-t.C: |
|
|
|
end := start.Add(certValidity) |
|
|
|
cc, err := newCertConfig(start, end) |
|
|
|
if err != nil { |
|
|
|
return err |
|
|
|
} |
|
|
|
m.mx.Lock() |
|
|
|
if m.nextConfig != nil { |
|
|
|
m.lastConfig = m.currentConfig |
|
|
|
m.currentConfig = m.nextConfig |
|
|
|
} |
|
|
|
m.nextConfig = cc |
|
|
|
if err := m.cacheAddrComponent(); err != nil { |
|
|
|
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() |
|
|
|
return err |
|
|
|
} |
|
|
|
m.mx.Unlock() |
|
|
|
} |
|
|
|
} |
|
|
|
}() |
|
|
|
} |
|
|
|
|
|
|
|
func (m *certManager) GetConfig() *tls.Config { |
|
|
|
m.mx.Lock() |
|
|
|
defer m.mx.Unlock() |
|
|
|
m.mx.RLock() |
|
|
|
defer m.mx.RUnlock() |
|
|
|
return m.currentConfig.tlsConf |
|
|
|
} |
|
|
|
|
|
|
|
func (m *certManager) AddrComponent() ma.Multiaddr { |
|
|
|
m.mx.Lock() |
|
|
|
defer m.mx.Unlock() |
|
|
|
m.mx.RLock() |
|
|
|
defer m.mx.RUnlock() |
|
|
|
return m.addrComp |
|
|
|
} |
|
|
|
|
|
|
|