Skip to content

Commit

Permalink
[FAB-9244] 3.Changes for nonce management
Browse files Browse the repository at this point in the history
This change set contains changes to manage nonces.
When the server gets idemix credential request, it
first issues a nonce and stores it in the database.
When the user sends a credential request based on this
nonce, server retries the nonce from db, verifies and
removes it rom the database. It also sweeps the
expired nonces at regular intervals.

Change-Id: Idfe3055bf4130e66d3dccc0ddc80a57f1e4e34bc
Signed-off-by: Anil Ambati <[email protected]>
  • Loading branch information
Anil Ambati committed May 17, 2018
1 parent 1d632b8 commit 33900e7
Show file tree
Hide file tree
Showing 12 changed files with 748 additions and 12 deletions.
2 changes: 2 additions & 0 deletions docs/source/servercli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ Fabric-CA Server's CLI
--cacount int Number of non-default CA instances
--cafiles stringSlice A list of comma-separated CA configuration files
--cfg.affiliations.allowremove Enables removal of affiliations dynamically
--cfg.idemix.nonceexpiration string Duration after which a nonce expires (default "15s")
--cfg.idemix.noncesweepinterval string Interval at which expired nonces are deleted (default "15m")
--cfg.idemix.revocationhandlepoolsize int Specifies revocation handle pool size (default 100)
--cfg.identities.allowremove Enables removal of identities dynamically
--crl.expiry duration Expiration for the CRL generated by the gencrl request (default 24h0m0s)
Expand Down
22 changes: 22 additions & 0 deletions lib/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ type CA struct {
// A random number used in generation of Idemix nonces and credentials
idemixRand *amcl.RAND
rc idemix.RevocationComponent
nm idemix.NonceManager
// The options to use in verifying a signature in token-based authentication
verifyOptions *x509.VerifyOptions
// The attribute manager
Expand Down Expand Up @@ -542,6 +543,12 @@ func (ca *CA) initConfig() (err error) {
if ca.Config.Cfg.Idemix.RevocationHandlePoolSize == 0 {
ca.Config.Cfg.Idemix.RevocationHandlePoolSize = idemix.DefaultRevocationHandlePoolSize
}
if ca.Config.Cfg.Idemix.NonceExpiration == "" {
ca.Config.Cfg.Idemix.NonceExpiration = idemix.DefaultNonceExpiration
}
if ca.Config.Cfg.Idemix.NonceSweepInterval == "" {
ca.Config.Cfg.Idemix.NonceSweepInterval = idemix.DefaultNonceSweepInterval
}
return nil
}

Expand Down Expand Up @@ -688,6 +695,10 @@ func (ca *CA) initDB() error {
if err != nil {
return err
}
ca.nm, err = idemix.NewNonceManager(ca, &ca.Config.Cfg.Idemix, idemix.NewLib(), &wallClock{}, ca.levels.Nonce)
if err != nil {
return err
}

// If DB initialization fails and we need to reinitialize DB, need to make sure to set the DB accessor for the signer
if ca.enrollSigner != nil {
Expand Down Expand Up @@ -941,6 +952,11 @@ func (ca *CA) RevocationComponent() idemix.RevocationComponent {
return ca.rc
}

// NonceManager returns nonce manager of this CA
func (ca *CA) NonceManager() idemix.NonceManager {
return ca.nm
}

// DB returns the FabricCADB object (which represents database handle
// to the CA database) associated with this CA
func (ca *CA) DB() dbutil.FabricCADB {
Expand Down Expand Up @@ -1427,3 +1443,9 @@ func getIntLevel(properties map[string]string, version string) int {
}
return intVersion
}

type wallClock struct{}

func (wc wallClock) Now() time.Time {
return time.Now()
}
38 changes: 34 additions & 4 deletions lib/dbutil/dbutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ type Levels struct {
Certificate int
Credential int
RCInfo int
Nonce int
}

// BeginTx implements BeginTx method of FabricCADB interface
Expand Down Expand Up @@ -125,7 +126,7 @@ func createSQLiteDBTables(datasource string) error {
if _, err := db.Exec("CREATE TABLE IF NOT EXISTS properties (property VARCHAR(255), value VARCHAR(256), PRIMARY KEY(property))"); err != nil {
return errors.Wrap(err, "Error creating properties table")
}
_, err = db.Exec(db.Rebind("INSERT INTO properties (property, value) VALUES ('identity.level', '0'), ('affiliation.level', '0'), ('certificate.level', '0'), ('credential.level', '0'), ('rcinfo.level', '0')"))
_, err = db.Exec(db.Rebind("INSERT INTO properties (property, value) VALUES ('identity.level', '0'), ('affiliation.level', '0'), ('certificate.level', '0'), ('credential.level', '0'), ('rcinfo.level', '0'), ('nonce.level', '0')"))
if err != nil {
if !strings.Contains(err.Error(), "UNIQUE constraint failed") {
return errors.Wrap(err, "Failed to initialize properties table")
Expand Down Expand Up @@ -155,6 +156,10 @@ func createAllSQLiteTables(tx *sqlx.Tx, args ...interface{}) error {
if err != nil {
return err
}
err = createSQLiteNoncesTable(tx)
if err != nil {
return err
}
return nil
}

Expand Down Expand Up @@ -198,6 +203,14 @@ func createSQLiteRevocationComponentTable(tx *sqlx.Tx) error {
return nil
}

func createSQLiteNoncesTable(tx *sqlx.Tx) error {
log.Debug("Creating nonces table if it does not exist")
if _, err := tx.Exec("CREATE TABLE IF NOT EXISTS nonces (val VARCHAR(1024) NOT NULL UNIQUE, expiry timestamp, level INTEGER DEFAULT 0, PRIMARY KEY(val))"); err != nil {
return errors.Wrap(err, "Error creating nonces table")
}
return nil
}

// NewUserRegistryPostgres opens a connection to a postgres database
func NewUserRegistryPostgres(datasource string, clientTLSConfig *tls.ClientTLSConfig) (*DB, error) {
log.Debugf("Using postgres database, connecting to database...")
Expand Down Expand Up @@ -299,11 +312,15 @@ func createPostgresTables(dbName string, db *sqlx.DB) error {
if _, err := db.Exec("CREATE TABLE IF NOT EXISTS revocation_component_info (epoch INTEGER, next_handle INTEGER, lasthandle_in_pool INTEGER, level INTEGER DEFAULT 0, PRIMARY KEY(epoch))"); err != nil {
return errors.Wrap(err, "Error creating revocation_component_info table")
}
log.Debug("Creating nonces table if it does not exist")
if _, err := db.Exec("CREATE TABLE IF NOT EXISTS nonces (val VARCHAR(255) NOT NULL UNIQUE, expiry timestamp, level INTEGER DEFAULT 0, PRIMARY KEY (val))"); err != nil {
return errors.Wrap(err, "Error creating nonces table")
}
log.Debug("Creating properties table if it does not exist")
if _, err := db.Exec("CREATE TABLE IF NOT EXISTS properties (property VARCHAR(255), value VARCHAR(256), PRIMARY KEY(property))"); err != nil {
return errors.Wrap(err, "Error creating properties table")
}
_, err := db.Exec(db.Rebind("INSERT INTO properties (property, value) VALUES ('identity.level', '0'), ('affiliation.level', '0'), ('certificate.level', '0'), ('credential.level', '0'), ('rcinfo.level', '0')"))
_, err := db.Exec(db.Rebind("INSERT INTO properties (property, value) VALUES ('identity.level', '0'), ('affiliation.level', '0'), ('certificate.level', '0'), ('credential.level', '0'), ('rcinfo.level', '0'), ('nonce.level', '0')"))
if err != nil {
if !strings.Contains(err.Error(), "duplicate key") {
return err
Expand Down Expand Up @@ -399,11 +416,15 @@ func createMySQLTables(dbName string, db *sqlx.DB) error {
if _, err := db.Exec("CREATE TABLE IF NOT EXISTS revocation_component_info (epoch INTEGER, next_handle INTEGER, lasthandle_in_pool INTEGER, level INTEGER DEFAULT 0, PRIMARY KEY (epoch))"); err != nil {
return errors.Wrap(err, "Error creating revocation_component_info table")
}
log.Debug("Creating nonces table if it does not exist")
if _, err := db.Exec("CREATE TABLE IF NOT EXISTS nonces (val VARCHAR(255) NOT NULL, expiry timestamp, level INTEGER DEFAULT 0, PRIMARY KEY (val))"); err != nil {
return errors.Wrap(err, "Error creating nonces table")
}
log.Debug("Creating properties table if it does not exist")
if _, err := db.Exec("CREATE TABLE IF NOT EXISTS properties (property VARCHAR(255), value VARCHAR(256), PRIMARY KEY(property))"); err != nil {
return errors.Wrap(err, "Error creating properties table")
}
_, err := db.Exec(db.Rebind("INSERT INTO properties (property, value) VALUES ('identity.level', '0'), ('affiliation.level', '0'), ('certificate.level', '0'), ('credential.level', '0'), ('rcinfo.level', '0')"))
_, err := db.Exec(db.Rebind("INSERT INTO properties (property, value) VALUES ('identity.level', '0'), ('affiliation.level', '0'), ('certificate.level', '0'), ('credential.level', '0'), ('rcinfo.level', '0'), ('nonce.level', '0')"))
if err != nil {
if !strings.Contains(err.Error(), "1062") { // MySQL error code for duplicate entry
return err
Expand Down Expand Up @@ -514,12 +535,16 @@ func UpdateDBLevel(db *DB, levels *Levels) error {
if err != nil {
return err
}
_, err = db.Exec(db.Rebind("UPDATE properties SET value = ? WHERE (property = 'nonce.level')"), levels.Nonce)
if err != nil {
return err
}
return nil
}

func currentDBLevels(db *DB) (*Levels, error) {
var err error
var identityLevel, affiliationLevel, certificateLevel, credentialLevel, rcinfoLevel int
var identityLevel, affiliationLevel, certificateLevel, credentialLevel, rcinfoLevel, nonceLevel int

err = db.Get(&identityLevel, "Select value FROM properties WHERE (property = 'identity.level')")
if err != nil {
Expand All @@ -541,12 +566,17 @@ func currentDBLevels(db *DB) (*Levels, error) {
if err != nil {
return nil, err
}
err = db.Get(&nonceLevel, "Select value FROM properties WHERE (property = 'nonce.level')")
if err != nil {
return nil, err
}
return &Levels{
Identity: identityLevel,
Affiliation: affiliationLevel,
Certificate: certificateLevel,
Credential: credentialLevel,
RCInfo: rcinfoLevel,
Nonce: nonceLevel,
}, nil
}

Expand Down
4 changes: 4 additions & 0 deletions lib/metadata/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ var versionToLevelsMapping = []versionLevels{
version: "1.1.0",
levels: &dbutil.Levels{Identity: 1, Affiliation: 1, Certificate: 1},
},
{
version: "1.2.0",
levels: &dbutil.Levels{Identity: 1, Affiliation: 1, Certificate: 1, Credential: 1, RCInfo: 1, Nonce: 1},
},
}

type versionLevels struct {
Expand Down
14 changes: 10 additions & 4 deletions lib/server/idemix/enrollhandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type CA interface {
IdemixRand() *amcl.RAND
IssuerCredential() IssuerCredential
RevocationComponent() RevocationComponent
NonceManager() NonceManager
CredDBAccessor() CredDBAccessor
}

Expand Down Expand Up @@ -93,9 +94,10 @@ func (h *EnrollRequestHandler) HandleIdemixEnroll() (*EnrollmentResponse, error)
}

if req.CredRequest == nil {
nonce := h.GenerateNonce()

// TODO: store the nonce so it can be validated later
nonce, err := h.CA.NonceManager().GetNonce()
if err != nil {
return nil, errors.New("Failed to generate nonce")
}

resp := &EnrollmentResponse{
Nonce: util.B64Encode(idemix.BigToBytes(nonce)),
Expand All @@ -116,7 +118,11 @@ func (h *EnrollRequestHandler) HandleIdemixEnroll() (*EnrollmentResponse, error)
return nil, err
}

// TODO: validate issuer nonce
nonce := fp256bn.FromBytes(req.GetIssuerNonce())
err = h.CA.NonceManager().CheckNonce(nonce)
if err != nil {
return nil, errors.WithMessage(err, "Invalid nonce")
}

// Check the if credential request is valid
err = req.CredRequest.Check(ik.GetIPk())
Expand Down
18 changes: 17 additions & 1 deletion lib/server/idemix/enrollhandler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,16 @@ func TestHandleIdemixEnrollForNonce(t *testing.T) {
req := api.IdemixEnrollmentRequestNet{}
req.CredRequest = nil
ctx.On("ReadBody", &req).Return(nil)

ca := new(mocks.CA)
ca.On("IdemixRand").Return(rnd)

nm := new(mocks.NonceManager)
nm.On("GetNonce").Return(amcl.NewBIG(), nil)
ca.On("NonceManager").Return(nm)

ctx.On("GetCA").Return(ca, nil)

_, err = handler.HandleIdemixEnroll()
assert.NoError(t, err, "Idemix enroll should return a valid nonce")
}
Expand All @@ -101,6 +108,11 @@ func TestHandleIdemixEnrollForNonceTokenAuth(t *testing.T) {
ctx.On("ReadBody", &req).Return(nil)
ca := new(mocks.CA)
ca.On("IdemixRand").Return(rnd)

nm := new(mocks.NonceManager)
nm.On("GetNonce").Return(amcl.NewBIG(), nil)
ca.On("NonceManager").Return(nm)

ctx.On("GetCA").Return(ca, nil)
_, err = handler.HandleIdemixEnroll()
assert.NoError(t, err, "Idemix enroll should return a valid nonce")
Expand Down Expand Up @@ -180,7 +192,10 @@ func TestHandleIdemixEnrollForCredentialSuccess(t *testing.T) {
ca.On("RevocationComponent").Return(rc)

handler := EnrollRequestHandler{Ctx: ctx, IsBasicAuth: true, IdmxLib: idemixlib, CA: ca}
nonce := handler.GenerateNonce()
nm := new(mocks.NonceManager)
nonce := idemix.RandModOrder(rnd)
nm.On("GetNonce").Return(nonce, nil)
nm.On("CheckNonce", nonce).Return(nil)

caller := new(mocks.User)
caller.On("GetName").Return("foo")
Expand Down Expand Up @@ -211,6 +226,7 @@ func TestHandleIdemixEnrollForCredentialSuccess(t *testing.T) {
CALabel: "", ID: "foo", Status: "good", Cred: b64CredBytes}).Return(nil)

ca.On("CredDBAccessor").Return(credAccessor, nil)
ca.On("NonceManager").Return(nm)

ctx.On("BasicAuthentication").Return("foo", nil)
f := getReadBodyFunc(t, credReq)
Expand Down
16 changes: 16 additions & 0 deletions lib/server/idemix/mocks/CA.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 40 additions & 0 deletions lib/server/idemix/mocks/Clock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 33900e7

Please sign in to comment.