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 }