github.com/blockchain-gm/fabric-ca@v0.0.0-20200423072702-b2c40c7ac69c/lib/server/idemix/issuer.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package idemix
     8  
     9  import (
    10  	// "crypto/x509"
    11  	"encoding/pem"
    12  	"fmt"
    13  	"reflect"
    14  	"strings"
    15  	"sync"
    16  	"time"
    17  
    18  	sm2 "github.com/tjfoc/gmsm/sm2"
    19  
    20  	"github.com/cloudflare/cfssl/log"
    21  	proto "github.com/golang/protobuf/proto"
    22  	"github.com/hyperledger/fabric-amcl/amcl"
    23  	fp256bn "github.com/hyperledger/fabric-amcl/amcl/FP256BN"
    24  	"github.com/hyperledger/fabric-ca/api"
    25  	"github.com/hyperledger/fabric-ca/lib/common"
    26  	"github.com/hyperledger/fabric-ca/lib/server/db"
    27  	dbutil "github.com/hyperledger/fabric-ca/lib/server/db/util"
    28  	"github.com/hyperledger/fabric-ca/lib/server/user"
    29  	"github.com/hyperledger/fabric-ca/util"
    30  	"github.com/hyperledger/fabric/bccsp"
    31  	"github.com/hyperledger/fabric/idemix"
    32  	"github.com/pkg/errors"
    33  )
    34  
    35  // Issuer is the interface to the Issuer for external components
    36  type Issuer interface {
    37  	Init(renew bool, db db.FabricCADB, levels *dbutil.Levels) error
    38  	IssuerPublicKey() ([]byte, error)
    39  	RevocationPublicKey() ([]byte, error)
    40  	IssueCredential(ctx ServerRequestCtx) (*EnrollmentResponse, error)
    41  	GetCRI(ctx ServerRequestCtx) (*api.GetCRIResponse, error)
    42  	VerifyToken(authHdr, method, uri string, body []byte) (string, error)
    43  }
    44  
    45  //go:generate mockery -name MyIssuer -case underscore
    46  
    47  // MyIssuer provides functions for accessing issuer components
    48  type MyIssuer interface {
    49  	Name() string
    50  	HomeDir() string
    51  	Config() *Config
    52  	IdemixLib() Lib
    53  	DB() db.FabricCADB
    54  	IdemixRand() *amcl.RAND
    55  	IssuerCredential() IssuerCredential
    56  	RevocationAuthority() RevocationAuthority
    57  	NonceManager() NonceManager
    58  	CredDBAccessor() CredDBAccessor
    59  }
    60  
    61  //go:generate mockery -name ServerRequestCtx -case underscore
    62  
    63  // ServerRequestCtx is the server request context that Idemix enroll expects
    64  type ServerRequestCtx interface {
    65  	IsBasicAuth() bool
    66  	BasicAuthentication() (string, error)
    67  	TokenAuthentication() (string, error)
    68  	GetCaller() (user.User, error)
    69  	ReadBody(body interface{}) error
    70  }
    71  
    72  type issuer struct {
    73  	name      string
    74  	homeDir   string
    75  	cfg       *Config
    76  	idemixLib Lib
    77  	db        db.FabricCADB
    78  	csp       bccsp.BCCSP
    79  	// The Idemix credential DB accessor
    80  	credDBAccessor CredDBAccessor
    81  	// idemix issuer credential for the CA
    82  	issuerCred IssuerCredential
    83  	// A random number used in generation of Idemix nonces and credentials
    84  	idemixRand    *amcl.RAND
    85  	rc            RevocationAuthority
    86  	nm            NonceManager
    87  	isInitialized bool
    88  	mutex         sync.Mutex
    89  }
    90  
    91  // NewIssuer returns an object that implements Issuer interface
    92  func NewIssuer(name, homeDir string, config *Config, csp bccsp.BCCSP, idemixLib Lib) Issuer {
    93  	issuer := issuer{name: name, homeDir: homeDir, cfg: config, csp: csp, idemixLib: idemixLib}
    94  	return &issuer
    95  }
    96  
    97  func (i *issuer) Init(renew bool, db db.FabricCADB, levels *dbutil.Levels) error {
    98  
    99  	if i.isInitialized {
   100  		return nil
   101  	}
   102  
   103  	i.mutex.Lock()
   104  	defer i.mutex.Unlock()
   105  
   106  	// After obtaining a lock, check again to see if issuer has been initialized by another thread
   107  	if i.isInitialized {
   108  		return nil
   109  	}
   110  
   111  	if db == nil || reflect.ValueOf(db).IsNil() || !db.IsInitialized() {
   112  		log.Debugf("Returning without initializing Idemix issuer for CA '%s' as the database is not initialized", i.Name())
   113  		return nil
   114  	}
   115  	i.db = db
   116  	err := i.cfg.init(i.homeDir)
   117  	if err != nil {
   118  		return err
   119  	}
   120  	err = i.initKeyMaterial(renew)
   121  	if err != nil {
   122  		return err
   123  	}
   124  	i.credDBAccessor = NewCredentialAccessor(i.db, levels.Credential)
   125  	log.Debugf("Intializing revocation authority for issuer '%s'", i.Name())
   126  	i.rc, err = NewRevocationAuthority(i, levels.RAInfo)
   127  	if err != nil {
   128  		return err
   129  	}
   130  	log.Debugf("Intializing nonce manager for issuer '%s'", i.Name())
   131  	i.nm, err = NewNonceManager(i, &wallClock{}, levels.Nonce)
   132  	if err != nil {
   133  		return err
   134  	}
   135  	i.isInitialized = true
   136  	return nil
   137  }
   138  
   139  func (i *issuer) IssuerPublicKey() ([]byte, error) {
   140  	if !i.isInitialized {
   141  		return nil, errors.New("Issuer is not initialized")
   142  	}
   143  	ik, err := i.issuerCred.GetIssuerKey()
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  	ipkBytes, err := proto.Marshal(ik.Ipk)
   148  	if err != nil {
   149  		return nil, err
   150  	}
   151  	return ipkBytes, nil
   152  }
   153  
   154  func (i *issuer) RevocationPublicKey() ([]byte, error) {
   155  	if !i.isInitialized {
   156  		return nil, errors.New("Issuer is not initialized")
   157  	}
   158  	rpk := i.RevocationAuthority().PublicKey()
   159  	encodedPubKey, err := sm2.MarshalPKIXPublicKey(rpk)
   160  	if err != nil {
   161  		return nil, errors.Wrapf(err, "Failed to encode revocation authority public key of the issuer %s", i.Name())
   162  	}
   163  	pemEncodedPubKey := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: encodedPubKey})
   164  	return pemEncodedPubKey, nil
   165  }
   166  
   167  func (i *issuer) IssueCredential(ctx ServerRequestCtx) (*EnrollmentResponse, error) {
   168  	if !i.isInitialized {
   169  		return nil, errors.New("Issuer is not initialized")
   170  	}
   171  	handler := EnrollRequestHandler{
   172  		Ctx:     ctx,
   173  		Issuer:  i,
   174  		IdmxLib: i.idemixLib,
   175  	}
   176  
   177  	return handler.HandleRequest()
   178  }
   179  
   180  func (i *issuer) GetCRI(ctx ServerRequestCtx) (*api.GetCRIResponse, error) {
   181  	if !i.isInitialized {
   182  		return nil, errors.New("Issuer is not initialized")
   183  	}
   184  	handler := CRIRequestHandler{
   185  		Ctx:    ctx,
   186  		Issuer: i,
   187  	}
   188  
   189  	return handler.HandleRequest()
   190  }
   191  
   192  func (i *issuer) VerifyToken(authHdr, method, uri string, body []byte) (string, error) {
   193  	if !i.isInitialized {
   194  		return "", errors.New("Issuer is not initialized")
   195  	}
   196  	// Disclosure array indicates which attributes are disclosed. 1 means disclosed. Currently four attributes are
   197  	// supported: OU, role, enrollmentID and revocationHandle. Third element of disclosure array is set to 1
   198  	// to indicate that the server expects enrollmentID to be disclosed in the signature sent in the authorization token.
   199  	// EnrollmentID is disclosed to check if the signature was infact created using credential of a user whose
   200  	// enrollment ID is the one specified in the token. So, enrollment ID in the token is used to check if the user
   201  	// is valid and has a credential (by checking the DB) and it is used to verify zero knowledge proof.
   202  	disclosure := []byte{0, 0, 1, 0}
   203  	parts := getTokenParts(authHdr)
   204  	if parts == nil {
   205  		return "", errors.New("Invalid Idemix token format; token format must be: 'idemix.<enrollment ID>.<base64 encoding of Idemix signature bytes>'")
   206  	}
   207  	if parts[1] != common.IdemixTokenVersion1 {
   208  		return "", errors.New("Invalid version found in the Idemix token. Version must be 1")
   209  	}
   210  	enrollmentID := parts[2]
   211  	creds, err := i.credDBAccessor.GetCredentialsByID(enrollmentID)
   212  	if err != nil {
   213  		return "", errors.Errorf("Failed to check if enrollment ID '%s' is valid", enrollmentID)
   214  	}
   215  	if len(creds) == 0 {
   216  		return "", errors.Errorf("Enrollment ID '%s' does not have any Idemix credentials", enrollmentID)
   217  	}
   218  	idBytes := []byte(enrollmentID)
   219  	attrs := []*fp256bn.BIG{nil, nil, idemix.HashModOrder(idBytes), nil}
   220  	b64body := util.B64Encode(body)
   221  	b64uri := util.B64Encode([]byte(uri))
   222  	msg := method + "." + b64uri + "." + b64body
   223  	digest, digestError := i.csp.Hash([]byte(msg), &bccsp.SHAOpts{})
   224  	if digestError != nil {
   225  		return "", errors.WithMessage(digestError, fmt.Sprintf("Failed to create authentication token '%s'", msg))
   226  	}
   227  
   228  	issuerKey, err := i.issuerCred.GetIssuerKey()
   229  	if err != nil {
   230  		return "", errors.WithMessage(err, "Failed to get issuer key")
   231  	}
   232  	ra := i.RevocationAuthority()
   233  	epoch, err := ra.Epoch()
   234  	if err != nil {
   235  		return "", err
   236  	}
   237  
   238  	sigBytes, err := util.B64Decode(parts[3])
   239  	if err != nil {
   240  		return "", errors.WithMessage(err, "Failed to base64 decode signature specified in the token")
   241  	}
   242  	sig := &idemix.Signature{}
   243  	err = proto.Unmarshal(sigBytes, sig)
   244  	if err != nil {
   245  		return "", errors.WithMessage(err, "Failed to unmarshal signature bytes specified in the token")
   246  	}
   247  	err = sig.Ver(disclosure, issuerKey.Ipk, digest, attrs, 3, ra.PublicKey(), epoch)
   248  	if err != nil {
   249  		return "", errors.WithMessage(err, "Failed to verify the token")
   250  	}
   251  	return enrollmentID, nil
   252  }
   253  
   254  // Name returns the name of the issuer
   255  func (i *issuer) Name() string {
   256  	return i.name
   257  }
   258  
   259  // HomeDir returns the home directory of the issuer
   260  func (i *issuer) HomeDir() string {
   261  	return i.homeDir
   262  }
   263  
   264  // Config returns config of this issuer
   265  func (i *issuer) Config() *Config {
   266  	return i.cfg
   267  }
   268  
   269  // IdemixLib return idemix library instance
   270  func (i *issuer) IdemixLib() Lib {
   271  	return i.idemixLib
   272  }
   273  
   274  // DB returns the FabricCADB object (which represents database handle
   275  // to the CA database) associated with this issuer
   276  func (i *issuer) DB() db.FabricCADB {
   277  	return i.db
   278  }
   279  
   280  // IdemixRand returns random number used by this issuer in generation of nonces
   281  // and Idemix credentials
   282  func (i *issuer) IdemixRand() *amcl.RAND {
   283  	return i.idemixRand
   284  }
   285  
   286  // IssuerCredential returns IssuerCredential of this issuer
   287  func (i *issuer) IssuerCredential() IssuerCredential {
   288  	return i.issuerCred
   289  }
   290  
   291  // RevocationAuthority returns revocation authority of this issuer
   292  func (i *issuer) RevocationAuthority() RevocationAuthority {
   293  	return i.rc
   294  }
   295  
   296  // NonceManager returns nonce manager of this issuer
   297  func (i *issuer) NonceManager() NonceManager {
   298  	return i.nm
   299  }
   300  
   301  // CredDBAccessor returns the Idemix credential DB accessor for issuer
   302  func (i *issuer) CredDBAccessor() CredDBAccessor {
   303  	return i.credDBAccessor
   304  }
   305  
   306  func (i *issuer) initKeyMaterial(renew bool) error {
   307  	//log.Debug("Initialize Idemix issuer key material")
   308  
   309  	rng, err := i.idemixLib.GetRand()
   310  	if err != nil {
   311  		return errors.Wrapf(err, "Error generating random number")
   312  	}
   313  	i.idemixRand = rng
   314  
   315  	idemixPubKey := i.cfg.IssuerPublicKeyfile
   316  	idemixSecretKey := i.cfg.IssuerSecretKeyfile
   317  	issuerCred := NewIssuerCredential(idemixPubKey, idemixSecretKey, i.idemixLib)
   318  
   319  	if !renew {
   320  		pubKeyFileExists := util.FileExists(idemixPubKey)
   321  		privKeyFileExists := util.FileExists(idemixSecretKey)
   322  		// If they both exist, the CA was already initialized, load the keys from the disk
   323  		if pubKeyFileExists && privKeyFileExists {
   324  			log.Info("The Idemix issuer public and secret key files already exist")
   325  			log.Infof("   secret key file location: %s", idemixSecretKey)
   326  			log.Infof("   public key file location: %s", idemixPubKey)
   327  			err := issuerCred.Load()
   328  			if err != nil {
   329  				return err
   330  			}
   331  			i.issuerCred = issuerCred
   332  			return nil
   333  		}
   334  	}
   335  	ik, err := issuerCred.NewIssuerKey()
   336  	if err != nil {
   337  		return err
   338  	}
   339  	//log.Infof("Idemix issuer public and secret keys were generated for CA '%s'", i.name)
   340  	issuerCred.SetIssuerKey(ik)
   341  	err = issuerCred.Store()
   342  	if err != nil {
   343  		return err
   344  	}
   345  	i.issuerCred = issuerCred
   346  	return nil
   347  }
   348  
   349  func getTokenParts(token string) []string {
   350  	parts := strings.Split(token, ".")
   351  	if len(parts) == 4 && parts[0] == "idemix" {
   352  		return parts
   353  	}
   354  	return nil
   355  }
   356  
   357  // IsToken returns true if the specified token has the format expected of an authorization token
   358  // that is created using an Idemix credential
   359  func IsToken(token string) bool {
   360  	if getTokenParts(token) != nil {
   361  		return true
   362  	}
   363  	return false
   364  }
   365  
   366  type wallClock struct{}
   367  
   368  func (wc wallClock) Now() time.Time {
   369  	return time.Now()
   370  }