Skip to content

Commit

Permalink
[FAB-10101] Verify token based on idemix cred
Browse files Browse the repository at this point in the history
This change set contains changes for verifying token
based on an Idemix credential.

Change-Id: I5b2861833d292280b044f28794772d43f93faa33
Signed-off-by: Anil Ambati <[email protected]>
  • Loading branch information
Anil Ambati authored and Saad Karim committed May 23, 2018
1 parent 2032d77 commit 69d5be1
Show file tree
Hide file tree
Showing 13 changed files with 430 additions and 11 deletions.
2 changes: 1 addition & 1 deletion lib/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func initCA(ca *CA, homeDir string, config *CAConfig, server *Server, renew bool
}
log.Debug("Initializing Idemix issuer...")
ca.issuer = idemix.NewIssuer(ca.Config.CA.Name, ca.HomeDir,
&ca.Config.Idemix, idemix.NewLib())
&ca.Config.Idemix, ca.csp, idemix.NewLib())
err = ca.issuer.Init(renew, ca.db, ca.levels)
if err != nil {
return errors.WithMessage(err, fmt.Sprintf("Failed to initialize Idemix issuer for CA '%s'", err.Error()))
Expand Down
3 changes: 2 additions & 1 deletion lib/client/credential/idemix/credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/golang/protobuf/proto"
fp256bn "github.com/hyperledger/fabric-amcl/amcl/FP256BN"
"github.com/hyperledger/fabric-ca/api"
"github.com/hyperledger/fabric-ca/lib/common"
"github.com/hyperledger/fabric-ca/util"
"github.com/hyperledger/fabric/bccsp"
idemix "github.com/hyperledger/fabric/idemix"
Expand Down Expand Up @@ -161,7 +162,7 @@ func (cred *Credential) CreateToken(req *http.Request, reqBody []byte) (string,
return "", errors.Wrapf(err, "Failed to create signature while creating token")
}
sigBytes, err := proto.Marshal(sig)
token := "idemix." + enrollmentID + "." + util.B64Encode(sigBytes)
token := "idemix." + common.IdemixTokenVersion1 + "." + enrollmentID + "." + util.B64Encode(sigBytes)
return token, nil
}

Expand Down
62 changes: 62 additions & 0 deletions lib/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,68 @@ func TestIdemixEnroll(t *testing.T) {
assert.Error(t, err, "Idemix enroll should fail as the certificate is of unregistered user")
}

func TestGetCRIUsingIdemixToken(t *testing.T) {
srvHome, err := ioutil.TempDir(testdataDir, "idemixgetcrisrv")
if err != nil {
t.Fatal("Failed to create server home directory")
}
clientHome, err := ioutil.TempDir(testdataDir, "idemixgetcriclient")
if err != nil {
t.Fatal("Failed to create server home directory")
}

server := TestGetServer(ctport1, srvHome, "", 5, t)
if server == nil {
t.Fatal("Failed to create test server")
}
err = server.Start()
if err != nil {
t.Fatalf("Failed to start server: %s", err)
}
stopserver := true
defer func() {
if stopserver {
err = server.Stop()
if err != nil {
t.Errorf("Failed to stop server: %s", err)
}
}
os.RemoveAll(srvHome)
os.RemoveAll(clientHome)
}()

client := &Client{
Config: &ClientConfig{URL: fmt.Sprintf("http://localhost:%d", ctport1)},
HomeDir: clientHome,
}

cainfo, err := client.GetCAInfo(&api.GetCAInfoRequest{})
if err != nil {
t.Fatalf("Failed to get CA info: %s", err)
}
err = util.WriteFile(filepath.Join(clientHome, "msp/IssuerPublicKey"), cainfo.IssuerPublicKey, 0644)
if err != nil {
t.Fatalf("Failed to store CA's idemix public key: %s", err)
}

req := &api.EnrollmentRequest{
Type: "idemix",
Name: "admin",
Secret: "adminpw",
}

enrollRes, err := client.Enroll(req)
assert.NoError(t, err, "Idemix enroll should not have failed with valid userid/password")
err = enrollRes.Identity.Store()
if err != nil {
t.Fatalf("Failed to store idenditity: %s", err.Error())
}

criRes, err := enrollRes.Identity.GetCRI(&api.GetCRIRequest{CAName: ""})
assert.NoError(t, err)
assert.NotNil(t, criRes)
}

func TestGetCRIUsingX509Token(t *testing.T) {
srvHome, err := ioutil.TempDir(testdataDir, "idemixgetcrix509srv")
if err != nil {
Expand Down
5 changes: 5 additions & 0 deletions lib/common/serverresponses.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ SPDX-License-Identifier: Apache-2.0

package common

const (
// IdemixTokenVersion1 represents version 1 of the authorization token created using Idemix credential
IdemixTokenVersion1 = "1"
)

// CAInfoResponseNet is the response to the GET /info request
type CAInfoResponseNet struct {
// CAName is a unique name associated with fabric-ca-server's CA
Expand Down
89 changes: 87 additions & 2 deletions lib/server/idemix/issuer.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,23 @@ SPDX-License-Identifier: Apache-2.0
package idemix

import (
"fmt"
"reflect"
"strings"
"sync"
"time"

"github.com/cloudflare/cfssl/log"
proto "github.com/golang/protobuf/proto"
"github.com/hyperledger/fabric-amcl/amcl"
fp256bn "github.com/hyperledger/fabric-amcl/amcl/FP256BN"
"github.com/hyperledger/fabric-ca/api"
"github.com/hyperledger/fabric-ca/lib/common"
"github.com/hyperledger/fabric-ca/lib/dbutil"
"github.com/hyperledger/fabric-ca/lib/spi"
"github.com/hyperledger/fabric-ca/util"
"github.com/hyperledger/fabric/bccsp"
"github.com/hyperledger/fabric/idemix"
"github.com/pkg/errors"
)

Expand All @@ -27,6 +33,7 @@ type Issuer interface {
IssuerPublicKey() ([]byte, error)
IssueCredential(ctx ServerRequestCtx) (*EnrollmentResponse, error)
GetCRI(ctx ServerRequestCtx) (*api.GetCRIResponse, error)
VerifyToken(authHdr string, body []byte) (string, error)
}

// MyIssuer provides functions for accessing issuer components
Expand Down Expand Up @@ -57,6 +64,7 @@ type issuer struct {
cfg *Config
idemixLib Lib
db dbutil.FabricCADB
csp bccsp.BCCSP
// The Idemix credential DB accessor
credDBAccessor CredDBAccessor
// idemix issuer credential for the CA
Expand All @@ -70,8 +78,8 @@ type issuer struct {
}

// NewIssuer returns an object that implements Issuer interface
func NewIssuer(name, homeDir string, config *Config, idemixLib Lib) Issuer {
issuer := issuer{name: name, homeDir: homeDir, cfg: config, idemixLib: idemixLib}
func NewIssuer(name, homeDir string, config *Config, csp bccsp.BCCSP, idemixLib Lib) Issuer {
issuer := issuer{name: name, homeDir: homeDir, cfg: config, csp: csp, idemixLib: idemixLib}
return &issuer
}

Expand Down Expand Up @@ -157,6 +165,66 @@ func (i *issuer) GetCRI(ctx ServerRequestCtx) (*api.GetCRIResponse, error) {
return handler.HandleRequest()
}

func (i *issuer) VerifyToken(authHdr string, body []byte) (string, error) {
if !i.isInitialized {
return "", errors.New("Issuer is not initialized")
}
// Disclosure array indicates which attributes are disclosed. 1 means disclosed. Currently four attributes are
// supported: OU, isAdmin, enrollmentID and revocationHandle. Third element of disclosure array is set to 1
// to indicate that the server expects enrollmentID to be disclosed in the signature sent in the authorization token.
// EnrollmentID is disclosed to check if the signature was infact created using credential of a user whose
// enrollment ID is the one specified in the token. So, enrollment ID in the token is used to check if the user
// is valid and has a credential (by checking the DB) and it is used to verify zero knowledge proof.
disclosure := []byte{0, 0, 1, 0}
parts := getTokenParts(authHdr)
if parts == nil {
return "", errors.New("Invalid Idemix token format; token format must be: 'idemix.<enrollment ID>.<base64 encoding of Idemix signature bytes>'")
}
if parts[1] != common.IdemixTokenVersion1 {
return "", errors.New("Invalid version found in the Idemix token. Version must be 1")
}
enrollmentID := parts[2]
creds, err := i.credDBAccessor.GetCredentialsByID(enrollmentID)
if err != nil {
return "", errors.Errorf("Failed to check if enrollment ID '%s' is valid", enrollmentID)
}
if len(creds) == 0 {
return "", errors.Errorf("Enrollment ID '%s' does not have any Idemix credentials", enrollmentID)
}
idBytes := []byte(enrollmentID)
attrs := []*fp256bn.BIG{nil, nil, idemix.HashModOrder(idBytes), nil}
msg := util.B64Encode(body)
digest, digestError := i.csp.Hash([]byte(msg), &bccsp.SHAOpts{})
if digestError != nil {
return "", errors.WithMessage(digestError, fmt.Sprintf("Failed to create authentication token '%s'", msg))
}

issuerKey, err := i.issuerCred.GetIssuerKey()
if err != nil {
return "", errors.WithMessage(err, "Failed to get issuer key")
}
ra := i.RevocationAuthority()
epoch, err := ra.Epoch()
if err != nil {
return "", err
}

sigBytes, err := util.B64Decode(parts[3])
if err != nil {
return "", errors.WithMessage(err, "Failed to base64 decode signature specified in the token")
}
sig := &idemix.Signature{}
err = proto.Unmarshal(sigBytes, sig)
if err != nil {
return "", errors.WithMessage(err, "Failed to unmarshal signature bytes specified in the token")
}
err = sig.Ver(disclosure, issuerKey.IPk, digest, attrs, 3, ra.PublicKey(), epoch)
if err != nil {
return "", errors.WithMessage(err, "Failed to verify the token")
}
return enrollmentID, nil
}

// Name returns the name of the issuer
func (i *issuer) Name() string {
return i.name
Expand Down Expand Up @@ -247,6 +315,23 @@ func (i *issuer) initKeyMaterial(renew bool) error {
return nil
}

func getTokenParts(token string) []string {
parts := strings.Split(token, ".")
if len(parts) == 4 && parts[0] == "idemix" {
return parts
}
return nil
}

// IsToken returns true if the specified token has the format expected of an authorization token
// that is created using an Idemix credential
func IsToken(token string) bool {
if getTokenParts(token) != nil {
return true
}
return false
}

type wallClock struct{}

func (wc wallClock) Now() time.Time {
Expand Down
Loading

0 comments on commit 69d5be1

Please sign in to comment.