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