Skip to content

Commit

Permalink
[FAB-10100] Client changes for getting CRI
Browse files Browse the repository at this point in the history
Changes include:
1. Client changes for creating CRI
2. Changes for creating authorization token using Idemix credential

Change-Id: Idca8330e9d9ef1235989d2331033aa2ccdc6e249
Signed-off-by: Anil Ambati <aambati@us.ibm.com>
  • Loading branch information
Anil Ambati committed May 21, 2018
1 parent 77dc5a6 commit bedd37c
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 109 deletions.
4 changes: 2 additions & 2 deletions lib/client/credential/credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ type Credential interface {
Store() error
// Loads the credential value from disk and sets the value of this credential
Load() error
// CreateOAuthToken returns oauth autentication token for that request with
// CreateToken returns authorization token for the specified request with
// specified body
CreateOAuthToken(req *http.Request, reqBody []byte) (string, error)
CreateToken(req *http.Request, reqBody []byte) (string, error)
// Submits revoke request to the Fabric CA server to revoke this credential
RevokeSelf() (*api.RevocationResponse, error)
}
109 changes: 56 additions & 53 deletions lib/client/credential/idemix/credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"net/http"

"github.com/cloudflare/cfssl/log"
"github.com/golang/protobuf/proto"
fp256bn "github.com/hyperledger/fabric-amcl/amcl/FP256BN"
"github.com/hyperledger/fabric-ca/api"
"github.com/hyperledger/fabric-ca/util"
"github.com/hyperledger/fabric/bccsp"
Expand Down Expand Up @@ -52,24 +54,24 @@ func (cred *Credential) Type() string {
// Val returns *SignerConfig associated with this Idemix credential
func (cred *Credential) Val() (interface{}, error) {
if cred.val == nil {
return nil, errors.New("Credential value is not set")
return nil, errors.New("Idemix credential value is not set")
}
return cred.val, nil
}

// EnrollmentID returns enrollment ID associated with this Idemix credential
func (cred *Credential) EnrollmentID() (string, error) {
if cred.val == nil {
return "", errors.New("Credential value is not set")
return "", errors.New("Idemix credential value is not set")
}
return "", errors.New("Not implemented") // TODO
return cred.val.EnrollmentID, nil
}

// SetVal sets *SignerConfig for this Idemix credential
func (cred *Credential) SetVal(val interface{}) error {
s, ok := val.(*SignerConfig)
if !ok {
return errors.New("The credential should be of type *SignerConfig for idemix Credential")
return errors.New("The Idemix credential value must be of type *SignerConfig for idemix Credential")
}
cred.val = s
return nil
Expand Down Expand Up @@ -111,55 +113,56 @@ func (cred *Credential) Load() error {
return nil
}

// CreateOAuthToken creates oauth token based on this Idemix credential
func (cred *Credential) CreateOAuthToken(req *http.Request, reqBody []byte) (string, error) {
return "", errors.New("Not implemented") // TODO
// enrollmentID, err := cred.EnrollmentID()
// if err != nil {
// return "", err
// }
// rng, err := idemix.GetRand()
// if err != nil {
// return "", errors.WithMessage(err, "Failed to get a random number while creating oauth token")
// }
// // Get user's secret key
// sk := amcl.FromBytes(cred.val.GetSk())

// // Get issuer public key
// ipk, err := cred.client.GetIssuerPubKey()
// if err != nil {
// return "", errors.WithMessage(err, "Failed to get CA's Idemix public key while creating oauth token")
// }

// // Generate a fresh Pseudonym (and a corresponding randomness)
// nym, randNym := idemix.MakeNym(sk, ipk, rng)

// nymBytes := []byte{}
// nym.ToBytes(nymBytes)
// nym64Encoding := util.B64Encode(nymBytes)
// body64Encoding := util.B64Encode(reqBody)
// msg := nym64Encoding + "." + body64Encoding

// digest, digestError := cred.client.GetCSP().Hash([]byte(msg), &bccsp.SHAOpts{})
// if digestError != nil {
// return "", errors.WithMessage(digestError, fmt.Sprintf("Failed to create authentication token '%s'", msg))
// }

// // a disclosure vector is formed (indicating that all attributes from the credential are revealed)
// disclosure := []byte{1, 1, 1, 1}

// var credential *idemix.Credential
// err = json.Unmarshal(cred.val.GetCred(), credential)
// if err != nil {
// errors.Wrapf(err, "Failed to unmarshal Idemix credential while creating oauth token")
// }
// sig, err := idemix.NewSignature(credential, sk, nym, randNym, ipk, disclosure, digest, rng)
// if err != nil {
// errors.Wrapf(err, "Failed to create signature while creating oauth token")
// }
// sigBytes, err := json.Marshal(sig)
// token := enrollmentID + "." + util.B64Encode(sigBytes)
// return token, nil
// CreateToken creates authorization token based on this Idemix credential
func (cred *Credential) CreateToken(req *http.Request, reqBody []byte) (string, error) {
enrollmentID, err := cred.EnrollmentID()
if err != nil {
return "", err
}
rng, err := idemix.GetRand()
if err != nil {
return "", errors.WithMessage(err, "Failed to get a random number while creating token")
}
// Get user's secret key
sk := fp256bn.FromBytes(cred.val.GetSk())

// Get issuer public key
ipk, err := cred.client.GetIssuerPubKey()
if err != nil {
return "", errors.WithMessage(err, "Failed to get CA's Idemix public key while creating token")
}

// Generate a fresh Pseudonym (and a corresponding randomness)
nym, randNym := idemix.MakeNym(sk, ipk, rng)

msg := util.B64Encode(reqBody)

digest, digestError := cred.client.GetCSP().Hash([]byte(msg), &bccsp.SHAOpts{})
if digestError != nil {
return "", errors.WithMessage(digestError, fmt.Sprintf("Failed to create token '%s'", msg))
}

// A disclosure vector is formed (indicating that only enrollment ID from the credential is revealed)
disclosure := []byte{0, 0, 1, 0}

credential := idemix.Credential{}
err = proto.Unmarshal(cred.val.GetCred(), &credential)
if err != nil {
return "", errors.Wrapf(err, "Failed to unmarshal Idemix credential while creating token")
}
cri := idemix.CredentialRevocationInformation{}
err = proto.Unmarshal(cred.val.GetCredentialRevocationInformation(), &cri)
if err != nil {
return "", errors.Wrapf(err, "Failed to unmarshal Idemix CRI while creating token")
}

sig, err := idemix.NewSignature(&credential, sk, nym, randNym, ipk, disclosure, digest, 3, &cri, rng)
if err != nil {
return "", errors.Wrapf(err, "Failed to create signature while creating token")
}
sigBytes, err := proto.Marshal(sig)
token := "idemix." + enrollmentID + "." + util.B64Encode(sigBytes)
return token, nil
}

// RevokeSelf revokes this Idemix credential
Expand Down
63 changes: 54 additions & 9 deletions lib/client/credential/idemix/credential_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
const (
testDataDir = "../../../../testdata"
testSignerConfigFile = testDataDir + "/IdemixSignerConfig"
testIssuerPublicFile = testDataDir + "/IdemixPublicKey"
)

func TestIdemixCredential(t *testing.T) {
Expand All @@ -39,19 +40,33 @@ func TestIdemixCredential(t *testing.T) {
},
HomeDir: clientHome,
}
err = client.Init()
if err != nil {
t.Fatalf("Failed to initialize client: %s", err.Error())
}

idemixCred := NewCredential(signerConfig, client)

assert.Equal(t, idemixCred.Type(), CredType, "Type for a IdemixCredential instance must be Idemix")
_, err = idemixCred.Val()
assert.Error(t, err, "Val should return error if credential has not been loaded from disk or set")
if err != nil {
assert.Equal(t, err.Error(), "Credential value is not set")
assert.Equal(t, err.Error(), "Idemix credential value is not set")
}
_, err = idemixCred.EnrollmentID()
assert.Error(t, err, "EnrollmentID should return an error if credential has not been loaded from disk or set")
if err != nil {
assert.Equal(t, err.Error(), "Credential value is not set")
assert.Equal(t, err.Error(), "Idemix credential value is not set")
}
body := []byte("hello")
req, err := http.NewRequest("GET", "localhost:7054/enroll", bytes.NewReader(body))
if err != nil {
t.Fatalf("Failed to create HTTP request: %s", err.Error())
}
_, err = idemixCred.CreateToken(req, body)
assert.Error(t, err, "CreateToken should return an error if credential has not been loaded from disk or set")
if err != nil {
assert.Equal(t, err.Error(), "Idemix credential value is not set")
}

err = idemixCred.SetVal("hello")
Expand All @@ -75,6 +90,16 @@ func TestIdemixCredential(t *testing.T) {
t.Fatalf("Failed to copy %s to %s: %s", testSignerConfigFile, signerConfig, err.Error())
}

clientPubKeyFile := filepath.Join(clientHome, "msp/IssuerPublicKey")
err = os.MkdirAll(filepath.Join(clientHome, "msp"), 0744)
if err != nil {
t.Fatalf("Failed to create msp directory: %s", err.Error())
}
err = lib.CopyFile(testIssuerPublicFile, clientPubKeyFile)
if err != nil {
t.Fatalf("Failed to copy %s to %s: %s", testIssuerPublicFile, clientPubKeyFile, err.Error())
}

err = idemixCred.Load()
assert.NoError(t, err, "Load should not return error as %s exists and is valid", signerConfig)

Expand All @@ -85,11 +110,14 @@ func TestIdemixCredential(t *testing.T) {
cred := signercfg.GetCred()
assert.NotNil(t, cred)
assert.True(t, len(cred) > 0, "Credential bytes length should be more than zero")

enrollID := signercfg.GetEnrollmentID()
assert.Equal(t, "admin", enrollID, "Enrollment ID of the Idemix credential in testdata/IdemixSignerConfig should be admin")

sk := signercfg.GetSk()
assert.NotNil(t, sk, "secret key should not be nil")
assert.True(t, len(sk) > 0, "Secret key bytes length should be more than zero")

signercfg.GetOrganizationalUnitIdentifier()
isAdmin := signercfg.GetIsAdmin()
assert.False(t, isAdmin)
Expand All @@ -113,15 +141,32 @@ func TestIdemixCredential(t *testing.T) {
assert.NoError(t, err, "Val should not return error as Idemix credential has been loaded")

_, err = idemixCred.EnrollmentID()
assert.Error(t, err, "EnrollmentID is not implemented for Idemix credential")
assert.NoError(t, err, "EnrollmentID should not return error as Idemix credential has been loaded")

body := []byte("hello")
req, err := http.NewRequest("GET", "localhost:7054/enroll", bytes.NewReader(body))
if err != nil {
t.Fatalf("Failed to create HTTP request: %s", err.Error())
if err = os.Chmod(clientPubKeyFile, 0000); err != nil {
t.Fatalf("Failed to chmod SignerConfig file %s: %v", clientPubKeyFile, err)
}
_, err = idemixCred.CreateToken(req, body)
assert.Error(t, err, "CreateToken should fail as %s is not readable", clientPubKeyFile)

if err = os.Chmod(clientPubKeyFile, 0644); err != nil {
t.Fatalf("Failed to chmod SignerConfig file %s: %v", clientPubKeyFile, err)
}
_, err = idemixCred.CreateOAuthToken(req, body)
assert.Error(t, err, "CreateOAuthToken is not implemented for Idemix credential")

origCred := signercfg.Cred
signercfg.Cred = []byte("fakecred")
_, err = idemixCred.CreateToken(req, body)
assert.Error(t, err, "CreateToken should fail credential is junk bytes in the signerconfig")
signercfg.Cred = origCred

origCri := signercfg.CredentialRevocationInformation
signercfg.CredentialRevocationInformation = []byte("fakecred")
_, err = idemixCred.CreateToken(req, body)
assert.Error(t, err, "CreateToken should fail credential revocation information is junk bytes in the signerconfig")
signercfg.CredentialRevocationInformation = origCri

_, err = idemixCred.CreateToken(req, body)
assert.NoError(t, err, "CreateToken should not return error as Idemix credential has been loaded")

_, err = idemixCred.RevokeSelf()
assert.Error(t, err, "RevokeSelf should fail as it is not implmented for Idemix credential")
Expand Down
24 changes: 11 additions & 13 deletions lib/client/credential/x509/credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,15 @@ func (cred *Credential) Type() string {
// Val returns *Signer associated with this X509 credential
func (cred *Credential) Val() (interface{}, error) {
if cred.val == nil {
return nil, errors.New("Credential value is not set")
return nil, errors.New("X509 Credential value is not set")
}
return cred.val, nil
}

// EnrollmentID returns enrollment ID of this X509 credential
func (cred *Credential) EnrollmentID() (string, error) {
if cred.val == nil {
return "", errors.New("Credential value is not set")
return "", errors.New("X509 Credential value is not set")
}
return cred.val.GetName(), nil
}
Expand All @@ -75,7 +75,7 @@ func (cred *Credential) EnrollmentID() (string, error) {
func (cred *Credential) SetVal(val interface{}) error {
s, ok := val.(*Signer)
if !ok {
return errors.New("The credential value should be of type *Signer for X509 credential")
return errors.New("The X509 credential value must be of type *Signer for X509 credential")
}
cred.val = s
return nil
Expand Down Expand Up @@ -112,7 +112,7 @@ func (cred *Credential) Load() error {
// specified by certFile attribute
func (cred *Credential) Store() error {
if cred.val == nil {
return errors.New("Certificate is not set")
return errors.New("X509 Credential value is not set")
}
err := util.WriteFile(cred.certFile, cred.val.Cert(), 0644)
if err != nil {
Expand All @@ -122,27 +122,25 @@ func (cred *Credential) Store() error {
return nil
}

// CreateOAuthToken creates oauth token based on this X509 credential
func (cred *Credential) CreateOAuthToken(req *http.Request, reqBody []byte) (string, error) {
// CreateToken creates token based on this X509 credential
func (cred *Credential) CreateToken(req *http.Request, reqBody []byte) (string, error) {
return util.CreateToken(cred.getCSP(), cred.val.certBytes, cred.val.key, reqBody)
}

// RevokeSelf revokes this X509 credential
func (cred *Credential) RevokeSelf() (*api.RevocationResponse, error) {
val := cred.val
if val == nil {
return nil, errors.New("Credential value is not set")
name, err := cred.EnrollmentID()
if err != nil {
return nil, err
}
val := cred.val
serial := util.GetSerialAsHex(val.cert.SerialNumber)
aki := hex.EncodeToString(val.cert.AuthorityKeyId)
req := &api.RevocationRequest{
Serial: serial,
AKI: aki,
}
name, err := cred.EnrollmentID()
if err != nil {
return nil, err
}

id := cred.client.NewX509Identity(name, []credential.Credential{cred})
return id.Revoke(req)
}
Expand Down
Loading

0 comments on commit bedd37c

Please sign in to comment.