1// +build pkcs11 2 3package yubikey 4 5import ( 6 "crypto" 7 "crypto/ecdsa" 8 "crypto/elliptic" 9 "crypto/rand" 10 "crypto/sha256" 11 "crypto/x509" 12 "errors" 13 "fmt" 14 "io" 15 "math/big" 16 "os" 17 "time" 18 19 "github.com/miekg/pkcs11" 20 "github.com/sirupsen/logrus" 21 "github.com/theupdateframework/notary" 22 "github.com/theupdateframework/notary/trustmanager" 23 "github.com/theupdateframework/notary/tuf/data" 24 "github.com/theupdateframework/notary/tuf/signed" 25 "github.com/theupdateframework/notary/tuf/utils" 26) 27 28const ( 29 // UserPin is the user pin of a yubikey (in PIV parlance, is the PIN) 30 UserPin = "123456" 31 // SOUserPin is the "Security Officer" user pin - this is the PIV management 32 // (MGM) key, which is different than the admin pin of the Yubikey PGP interface 33 // (which in PIV parlance is the PUK, and defaults to 12345678) 34 SOUserPin = "010203040506070801020304050607080102030405060708" 35 numSlots = 4 // number of slots in the yubikey 36 37 // KeymodeNone means that no touch or PIN is required to sign with the yubikey 38 KeymodeNone = 0 39 // KeymodeTouch means that only touch is required to sign with the yubikey 40 KeymodeTouch = 1 41 // KeymodePinOnce means that the pin entry is required once the first time to sign with the yubikey 42 KeymodePinOnce = 2 43 // KeymodePinAlways means that pin entry is required every time to sign with the yubikey 44 KeymodePinAlways = 4 45 46 // the key size, when importing a key into yubikey, MUST be 32 bytes 47 ecdsaPrivateKeySize = 32 48 49 sigAttempts = 5 50) 51 52// what key mode to use when generating keys 53var ( 54 yubikeyKeymode = KeymodeTouch | KeymodePinOnce 55 // order in which to prefer token locations on the yubikey. 56 // corresponds to: 9c, 9e, 9d, 9a 57 slotIDs = []int{2, 1, 3, 0} 58) 59 60// SetYubikeyKeyMode - sets the mode when generating yubikey keys. 61// This is to be used for testing. It does nothing if not building with tag 62// pkcs11. 63func SetYubikeyKeyMode(keyMode int) error { 64 // technically 7 (1 | 2 | 4) is valid, but KeymodePinOnce + 65 // KeymdoePinAlways don't really make sense together 66 if keyMode < 0 || keyMode > 5 { 67 return errors.New("Invalid key mode") 68 } 69 yubikeyKeymode = keyMode 70 return nil 71} 72 73// SetTouchToSignUI - allows configurable UX for notifying a user that they 74// need to touch the yubikey to sign. The callback may be used to provide a 75// mechanism for updating a GUI (such as removing a modal) after the touch 76// has been made 77func SetTouchToSignUI(notifier func(), callback func()) { 78 touchToSignUI = notifier 79 if callback != nil { 80 touchDoneCallback = callback 81 } 82} 83 84var touchToSignUI = func() { 85 fmt.Println("Please touch the attached Yubikey to perform signing.") 86} 87 88var touchDoneCallback = func() { 89 // noop 90} 91 92var pkcs11Lib string 93 94func init() { 95 for _, loc := range possiblePkcs11Libs { 96 _, err := os.Stat(loc) 97 if err == nil { 98 p := pkcs11.New(loc) 99 if p != nil { 100 pkcs11Lib = loc 101 return 102 } 103 } 104 } 105} 106 107// ErrBackupFailed is returned when a YubiStore fails to back up a key that 108// is added 109type ErrBackupFailed struct { 110 err string 111} 112 113func (err ErrBackupFailed) Error() string { 114 return fmt.Sprintf("Failed to backup private key to: %s", err.err) 115} 116 117// An error indicating that the HSM is not present (as opposed to failing), 118// i.e. that we can confidently claim that the key is not stored in the HSM 119// without notifying the user about a missing or failing HSM. 120type errHSMNotPresent struct { 121 err string 122} 123 124func (err errHSMNotPresent) Error() string { 125 return err.err 126} 127 128type yubiSlot struct { 129 role data.RoleName 130 slotID []byte 131} 132 133// YubiPrivateKey represents a private key inside of a yubikey 134type YubiPrivateKey struct { 135 data.ECDSAPublicKey 136 passRetriever notary.PassRetriever 137 slot []byte 138 libLoader pkcs11LibLoader 139} 140 141// yubikeySigner wraps a YubiPrivateKey and implements the crypto.Signer interface 142type yubikeySigner struct { 143 YubiPrivateKey 144} 145 146// NewYubiPrivateKey returns a YubiPrivateKey, which implements the data.PrivateKey 147// interface except that the private material is inaccessible 148func NewYubiPrivateKey(slot []byte, pubKey data.ECDSAPublicKey, 149 passRetriever notary.PassRetriever) *YubiPrivateKey { 150 151 return &YubiPrivateKey{ 152 ECDSAPublicKey: pubKey, 153 passRetriever: passRetriever, 154 slot: slot, 155 libLoader: defaultLoader, 156 } 157} 158 159// Public is a required method of the crypto.Signer interface 160func (ys *yubikeySigner) Public() crypto.PublicKey { 161 publicKey, err := x509.ParsePKIXPublicKey(ys.YubiPrivateKey.Public()) 162 if err != nil { 163 return nil 164 } 165 166 return publicKey 167} 168 169func (y *YubiPrivateKey) setLibLoader(loader pkcs11LibLoader) { 170 y.libLoader = loader 171} 172 173// CryptoSigner returns a crypto.Signer tha wraps the YubiPrivateKey. Needed for 174// Certificate generation only 175func (y *YubiPrivateKey) CryptoSigner() crypto.Signer { 176 return &yubikeySigner{YubiPrivateKey: *y} 177} 178 179// Private is not implemented in hardware keys 180func (y *YubiPrivateKey) Private() []byte { 181 // We cannot return the private material from a Yubikey 182 // TODO(david): We probably want to return an error here 183 return nil 184} 185 186// SignatureAlgorithm returns which algorithm this key uses to sign - currently 187// hardcoded to ECDSA 188func (y YubiPrivateKey) SignatureAlgorithm() data.SigAlgorithm { 189 return data.ECDSASignature 190} 191 192// Sign is a required method of the crypto.Signer interface and the data.PrivateKey 193// interface 194func (y *YubiPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error) { 195 ctx, session, err := SetupHSMEnv(pkcs11Lib, y.libLoader) 196 if err != nil { 197 return nil, err 198 } 199 defer cleanup(ctx, session) 200 201 v := signed.Verifiers[data.ECDSASignature] 202 for i := 0; i < sigAttempts; i++ { 203 sig, err := sign(ctx, session, y.slot, y.passRetriever, msg) 204 if err != nil { 205 return nil, fmt.Errorf("failed to sign using Yubikey: %v", err) 206 } 207 if err := v.Verify(&y.ECDSAPublicKey, sig, msg); err == nil { 208 return sig, nil 209 } 210 } 211 return nil, errors.New("failed to generate signature on Yubikey") 212} 213 214// If a byte array is less than the number of bytes specified by 215// ecdsaPrivateKeySize, left-zero-pad the byte array until 216// it is the required size. 217func ensurePrivateKeySize(payload []byte) []byte { 218 final := payload 219 if len(payload) < ecdsaPrivateKeySize { 220 final = make([]byte, ecdsaPrivateKeySize) 221 copy(final[ecdsaPrivateKeySize-len(payload):], payload) 222 } 223 return final 224} 225 226// addECDSAKey adds a key to the yubikey 227func addECDSAKey( 228 ctx IPKCS11Ctx, 229 session pkcs11.SessionHandle, 230 privKey data.PrivateKey, 231 pkcs11KeyID []byte, 232 passRetriever notary.PassRetriever, 233 role data.RoleName, 234) error { 235 logrus.Debugf("Attempting to add key to yubikey with ID: %s", privKey.ID()) 236 237 err := login(ctx, session, passRetriever, pkcs11.CKU_SO, SOUserPin) 238 if err != nil { 239 return err 240 } 241 defer ctx.Logout(session) 242 243 // Create an ecdsa.PrivateKey out of the private key bytes 244 ecdsaPrivKey, err := x509.ParseECPrivateKey(privKey.Private()) 245 if err != nil { 246 return err 247 } 248 249 ecdsaPrivKeyD := ensurePrivateKeySize(ecdsaPrivKey.D.Bytes()) 250 251 // Hard-coded policy: the generated certificate expires in 10 years. 252 startTime := time.Now() 253 template, err := utils.NewCertificate(role.String(), startTime, startTime.AddDate(10, 0, 0)) 254 if err != nil { 255 return fmt.Errorf("failed to create the certificate template: %v", err) 256 } 257 258 certBytes, err := x509.CreateCertificate(rand.Reader, template, template, ecdsaPrivKey.Public(), ecdsaPrivKey) 259 if err != nil { 260 return fmt.Errorf("failed to create the certificate: %v", err) 261 } 262 263 certTemplate := []*pkcs11.Attribute{ 264 pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE), 265 pkcs11.NewAttribute(pkcs11.CKA_VALUE, certBytes), 266 pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID), 267 } 268 269 privateKeyTemplate := []*pkcs11.Attribute{ 270 pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY), 271 pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_ECDSA), 272 pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID), 273 pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07}), 274 pkcs11.NewAttribute(pkcs11.CKA_VALUE, ecdsaPrivKeyD), 275 pkcs11.NewAttribute(pkcs11.CKA_VENDOR_DEFINED, yubikeyKeymode), 276 } 277 278 _, err = ctx.CreateObject(session, certTemplate) 279 if err != nil { 280 return fmt.Errorf("error importing: %v", err) 281 } 282 283 _, err = ctx.CreateObject(session, privateKeyTemplate) 284 if err != nil { 285 return fmt.Errorf("error importing: %v", err) 286 } 287 288 return nil 289} 290 291func getECDSAKey(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte) (*data.ECDSAPublicKey, data.RoleName, error) { 292 findTemplate := []*pkcs11.Attribute{ 293 pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true), 294 pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID), 295 pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY), 296 } 297 298 attrTemplate := []*pkcs11.Attribute{ 299 pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, []byte{0}), 300 pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, []byte{0}), 301 pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{0}), 302 } 303 304 if err := ctx.FindObjectsInit(session, findTemplate); err != nil { 305 logrus.Debugf("Failed to init: %s", err.Error()) 306 return nil, "", err 307 } 308 obj, _, err := ctx.FindObjects(session, 1) 309 if err != nil { 310 logrus.Debugf("Failed to find objects: %v", err) 311 return nil, "", err 312 } 313 if err := ctx.FindObjectsFinal(session); err != nil { 314 logrus.Debugf("Failed to finalize: %s", err.Error()) 315 return nil, "", err 316 } 317 if len(obj) != 1 { 318 logrus.Debugf("should have found one object") 319 return nil, "", errors.New("no matching keys found inside of yubikey") 320 } 321 322 // Retrieve the public-key material to be able to create a new ECSAKey 323 attr, err := ctx.GetAttributeValue(session, obj[0], attrTemplate) 324 if err != nil { 325 logrus.Debugf("Failed to get Attribute for: %v", obj[0]) 326 return nil, "", err 327 } 328 329 // Iterate through all the attributes of this key and saves CKA_PUBLIC_EXPONENT and CKA_MODULUS. Removes ordering specific issues. 330 var rawPubKey []byte 331 for _, a := range attr { 332 if a.Type == pkcs11.CKA_EC_POINT { 333 rawPubKey = a.Value 334 } 335 336 } 337 338 ecdsaPubKey := ecdsa.PublicKey{Curve: elliptic.P256(), X: new(big.Int).SetBytes(rawPubKey[3:35]), Y: new(big.Int).SetBytes(rawPubKey[35:])} 339 pubBytes, err := x509.MarshalPKIXPublicKey(&ecdsaPubKey) 340 if err != nil { 341 logrus.Debugf("Failed to Marshal public key") 342 return nil, "", err 343 } 344 345 return data.NewECDSAPublicKey(pubBytes), data.CanonicalRootRole, nil 346} 347 348// sign returns a signature for a given signature request 349func sign(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, passRetriever notary.PassRetriever, payload []byte) ([]byte, error) { 350 err := login(ctx, session, passRetriever, pkcs11.CKU_USER, UserPin) 351 if err != nil { 352 return nil, fmt.Errorf("error logging in: %v", err) 353 } 354 defer ctx.Logout(session) 355 356 // Define the ECDSA Private key template 357 class := pkcs11.CKO_PRIVATE_KEY 358 privateKeyTemplate := []*pkcs11.Attribute{ 359 pkcs11.NewAttribute(pkcs11.CKA_CLASS, class), 360 pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_ECDSA), 361 pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID), 362 } 363 364 if err := ctx.FindObjectsInit(session, privateKeyTemplate); err != nil { 365 logrus.Debugf("Failed to init find objects: %s", err.Error()) 366 return nil, err 367 } 368 obj, _, err := ctx.FindObjects(session, 1) 369 if err != nil { 370 logrus.Debugf("Failed to find objects: %v", err) 371 return nil, err 372 } 373 if err = ctx.FindObjectsFinal(session); err != nil { 374 logrus.Debugf("Failed to finalize find objects: %s", err.Error()) 375 return nil, err 376 } 377 if len(obj) != 1 { 378 return nil, errors.New("length of objects found not 1") 379 } 380 381 var sig []byte 382 err = ctx.SignInit( 383 session, []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_ECDSA, nil)}, obj[0]) 384 if err != nil { 385 return nil, err 386 } 387 388 // Get the SHA256 of the payload 389 digest := sha256.Sum256(payload) 390 391 if (yubikeyKeymode & KeymodeTouch) > 0 { 392 touchToSignUI() 393 defer touchDoneCallback() 394 } 395 // a call to Sign, whether or not Sign fails, will clear the SignInit 396 sig, err = ctx.Sign(session, digest[:]) 397 if err != nil { 398 logrus.Debugf("Error while signing: %s", err) 399 return nil, err 400 } 401 402 if sig == nil { 403 return nil, errors.New("Failed to create signature") 404 } 405 return sig[:], nil 406} 407 408func yubiRemoveKey(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, passRetriever notary.PassRetriever, keyID string) error { 409 err := login(ctx, session, passRetriever, pkcs11.CKU_SO, SOUserPin) 410 if err != nil { 411 return err 412 } 413 defer ctx.Logout(session) 414 415 template := []*pkcs11.Attribute{ 416 pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true), 417 pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID), 418 //pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY), 419 pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE), 420 } 421 422 if err := ctx.FindObjectsInit(session, template); err != nil { 423 logrus.Debugf("Failed to init find objects: %s", err.Error()) 424 return err 425 } 426 obj, b, err := ctx.FindObjects(session, 1) 427 if err != nil { 428 logrus.Debugf("Failed to find objects: %s %v", err.Error(), b) 429 return err 430 } 431 if err := ctx.FindObjectsFinal(session); err != nil { 432 logrus.Debugf("Failed to finalize find objects: %s", err.Error()) 433 return err 434 } 435 if len(obj) != 1 { 436 logrus.Debugf("should have found exactly one object") 437 return err 438 } 439 440 // Delete the certificate 441 err = ctx.DestroyObject(session, obj[0]) 442 if err != nil { 443 logrus.Debugf("Failed to delete cert") 444 return err 445 } 446 return nil 447} 448 449func yubiListKeys(ctx IPKCS11Ctx, session pkcs11.SessionHandle) (keys map[string]yubiSlot, err error) { 450 keys = make(map[string]yubiSlot) 451 452 attrTemplate := []*pkcs11.Attribute{ 453 pkcs11.NewAttribute(pkcs11.CKA_ID, []byte{0}), 454 pkcs11.NewAttribute(pkcs11.CKA_VALUE, []byte{0}), 455 } 456 457 objs, err := listObjects(ctx, session) 458 if err != nil { 459 return nil, err 460 } 461 462 if len(objs) == 0 { 463 return nil, errors.New("no keys found in yubikey") 464 } 465 logrus.Debugf("Found %d objects matching list filters", len(objs)) 466 for _, obj := range objs { 467 var ( 468 cert *x509.Certificate 469 slot []byte 470 ) 471 // Retrieve the public-key material to be able to create a new ECDSA 472 attr, err := ctx.GetAttributeValue(session, obj, attrTemplate) 473 if err != nil { 474 logrus.Debugf("Failed to get Attribute for: %v", obj) 475 continue 476 } 477 478 // Iterate through all the attributes of this key and saves CKA_PUBLIC_EXPONENT and CKA_MODULUS. Removes ordering specific issues. 479 for _, a := range attr { 480 if a.Type == pkcs11.CKA_ID { 481 slot = a.Value 482 } 483 if a.Type == pkcs11.CKA_VALUE { 484 cert, err = x509.ParseCertificate(a.Value) 485 if err != nil { 486 continue 487 } 488 if !data.ValidRole(data.RoleName(cert.Subject.CommonName)) { 489 continue 490 } 491 } 492 } 493 494 // we found nothing 495 if cert == nil { 496 continue 497 } 498 499 var ecdsaPubKey *ecdsa.PublicKey 500 switch cert.PublicKeyAlgorithm { 501 case x509.ECDSA: 502 ecdsaPubKey = cert.PublicKey.(*ecdsa.PublicKey) 503 default: 504 logrus.Infof("Unsupported x509 PublicKeyAlgorithm: %d", cert.PublicKeyAlgorithm) 505 continue 506 } 507 508 pubBytes, err := x509.MarshalPKIXPublicKey(ecdsaPubKey) 509 if err != nil { 510 logrus.Debugf("Failed to Marshal public key") 511 continue 512 } 513 514 keys[data.NewECDSAPublicKey(pubBytes).ID()] = yubiSlot{ 515 role: data.RoleName(cert.Subject.CommonName), 516 slotID: slot, 517 } 518 } 519 return 520} 521 522func listObjects(ctx IPKCS11Ctx, session pkcs11.SessionHandle) ([]pkcs11.ObjectHandle, error) { 523 findTemplate := []*pkcs11.Attribute{ 524 pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true), 525 pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE), 526 } 527 528 if err := ctx.FindObjectsInit(session, findTemplate); err != nil { 529 logrus.Debugf("Failed to init: %s", err.Error()) 530 return nil, err 531 } 532 533 objs, b, err := ctx.FindObjects(session, numSlots) 534 for err == nil { 535 var o []pkcs11.ObjectHandle 536 o, b, err = ctx.FindObjects(session, numSlots) 537 if err != nil { 538 continue 539 } 540 if len(o) == 0 { 541 break 542 } 543 objs = append(objs, o...) 544 } 545 if err != nil { 546 logrus.Debugf("Failed to find: %s %v", err.Error(), b) 547 if len(objs) == 0 { 548 return nil, err 549 } 550 } 551 if err := ctx.FindObjectsFinal(session); err != nil { 552 logrus.Debugf("Failed to finalize: %s", err.Error()) 553 return nil, err 554 } 555 return objs, nil 556} 557 558func getNextEmptySlot(ctx IPKCS11Ctx, session pkcs11.SessionHandle) ([]byte, error) { 559 findTemplate := []*pkcs11.Attribute{ 560 pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true), 561 } 562 attrTemplate := []*pkcs11.Attribute{ 563 pkcs11.NewAttribute(pkcs11.CKA_ID, []byte{0}), 564 } 565 566 if err := ctx.FindObjectsInit(session, findTemplate); err != nil { 567 logrus.Debugf("Failed to init: %s", err.Error()) 568 return nil, err 569 } 570 objs, b, err := ctx.FindObjects(session, numSlots) 571 // if there are more objects than `numSlots`, get all of them until 572 // there are no more to get 573 for err == nil { 574 var o []pkcs11.ObjectHandle 575 o, b, err = ctx.FindObjects(session, numSlots) 576 if err != nil { 577 continue 578 } 579 if len(o) == 0 { 580 break 581 } 582 objs = append(objs, o...) 583 } 584 taken := make(map[int]bool) 585 if err != nil { 586 logrus.Debugf("Failed to find: %s %v", err.Error(), b) 587 return nil, err 588 } 589 if err = ctx.FindObjectsFinal(session); err != nil { 590 logrus.Debugf("Failed to finalize: %s\n", err.Error()) 591 return nil, err 592 } 593 for _, obj := range objs { 594 // Retrieve the slot ID 595 attr, err := ctx.GetAttributeValue(session, obj, attrTemplate) 596 if err != nil { 597 continue 598 } 599 600 // Iterate through attributes. If an ID attr was found, mark it as taken 601 for _, a := range attr { 602 if a.Type == pkcs11.CKA_ID { 603 if len(a.Value) < 1 { 604 continue 605 } 606 // a byte will always be capable of representing all slot IDs 607 // for the Yubikeys 608 slotNum := int(a.Value[0]) 609 if slotNum >= numSlots { 610 // defensive 611 continue 612 } 613 taken[slotNum] = true 614 } 615 } 616 } 617 // iterate the token locations in our preferred order and use the first 618 // available one. Otherwise exit the loop and return an error. 619 for _, loc := range slotIDs { 620 if !taken[loc] { 621 return []byte{byte(loc)}, nil 622 } 623 } 624 return nil, errors.New("yubikey has no available slots") 625} 626 627// YubiStore is a KeyStore for private keys inside a Yubikey 628type YubiStore struct { 629 passRetriever notary.PassRetriever 630 keys map[string]yubiSlot 631 backupStore trustmanager.KeyStore 632 libLoader pkcs11LibLoader 633} 634 635// NewYubiStore returns a YubiStore, given a backup key store to write any 636// generated keys to (usually a KeyFileStore) 637func NewYubiStore(backupStore trustmanager.KeyStore, passphraseRetriever notary.PassRetriever) ( 638 *YubiStore, error) { 639 640 s := &YubiStore{ 641 passRetriever: passphraseRetriever, 642 keys: make(map[string]yubiSlot), 643 backupStore: backupStore, 644 libLoader: defaultLoader, 645 } 646 s.ListKeys() // populate keys field 647 return s, nil 648} 649 650// Name returns a user friendly name for the location this store 651// keeps its data 652func (s YubiStore) Name() string { 653 return "yubikey" 654} 655 656func (s *YubiStore) setLibLoader(loader pkcs11LibLoader) { 657 s.libLoader = loader 658} 659 660// ListKeys returns a list of keys in the yubikey store 661func (s *YubiStore) ListKeys() map[string]trustmanager.KeyInfo { 662 if len(s.keys) > 0 { 663 return buildKeyMap(s.keys) 664 } 665 ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader) 666 if err != nil { 667 logrus.Debugf("No yubikey found, using alternative key storage: %s", err.Error()) 668 return nil 669 } 670 defer cleanup(ctx, session) 671 672 keys, err := yubiListKeys(ctx, session) 673 if err != nil { 674 logrus.Debugf("Failed to list key from the yubikey: %s", err.Error()) 675 return nil 676 } 677 s.keys = keys 678 679 return buildKeyMap(keys) 680} 681 682// AddKey puts a key inside the Yubikey, as well as writing it to the backup store 683func (s *YubiStore) AddKey(keyInfo trustmanager.KeyInfo, privKey data.PrivateKey) error { 684 added, err := s.addKey(privKey.ID(), keyInfo.Role, privKey) 685 if err != nil { 686 return err 687 } 688 if added && s.backupStore != nil { 689 err = s.backupStore.AddKey(keyInfo, privKey) 690 if err != nil { 691 defer s.RemoveKey(privKey.ID()) 692 return ErrBackupFailed{err: err.Error()} 693 } 694 } 695 return nil 696} 697 698// Only add if we haven't seen the key already. Return whether the key was 699// added. 700func (s *YubiStore) addKey(keyID string, role data.RoleName, privKey data.PrivateKey) ( 701 bool, error) { 702 703 // We only allow adding root keys for now 704 if role != data.CanonicalRootRole { 705 return false, fmt.Errorf( 706 "yubikey only supports storing root keys, got %s for key: %s", role, keyID) 707 } 708 709 ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader) 710 if err != nil { 711 logrus.Debugf("No yubikey found, using alternative key storage: %s", err.Error()) 712 return false, err 713 } 714 defer cleanup(ctx, session) 715 716 if k, ok := s.keys[keyID]; ok { 717 if k.role == role { 718 // already have the key and it's associated with the correct role 719 return false, nil 720 } 721 } 722 723 slot, err := getNextEmptySlot(ctx, session) 724 if err != nil { 725 logrus.Debugf("Failed to get an empty yubikey slot: %s", err.Error()) 726 return false, err 727 } 728 logrus.Debugf("Attempting to store key using yubikey slot %v", slot) 729 730 err = addECDSAKey( 731 ctx, session, privKey, slot, s.passRetriever, role) 732 if err == nil { 733 s.keys[privKey.ID()] = yubiSlot{ 734 role: role, 735 slotID: slot, 736 } 737 return true, nil 738 } 739 logrus.Debugf("Failed to add key to yubikey: %v", err) 740 741 return false, err 742} 743 744// GetKey retrieves a key from the Yubikey only (it does not look inside the 745// backup store) 746func (s *YubiStore) GetKey(keyID string) (data.PrivateKey, data.RoleName, error) { 747 ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader) 748 if err != nil { 749 logrus.Debugf("No yubikey found, using alternative key storage: %s", err.Error()) 750 if _, ok := err.(errHSMNotPresent); ok { 751 err = trustmanager.ErrKeyNotFound{KeyID: keyID} 752 } 753 return nil, "", err 754 } 755 defer cleanup(ctx, session) 756 757 key, ok := s.keys[keyID] 758 if !ok { 759 return nil, "", trustmanager.ErrKeyNotFound{KeyID: keyID} 760 } 761 762 pubKey, alias, err := getECDSAKey(ctx, session, key.slotID) 763 if err != nil { 764 logrus.Debugf("Failed to get key from slot %s: %s", key.slotID, err.Error()) 765 return nil, "", err 766 } 767 // Check to see if we're returning the intended keyID 768 if pubKey.ID() != keyID { 769 return nil, "", fmt.Errorf("expected root key: %s, but found: %s", keyID, pubKey.ID()) 770 } 771 privKey := NewYubiPrivateKey(key.slotID, *pubKey, s.passRetriever) 772 if privKey == nil { 773 return nil, "", errors.New("could not initialize new YubiPrivateKey") 774 } 775 776 return privKey, alias, err 777} 778 779// RemoveKey deletes a key from the Yubikey only (it does not remove it from the 780// backup store) 781func (s *YubiStore) RemoveKey(keyID string) error { 782 ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader) 783 if err != nil { 784 logrus.Debugf("No yubikey found, using alternative key storage: %s", err.Error()) 785 return nil 786 } 787 defer cleanup(ctx, session) 788 789 key, ok := s.keys[keyID] 790 if !ok { 791 return errors.New("Key not present in yubikey") 792 } 793 err = yubiRemoveKey(ctx, session, key.slotID, s.passRetriever, keyID) 794 if err == nil { 795 delete(s.keys, keyID) 796 } else { 797 logrus.Debugf("Failed to remove from the yubikey KeyID %s: %v", keyID, err) 798 } 799 800 return err 801} 802 803// GetKeyInfo is not yet implemented 804func (s *YubiStore) GetKeyInfo(keyID string) (trustmanager.KeyInfo, error) { 805 return trustmanager.KeyInfo{}, fmt.Errorf("Not yet implemented") 806} 807 808func cleanup(ctx IPKCS11Ctx, session pkcs11.SessionHandle) { 809 err := ctx.CloseSession(session) 810 if err != nil { 811 logrus.Debugf("Error closing session: %s", err.Error()) 812 } 813 finalizeAndDestroy(ctx) 814} 815 816func finalizeAndDestroy(ctx IPKCS11Ctx) { 817 err := ctx.Finalize() 818 if err != nil { 819 logrus.Debugf("Error finalizing: %s", err.Error()) 820 } 821 ctx.Destroy() 822} 823 824// SetupHSMEnv is a method that depends on the existences 825func SetupHSMEnv(libraryPath string, libLoader pkcs11LibLoader) ( 826 IPKCS11Ctx, pkcs11.SessionHandle, error) { 827 828 if libraryPath == "" { 829 return nil, 0, errHSMNotPresent{err: "no library found"} 830 } 831 p := libLoader(libraryPath) 832 833 if p == nil { 834 return nil, 0, fmt.Errorf("failed to load library %s", libraryPath) 835 } 836 837 if err := p.Initialize(); err != nil { 838 defer finalizeAndDestroy(p) 839 return nil, 0, fmt.Errorf("found library %s, but initialize error %s", libraryPath, err.Error()) 840 } 841 842 slots, err := p.GetSlotList(true) 843 if err != nil { 844 defer finalizeAndDestroy(p) 845 return nil, 0, fmt.Errorf( 846 "loaded library %s, but failed to list HSM slots %s", libraryPath, err) 847 } 848 // Check to see if we got any slots from the HSM. 849 if len(slots) < 1 { 850 defer finalizeAndDestroy(p) 851 return nil, 0, fmt.Errorf( 852 "loaded library %s, but no HSM slots found", libraryPath) 853 } 854 855 // CKF_SERIAL_SESSION: TRUE if cryptographic functions are performed in serial with the application; FALSE if the functions may be performed in parallel with the application. 856 // CKF_RW_SESSION: TRUE if the session is read/write; FALSE if the session is read-only 857 session, err := p.OpenSession(slots[0], pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION) 858 if err != nil { 859 defer cleanup(p, session) 860 return nil, 0, fmt.Errorf( 861 "loaded library %s, but failed to start session with HSM %s", 862 libraryPath, err) 863 } 864 865 logrus.Debugf("Initialized PKCS11 library %s and started HSM session", libraryPath) 866 return p, session, nil 867} 868 869// IsAccessible returns true if a Yubikey can be accessed 870func IsAccessible() bool { 871 if pkcs11Lib == "" { 872 return false 873 } 874 ctx, session, err := SetupHSMEnv(pkcs11Lib, defaultLoader) 875 if err != nil { 876 return false 877 } 878 defer cleanup(ctx, session) 879 return true 880} 881 882func login(ctx IPKCS11Ctx, session pkcs11.SessionHandle, passRetriever notary.PassRetriever, userFlag uint, defaultPassw string) error { 883 // try default password 884 err := ctx.Login(session, userFlag, defaultPassw) 885 if err == nil { 886 return nil 887 } 888 889 // default failed, ask user for password 890 for attempts := 0; ; attempts++ { 891 var ( 892 giveup bool 893 err error 894 user string 895 ) 896 if userFlag == pkcs11.CKU_SO { 897 user = "SO Pin" 898 } else { 899 user = "User Pin" 900 } 901 passwd, giveup, err := passRetriever(user, "yubikey", false, attempts) 902 // Check if the passphrase retriever got an error or if it is telling us to give up 903 if giveup || err != nil { 904 return trustmanager.ErrPasswordInvalid{} 905 } 906 if attempts > 2 { 907 return trustmanager.ErrAttemptsExceeded{} 908 } 909 910 // attempt to login. Loop if failed 911 err = ctx.Login(session, userFlag, passwd) 912 if err == nil { 913 return nil 914 } 915 } 916} 917 918func buildKeyMap(keys map[string]yubiSlot) map[string]trustmanager.KeyInfo { 919 res := make(map[string]trustmanager.KeyInfo) 920 for k, v := range keys { 921 res[k] = trustmanager.KeyInfo{Role: v.role, Gun: ""} 922 } 923 return res 924} 925