Skip to content

Commit

Permalink
[FAB-10372] Store revocation keys on the disk
Browse files Browse the repository at this point in the history
Currently, Idemix issuer revocation public and private keys
are stored in the database. There are two problems with this
approach:
  1. Not a good practice to store private keys in the database
  2. More migration steps are needed when we integrate with bccsp version
that support Idemix keys in the future.

This change set contains changes to store the revocation keys
on the disk. Public key will be stored in the server home directory
and private key in the msp/keystore directory.

Change-Id: I6a475ae7bd158830f0c2402e89a882bc8270c6b6
Signed-off-by: Anil Ambati <[email protected]>
  • Loading branch information
Anil Ambati committed May 30, 2018
1 parent a7a4075 commit fb732d6
Show file tree
Hide file tree
Showing 17 changed files with 994 additions and 328 deletions.
3 changes: 2 additions & 1 deletion cmd/fabric-ca-client/command/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2138,6 +2138,7 @@ func TestCleanUp(t *testing.T) {
os.Remove(filepath.Join(tdDir, "ca-key.pem"))
os.Remove(filepath.Join(tdDir, "IssuerPublicKey"))
os.Remove(filepath.Join(tdDir, "IssuerSecretKey"))
os.Remove(filepath.Join(tdDir, "IssuerRevocationPublicKey"))
os.Remove(testYaml)
os.Remove(fabricCADB)
os.RemoveAll(mspDir)
Expand All @@ -2149,7 +2150,7 @@ func cleanMultiCADir() {
caFolder := filepath.Join(tdDir, "ca/rootca")
nestedFolders := []string{"ca1", "ca2"}
removeFiles := []string{"msp", "ca-cert.pem",
"fabric-ca-server.db", "fabric-ca2-server.db", "ca-chain.pem", "IssuerPublicKey", "IssuerSecretKey"}
"fabric-ca-server.db", "fabric-ca2-server.db", "ca-chain.pem", "IssuerPublicKey", "IssuerSecretKey", "IssuerRevocationPublicKey"}

for _, nestedFolder := range nestedFolders {
path := filepath.Join(caFolder, nestedFolder)
Expand Down
4 changes: 3 additions & 1 deletion cmd/fabric-ca-server/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ func TestClean(t *testing.T) {
os.Remove("ca-cert.pem")
os.Remove("IssuerSecretKey")
os.Remove("IssuerPublicKey")
os.Remove("IssuerRevocationPublicKey")
os.Remove("fabric-ca-server.db")
os.RemoveAll("keystore")
os.RemoveAll("msp")
Expand All @@ -403,6 +404,7 @@ func TestClean(t *testing.T) {
os.Remove("../../testdata/ca-cert.pem")
os.Remove("../../testdata/IssuerSecretKey")
os.Remove("../../testdata/IssuerPublicKey")
os.Remove("../../testdata/IssuerRevocationPublicKey")
os.RemoveAll(ldapTestDir)
os.RemoveAll("testregattr")
}
Expand All @@ -411,7 +413,7 @@ func cleanUpMultiCAFiles() {
caFolder := "../../testdata/ca/rootca"
nestedFolders := []string{"ca1", "ca2"}
removeFiles := []string{"msp", "ca-cert.pem", "ca-key.pem", "fabric-ca-server.db",
"fabric-ca2-server.db", "IssuerSecretKey", "IssuerPublicKey"}
"fabric-ca2-server.db", "IssuerSecretKey", "IssuerPublicKey", "IssuerRevocationPublicKey"}

for _, nestedFolder := range nestedFolders {
path := filepath.Join(caFolder, nestedFolder)
Expand Down
6 changes: 3 additions & 3 deletions lib/dbutil/dbutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ func createSQLiteCredentialsTable(tx *sqlx.Tx) error {

func createSQLiteRevocationComponentTable(tx *sqlx.Tx) error {
log.Debug("Creating revocation_authority_info table if it does not exist")
if _, err := tx.Exec("CREATE TABLE IF NOT EXISTS revocation_authority_info (epoch INTEGER, private_key blob NOT NULL, public_key blob NOT NULL, next_handle INTEGER, lasthandle_in_pool INTEGER, level INTEGER DEFAULT 0, PRIMARY KEY(epoch))"); err != nil {
if _, err := tx.Exec("CREATE TABLE IF NOT EXISTS revocation_authority_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_authority_info table")
}
return nil
Expand Down Expand Up @@ -307,7 +307,7 @@ func createPostgresTables(dbName string, db *sqlx.DB) error {
return errors.Wrap(err, "Error creating certificates table")
}
log.Debug("Creating revocation_authority_info table if it does not exist")
if _, err := db.Exec("CREATE TABLE IF NOT EXISTS revocation_authority_info (epoch INTEGER, private_key bytea NOT NULL, public_key bytea NOT NULL, next_handle INTEGER, lasthandle_in_pool INTEGER, level INTEGER DEFAULT 0, PRIMARY KEY(epoch))"); err != nil {
if _, err := db.Exec("CREATE TABLE IF NOT EXISTS revocation_authority_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_authority_info table")
}
log.Debug("Creating nonces table if it does not exist")
Expand Down Expand Up @@ -411,7 +411,7 @@ func createMySQLTables(dbName string, db *sqlx.DB) error {
return errors.Wrap(err, "Error creating certificates table")
}
log.Debug("Creating revocation_authority_info table if it does not exist")
if _, err := db.Exec("CREATE TABLE IF NOT EXISTS revocation_authority_info (epoch INTEGER, private_key varbinary(4096) NOT NULL, public_key varbinary(4096) NOT NULL, next_handle INTEGER, lasthandle_in_pool INTEGER, level INTEGER DEFAULT 0, PRIMARY KEY (epoch))"); err != nil {
if _, err := db.Exec("CREATE TABLE IF NOT EXISTS revocation_authority_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_authority_info table")
}
log.Debug("Creating nonces table if it does not exist")
Expand Down
21 changes: 15 additions & 6 deletions lib/server/idemix/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,31 @@ const (
DefaultIssuerPublicKeyFile = "IssuerPublicKey"
// DefaultIssuerSecretKeyFile is the default name of the file that contains issuer secret key
DefaultIssuerSecretKeyFile = "IssuerSecretKey"
// DefaultRevocationPublicKeyFile is the name of the file where revocation public key is stored
DefaultRevocationPublicKeyFile = "IssuerRevocationPublicKey"
// DefaultRevocationPrivateKeyFile is the name of the file where revocation private key is stored
DefaultRevocationPrivateKeyFile = "IssuerRevocationPrivateKey"
// KeystoreDir is the keystore directory where all keys are stored. It is relative to the server home directory.
KeystoreDir = "msp/keystore"
)

// Config encapsulates Idemix related the configuration options
type Config struct {
IssuerPublicKeyfile string `def:"IssuerPublicKey" skip:"true" help:"Name of the file that contains marshalled bytes of CA's Idemix public key"`
IssuerSecretKeyfile string `def:"IssuerSecretKey" skip:"true" help:"Name of the file that contains CA's Idemix secret key"`
RHPoolSize int `def:"100" help:"Specifies revocation handle pool size"`
NonceExpiration string `def:"15s" help:"Duration after which a nonce expires"`
NonceSweepInterval string `def:"15m" help:"Interval at which expired nonces are deleted"`
IssuerPublicKeyfile string `def:"IssuerPublicKey" skip:"true" help:"Name of the file that contains marshalled bytes of CA's Idemix issuer public key"`
IssuerSecretKeyfile string `def:"IssuerSecretKey" skip:"true" help:"Name of the file that contains CA's Idemix issuer secret key"`
RevocationPublicKeyfile string `def:"IssuerRevocationPublicKey" skip:"true" help:"Name of the file that contains Idemix issuer revocation public key"`
RevocationPrivateKeyfile string `def:"IssuerRevocationPrivateKey" skip:"true" help:"Name of the file that contains Idemix issuer revocation private key"`
RHPoolSize int `def:"100" help:"Specifies revocation handle pool size"`
NonceExpiration string `def:"15s" help:"Duration after which a nonce expires"`
NonceSweepInterval string `def:"15m" help:"Interval at which expired nonces are deleted"`
}

// InitConfig initializes Idemix configuration
func (c *Config) init(homeDir string) error {
c.IssuerPublicKeyfile = DefaultIssuerPublicKeyFile
c.IssuerSecretKeyfile = filepath.Join(KeystoreDir, DefaultIssuerSecretKeyFile)
c.RevocationPublicKeyfile = DefaultRevocationPublicKeyFile
c.RevocationPrivateKeyfile = filepath.Join(KeystoreDir, DefaultRevocationPrivateKeyFile)
if c.RHPoolSize == 0 {
c.RHPoolSize = DefaultRevocationHandlePoolSize
}
Expand All @@ -41,9 +49,10 @@ func (c *Config) init(homeDir string) error {
c.NonceSweepInterval = DefaultNonceSweepInterval
}
fields := []*string{

&c.IssuerPublicKeyfile,
&c.IssuerSecretKeyfile,
&c.RevocationPublicKeyfile,
&c.RevocationPrivateKeyfile,
}
err := util.MakeFileNamesAbsolute(fields, homeDir)
if err != nil {
Expand Down
8 changes: 7 additions & 1 deletion lib/server/idemix/issuer.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type Issuer interface {
// MyIssuer provides functions for accessing issuer components
type MyIssuer interface {
Name() string
HomeDir() string
Config() *Config
IdemixLib() Lib
DB() dbutil.FabricCADB
Expand Down Expand Up @@ -246,6 +247,11 @@ func (i *issuer) Name() string {
return i.name
}

// HomeDir returns the home directory of the issuer
func (i *issuer) HomeDir() string {
return i.homeDir
}

// Config returns config of this issuer
func (i *issuer) Config() *Config {
return i.cfg
Expand Down Expand Up @@ -289,7 +295,7 @@ func (i *issuer) CredDBAccessor() CredDBAccessor {
}

func (i *issuer) initKeyMaterial(renew bool) error {
log.Debug("Initialize Idemix key material")
log.Debug("Initialize Idemix issuer key material")

rng, err := i.idemixLib.GetRand()
if err != nil {
Expand Down
8 changes: 1 addition & 7 deletions lib/server/idemix/issuer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -416,10 +416,6 @@ func getIssuer(t *testing.T, testDir string, getranderror, newIssuerKeyerror boo
if err != nil {
t.Fatalf("Failed to generate key: %s", err.Error())
}
pkStr, pubStr, err := EncodeKeys(key, &key.PublicKey)
if err != nil {
t.Fatalf("Failed to encode revocation authority long term key: %s", err.Error())
}
lib.On("GenerateLongTermRevocationKey").Return(key, nil)

cfg := &Config{
Expand All @@ -429,7 +425,7 @@ func getIssuer(t *testing.T, testDir string, getranderror, newIssuerKeyerror boo
}
issuer := NewIssuer("ca1", testDir, cfg, util.GetDefaultBCCSP(), lib)

f := getSelectFunc(t, nil, true, false, false)
f := getSelectFunc(t, true, false)

rcInfosForSelect := []RevocationAuthorityInfo{}
db.On("Select", &rcInfosForSelect, SelectRAInfo).Return(f)
Expand All @@ -438,8 +434,6 @@ func getIssuer(t *testing.T, testDir string, getranderror, newIssuerKeyerror boo
NextRevocationHandle: 1,
LastHandleInPool: 100,
Level: 1,
PrivateKey: pkStr,
PublicKey: pubStr,
}
result := new(dmocks.Result)
result.On("RowsAffected").Return(int64(1), nil)
Expand Down
26 changes: 13 additions & 13 deletions lib/server/idemix/issuercredential.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,26 +64,26 @@ func NewIssuerCredential(pubKeyFile, secretKeyFile string, lib Lib) IssuerCreden
func (ic *caIdemixCredential) Load() error {
pubKeyBytes, err := ioutil.ReadFile(ic.pubKeyFile)
if err != nil {
return errors.Wrapf(err, "Failed to read CA's Idemix public key")
return errors.Wrapf(err, "Failed to read Issuer public key")
}
if len(pubKeyBytes) == 0 {
return errors.New("CA's Idemix public key file is empty")
return errors.New("Issuer public key file is empty")
}
pubKey := &idemix.IssuerPublicKey{}
err = proto.Unmarshal(pubKeyBytes, pubKey)
if err != nil {
return errors.Wrapf(err, "Failed to unmarshal CA's Idemix public key bytes")
return errors.Wrapf(err, "Failed to unmarshal Issuer public key bytes")
}
err = pubKey.Check()
if err != nil {
return errors.Wrapf(err, "CA Idemix public key check failed")
return errors.Wrapf(err, "Issuer public key check failed")
}
privKey, err := ioutil.ReadFile(ic.secretKeyFile)
if err != nil {
return errors.Wrapf(err, "Failed to read CA's Idemix secret key")
return errors.Wrapf(err, "Failed to read Issuer secret key")
}
if len(privKey) == 0 {
return errors.New("CA's Idemix secret key file is empty")
return errors.New("Issuer secret key file is empty")
}
ic.issuerKey = &idemix.IssuerKey{
IPk: pubKey,
Expand All @@ -103,22 +103,22 @@ func (ic *caIdemixCredential) Store() error {

ipkBytes, err := proto.Marshal(ik.IPk)
if err != nil {
return errors.New("Failed to marshal CA's Idemix public key")
return errors.New("Failed to marshal Issuer public key")
}

err = util.WriteFile(ic.pubKeyFile, ipkBytes, 0644)
if err != nil {
log.Errorf("Failed to store CA's Idemix public key: %s", err.Error())
return errors.New("Failed to store CA's Idemix public key")
log.Errorf("Failed to store Issuer public key: %s", err.Error())
return errors.New("Failed to store Issuer public key")
}

err = util.WriteFile(ic.secretKeyFile, ik.ISk, 0644)
if err != nil {
log.Errorf("Failed to store CA's Idemix secret key: %s", err.Error())
return errors.New("Failed to store CA's Idemix secret key")
log.Errorf("Failed to store Issuer secret key: %s", err.Error())
return errors.New("Failed to store Issuer secret key")
}

log.Infof("The CA's issuer key was successfully stored. The public key is at: %s, secret key is at: %s",
log.Infof("The issuer key was successfully stored. The public key is at: %s, secret key is at: %s",
ic.pubKeyFile, ic.secretKeyFile)
return nil
}
Expand All @@ -127,7 +127,7 @@ func (ic *caIdemixCredential) Store() error {
// this CAIdemixCredential
func (ic *caIdemixCredential) GetIssuerKey() (*idemix.IssuerKey, error) {
if ic.issuerKey == nil {
return nil, errors.New("CA's Idemix credential is not set")
return nil, errors.New("Issuer credential is not set")
}
return ic.issuerKey, nil
}
Expand Down
53 changes: 40 additions & 13 deletions lib/server/idemix/issuercredential_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,36 @@ const (

func TestLoadEmptyIdemixPublicKey(t *testing.T) {
testdir, err := ioutil.TempDir(".", "issuerkeyloadTest")
if err != nil {
t.Fatalf("Failed to create temp directory: %s", err.Error())
}
pubkeyfile, err := ioutil.TempFile(testdir, "IdemixPublicKey")
if err != nil {
t.Fatalf("Failed to create temp file: %s", err.Error())
}
defer os.RemoveAll(testdir)
idemixLib := new(mocks.Lib)
ic := NewIssuerCredential(pubkeyfile.Name(), testSecretKeyFile, idemixLib)
err = ic.Load()
assert.Error(t, err, "Should have failed to load non existing issuer public key")
if err != nil {
assert.Contains(t, err.Error(), "CA's Idemix public key file is empty")
assert.Contains(t, err.Error(), "Issuer public key file is empty")
}
}

func TestLoadFakeIdemixPublicKey(t *testing.T) {
testdir, err := ioutil.TempDir(".", "issuerkeyloadTest")
if err != nil {
t.Fatalf("Failed to create temp directory: %s", err.Error())
}
pubkeyfile, err := ioutil.TempFile(testdir, "IdemixPublicKey")
if err != nil {
t.Fatalf("Failed to create temp file: %s", err.Error())
}
privkeyfile, err := ioutil.TempFile(testdir, "IdemixSecretKey")
if err != nil {
t.Fatalf("Failed to create temp file: %s", err.Error())
}
defer os.RemoveAll(testdir)
_, err = pubkeyfile.WriteString("foo")
if err != nil {
Expand All @@ -53,43 +68,52 @@ func TestLoadFakeIdemixPublicKey(t *testing.T) {
err = ik.Load()
assert.Error(t, err, "Should have failed to load non existing issuer public key")
if err != nil {
assert.Contains(t, err.Error(), "Failed to unmarshal CA's Idemix public key bytes")
assert.Contains(t, err.Error(), "Failed to unmarshal Issuer public key bytes")
}
}

func TestLoadNonExistentIdemixSecretKey(t *testing.T) {
func TestLoadEmptyIdemixSecretKey(t *testing.T) {
testdir, err := ioutil.TempDir(".", "issuerkeyloadTest")
if err != nil {
t.Fatalf("Failed to create temp directory: %s", err.Error())
}
privkeyfile, err := ioutil.TempFile(testdir, "IdemixSecretKey")
if err != nil {
t.Fatalf("Failed to create temp file: %s", err.Error())
}
defer os.RemoveAll(testdir)
idemixLib := new(mocks.Lib)
ik := NewIssuerCredential(testPublicKeyFile, privkeyfile.Name(), idemixLib)
err = ik.Load()
assert.Error(t, err, "Should have failed to load non existing issuer secret key")
if err != nil {
assert.Contains(t, err.Error(), "CA's Idemix secret key file is empty")
assert.Contains(t, err.Error(), "Issuer secret key file is empty")
}
}

func TestLoadEmptyIdemixSecretKey(t *testing.T) {
func TestLoadNonExistentIdemixSecretKey(t *testing.T) {
testdir, err := ioutil.TempDir(".", "issuerkeyloadTest")
if err != nil {
t.Fatalf("Failed to create temp directory: %s", err.Error())
}
defer os.RemoveAll(testdir)
idemixLib := new(mocks.Lib)
ik := NewIssuerCredential(testPublicKeyFile, filepath.Join(testdir, "IdemixSecretKey"), idemixLib)
err = ik.Load()
assert.Error(t, err, "Should have failed to load non existing issuer secret key")
if err != nil {
assert.Contains(t, err.Error(), "Failed to read CA's Idemix secret key")
assert.Contains(t, err.Error(), "Failed to read Issuer secret key")
}
}

func TestLoad(t *testing.T) {
idemixLib := new(mocks.Lib)
ik := NewIssuerCredential(testPublicKeyFile, testSecretKeyFile, idemixLib)
err := ik.Load()
assert.NoError(t, err, "Failed to load CA's issuer idemix credential")
assert.NoError(t, err, "Failed to load Idemix issuer credential")

err = ik.Store()
assert.NoError(t, err, "Failed to store CA's issuer idemix credential")
assert.NoError(t, err, "Failed to store Idemix issuer credential")
}

func TestStoreNilIssuerKey(t *testing.T) {
Expand All @@ -98,7 +122,7 @@ func TestStoreNilIssuerKey(t *testing.T) {
err := ik.Store()
assert.Error(t, err, "Should fail if store is called without setting the issuer key or loading the issuer key from disk")
if err != nil {
assert.Equal(t, err.Error(), "CA's Idemix credential is not set")
assert.Equal(t, err.Error(), "Issuer credential is not set")
}
}

Expand All @@ -109,7 +133,7 @@ func TestStoreNilIdemixPublicKey(t *testing.T) {
err := ik.Store()
assert.Error(t, err, "Should fail if store is called with empty issuer public key byte array")
if err != nil {
assert.Equal(t, err.Error(), "Failed to marshal CA's Idemix public key")
assert.Equal(t, err.Error(), "Failed to marshal Issuer public key")
}
}

Expand All @@ -134,12 +158,15 @@ func TestStoreInvalidPublicKeyFilePath(t *testing.T) {
err = ik.Store()
assert.Error(t, err, "Should fail if issuer public key is being stored to non-existent directory")
if err != nil {
assert.Equal(t, err.Error(), "Failed to store CA's Idemix public key")
assert.Equal(t, err.Error(), "Failed to store Issuer public key")
}
}

func TestStoreInvalidSecretKeyFilePath(t *testing.T) {
testdir, err := ioutil.TempDir(".", "issuerkeystoreTest")
if err != nil {
t.Fatalf("Failed to create temp directory: %s", err.Error())
}
defer os.RemoveAll(testdir)

// foo directory is non-existent
Expand All @@ -162,7 +189,7 @@ func TestStoreInvalidSecretKeyFilePath(t *testing.T) {
err = ik.Store()
assert.Error(t, err, "Should fail if issuer secret key is being stored to non-existent directory")
if err != nil {
assert.Equal(t, "Failed to store CA's Idemix secret key", err.Error())
assert.Equal(t, "Failed to store Issuer secret key", err.Error())
}
}

Expand All @@ -172,7 +199,7 @@ func TestGetIssuerKey(t *testing.T) {
_, err := ik.GetIssuerKey()
assert.Error(t, err, "GetIssuerKey should return an error if it is called without setting the issuer key or loading the issuer key from disk")
if err != nil {
assert.Equal(t, err.Error(), "CA's Idemix credential is not set")
assert.Equal(t, err.Error(), "Issuer credential is not set")
}
err = ik.Load()
if err != nil {
Expand Down
Loading

0 comments on commit fb732d6

Please sign in to comment.