139 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			139 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Go
		
	
	
	
| // Copyright 2014 ISRG.  All rights reserved
 | |
| // This Source Code Form is subject to the terms of the Mozilla Public
 | |
| // License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
| // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 | |
| 
 | |
| package core
 | |
| 
 | |
| import (
 | |
| 	"crypto/aes"
 | |
| 	"crypto/cipher"
 | |
| 	"crypto/rand"
 | |
| 	"math/big"
 | |
| )
 | |
| 
 | |
| // MaxUsed defines the maximum number of Nonces we're willing to hold in
 | |
| // memory.
 | |
| const MaxUsed = 65536
 | |
| 
 | |
| // NonceService generates, cancels, and tracks Nonces.
 | |
| type NonceService struct {
 | |
| 	latest   int64
 | |
| 	earliest int64
 | |
| 	used     map[int64]bool
 | |
| 	gcm      cipher.AEAD
 | |
| 	maxUsed  int
 | |
| }
 | |
| 
 | |
| // NewNonceService constructs a NonceService with defaults
 | |
| func NewNonceService() (NonceService, error) {
 | |
| 	key := make([]byte, 16)
 | |
| 	if _, err := rand.Read(key); err != nil {
 | |
| 		return NonceService{}, err
 | |
| 	}
 | |
| 
 | |
| 	// It is safe to ignore these errors because they only happen
 | |
| 	// on key size and block size mismatches.
 | |
| 	c, _ := aes.NewCipher(key)
 | |
| 	gcm, _ := cipher.NewGCM(c)
 | |
| 
 | |
| 	return NonceService{
 | |
| 		earliest: 0,
 | |
| 		latest:   0,
 | |
| 		used:     make(map[int64]bool, MaxUsed),
 | |
| 		gcm:      gcm,
 | |
| 		maxUsed:  MaxUsed,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| func (ns NonceService) encrypt(counter int64) (string, error) {
 | |
| 	// Generate a nonce with upper 4 bytes zero
 | |
| 	nonce := make([]byte, 12)
 | |
| 	for i := 0; i < 4; i++ {
 | |
| 		nonce[i] = 0
 | |
| 	}
 | |
| 	if _, err := rand.Read(nonce[4:]); err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 
 | |
| 	// Encode counter to plaintext
 | |
| 	pt := make([]byte, 8)
 | |
| 	ctr := big.NewInt(counter)
 | |
| 	pad := 8 - len(ctr.Bytes())
 | |
| 	copy(pt[pad:], ctr.Bytes())
 | |
| 
 | |
| 	// Encrypt
 | |
| 	ret := make([]byte, 32)
 | |
| 	ct := ns.gcm.Seal(nil, nonce, pt, nil)
 | |
| 	copy(ret, nonce[4:])
 | |
| 	copy(ret[8:], ct)
 | |
| 	return B64enc(ret), nil
 | |
| }
 | |
| 
 | |
| func (ns NonceService) decrypt(nonce string) (int64, error) {
 | |
| 	decoded, err := B64dec(nonce)
 | |
| 	if err != nil {
 | |
| 		return 0, err
 | |
| 	}
 | |
| 
 | |
| 	n := make([]byte, 12)
 | |
| 	for i := 0; i < 4; i++ {
 | |
| 		n[i] = 0
 | |
| 	}
 | |
| 	copy(n[4:], decoded[:8])
 | |
| 
 | |
| 	pt, err := ns.gcm.Open(nil, n, decoded[8:], nil)
 | |
| 	if err != nil {
 | |
| 		return 0, err
 | |
| 	}
 | |
| 
 | |
| 	ctr := big.NewInt(0)
 | |
| 	ctr.SetBytes(pt)
 | |
| 	return ctr.Int64(), nil
 | |
| }
 | |
| 
 | |
| // Nonce provides a new Nonce.
 | |
| func (ns *NonceService) Nonce() (string, error) {
 | |
| 	ns.latest++
 | |
| 	return ns.encrypt(ns.latest)
 | |
| }
 | |
| 
 | |
| func (ns *NonceService) minUsed() int64 {
 | |
| 	min := ns.latest
 | |
| 	for t := range ns.used {
 | |
| 		if t < min {
 | |
| 			min = t
 | |
| 		}
 | |
| 	}
 | |
| 	return min
 | |
| }
 | |
| 
 | |
| // Valid determines whether the provided Nonce string is valid, returning
 | |
| // true if so.
 | |
| func (ns *NonceService) Valid(nonce string) bool {
 | |
| 	c, err := ns.decrypt(nonce)
 | |
| 	if err != nil {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	if c > ns.latest {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	if c <= ns.earliest {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	if ns.used[c] {
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	ns.used[c] = true
 | |
| 	if len(ns.used) > ns.maxUsed {
 | |
| 		ns.earliest = ns.minUsed()
 | |
| 		delete(ns.used, ns.earliest)
 | |
| 	}
 | |
| 
 | |
| 	return true
 | |
| }
 |