github.com/adecaro/fabric-ca@v2.0.0-alpha+incompatible/lib/server/idemix/nonce.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package idemix 8 9 import ( 10 "fmt" 11 "time" 12 13 "github.com/cloudflare/cfssl/log" 14 fp256bn "github.com/hyperledger/fabric-amcl/amcl/FP256BN" 15 "github.com/hyperledger/fabric-ca/lib/server/db" 16 "github.com/hyperledger/fabric-ca/util" 17 "github.com/hyperledger/fabric/idemix" 18 "github.com/pkg/errors" 19 ) 20 21 const ( 22 // InsertNonce is the SQL for inserting a nonce 23 InsertNonce = "INSERT into nonces(val, expiry, level) VALUES (:val, :expiry, :level)" 24 // SelectNonce is query string for getting a particular nonce 25 SelectNonce = "SELECT * FROM nonces WHERE (val = ?)" 26 // RemoveNonce is the query string for removing a specified nonce 27 RemoveNonce = "DELETE FROM nonces WHERE (val = ?)" 28 // RemoveExpiredNonces is the SQL string removing expired nonces 29 RemoveExpiredNonces = "DELETE FROM nonces WHERE (expiry < ?)" 30 // DefaultNonceExpiration is the default value for nonce expiration 31 DefaultNonceExpiration = "15s" 32 // DefaultNonceSweepInterval is the default value for nonce sweep interval 33 DefaultNonceSweepInterval = "15m" 34 ) 35 36 // Nonce represents a nonce 37 type Nonce struct { 38 Val string `db:"val"` 39 Expiry time.Time `db:"expiry"` 40 Level int `db:"level"` 41 } 42 43 // NonceManager represents nonce manager that is responsible for 44 // getting a new nonce 45 type NonceManager interface { 46 // GetNonce creates a nonce, stores it in the database and returns it 47 GetNonce() (*fp256bn.BIG, error) 48 // CheckNonce checks if the specified nonce exists in the database and has not expired 49 CheckNonce(nonce *fp256bn.BIG) error 50 // SweepExpiredNonces removes expired nonces from the database 51 SweepExpiredNonces() error 52 } 53 54 // Clock provides time related functions 55 type Clock interface { 56 Now() time.Time 57 } 58 59 // nonceManager implements NonceManager interface 60 type nonceManager struct { 61 nonceExpiration time.Duration 62 nonceSweepInterval time.Duration 63 clock Clock 64 issuer MyIssuer 65 level int 66 } 67 68 // NewNonceManager returns an instance of an object that implements NonceManager interface 69 func NewNonceManager(issuer MyIssuer, clock Clock, level int) (NonceManager, error) { 70 var err error 71 mgr := &nonceManager{ 72 issuer: issuer, 73 clock: clock, 74 level: level, 75 } 76 opts := issuer.Config() 77 mgr.nonceExpiration, err = time.ParseDuration(opts.NonceExpiration) 78 if err != nil { 79 return nil, errors.Wrapf(err, fmt.Sprintf("Failed to parse idemix.nonceexpiration config option while initializing Nonce manager for Issuer '%s'", 80 issuer.Name())) 81 } 82 mgr.nonceSweepInterval, err = time.ParseDuration(opts.NonceSweepInterval) 83 if err != nil { 84 return nil, errors.Wrapf(err, fmt.Sprintf("Failed to parse idemix.noncesweepinterval config option while initializing Nonce manager for Issuer '%s'", 85 issuer.Name())) 86 } 87 mgr.startNonceSweeper() 88 return mgr, nil 89 } 90 91 // GetNonce returns a new nonce 92 func (nm *nonceManager) GetNonce() (*fp256bn.BIG, error) { 93 idmixLib := nm.issuer.IdemixLib() 94 nonce, err := idmixLib.RandModOrder(nm.issuer.IdemixRand()) 95 if err != nil { 96 return nil, err 97 } 98 nonceBytes := idemix.BigToBytes(nonce) 99 err = nm.insertNonceInDB(&Nonce{ 100 Val: util.B64Encode(nonceBytes), 101 Expiry: nm.clock.Now().UTC().Add(nm.nonceExpiration), 102 Level: nm.level, 103 }) 104 if err != nil { 105 log.Errorf("Failed to store nonce: %s", err.Error()) 106 return nil, errors.WithMessage(err, "Failed to store nonce") 107 } 108 return nonce, nil 109 } 110 111 // CheckNonce checks if the specified nonce is valid (is in DB and has not expired) 112 // and the nonce is removed from DB 113 func (nm *nonceManager) CheckNonce(nonce *fp256bn.BIG) error { 114 nonceBytes := idemix.BigToBytes(nonce) 115 queryParam := util.B64Encode(nonceBytes) 116 nonceRec, err := doTransaction("CheckNonce", nm.issuer.DB(), nm.getNonceFromDB, queryParam) 117 if err != nil { 118 return err 119 } 120 nonceFromDB := nonceRec.(Nonce) 121 log.Debugf("Retrieved nonce from DB: %+v, %s", nonceRec, queryParam) 122 123 if nonceFromDB.Val != queryParam || nonceFromDB.Expiry.Before(time.Now().UTC()) { 124 return errors.New("Nonce is either unknown or has expired") 125 } 126 return nil 127 } 128 129 // SweepExpiredNonces sweeps expired nonces 130 func (nm *nonceManager) SweepExpiredNonces() error { 131 return nm.sweep(nm.clock.Now().UTC()) 132 } 133 134 func (nm *nonceManager) startNonceSweeper() { 135 ticker := time.NewTicker(nm.nonceSweepInterval) 136 go func() { 137 for t := range ticker.C { 138 log.Debugf("Cleaning up expired nonces for CA '%s'", nm.issuer.Name()) 139 nm.sweep(t.UTC()) 140 } 141 }() 142 } 143 144 // sweep deletes all nonces that have expired (whose expiry is less than current timestamp) 145 func (nm *nonceManager) sweep(curTime time.Time) error { 146 err := nm.removeExpiredNoncesFromDB(curTime) 147 if err != nil { 148 log.Errorf("Failed to deleted expired nonces from DB for CA %s: %s", nm.issuer.Name(), err.Error()) 149 return err 150 } 151 return nil 152 } 153 154 // Gets the specified nonce from DB and removes it from the DB 155 func (nm *nonceManager) getNonceFromDB(tx db.FabricCATx, args ...interface{}) (interface{}, error) { 156 nonces := []Nonce{} 157 err := tx.Select("GetNonce", &nonces, tx.Rebind(SelectNonce), args...) 158 if err != nil { 159 log.Errorf("Failed to get nonce from DB: %s", err.Error()) 160 return nil, errors.New("Failed to retrieve nonce from the datastore") 161 } 162 if len(nonces) == 0 { 163 return nil, errors.New("Nonce not found in the datastore") 164 } 165 result, err := tx.Exec("GetNonce", tx.Rebind(RemoveNonce), args...) 166 if err != nil { 167 log.Errorf("Failed to remove nonce %s from DB: %s", args[0], err.Error()) 168 return nonces[0], nil 169 } 170 numRowsAffected, err := result.RowsAffected() 171 if numRowsAffected != 1 { 172 log.Errorf("Tried to remove one nonce from DB but %d were removed", numRowsAffected) 173 } 174 return nonces[0], nil 175 } 176 177 func (nm *nonceManager) removeExpiredNoncesFromDB(curTime time.Time) error { 178 _, err := nm.issuer.DB().Exec("RemoveExpiredNonces", nm.issuer.DB().Rebind(RemoveExpiredNonces), curTime) 179 if err != nil { 180 log.Errorf("Failed to remove expired nonces from DB for CA '%s': %s", nm.issuer.Name(), err.Error()) 181 return errors.New("Failed to remove expired nonces from DB") 182 } 183 return nil 184 } 185 186 func (nm *nonceManager) insertNonceInDB(nonce *Nonce) error { 187 res, err := nm.issuer.DB().NamedExec("InsertNonce", InsertNonce, nonce) 188 if err != nil { 189 log.Errorf("Failed to add nonce to DB: %s", err.Error()) 190 return errors.New("Failed to add nonce to the datastore") 191 } 192 193 numRowsAffected, err := res.RowsAffected() 194 if numRowsAffected == 0 { 195 return errors.New("Failed to add nonce to the datastore; no rows affected") 196 } 197 198 if numRowsAffected != 1 { 199 return errors.Errorf("Expected to affect 1 entry in revocation component info table but affected %d", 200 numRowsAffected) 201 } 202 return err 203 }