github.com/zcqzcg/fabric-ca@v2.0.0-alpha.0.20200416163940-d878ee6db75a+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 }