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
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
|
|
}
|
|
|