package openid import ( "errors" "flag" "fmt" "sync" "time" ) var maxNonceAge = flag.Duration("openid-max-nonce-age", 60*time.Second, "Maximum accepted age for openid nonces. The bigger, the more"+ "memory is needed to store used nonces.") type NonceStore interface { // Returns nil if accepted, an error otherwise. Accept(endpoint, nonce string) error } type Nonce struct { T time.Time S string } type SimpleNonceStore struct { store map[string][]*Nonce mutex *sync.Mutex } func NewSimpleNonceStore() *SimpleNonceStore { return &SimpleNonceStore{store: map[string][]*Nonce{}, mutex: &sync.Mutex{}} } func (d *SimpleNonceStore) Accept(endpoint, nonce string) error { // Value: A string 255 characters or less in length, that MUST be // unique to this particular successful authentication response. if len(nonce) < 20 || len(nonce) > 256 { return errors.New("Invalid nonce") } // The nonce MUST start with the current time on the server, and MAY // contain additional ASCII characters in the range 33-126 inclusive // (printable non-whitespace characters), as necessary to make each // response unique. The date and time MUST be formatted as specified in // section 5.6 of [RFC3339], with the following restrictions: // All times must be in the UTC timezone, indicated with a "Z". No // fractional seconds are allowed For example: // 2005-05-15T17:11:51ZUNIQUE ts, err := time.Parse(time.RFC3339, nonce[0:20]) if err != nil { return err } now := time.Now() diff := now.Sub(ts) if diff > *maxNonceAge { return fmt.Errorf("Nonce too old: %.2fs", diff.Seconds()) } s := nonce[20:] // Meh.. now we have to use a mutex, to protect that map from // concurrent access. Could put a go routine in charge of it // though. d.mutex.Lock() defer d.mutex.Unlock() if nonces, hasOp := d.store[endpoint]; hasOp { // Delete old nonces while we are at it. newNonces := []*Nonce{{ts, s}} for _, n := range nonces { if n.T == ts && n.S == s { // If return early, just ignore the filtered list // we have been building so far... return errors.New("Nonce already used") } if now.Sub(n.T) < *maxNonceAge { newNonces = append(newNonces, n) } } d.store[endpoint] = newNonces } else { d.store[endpoint] = []*Nonce{{ts, s}} } return nil }