github.com/blockchain-gm/fabric-ca@v0.0.0-20200423072702-b2c40c7ac69c/lib/server/idemix/revocationauthority.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  	"bytes"
    11  	// "crypto/ecdsa"
    12  	"fmt"
    13  
    14  	"github.com/cloudflare/cfssl/log"
    15  	fp256bn "github.com/hyperledger/fabric-amcl/amcl/FP256BN"
    16  	"github.com/hyperledger/fabric-ca/lib/server/db"
    17  	"github.com/hyperledger/fabric-ca/util"
    18  	"github.com/hyperledger/fabric/idemix"
    19  	"github.com/jmoiron/sqlx"
    20  	"github.com/pkg/errors"
    21  	sm2 "github.com/tjfoc/gmsm/sm2"
    22  )
    23  
    24  const (
    25  	// InsertRAInfo is the SQL for inserting revocation authority info
    26  	InsertRAInfo = "INSERT into revocation_authority_info(epoch, next_handle, lasthandle_in_pool, level) VALUES (:epoch, :next_handle, :lasthandle_in_pool, :level)"
    27  	// SelectRAInfo is the query string for getting revocation authority info
    28  	SelectRAInfo = "SELECT * FROM revocation_authority_info"
    29  	// UpdateNextAndLastHandle is the SQL for updating next and last revocation handle
    30  	UpdateNextAndLastHandle = "UPDATE revocation_authority_info SET next_handle = ?, lasthandle_in_pool = ?, epoch = ? WHERE (epoch = ?)"
    31  	// UpdateNextHandle s the SQL for updating next revocation handle
    32  	UpdateNextHandle = "UPDATE revocation_authority_info SET next_handle = ? WHERE (epoch = ?)"
    33  	// DefaultRevocationHandlePoolSize is the default revocation handle pool size
    34  	DefaultRevocationHandlePoolSize = 1000
    35  )
    36  
    37  // RevocationAuthority is responsible for generating revocation handles and
    38  // credential revocation info (CRI)
    39  type RevocationAuthority interface {
    40  	// GetNewRevocationHandle returns new revocation handle, which is required to
    41  	// create a new Idemix credential
    42  	GetNewRevocationHandle() (*fp256bn.BIG, error)
    43  	// CreateCRI returns latest credential revocation information (CRI). CRI contains
    44  	// information that allows a prover to create a proof that the revocation handle associated
    45  	// with his credential is not revoked and by the verifier to verify the non-revocation
    46  	// proof of the prover. Verification will fail if the version of the CRI that verifier has
    47  	// does not match the version of the CRI that prover used to create non-revocation proof.
    48  	// The version of the CRI is specified by the Epoch value associated with the CRI.
    49  	CreateCRI() (*idemix.CredentialRevocationInformation, error)
    50  	// Epoch returns epoch value of the latest CRI
    51  	Epoch() (int, error)
    52  	// PublicKey returns revocation authority's public key
    53  	PublicKey() *sm2.PublicKey
    54  }
    55  
    56  // RevocationAuthorityInfo is the revocation authority information record that is
    57  // stored in the database
    58  type RevocationAuthorityInfo struct {
    59  	Epoch                int `db:"epoch"`
    60  	NextRevocationHandle int `db:"next_handle"`
    61  	LastHandleInPool     int `db:"lasthandle_in_pool"`
    62  	Level                int `db:"level"`
    63  }
    64  
    65  // revocationAuthority implements RevocationComponent interface
    66  type revocationAuthority struct {
    67  	issuer     MyIssuer
    68  	key        RevocationKey
    69  	db         db.FabricCADB
    70  	currentCRI *idemix.CredentialRevocationInformation
    71  }
    72  
    73  // NewRevocationAuthority constructor for revocation authority
    74  func NewRevocationAuthority(issuer MyIssuer, level int) (RevocationAuthority, error) {
    75  	ra := &revocationAuthority{
    76  		issuer: issuer,
    77  		db:     issuer.DB(),
    78  	}
    79  	var err error
    80  
    81  	err = ra.initKeyMaterial(false)
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  
    86  	info, err := ra.getRAInfoFromDB()
    87  	if err != nil {
    88  		return nil, errors.WithMessage(err,
    89  			fmt.Sprintf("Failed to initialize revocation authority for issuer '%s'", issuer.Name()))
    90  	}
    91  
    92  	// If epoch is 0, it means this is the first time revocation authority is being
    93  	// initialized. Initilize revocation authority info and store it in the database
    94  	if info.Epoch == 0 {
    95  		rcInfo := RevocationAuthorityInfo{
    96  			Epoch:                1,
    97  			NextRevocationHandle: 1,
    98  			LastHandleInPool:     issuer.Config().RHPoolSize,
    99  			Level:                level,
   100  		}
   101  		err = ra.addRAInfoToDB(&rcInfo)
   102  		if err != nil {
   103  			return nil, errors.WithMessage(err,
   104  				fmt.Sprintf("Failed to initialize revocation authority for issuer '%s'", issuer.Name()))
   105  		}
   106  		info = &rcInfo
   107  	}
   108  
   109  	return ra, nil
   110  }
   111  
   112  func (ra *revocationAuthority) initKeyMaterial(renew bool) error {
   113  	log.Debug("Initialize Idemix issuer revocation key material")
   114  	revocationPubKey := ra.issuer.Config().RevocationPublicKeyfile
   115  	revocationPrivKey := ra.issuer.Config().RevocationPrivateKeyfile
   116  	rk := NewRevocationKey(revocationPubKey, revocationPrivKey, ra.issuer.IdemixLib())
   117  
   118  	if !renew {
   119  		pubKeyFileExists := util.FileExists(revocationPubKey)
   120  		privKeyFileExists := util.FileExists(revocationPrivKey)
   121  		// If they both exist, the CA was already initialized, load the keys from the disk
   122  		if pubKeyFileExists && privKeyFileExists {
   123  			log.Info("The Idemix issuer revocation public and secret key files already exist")
   124  			log.Infof("   private key file location: %s", revocationPrivKey)
   125  			log.Infof("   public key file location: %s", revocationPubKey)
   126  			err := rk.Load()
   127  			if err != nil {
   128  				return errors.WithMessage(err, fmt.Sprintf("Failed to load revocation key for issuer '%s'", ra.issuer.Name()))
   129  			}
   130  			ra.key = rk
   131  			return nil
   132  		}
   133  	}
   134  	err := rk.SetNewKey()
   135  	if err != nil {
   136  		return errors.WithMessage(err,
   137  			fmt.Sprintf("Failed to generate revocation key for issuer '%s'", ra.issuer.Name()))
   138  	}
   139  	log.Infof("Idemix issuer revocation public and secret keys were generated for CA '%s'", ra.issuer.Name())
   140  	err = rk.Store()
   141  	if err != nil {
   142  		return errors.WithMessage(err, fmt.Sprintf("Failed to store revocation key of issuer '%s'", ra.issuer.Name()))
   143  	}
   144  	ra.key = rk
   145  	return nil
   146  }
   147  
   148  // CreateCRI returns latest credential revocation information (CRI). CRI contains
   149  // information that allows a prover to create a proof that the revocation handle associated
   150  // with his credential is not revoked and by the verifier to verify the non-revocation
   151  // proof of the prover. Verification will fail if the version of the CRI that verifier has
   152  // does not match the version of the CRI that prover used to create non-revocation proof.
   153  // The version of the CRI is specified by the Epoch value associated with the CRI.
   154  func (ra *revocationAuthority) CreateCRI() (*idemix.CredentialRevocationInformation, error) {
   155  	info, err := ra.getRAInfoFromDB()
   156  	if err != nil {
   157  		return nil, errors.WithMessage(err, "Failed to get revocation authority info from datastore")
   158  	}
   159  	if ra.currentCRI != nil && ra.currentCRI.Epoch == int64(info.Epoch) {
   160  		return ra.currentCRI, nil
   161  	}
   162  
   163  	revokedCreds, err := ra.issuer.CredDBAccessor().GetRevokedCredentials()
   164  	if err != nil {
   165  		return nil, errors.WithMessage(err, fmt.Sprintf("Failed to get revoked credentials while generating CRI for issuer: '%s'", ra.issuer.Name()))
   166  	}
   167  
   168  	unrevokedHandles := ra.getUnRevokedHandles(info, revokedCreds)
   169  
   170  	alg := idemix.ALG_NO_REVOCATION
   171  	cri, err := ra.issuer.IdemixLib().CreateCRI(ra.key.GetKey(), unrevokedHandles, info.Epoch, alg, ra.issuer.IdemixRand())
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  	ra.currentCRI = cri
   176  	return ra.currentCRI, nil
   177  }
   178  
   179  // GetNewRevocationHandle returns a new revocation handle
   180  func (ra *revocationAuthority) GetNewRevocationHandle() (*fp256bn.BIG, error) {
   181  	h, err := ra.getNextRevocationHandle()
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  	rh := fp256bn.NewBIGint(h)
   186  	return rh, err
   187  }
   188  
   189  // Epoch returns epoch value of the latest CRI
   190  func (ra *revocationAuthority) Epoch() (int, error) {
   191  	info, err := ra.getRAInfoFromDB()
   192  	if err != nil {
   193  		return 0, errors.WithMessage(err, "Revocation authority failed to get latest epoch")
   194  	}
   195  	return info.Epoch, nil
   196  }
   197  
   198  // PublicKey returns revocation authority's public key
   199  func (ra *revocationAuthority) PublicKey() *sm2.PublicKey {
   200  	return &ra.key.GetKey().PublicKey
   201  }
   202  
   203  func (ra *revocationAuthority) getUnRevokedHandles(info *RevocationAuthorityInfo, revokedCreds []CredRecord) []*fp256bn.BIG {
   204  	log.Debugf("RA '%s' is getting revoked revocation handles for epoch %d", ra.issuer.Name(), info.Epoch)
   205  	isRevokedHandle := func(rh *fp256bn.BIG) bool {
   206  		for i := 0; i <= len(revokedCreds)-1; i++ {
   207  			rrhBytes, err := util.B64Decode(revokedCreds[i].RevocationHandle)
   208  			if err != nil {
   209  				log.Debugf("Failed to Base64 decode revocation handle '%s': %s", revokedCreds[i].RevocationHandle, err.Error())
   210  				return false
   211  			}
   212  			rhBytes := idemix.BigToBytes(rh)
   213  			if bytes.Compare(rhBytes, rrhBytes) == 0 {
   214  				return true
   215  			}
   216  		}
   217  		return false
   218  	}
   219  	validHandles := []*fp256bn.BIG{}
   220  	for i := 1; i <= info.LastHandleInPool; i = i + 1 {
   221  		validHandles = append(validHandles, fp256bn.NewBIGint(i))
   222  	}
   223  	for i := len(validHandles) - 1; i >= 0; i-- {
   224  		isrevoked := isRevokedHandle(validHandles[i])
   225  		if isrevoked {
   226  			validHandles = append(validHandles[:i], validHandles[i+1:]...)
   227  		}
   228  	}
   229  	return validHandles
   230  }
   231  
   232  func (ra *revocationAuthority) getRAInfoFromDB() (*RevocationAuthorityInfo, error) {
   233  	rcinfos := []RevocationAuthorityInfo{}
   234  	err := ra.db.Select("GetRAInfo", &rcinfos, SelectRAInfo)
   235  	if err != nil {
   236  		return nil, err
   237  	}
   238  	if len(rcinfos) == 0 {
   239  		return &RevocationAuthorityInfo{
   240  			0, 0, 0, 0,
   241  		}, nil
   242  	}
   243  	return &rcinfos[0], nil
   244  }
   245  
   246  func (ra *revocationAuthority) addRAInfoToDB(rcInfo *RevocationAuthorityInfo) error {
   247  	res, err := ra.db.NamedExec("AddRAInfo", InsertRAInfo, rcInfo)
   248  	if err != nil {
   249  		return errors.New("Failed to insert revocation authority info into database")
   250  	}
   251  
   252  	numRowsAffected, err := res.RowsAffected()
   253  	if numRowsAffected == 0 {
   254  		return errors.New("Failed to insert the revocation authority info record; no rows affected")
   255  	}
   256  
   257  	if numRowsAffected != 1 {
   258  		return errors.Errorf("Expected to affect 1 entry in revocation authority info table but affected %d",
   259  			numRowsAffected)
   260  	}
   261  	return err
   262  }
   263  
   264  // getNextRevocationHandle returns next revocation handle
   265  func (ra *revocationAuthority) getNextRevocationHandle() (int, error) {
   266  	result, err := doTransaction("GetNextRevocationHandle", ra.db, ra.getNextRevocationHandleTx, nil)
   267  	if err != nil {
   268  		return 0, err
   269  	}
   270  
   271  	nextHandle := result.(int)
   272  	return nextHandle, nil
   273  }
   274  
   275  func (ra *revocationAuthority) getNextRevocationHandleTx(tx db.FabricCATx, args ...interface{}) (interface{}, error) {
   276  	var err error
   277  
   278  	// Get the latest revocation authority info from the database
   279  	rcInfos := []RevocationAuthorityInfo{}
   280  	query := SelectRAInfo
   281  	err = tx.Select("GetRAInfo", &rcInfos, tx.Rebind(query))
   282  	if err != nil {
   283  		return nil, errors.New("Failed to get revocation authority info from database")
   284  	}
   285  	if len(rcInfos) == 0 {
   286  		return nil, errors.New("No revocation authority info found in database")
   287  	}
   288  	rcInfo := rcInfos[0]
   289  
   290  	nextHandle := rcInfo.NextRevocationHandle
   291  	newNextHandle := rcInfo.NextRevocationHandle + 1
   292  	var inQuery string
   293  	if nextHandle == rcInfo.LastHandleInPool {
   294  		newLastHandleInPool := rcInfo.LastHandleInPool + ra.issuer.Config().RHPoolSize
   295  		newEpoch := rcInfo.Epoch + 1
   296  		query = UpdateNextAndLastHandle
   297  		inQuery, args, err = sqlx.In(query, newNextHandle, newLastHandleInPool, newEpoch, rcInfo.Epoch)
   298  	} else {
   299  		query = UpdateNextHandle
   300  		inQuery, args, err = sqlx.In(query, newNextHandle, rcInfo.Epoch)
   301  	}
   302  	if err != nil {
   303  		return nil, errors.Wrapf(err, "Failed to construct query '%s'", query)
   304  	}
   305  	_, err = tx.Exec("GetNextRevocationHandle", tx.Rebind(inQuery), args...)
   306  	if err != nil {
   307  		return nil, errors.Wrapf(err, "Failed to update revocation authority info")
   308  	}
   309  
   310  	return nextHandle, nil
   311  }
   312  
   313  func doTransaction(funcName string, db db.FabricCADB, doit func(tx db.FabricCATx, args ...interface{}) (interface{}, error), args ...interface{}) (interface{}, error) {
   314  	if db == nil {
   315  		return nil, errors.New("Failed to correctly setup database connection")
   316  	}
   317  	tx := db.BeginTx()
   318  	result, err := doit(tx, args...)
   319  	if err != nil {
   320  		err2 := tx.Rollback(funcName)
   321  		if err2 != nil {
   322  			errMsg := fmt.Sprintf("Error encountered while rolling back transaction: %s, original error: %s", err2.Error(), err.Error())
   323  			log.Errorf(errMsg)
   324  			return nil, errors.New(errMsg)
   325  		}
   326  		return nil, err
   327  	}
   328  
   329  	err = tx.Commit(funcName)
   330  	if err != nil {
   331  		return nil, errors.Wrap(err, "Error encountered while committing transaction")
   332  	}
   333  
   334  	return result, nil
   335  }