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