You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

63 lines
1.7 KiB

package startup
import (
"context"
"crypto/rand"
"crypto/subtle"
"encoding/hex"
"errors"
"sync"
"time"
)
// ClockBootstrap only repairs an uninitialized clock; it is not a public
// replacement for the authenticated time settings API.
type ClockBootstrap struct {
mu sync.Mutex
now func() time.Time
apply func(context.Context, time.Time) error
token string
used bool
lastAttempt time.Time
}
func NewClockBootstrap(now func() time.Time, apply func(context.Context, time.Time) error) (*ClockBootstrap, error) {
var token [32]byte
if _, err := rand.Read(token[:]); err != nil {
return nil, err
}
return &ClockBootstrap{now: now, apply: apply, token: hex.EncodeToString(token[:])}, nil
}
func (b *ClockBootstrap) Token() string {
b.mu.Lock()
defer b.mu.Unlock()
if b.used || b.now().Year() >= 2000 {
return ""
}
return b.token
}
func (b *ClockBootstrap) Sync(ctx context.Context, token string, target time.Time) error {
b.mu.Lock()
defer b.mu.Unlock()
if b.used || b.now().Year() >= 2000 {
return errors.New("clock bootstrap is no longer available")
}
if subtle.ConstantTimeCompare([]byte(token), []byte(b.token)) != 1 {
return errors.New("invalid clock bootstrap token")
}
if target.Year() < 2020 || target.Year() > 2100 {
return errors.New("PC clock is outside the supported range")
}
if !b.lastAttempt.IsZero() && time.Since(b.lastAttempt) < 3*time.Second {
return errors.New("clock bootstrap retry is too soon")
}
b.lastAttempt = time.Now()
if err := b.apply(ctx, target); err != nil {
return err
}
if b.now().Year() < 2000 {
return errors.New("device clock has not been updated")
}
b.used = true
return nil
}