From a7a40757035983cda3de2017c919180d88ddafd6 Mon Sep 17 00:00:00 2001 From: Anil Ambati Date: Wed, 23 May 2018 11:44:37 -0400 Subject: [PATCH] [FAB-10324] Add issuer revocation pub key to cainfo Idemix issuer revocation public key is needed for verifiers to verify an Idemix signature. But currently there is no way for verifiers to get revocation public key from the Fabric CA. With this change , verifiers can call /api/v1/cainfo API endpoint to get issuer revocation public key in addition to the issuer public key and X509 certificate chain of the CA. Change-Id: I410e393971dd94c8f571678d076ba02e3e2435da Signed-off-by: Anil Ambati --- cmd/fabric-ca-client/command/enroll.go | 6 ++++- cmd/fabric-ca-client/command/getcainfo.go | 28 +++++++++++++++----- lib/ca.go | 5 ++++ lib/client.go | 23 ++++++++++++----- lib/common/serverresponses.go | 4 ++- lib/server/idemix/issuer.go | 24 +++++++++++++++--- lib/server/idemix/issuer_test.go | 31 ++++++++++++++++++++++- lib/server/idemix/issuer_whitebox_test.go | 4 +++ swagger/swagger-fabric-ca.json | 14 +++++++++- 9 files changed, 117 insertions(+), 22 deletions(-) diff --git a/cmd/fabric-ca-client/command/enroll.go b/cmd/fabric-ca-client/command/enroll.go index 264e1a20e..12b440c24 100644 --- a/cmd/fabric-ca-client/command/enroll.go +++ b/cmd/fabric-ca-client/command/enroll.go @@ -85,5 +85,9 @@ func (c *enrollCmd) runEnroll(cmd *cobra.Command, args []string) error { if err != nil { return err } - return nil + err = storeIssuerPublicKey(cfg, &resp.CAInfo) + if err != nil { + return err + } + return storeIssuerRevocationPublicKey(cfg, &resp.CAInfo) } diff --git a/cmd/fabric-ca-client/command/getcainfo.go b/cmd/fabric-ca-client/command/getcainfo.go index ca878d3ea..6528f17f1 100644 --- a/cmd/fabric-ca-client/command/getcainfo.go +++ b/cmd/fabric-ca-client/command/getcainfo.go @@ -99,7 +99,11 @@ func (c *getCAInfoCmd) runGetCACert(cmd *cobra.Command, args []string) error { if err != nil { return err } - return storeIssuerPublicKey(client.Config, si) + err = storeIssuerPublicKey(client.Config, si) + if err != nil { + return err + } + return storeIssuerRevocationPublicKey(client.Config, si) } // Store the CAChain in the CACerts folder of MSP (Membership Service Provider) @@ -157,12 +161,12 @@ func storeCAChain(config *lib.ClientConfig, si *lib.GetCAInfoResponse) error { certBytes := bytes.Join(rootBlks, []byte("")) if len(certBytes) > 0 { if config.Enrollment.Profile == "tls" { - err := storeCert("TLS root CA certificate", tlsRootCACertsDir, tlsfname, certBytes) + err := storeToFile("TLS root CA certificate", tlsRootCACertsDir, tlsfname, certBytes) if err != nil { return err } } else { - err = storeCert("root CA certificate", rootCACertsDir, fname, certBytes) + err = storeToFile("root CA certificate", rootCACertsDir, fname, certBytes) if err != nil { return err } @@ -173,12 +177,12 @@ func storeCAChain(config *lib.ClientConfig, si *lib.GetCAInfoResponse) error { certBytes = bytes.Join(intBlks, []byte("")) if len(certBytes) > 0 { if config.Enrollment.Profile == "tls" { - err = storeCert("TLS intermediate certificates", tlsIntCACertsDir, tlsfname, certBytes) + err = storeToFile("TLS intermediate certificates", tlsIntCACertsDir, tlsfname, certBytes) if err != nil { return err } } else { - err = storeCert("intermediate CA certificates", intCACertsDir, fname, certBytes) + err = storeToFile("intermediate CA certificates", intCACertsDir, fname, certBytes) if err != nil { return err } @@ -189,7 +193,17 @@ func storeCAChain(config *lib.ClientConfig, si *lib.GetCAInfoResponse) error { func storeIssuerPublicKey(config *lib.ClientConfig, si *lib.GetCAInfoResponse) error { if len(si.IssuerPublicKey) > 0 { - err := storeCert("Issuer public key", config.MSPDir, "IssuerPublicKey", si.IssuerPublicKey) + err := storeToFile("Issuer public key", config.MSPDir, "IssuerPublicKey", si.IssuerPublicKey) + if err != nil { + return err + } + } + return nil +} + +func storeIssuerRevocationPublicKey(config *lib.ClientConfig, si *lib.GetCAInfoResponse) error { + if len(si.IssuerRevocationPublicKey) > 0 { + err := storeToFile("Issuer revocation public key", config.MSPDir, "IssuerRevocationPublicKey", si.IssuerRevocationPublicKey) if err != nil { return err } @@ -197,7 +211,7 @@ func storeIssuerPublicKey(config *lib.ClientConfig, si *lib.GetCAInfoResponse) e return nil } -func storeCert(what, dir, fname string, contents []byte) error { +func storeToFile(what, dir, fname string, contents []byte) error { err := os.MkdirAll(dir, 0755) if err != nil { return errors.Wrapf(err, "Failed to create directory for %s at '%s'", what, dir) diff --git a/lib/ca.go b/lib/ca.go index 2e2e366a2..e8906eb7a 100644 --- a/lib/ca.go +++ b/lib/ca.go @@ -983,7 +983,12 @@ func (ca *CA) fillCAInfo(info *common.CAInfoResponseNet) error { if err != nil { return err } + rpkBytes, err := ca.issuer.RevocationPublicKey() + if err != nil { + return err + } info.IssuerPublicKey = util.B64Encode(ipkBytes) + info.IssuerRevocationPublicKey = util.B64Encode(rpkBytes) return nil } diff --git a/lib/client.go b/lib/client.go index fc68a411e..c9946ef15 100644 --- a/lib/client.go +++ b/lib/client.go @@ -65,6 +65,8 @@ type GetCAInfoResponse struct { CAChain []byte // Idemix issuer public key of the CA IssuerPublicKey []byte + // Idemix issuer revocation public key of the CA + IssuerRevocationPublicKey []byte // Version of the server Version string } @@ -181,7 +183,7 @@ func (c *Client) GetCAInfo(req *api.GetCAInfoRequest) (*GetCAInfoResponse, error return nil, err } localSI := &GetCAInfoResponse{} - err = c.net2LocalServerInfo(netSI, localSI) + err = c.net2LocalCAInfo(netSI, localSI) if err != nil { return nil, err } @@ -235,19 +237,26 @@ func (c *Client) Enroll(req *api.EnrollmentRequest) (*EnrollmentResponse, error) return c.handleX509Enroll(req) } -// Convert from network to local server information -func (c *Client) net2LocalServerInfo(net *common.CAInfoResponseNet, local *GetCAInfoResponse) error { +// Convert from network to local CA information +func (c *Client) net2LocalCAInfo(net *common.CAInfoResponseNet, local *GetCAInfoResponse) error { caChain, err := util.B64Decode(net.CAChain) if err != nil { - return err + return errors.WithMessage(err, "Failed to decode CA chain") } if net.IssuerPublicKey != "" { ipk, err := util.B64Decode(net.IssuerPublicKey) if err != nil { - return err + return errors.WithMessage(err, "Failed to decode issuer public key") } local.IssuerPublicKey = ipk } + if net.IssuerRevocationPublicKey != "" { + rpk, err := util.B64Decode(net.IssuerRevocationPublicKey) + if err != nil { + return errors.WithMessage(err, "Failed to decode issuer revocation key") + } + local.IssuerRevocationPublicKey = rpk + } local.CAName = net.CAName local.CAChain = caChain local.Version = net.Version @@ -423,7 +432,7 @@ func (c *Client) newEnrollmentResponse(result *common.EnrollmentResponseNet, id resp := &EnrollmentResponse{ Identity: NewIdentity(c, id, []credential.Credential{x509Cred}), } - err = c.net2LocalServerInfo(&result.ServerInfo, &resp.CAInfo) + err = c.net2LocalCAInfo(&result.ServerInfo, &resp.CAInfo) if err != nil { return nil, err } @@ -471,7 +480,7 @@ func (c *Client) newIdemixEnrollmentResponse(identity *Identity, result *common. resp := &EnrollmentResponse{ Identity: identity, } - err = c.net2LocalServerInfo(&result.CAInfo, &resp.CAInfo) + err = c.net2LocalCAInfo(&result.CAInfo, &resp.CAInfo) if err != nil { return nil, err } diff --git a/lib/common/serverresponses.go b/lib/common/serverresponses.go index e7cd80801..63e150d82 100644 --- a/lib/common/serverresponses.go +++ b/lib/common/serverresponses.go @@ -17,8 +17,10 @@ type CAInfoResponseNet struct { CAName string // Base64 encoding of PEM-encoded certificate chain CAChain string - // Base64 encoding of idemix issuer public key + // Base64 encoding of Idemix issuer public key IssuerPublicKey string + // Base64 encoding of PEM-encoded Idemix issuer revocation public key + IssuerRevocationPublicKey string // Version of the server Version string } diff --git a/lib/server/idemix/issuer.go b/lib/server/idemix/issuer.go index abebce1d4..a65897f84 100644 --- a/lib/server/idemix/issuer.go +++ b/lib/server/idemix/issuer.go @@ -7,6 +7,8 @@ SPDX-License-Identifier: Apache-2.0 package idemix import ( + "crypto/x509" + "encoding/pem" "fmt" "reflect" "strings" @@ -31,6 +33,7 @@ import ( type Issuer interface { Init(renew bool, db dbutil.FabricCADB, levels *dbutil.Levels) error IssuerPublicKey() ([]byte, error) + RevocationPublicKey() ([]byte, error) IssueCredential(ctx ServerRequestCtx) (*EnrollmentResponse, error) GetCRI(ctx ServerRequestCtx) (*api.GetCRIResponse, error) VerifyToken(authHdr string, body []byte) (string, error) @@ -98,7 +101,7 @@ func (i *issuer) Init(renew bool, db dbutil.FabricCADB, levels *dbutil.Levels) e } if db == nil || reflect.ValueOf(db).IsNil() || !db.IsInitialized() { - log.Debugf("Returning without initializing Issuer for CA '%s' as the database is not initialized", i.Name()) + log.Debugf("Returning without initializing Idemix issuer for CA '%s' as the database is not initialized", i.Name()) return nil } i.db = db @@ -111,12 +114,12 @@ func (i *issuer) Init(renew bool, db dbutil.FabricCADB, levels *dbutil.Levels) e return err } i.credDBAccessor = NewCredentialAccessor(i.db, levels.Credential) - log.Debugf("Intializing revocation authority for issuer %s", i.Name()) + log.Debugf("Intializing revocation authority for issuer '%s'", i.Name()) i.rc, err = NewRevocationAuthority(i, levels.RAInfo) if err != nil { return err } - log.Debugf("Intializing nonce manager for issuer %s", i.Name()) + log.Debugf("Intializing nonce manager for issuer '%s'", i.Name()) i.nm, err = NewNonceManager(i, &wallClock{}, levels.Nonce) if err != nil { return err @@ -140,6 +143,19 @@ func (i *issuer) IssuerPublicKey() ([]byte, error) { return ipkBytes, nil } +func (i *issuer) RevocationPublicKey() ([]byte, error) { + if !i.isInitialized { + return nil, errors.New("Issuer is not initialized") + } + rpk := i.RevocationAuthority().PublicKey() + encodedPubKey, err := x509.MarshalPKIXPublicKey(rpk) + if err != nil { + return nil, errors.Wrapf(err, "Failed to encode revocation authority public key of the issuer %s", i.Name()) + } + pemEncodedPubKey := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: encodedPubKey}) + return pemEncodedPubKey, nil +} + func (i *issuer) IssueCredential(ctx ServerRequestCtx) (*EnrollmentResponse, error) { if !i.isInitialized { return nil, errors.New("Issuer is not initialized") @@ -305,7 +321,7 @@ func (i *issuer) initKeyMaterial(renew bool) error { if err != nil { return err } - log.Infof("The Idemix public and secret keys were generated for Issuer %s", i.name) + log.Infof("Idemix issuer public and secret keys were generated for CA '%s'", i.name) issuerCred.SetIssuerKey(ik) err = issuerCred.Store() if err != nil { diff --git a/lib/server/idemix/issuer_test.go b/lib/server/idemix/issuer_test.go index 59811cc72..7e03cb7be 100644 --- a/lib/server/idemix/issuer_test.go +++ b/lib/server/idemix/issuer_test.go @@ -70,7 +70,6 @@ func TestInit(t *testing.T) { assert.Error(t, err, "IssuerCredential should fail") _, err = issuer.GetCRI(ctx) assert.Error(t, err, "GetCRI should fail") - } func TestInitDBNotInitialized(t *testing.T) { @@ -341,6 +340,36 @@ func TestIsToken(t *testing.T) { assert.True(t, IsToken(token)) } +func TestRevocationPublicKey(t *testing.T) { + testdir, err := ioutil.TempDir(".", "revocationpubkeytest") + if err != nil { + t.Fatalf("Failed to create temp directory: %s", err.Error()) + } + defer os.RemoveAll(testdir) + + err = os.MkdirAll(filepath.Join(testdir, "msp/keystore"), 0777) + if err != nil { + t.Fatalf("Failed to create directory: %s", err.Error()) + } + err = lib.CopyFile(testPublicKeyFile, filepath.Join(testdir, "IssuerPublicKey")) + if err != nil { + t.Fatalf("Failed to copy file: %s", err.Error()) + } + err = lib.CopyFile(testSecretKeyFile, filepath.Join(testdir, "msp/keystore/IssuerSecretKey")) + if err != nil { + t.Fatalf("Failed to copy file: %s", err.Error()) + } + + db, issuer := getIssuer(t, testdir, false, false) + assert.NotNil(t, issuer) + + err = issuer.Init(false, db, &dbutil.Levels{Credential: 1, RAInfo: 1, Nonce: 1}) + assert.NoError(t, err, "Init should not return an error") + + _, err = issuer.RevocationPublicKey() + assert.NoError(t, err, "RevocationPublicKey should not return an error") +} + func getIssuer(t *testing.T, testDir string, getranderror, newIssuerKeyerror bool) (*dmocks.FabricCADB, Issuer) { err := os.MkdirAll(filepath.Join(testDir, "msp/keystore"), 0777) if err != nil { diff --git a/lib/server/idemix/issuer_whitebox_test.go b/lib/server/idemix/issuer_whitebox_test.go index 664e53c4c..aef2485ef 100644 --- a/lib/server/idemix/issuer_whitebox_test.go +++ b/lib/server/idemix/issuer_whitebox_test.go @@ -49,6 +49,10 @@ func TestIssuer(t *testing.T) { assert.Error(t, err, "IssuerPublicKey should return an error because issuer is not initialized") assert.Equal(t, "Issuer is not initialized", err.Error()) + _, err = issuer.RevocationPublicKey() + assert.Error(t, err, "RevocationPublicKey should return an error because issuer is not initialized") + assert.Equal(t, "Issuer is not initialized", err.Error()) + _, err = issuer.IssueCredential(nil) assert.Error(t, err, "IssueCredential should return an error because issuer is not initialized") assert.Equal(t, "Issuer is not initialized", err.Error()) diff --git a/swagger/swagger-fabric-ca.json b/swagger/swagger-fabric-ca.json index 8db81b4fe..c98d60dc0 100644 --- a/swagger/swagger-fabric-ca.json +++ b/swagger/swagger-fabric-ca.json @@ -162,6 +162,10 @@ "type": "string", "description": "Base 64 encoding of the CA's idemix public key" }, + "issuerrevocationpublickey": { + "type": "string", + "description": "Base 64 encoding of PEM-encoded bytes of the CA's Idemix Issuer revocation public key" + }, "version": { "type": "string", "description": "Version of the server" @@ -334,6 +338,10 @@ "type": "string", "description": "Base 64 encoding of the CA's Idemix public key" }, + "issuerrevocationpublickey": { + "type": "string", + "description": "Base 64 encoding of PEM-encoded bytes of the CA's Idemix Issuer revocation public key" + }, "version": { "type": "string", "description": "Version of the server" @@ -505,7 +513,11 @@ }, "issuerpublickey": { "type": "string", - "description": "Base 64 encoding of the CA's idemix public key" + "description": "Base 64 encoding of the CA's Idemix Issuer public key" + }, + "issuerrevocationpublickey": { + "type": "string", + "description": "Base 64 encoding of PEM-encoded bytes of the CA's Idemix Issuer revocation public key" }, "version": { "type": "string",