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  }