Skip to content

Commit

Permalink
[FAB-10324] Add issuer revocation pub key to cainfo
Browse files Browse the repository at this point in the history
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 <aambati@us.ibm.com>
  • Loading branch information
Anil Ambati committed May 24, 2018
1 parent 69d5be1 commit a7a4075
Show file tree
Hide file tree
Showing 9 changed files with 117 additions and 22 deletions.
6 changes: 5 additions & 1 deletion cmd/fabric-ca-client/command/enroll.go
Expand Up @@ -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)
}
28 changes: 21 additions & 7 deletions cmd/fabric-ca-client/command/getcainfo.go
Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand All @@ -189,15 +193,25 @@ 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
}
}
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)
Expand Down
5 changes: 5 additions & 0 deletions lib/ca.go
Expand Up @@ -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
}

Expand Down
23 changes: 16 additions & 7 deletions lib/client.go
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down
4 changes: 3 additions & 1 deletion lib/common/serverresponses.go
Expand Up @@ -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
}
Expand Down
24 changes: 20 additions & 4 deletions lib/server/idemix/issuer.go
Expand Up @@ -7,6 +7,8 @@ SPDX-License-Identifier: Apache-2.0
package idemix

import (
"crypto/x509"
"encoding/pem"
"fmt"
"reflect"
"strings"
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -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 {
Expand Down
31 changes: 30 additions & 1 deletion lib/server/idemix/issuer_test.go
Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions lib/server/idemix/issuer_whitebox_test.go
Expand Up @@ -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())
Expand Down
14 changes: 13 additions & 1 deletion swagger/swagger-fabric-ca.json
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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",
Expand Down

0 comments on commit a7a4075

Please sign in to comment.