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 }