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