Skip to content

Commit 6d43670

Browse files
committed
general: implement random nonce
1 parent bbce33b commit 6d43670

File tree

5 files changed

+118
-39
lines changed

5 files changed

+118
-39
lines changed

auth_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
)
66

77
func TestMacaroon(t *testing.T) {
8-
auth, _ := NewAuth("", NewInMemoryDB([]byte("kek")))
8+
auth, _ := NewAuth("", NewInMemoryDB([]byte("kek"), MacaroonLifetime))
99

1010
// Emulate generation token on the server, with the user id taken from other
1111
// token, for example jwt.

db.go

Lines changed: 70 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
package auth
22

3+
import (
4+
"sync"
5+
"time"
6+
"fmt"
7+
)
8+
39
// DB represent the storage for macaroon application authentication which is
410
// needed to keep it secure.
511
type DB interface {
6-
// GetLastNonceByID returns last used nonce by the given user or
7-
// application id.
8-
GetLastNonceByID(id uint32) (int64, error)
9-
10-
// PutLastNonceByID assigns new nonce to the given user or applciation id.
11-
PutLastNonceByID(id uint32, nonce int64) error
12+
// UseNonce mark the nonce as used by the given user.
13+
UseNonce(id uint32, nonce int64) bool
1214

1315
// GetRootKey returns last stored root key.
1416
GetRootKey() ([]byte, error)
@@ -24,43 +26,89 @@ type DB interface {
2426
// NOTE: If macaroon lifetime becomes bigger enough such schema might become
2527
// insecure.
2628
type InMemoryDB struct {
27-
nonces map[uint32]int64
28-
rootKey []byte
29+
nonces map[string]time.Time
30+
rootKey []byte
31+
nonceLifetime time.Duration
32+
33+
mutex sync.Mutex
34+
wg sync.WaitGroup
35+
quit chan struct{}
2936
}
3037

31-
func NewInMemoryDB(rootKey []byte) *InMemoryDB {
38+
func NewInMemoryDB(rootKey []byte, nonceLifetime time.Duration) *InMemoryDB {
3239
return &InMemoryDB{
33-
nonces: make(map[uint32]int64),
34-
rootKey: rootKey,
40+
nonces: make(map[string]time.Time),
41+
rootKey: rootKey,
42+
quit: make(chan struct{}),
43+
nonceLifetime: nonceLifetime,
3544
}
3645
}
3746

47+
func (db *InMemoryDB) StartFlushing() {
48+
db.wg.Add(1)
49+
go func() {
50+
defer db.wg.Done()
51+
52+
for {
53+
select {
54+
case <-time.After(db.nonceLifetime):
55+
case <-db.quit:
56+
return
57+
}
58+
59+
db.mutex.Lock()
60+
61+
for key, t := range db.nonces {
62+
if t.Add(db.nonceLifetime).Before(time.Now()) {
63+
delete(db.nonces, key)
64+
}
65+
}
66+
67+
db.mutex.Unlock()
68+
}
69+
}()
70+
}
71+
72+
func (db *InMemoryDB) StopFlushing() {
73+
close(db.quit)
74+
db.wg.Wait()
75+
}
76+
3877
// Runtime check to ensure that InMemoryDB implements DB.
3978
var _ DB = (*InMemoryDB)(nil)
4079

41-
func (db InMemoryDB) GetLastNonceByID(id uint32) (int64, error) {
42-
nonce, ok := db.nonces[id]
43-
if !ok {
80+
func (db *InMemoryDB) UseNonce(id uint32, nonce int64) bool {
81+
db.mutex.Lock()
82+
defer db.mutex.Unlock()
83+
84+
key := getKey(id, nonce)
85+
if _, ok := db.nonces[key]; !ok {
4486
// If service has been shutdown and started faster than macaroon
4587
// lifetime attacker would have a period of time where he could reuse
4688
// the stolen macaroon, because in this case db don't have nonce for id
4789
// and returns zero.
48-
return 0, nil
90+
return false
4991
}
5092

51-
return nonce, nil
93+
db.nonces[key] = time.Now()
94+
return true
5295
}
5396

54-
func (db InMemoryDB) PutLastNonceByID(id uint32, nonce int64) error {
55-
db.nonces[id] = nonce
56-
return nil
57-
}
97+
func (db *InMemoryDB) GetRootKey() ([]byte, error) {
98+
db.mutex.Lock()
99+
defer db.mutex.Unlock()
58100

59-
func (db InMemoryDB) GetRootKey() ([]byte, error) {
60101
return db.rootKey, nil
61102
}
62103

63-
func (db InMemoryDB) PutRootKey(rootKey []byte) error {
104+
func (db *InMemoryDB) PutRootKey(rootKey []byte) error {
105+
db.mutex.Lock()
106+
defer db.mutex.Unlock()
107+
64108
db.rootKey = rootKey
65109
return nil
66110
}
111+
112+
func getKey(id uint32, nonce int64) string {
113+
return fmt.Sprintf("%v_%v", id, nonce)
114+
}

errors.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ var (
88
ErrRepeatedField = errors.Errorf("repeated conditions")
99

1010
ErrMacaroonExpired = errors.Errorf("macaroon expired")
11-
ErrNonceRepeated = errors.Errorf("nonce is used already")
11+
ErrNonceUsed = errors.Errorf("nonce is used already")
1212

1313
ErrOperNotAllowed = errors.Errorf("operation not allowed")
1414
)

nonce.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,9 @@ func CheckNonce(m *macaroon.Macaroon, id uint32, db DB,
8282
return err
8383
}
8484

85-
dbNonce, err := db.GetLastNonceByID(id)
86-
if err != nil {
87-
return err
88-
}
89-
90-
if dbNonce >= macaroonNonce {
91-
return ErrNonceRepeated
85+
if db.UseNonce(id, macaroonNonce) {
86+
return ErrNonceUsed
9287
}
9388

94-
return db.PutLastNonceByID(id, macaroonNonce)
89+
return nil
9590
}

nonce_test.go

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package auth
33
import (
44
"testing"
55
"gopkg.in/macaroon.v2"
6+
"time"
67
)
78

89
func TestCheckNonceFunction(t *testing.T) {
@@ -16,7 +17,7 @@ func TestCheckNonceFunction(t *testing.T) {
1617
// Check that check nonce function fail because macaroon not contains
1718
// nonce and time constraint/caveat/field.
1819
{
19-
db := NewInMemoryDB(rootKey)
20+
db := NewInMemoryDB(rootKey, MacaroonLifetime)
2021

2122
if err := CheckNonce(m, 1, db, MacaroonLifetime); err == nil {
2223
t.Fatalf("expected to fail because don't have time and nonce fields"+
@@ -33,7 +34,7 @@ func TestCheckNonceFunction(t *testing.T) {
3334
// Check that check nonce function fail because macaroon not contains time
3435
// constraint/caveat/field.
3536
{
36-
db := NewInMemoryDB(rootKey)
37+
db := NewInMemoryDB(rootKey, MacaroonLifetime)
3738

3839
if err := CheckNonce(m, 1, db, MacaroonLifetime); err == nil {
3940
t.Fatalf("expected to fail because don't have time field: %v", err)
@@ -48,7 +49,7 @@ func TestCheckNonceFunction(t *testing.T) {
4849
// Check that check nonce function fail because macaroon has expired.
4950
// working.
5051
{
51-
db := NewInMemoryDB(rootKey)
52+
db := NewInMemoryDB(rootKey, MacaroonLifetime)
5253

5354
if err := CheckNonce(m, 1, db, 0); err != ErrMacaroonExpired {
5455
t.Fatalf("expected to fail because macaron expired: %v", err)
@@ -58,22 +59,57 @@ func TestCheckNonceFunction(t *testing.T) {
5859
// Check that check nonce function fail because nonce has been used.
5960
{
6061
db := &InMemoryDB{
61-
nonces: map[uint32]int64{1: nonce},
62+
nonces: map[string]time.Time{getKey(1, nonce): time.Now()},
6263
rootKey: rootKey,
6364
}
6465

65-
if err := CheckNonce(m, 1, db, MacaroonLifetime); err != ErrNonceRepeated {
66+
if err := CheckNonce(m, 1, db, MacaroonLifetime); err != ErrNonceUsed {
6667
t.Fatalf("expected to fail because macaron nonce has been used"+
6768
": %v", err)
6869
}
6970
}
7071

7172
// Check that check nonce function fail because nonce has been used.
7273
{
73-
db := NewInMemoryDB(rootKey)
74+
db := NewInMemoryDB(rootKey, MacaroonLifetime)
7475

7576
if err := CheckNonce(m, 1, db, MacaroonLifetime); err != nil {
76-
t.Fatalf("unable to check macaroon")
77+
t.Fatalf("unable to check macaroon: %v", err)
7778
}
7879
}
7980
}
81+
82+
func TestNonceFlush(t *testing.T) {
83+
rootKey := []byte("kek")
84+
m, err := macaroon.New(rootKey, nil, "bitlum", macaroon.LatestVersion)
85+
if err != nil {
86+
t.Fatalf("unable to create macaron: %v", err)
87+
}
88+
89+
flushPeriod := time.Millisecond * 50
90+
db := NewInMemoryDB(rootKey, flushPeriod)
91+
92+
// Pretend that nonce was already used
93+
nonce := int64(100)
94+
userID := uint32(1)
95+
db.nonces = map[string]time.Time{getKey(userID, nonce): time.Now()}
96+
97+
m, err = AddNonce(m, nonce)
98+
if err != nil {
99+
t.Fatalf("unable to add nonce: %v", err)
100+
}
101+
102+
m, err = AddCurrentTime(m)
103+
if err != nil {
104+
t.Fatalf("unable to add current time: %v", err)
105+
}
106+
107+
// Start nonce flushing and wait more than flushing period.
108+
db.StartFlushing()
109+
defer db.StopFlushing()
110+
time.Sleep(2 * flushPeriod)
111+
112+
if err := CheckNonce(m, userID, db, MacaroonLifetime); err != nil {
113+
t.Fatalf("unable to check macaroon: %v", err)
114+
}
115+
}

0 commit comments

Comments
 (0)