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