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