| // Copyright 2016 The Roughtime Authors. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. */ | |
| // client is a somewhat featured Roughtime client. | |
| package main | |
| import ( | |
| "crypto/rand" | |
| "encoding/binary" | |
| "encoding/json" | |
| "errors" | |
| "fmt" | |
| "io" | |
| "net" | |
| "time" | |
| "math/big" | |
| mathrand "math/rand" | |
| "golang.org/x/crypto/ed25519" | |
| "roughtime.googlesource.com/go/client/monotime" | |
| "roughtime.googlesource.com/go/config" | |
| "roughtime.googlesource.com/go/protocol" | |
| ) | |
| const ( | |
| // defaultMaxRadius is the maximum radius that we'll accept from a | |
| // server. | |
| defaultMaxRadius = 10 * time.Second | |
| // defaultMaxDifference is the maximum difference in time between any | |
| // sample from a server and the quorum-agreed time before we believe | |
| // that the server might be misbehaving. | |
| defaultMaxDifference = 60 * time.Second | |
| // defaultTimeout is the default maximum time that a server has to | |
| // answer a query. | |
| defaultTimeout = 2 * time.Second | |
| // defaultNumQueries is the default number of times we will try to | |
| // query a server. | |
| defaultNumQueries = 3 | |
| ) | |
| // Client represents a Roughtime client and exposes a number of members that | |
| // can be set in order to configure it. The zero value of a Client is always | |
| // ready to use and will set sensible defaults. | |
| type Client struct { | |
| // Permutation returns a random permutation of [0‥n) that is used to | |
| // query servers in a random order. If nil, a sensible default is used. | |
| Permutation func(n int) []int | |
| // MaxRadiusUs is the maximum interval radius that will be accepted | |
| // from a server. If zero, a sensible default is used. | |
| MaxRadius time.Duration | |
| // MaxDifference is the maximum difference in time between any sample | |
| // from a server and the quorum-agreed time before that sample is | |
| // considered suspect. If zero, a sensible default is used. | |
| MaxDifference time.Duration | |
| // QueryTimeout is the amount of time a server has to reply to a query. | |
| // If zero, a sensible default will be used. | |
| QueryTimeout time.Duration | |
| // NumQueries is the maximum number of times a query will be sent to a | |
| // specific server before giving up. If <= zero, a sensible default | |
| // will be used. | |
| NumQueries int | |
| // now returns a monotonic duration from some unspecified epoch. If | |
| // nil, the system monotonic time will be used. | |
| nowFunc func() time.Duration | |
| } | |
| func (c *Client) now() time.Duration { | |
| if c.nowFunc != nil { | |
| return c.nowFunc() | |
| } | |
| return monotime.Now() | |
| } | |
| func (c *Client) permutation(n int) []int { | |
| if c.Permutation != nil { | |
| return c.Permutation(n) | |
| } | |
| var randBuf [8]byte | |
| if _, err := io.ReadFull(rand.Reader, randBuf[:]); err != nil { | |
| panic(err) | |
| } | |
| seed := binary.LittleEndian.Uint64(randBuf[:]) | |
| rand := mathrand.New(mathrand.NewSource(int64(seed))) | |
| return rand.Perm(n) | |
| } | |
| func (c *Client) maxRadius() time.Duration { | |
| if c.MaxRadius != 0 { | |
| return c.MaxRadius | |
| } | |
| return defaultMaxRadius | |
| } | |
| func (c *Client) maxDifference() time.Duration { | |
| if c.MaxDifference != 0 { | |
| return c.MaxDifference | |
| } | |
| return defaultMaxDifference | |
| } | |
| func (c *Client) queryTimeout() time.Duration { | |
| if c.QueryTimeout != 0 { | |
| return c.QueryTimeout | |
| } | |
| return defaultTimeout | |
| } | |
| func (c *Client) numQueries() int { | |
| if c.NumQueries > 0 { | |
| return c.NumQueries | |
| } | |
| return defaultNumQueries | |
| } | |
| // LoadChain loads a JSON-format chain from the given JSON data. | |
| func LoadChain(jsonData []byte) (chain *config.Chain, err error) { | |
| chain = new(config.Chain) | |
| if err := json.Unmarshal(jsonData, chain); err != nil { | |
| return nil, err | |
| } | |
| for i, link := range chain.Links { | |
| if link.PublicKeyType != "ed25519" { | |
| return nil, fmt.Errorf("client: link #%d in chain file has unknown public key type %q", i, link.PublicKeyType) | |
| } | |
| if l := len(link.PublicKey); l != ed25519.PublicKeySize { | |
| return nil, fmt.Errorf("client: link #%d in chain file has bad public key of length %d", i, l) | |
| } | |
| if l := len(link.NonceOrBlind); l != protocol.NonceSize { | |
| return nil, fmt.Errorf("client: link #%d in chain file has bad nonce/blind of length %d", i, l) | |
| } | |
| var nonce [protocol.NonceSize]byte | |
| if i == 0 { | |
| copy(nonce[:], link.NonceOrBlind[:]) | |
| } else { | |
| nonce = protocol.CalculateChainNonce(chain.Links[i-1].Reply, link.NonceOrBlind[:]) | |
| } | |
| if _, _, err := protocol.VerifyReply(link.Reply, link.PublicKey, nonce); err != nil { | |
| return nil, fmt.Errorf("client: failed to verify link #%d in chain file", i) | |
| } | |
| } | |
| return chain, nil | |
| } | |
| // timeSample represents a time sample from the network. | |
| type timeSample struct { | |
| // server references the server that was queried. | |
| server *config.Server | |
| // base is a monotonic clock sample that is taken at a time before the | |
| // network could have answered the query. | |
| base *big.Int | |
| // min is the minimum real-time (in Roughtime UTC microseconds) that | |
| // could correspond to |base| (i.e. midpoint - radius). | |
| min *big.Int | |
| // max is the maximum real-time (in Roughtime UTC microseconds) that | |
| // could correspond to |base| (i.e. midpoint + radius + query time). | |
| max *big.Int | |
| // queryDuration contains the amount of time that the server took to | |
| // answer the query. | |
| queryDuration time.Duration | |
| } | |
| // midpoint returns the average of the min and max times. | |
| func (s *timeSample) midpoint() *big.Int { | |
| ret := new(big.Int).Add(s.min, s.max) | |
| return ret.Rsh(ret, 1) | |
| } | |
| // alignTo updates s so that its base value matches that from reference. | |
| func (s *timeSample) alignTo(reference *timeSample) { | |
| delta := new(big.Int).Sub(s.base, reference.base) | |
| delta.Div(delta, big.NewInt(int64(time.Microsecond))) | |
| s.base.Sub(s.base, delta) | |
| s.min.Sub(s.min, delta) | |
| s.max.Sub(s.max, delta) | |
| } | |
| // contains returns true iff p belongs to s | |
| func (s *timeSample) contains(p *big.Int) bool { | |
| return s.max.Cmp(p) >= 0 && s.min.Cmp(p) <= 0 | |
| } | |
| // overlaps returns true iff s and other have any timespan in common. | |
| func (s *timeSample) overlaps(other *timeSample) bool { | |
| return s.max.Cmp(other.min) >= 0 && other.max.Cmp(s.min) >= 0 | |
| } | |
| // query sends a request to s, appends it to chain, and returns the resulting | |
| // timeSample. | |
| func (c *Client) query(server *config.Server, chain *config.Chain) (*timeSample, error) { | |
| var prevReply []byte | |
| if len(chain.Links) > 0 { | |
| prevReply = chain.Links[len(chain.Links)-1].Reply | |
| } | |
| var baseTime, replyTime time.Duration | |
| var reply []byte | |
| var nonce, blind [protocol.NonceSize]byte | |
| for attempts := 0; attempts < c.numQueries(); attempts++ { | |
| var request []byte | |
| var err error | |
| if nonce, blind, request, err = protocol.CreateRequest(rand.Reader, prevReply); err != nil { | |
| return nil, err | |
| } | |
| if len(request) < protocol.MinRequestSize { | |
| panic("internal error: bad request length") | |
| } | |
| udpAddr, err := serverUDPAddr(server) | |
| if err != nil { | |
| panic(err) | |
| } | |
| conn, err := net.DialUDP("udp", nil, udpAddr) | |
| if err != nil { | |
| return nil, err | |
| } | |
| conn.SetReadDeadline(time.Now().Add(c.queryTimeout())) | |
| baseTime = c.now() | |
| conn.Write(request) | |
| var replyBytes [1024]byte | |
| n, err := conn.Read(replyBytes[:]) | |
| if err == nil { | |
| replyTime = c.now() | |
| reply = replyBytes[:n] | |
| break | |
| } | |
| if netErr, ok := err.(net.Error); ok { | |
| if !netErr.Timeout() { | |
| return nil, errors.New("client: error reading from UDP socket: " + err.Error()) | |
| } | |
| } | |
| } | |
| if reply == nil { | |
| return nil, fmt.Errorf("client: no reply from server %q", server.Name) | |
| } | |
| if replyTime < baseTime { | |
| panic("broken monotonic clock") | |
| } | |
| queryDuration := replyTime - baseTime | |
| midpoint, radius, err := protocol.VerifyReply(reply, server.PublicKey, nonce) | |
| if err != nil { | |
| return nil, err | |
| } | |
| if time.Duration(radius)*time.Microsecond > c.maxRadius() { | |
| return nil, fmt.Errorf("client: radius (%d) too large", radius) | |
| } | |
| nonceOrBlind := blind[:] | |
| if len(prevReply) == 0 { | |
| nonceOrBlind = nonce[:] | |
| } | |
| chain.Links = append(chain.Links, config.Link{ | |
| PublicKeyType: "ed25519", | |
| PublicKey: server.PublicKey, | |
| NonceOrBlind: nonceOrBlind, | |
| Reply: reply, | |
| }) | |
| queryDurationBig := new(big.Int).SetInt64(int64(queryDuration / time.Microsecond)) | |
| bigRadius := new(big.Int).SetUint64(uint64(radius)) | |
| min := new(big.Int).SetUint64(midpoint) | |
| min.Sub(min, bigRadius) | |
| min.Sub(min, queryDurationBig) | |
| max := new(big.Int).SetUint64(midpoint) | |
| max.Add(max, bigRadius) | |
| return &timeSample{ | |
| server: server, | |
| base: new(big.Int).SetInt64(int64(baseTime)), | |
| min: min, | |
| max: max, | |
| queryDuration: queryDuration, | |
| }, nil | |
| } | |
| func serverUDPAddr(server *config.Server) (*net.UDPAddr, error) { | |
| for _, addr := range server.Addresses { | |
| if addr.Protocol != "udp" { | |
| continue | |
| } | |
| return net.ResolveUDPAddr("udp", addr.Address) | |
| } | |
| return nil, nil | |
| } | |
| // LoadServers loads information about known servers from the given JSON data. | |
| // It only extracts information about servers with Ed25519 public keys and UDP | |
| // address. The number of servers skipped because of unsupported requirements | |
| // is returned in numSkipped. | |
| func LoadServers(jsonData []byte) (servers []config.Server, numSkipped int, err error) { | |
| var serversJSON config.ServersJSON | |
| if err := json.Unmarshal(jsonData, &serversJSON); err != nil { | |
| return nil, 0, err | |
| } | |
| seenNames := make(map[string]struct{}) | |
| for _, candidate := range serversJSON.Servers { | |
| if _, ok := seenNames[candidate.Name]; ok { | |
| return nil, 0, fmt.Errorf("client: duplicate server name %q", candidate.Name) | |
| } | |
| seenNames[candidate.Name] = struct{}{} | |
| if candidate.PublicKeyType != "ed25519" { | |
| numSkipped++ | |
| continue | |
| } | |
| udpAddr, err := serverUDPAddr(&candidate) | |
| if err != nil { | |
| return nil, 0, fmt.Errorf("client: server %q lists invalid UDP address: %s", candidate.Name, err) | |
| } | |
| if udpAddr == nil { | |
| numSkipped++ | |
| continue | |
| } | |
| servers = append(servers, candidate) | |
| } | |
| if len(servers) == 0 { | |
| return nil, 0, errors.New("client: no usable servers found") | |
| } | |
| return servers, 0, nil | |
| } | |
| // trimChain drops elements from the beginning of chain, as needed, so that its | |
| // length is <= n. | |
| func trimChain(chain *config.Chain, n int) { | |
| if n <= 0 { | |
| chain.Links = nil | |
| return | |
| } | |
| if len(chain.Links) <= n { | |
| return | |
| } | |
| numToTrim := len(chain.Links) - n | |
| for i := 0; i < numToTrim; i++ { | |
| // The NonceOrBlind of the first element is special because | |
| // it's an nonce. All the others are blinds and are combined | |
| // with the previous reply to make the nonce. That's not | |
| // possible for the first element because there is no previous | |
| // reply. Therefore, when removing the first element the blind | |
| // of the next element needs to be converted to an nonce. | |
| nonce := protocol.CalculateChainNonce(chain.Links[0].Reply, chain.Links[1].NonceOrBlind[:]) | |
| chain.Links[1].NonceOrBlind = nonce[:] | |
| chain.Links = chain.Links[1:] | |
| } | |
| } | |
| // intersection returns the timespan common to all the elements in samples, | |
| // which must be aligned to the same base. The caller must ensure that such a | |
| // timespan exists. | |
| func intersection(samples []*timeSample) *timeSample { | |
| ret := &timeSample{ | |
| base: samples[0].base, | |
| min: new(big.Int).Set(samples[0].min), | |
| max: new(big.Int).Set(samples[0].max), | |
| } | |
| for _, sample := range samples[1:] { | |
| if ret.min.Cmp(sample.min) < 0 { | |
| ret.min.Set(sample.min) | |
| } | |
| if ret.max.Cmp(sample.max) > 0 { | |
| ret.max.Set(sample.max) | |
| } | |
| } | |
| return ret | |
| } | |
| // findNOverlapping finds an n-element subset of samples where all the | |
| // members overlap. It returns the intersection if such a subset exists. | |
| func findNOverlapping(samples []*timeSample, n int) (sampleIntersection *timeSample, ok bool) { | |
| switch { | |
| case n <= 0: | |
| return nil, false | |
| case n == 1: | |
| return samples[0], true | |
| } | |
| overlapping := make([]*timeSample, 0, n) | |
| for _, initial := range samples { | |
| // An intersection of any subset of intervals will be an interval that contains | |
| // the starting point of one of the intervals (possibly as its own starting point). | |
| point := initial.min | |
| for _, candidate := range samples { | |
| if candidate.contains(point) { | |
| overlapping = append(overlapping, candidate) | |
| } | |
| if len(overlapping) == n { | |
| return intersection(overlapping), true | |
| } | |
| } | |
| overlapping = overlapping[:0] | |
| } | |
| return nil, false | |
| } | |
| // TimeResult is the result of trying to establish the current time by querying | |
| // a number of servers. | |
| type TimeResult struct { | |
| // MonoUTCDelta may be nil, in which case a time could not be | |
| // established. Otherwise it contains the difference between the | |
| // Roughtime epoch and the monotonic clock. | |
| MonoUTCDelta *time.Duration | |
| // ServerErrors maps from server name to query error. | |
| ServerErrors map[string]error | |
| // ServerInfo contains information about each server that was queried. | |
| ServerInfo map[string]ServerInfo | |
| // OutOfRangeAnswer is true if one or more of the queries contained a | |
| // significantly incorrect time, as defined by MaxDifference. In this | |
| // case, the reply will have been recorded in the chain. | |
| OutOfRangeAnswer bool | |
| } | |
| // ServerInfo contains information from a specific server. | |
| type ServerInfo struct { | |
| // QueryDuration is the amount of time that the server took to answer. | |
| QueryDuration time.Duration | |
| // Min and Max specify the time window given by the server. These | |
| // values have been adjusted so that they are comparible across | |
| // servers, even though they are queried at different times. | |
| Min, Max *big.Int | |
| } | |
| // EstablishTime queries a number of servers until it has a quorum of | |
| // overlapping results, or it runs out of servers. Results from the querying | |
| // the servers are appended to chain. | |
| func (c *Client) EstablishTime(chain *config.Chain, quorum int, servers []config.Server) (TimeResult, error) { | |
| perm := c.permutation(len(servers)) | |
| var samples []*timeSample | |
| var intersection *timeSample | |
| var result TimeResult | |
| for len(perm) > 0 { | |
| server := &servers[perm[0]] | |
| perm = perm[1:] | |
| sample, err := c.query(server, chain) | |
| if err != nil { | |
| if result.ServerErrors == nil { | |
| result.ServerErrors = make(map[string]error) | |
| } | |
| result.ServerErrors[server.Name] = err | |
| continue | |
| } | |
| if len(samples) > 0 { | |
| sample.alignTo(samples[0]) | |
| } | |
| samples = append(samples, sample) | |
| if result.ServerInfo == nil { | |
| result.ServerInfo = make(map[string]ServerInfo) | |
| } | |
| result.ServerInfo[server.Name] = ServerInfo{ | |
| QueryDuration: sample.queryDuration, | |
| Min: sample.min, | |
| Max: sample.max, | |
| } | |
| var ok bool | |
| if intersection, ok = findNOverlapping(samples, quorum); ok { | |
| break | |
| } | |
| intersection = nil | |
| } | |
| if intersection == nil { | |
| return result, nil | |
| } | |
| midpoint := intersection.midpoint() | |
| maxDifference := new(big.Int).SetUint64(uint64(c.maxDifference() / time.Microsecond)) | |
| for _, sample := range samples { | |
| delta := new(big.Int).Sub(midpoint, sample.midpoint()) | |
| delta.Abs(delta) | |
| if delta.Cmp(maxDifference) > 0 { | |
| result.OutOfRangeAnswer = true | |
| break | |
| } | |
| } | |
| midpoint.Mul(midpoint, big.NewInt(1000)) | |
| delta := new(big.Int).Sub(midpoint, intersection.base) | |
| if delta.BitLen() > 63 { | |
| return result, errors.New("client: cannot represent difference between monotonic and UTC time") | |
| } | |
| monoUTCDelta := time.Duration(delta.Int64()) | |
| result.MonoUTCDelta = &monoUTCDelta | |
| return result, nil | |
| } |