forked from toolshed/abra
chore: vendor
This commit is contained in:
31
vendor/github.com/theupdateframework/notary/trustmanager/errors.go
generated
vendored
Normal file
31
vendor/github.com/theupdateframework/notary/trustmanager/errors.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
package trustmanager
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ErrAttemptsExceeded is returned when too many attempts have been made to decrypt a key
|
||||
type ErrAttemptsExceeded struct{}
|
||||
|
||||
// ErrAttemptsExceeded is returned when too many attempts have been made to decrypt a key
|
||||
func (err ErrAttemptsExceeded) Error() string {
|
||||
return "maximum number of passphrase attempts exceeded"
|
||||
}
|
||||
|
||||
// ErrPasswordInvalid is returned when signing fails. It could also mean the signing
|
||||
// key file was corrupted, but we have no way to distinguish.
|
||||
type ErrPasswordInvalid struct{}
|
||||
|
||||
// ErrPasswordInvalid is returned when signing fails. It could also mean the signing
|
||||
// key file was corrupted, but we have no way to distinguish.
|
||||
func (err ErrPasswordInvalid) Error() string {
|
||||
return "password invalid, operation has failed."
|
||||
}
|
||||
|
||||
// ErrKeyNotFound is returned when the keystore fails to retrieve a specific key.
|
||||
type ErrKeyNotFound struct {
|
||||
KeyID string
|
||||
}
|
||||
|
||||
// ErrKeyNotFound is returned when the keystore fails to retrieve a specific key.
|
||||
func (err ErrKeyNotFound) Error() string {
|
||||
return fmt.Sprintf("signing key not found: %s", err.KeyID)
|
||||
}
|
8
vendor/github.com/theupdateframework/notary/trustmanager/importLogic.md
generated
vendored
Normal file
8
vendor/github.com/theupdateframework/notary/trustmanager/importLogic.md
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
###This document is intended as an overview of the logic we use for importing keys
|
||||
|
||||
# A flowchart to detail the logic of our import function in `utils/keys.go` (`func ImportKeys`)
|
||||
|
||||

|
||||
|
||||
### Should this logic change, you can edit this image at `https://www.draw.io/i/HQICWeO`
|
||||
|
54
vendor/github.com/theupdateframework/notary/trustmanager/interfaces.go
generated
vendored
Normal file
54
vendor/github.com/theupdateframework/notary/trustmanager/interfaces.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
package trustmanager
|
||||
|
||||
import (
|
||||
"github.com/theupdateframework/notary/tuf/data"
|
||||
)
|
||||
|
||||
// Storage implements the bare bones primitives (no hierarchy)
|
||||
type Storage interface {
|
||||
// Add writes a file to the specified location, returning an error if this
|
||||
// is not possible (reasons may include permissions errors). The path is cleaned
|
||||
// before being made absolute against the store's base dir.
|
||||
Set(fileName string, data []byte) error
|
||||
|
||||
// Remove deletes a file from the store relative to the store's base directory.
|
||||
// The path is cleaned before being made absolute to ensure no path traversal
|
||||
// outside the base directory is possible.
|
||||
Remove(fileName string) error
|
||||
|
||||
// Get returns the file content found at fileName relative to the base directory
|
||||
// of the file store. The path is cleaned before being made absolute to ensure
|
||||
// path traversal outside the store is not possible. If the file is not found
|
||||
// an error to that effect is returned.
|
||||
Get(fileName string) ([]byte, error)
|
||||
|
||||
// ListFiles returns a list of paths relative to the base directory of the
|
||||
// filestore. Any of these paths must be retrievable via the
|
||||
// Storage.Get method.
|
||||
ListFiles() []string
|
||||
|
||||
// Location returns a human readable name indicating where the implementer
|
||||
// is storing keys
|
||||
Location() string
|
||||
}
|
||||
|
||||
// KeyInfo stores the role and gun for a corresponding private key ID
|
||||
// It is assumed that each private key ID is unique
|
||||
type KeyInfo struct {
|
||||
Gun data.GUN
|
||||
Role data.RoleName
|
||||
}
|
||||
|
||||
// KeyStore is a generic interface for private key storage
|
||||
type KeyStore interface {
|
||||
// AddKey adds a key to the KeyStore, and if the key already exists,
|
||||
// succeeds. Otherwise, returns an error if it cannot add.
|
||||
AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error
|
||||
// Should fail with ErrKeyNotFound if the keystore is operating normally
|
||||
// and knows that it does not store the requested key.
|
||||
GetKey(keyID string) (data.PrivateKey, data.RoleName, error)
|
||||
GetKeyInfo(keyID string) (KeyInfo, error)
|
||||
ListKeys() map[string]KeyInfo
|
||||
RemoveKey(keyID string) error
|
||||
Name() string
|
||||
}
|
246
vendor/github.com/theupdateframework/notary/trustmanager/keys.go
generated
vendored
Normal file
246
vendor/github.com/theupdateframework/notary/trustmanager/keys.go
generated
vendored
Normal file
@ -0,0 +1,246 @@
|
||||
package trustmanager
|
||||
|
||||
import (
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/theupdateframework/notary"
|
||||
tufdata "github.com/theupdateframework/notary/tuf/data"
|
||||
"github.com/theupdateframework/notary/tuf/utils"
|
||||
)
|
||||
|
||||
// Exporter is a simple interface for the two functions we need from the Storage interface
|
||||
type Exporter interface {
|
||||
Get(string) ([]byte, error)
|
||||
ListFiles() []string
|
||||
}
|
||||
|
||||
// Importer is a simple interface for the one function we need from the Storage interface
|
||||
type Importer interface {
|
||||
Set(string, []byte) error
|
||||
}
|
||||
|
||||
// ExportKeysByGUN exports all keys filtered to a GUN
|
||||
func ExportKeysByGUN(to io.Writer, s Exporter, gun string) error {
|
||||
keys := s.ListFiles()
|
||||
sort.Strings(keys) // ensure consistency. ListFiles has no order guarantee
|
||||
for _, loc := range keys {
|
||||
keyFile, err := s.Get(loc)
|
||||
if err != nil {
|
||||
logrus.Warn("Could not parse key file at ", loc)
|
||||
continue
|
||||
}
|
||||
block, _ := pem.Decode(keyFile)
|
||||
keyGun := block.Headers["gun"]
|
||||
if keyGun == gun { // must be full GUN match
|
||||
if err := ExportKeys(to, s, loc); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExportKeysByID exports all keys matching the given ID
|
||||
func ExportKeysByID(to io.Writer, s Exporter, ids []string) error {
|
||||
want := make(map[string]struct{})
|
||||
for _, id := range ids {
|
||||
want[id] = struct{}{}
|
||||
}
|
||||
keys := s.ListFiles()
|
||||
for _, k := range keys {
|
||||
id := filepath.Base(k)
|
||||
if _, ok := want[id]; ok {
|
||||
if err := ExportKeys(to, s, k); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExportKeys copies a key from the store to the io.Writer
|
||||
func ExportKeys(to io.Writer, s Exporter, from string) error {
|
||||
// get PEM block
|
||||
k, err := s.Get(from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// parse PEM blocks if there are more than one
|
||||
for block, rest := pem.Decode(k); block != nil; block, rest = pem.Decode(rest) {
|
||||
// add from path in a header for later import
|
||||
block.Headers["path"] = from
|
||||
// write serialized PEM
|
||||
err = pem.Encode(to, block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ImportKeys expects an io.Reader containing one or more PEM blocks.
|
||||
// It reads PEM blocks one at a time until pem.Decode returns a nil
|
||||
// block.
|
||||
// Each block is written to the subpath indicated in the "path" PEM
|
||||
// header. If the file already exists, the file is truncated. Multiple
|
||||
// adjacent PEMs with the same "path" header are appended together.
|
||||
func ImportKeys(from io.Reader, to []Importer, fallbackRole string, fallbackGUN string, passRet notary.PassRetriever) error {
|
||||
// importLogic.md contains a small flowchart I made to clear up my understand while writing the cases in this function
|
||||
// it is very rough, but it may help while reading this piece of code
|
||||
data, err := ioutil.ReadAll(from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
writeTo string
|
||||
toWrite []byte
|
||||
errBlocks []string
|
||||
)
|
||||
for block, rest := pem.Decode(data); block != nil; block, rest = pem.Decode(rest) {
|
||||
handleLegacyPath(block)
|
||||
setFallbacks(block, fallbackGUN, fallbackRole)
|
||||
|
||||
loc, err := checkValidity(block)
|
||||
if err != nil {
|
||||
// already logged in checkValidity
|
||||
errBlocks = append(errBlocks, err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
// the path header is not of any use once we've imported the key so strip it away
|
||||
delete(block.Headers, "path")
|
||||
|
||||
// we are now all set for import but let's first encrypt the key
|
||||
blockBytes := pem.EncodeToMemory(block)
|
||||
// check if key is encrypted, note: if it is encrypted at this point, it will have had a path header
|
||||
if privKey, err := utils.ParsePEMPrivateKey(blockBytes, ""); err == nil {
|
||||
// Key is not encrypted- ask for a passphrase and encrypt this key
|
||||
var chosenPassphrase string
|
||||
for attempts := 0; ; attempts++ {
|
||||
var giveup bool
|
||||
chosenPassphrase, giveup, err = passRet(loc, block.Headers["role"], true, attempts)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if giveup || attempts > 10 {
|
||||
return errors.New("maximum number of passphrase attempts exceeded")
|
||||
}
|
||||
}
|
||||
blockBytes, err = utils.ConvertPrivateKeyToPKCS8(privKey, tufdata.RoleName(block.Headers["role"]), tufdata.GUN(block.Headers["gun"]), chosenPassphrase)
|
||||
if err != nil {
|
||||
return errors.New("failed to encrypt key with given passphrase")
|
||||
}
|
||||
}
|
||||
|
||||
if loc != writeTo {
|
||||
// next location is different from previous one. We've finished aggregating
|
||||
// data for the previous file. If we have data, write the previous file,
|
||||
// clear toWrite and set writeTo to the next path we're going to write
|
||||
if toWrite != nil {
|
||||
if err = importToStores(to, writeTo, toWrite); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// set up for aggregating next file's data
|
||||
toWrite = nil
|
||||
writeTo = loc
|
||||
}
|
||||
|
||||
toWrite = append(toWrite, blockBytes...)
|
||||
}
|
||||
if toWrite != nil { // close out final iteration if there's data left
|
||||
return importToStores(to, writeTo, toWrite)
|
||||
}
|
||||
if len(errBlocks) > 0 {
|
||||
return fmt.Errorf("failed to import all keys: %s", strings.Join(errBlocks, ", "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleLegacyPath(block *pem.Block) {
|
||||
// if there is a legacy path then we set the gun header from this path
|
||||
// this is the case when a user attempts to import a key bundle generated by an older client
|
||||
if rawPath := block.Headers["path"]; rawPath != "" && rawPath != filepath.Base(rawPath) {
|
||||
// this is a legacy filepath and we should try to deduce the gun name from it
|
||||
pathWOFileName := filepath.Dir(rawPath)
|
||||
if strings.HasPrefix(pathWOFileName, notary.NonRootKeysSubdir) {
|
||||
// remove the notary keystore-specific segment of the path, and any potential leading or trailing slashes
|
||||
gunName := strings.Trim(strings.TrimPrefix(pathWOFileName, notary.NonRootKeysSubdir), "/")
|
||||
if gunName != "" {
|
||||
block.Headers["gun"] = gunName
|
||||
}
|
||||
}
|
||||
block.Headers["path"] = filepath.Base(rawPath)
|
||||
}
|
||||
}
|
||||
|
||||
func setFallbacks(block *pem.Block, fallbackGUN, fallbackRole string) {
|
||||
if block.Headers["gun"] == "" {
|
||||
if fallbackGUN != "" {
|
||||
block.Headers["gun"] = fallbackGUN
|
||||
}
|
||||
}
|
||||
|
||||
if block.Headers["role"] == "" {
|
||||
if fallbackRole == "" {
|
||||
block.Headers["role"] = notary.DefaultImportRole
|
||||
} else {
|
||||
block.Headers["role"] = fallbackRole
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkValidity ensures the fields in the pem headers are valid and parses out the location.
|
||||
// While importing a collection of keys, errors from this function should result in only the
|
||||
// current pem block being skipped.
|
||||
func checkValidity(block *pem.Block) (string, error) {
|
||||
// A root key or a delegations key should not have a gun
|
||||
// Note that a key that is not any of the canonical roles (except root) is a delegations key and should not have a gun
|
||||
switch block.Headers["role"] {
|
||||
case tufdata.CanonicalSnapshotRole.String(), tufdata.CanonicalTargetsRole.String(), tufdata.CanonicalTimestampRole.String():
|
||||
// check if the key is missing a gun header or has an empty gun and error out since we don't know what gun it belongs to
|
||||
if block.Headers["gun"] == "" {
|
||||
logrus.Warnf("failed to import key (%s) to store: Cannot have canonical role key without a gun, don't know what gun it belongs to", block.Headers["path"])
|
||||
return "", errors.New("invalid key pem block")
|
||||
}
|
||||
default:
|
||||
delete(block.Headers, "gun")
|
||||
}
|
||||
|
||||
loc, ok := block.Headers["path"]
|
||||
// only if the path isn't specified do we get into this parsing path logic
|
||||
if !ok || loc == "" {
|
||||
// if the path isn't specified, we will try to infer the path rel to trust dir from the role (and then gun)
|
||||
// parse key for the keyID which we will save it by.
|
||||
// if the key is encrypted at this point, we will generate an error and continue since we don't know the ID to save it by
|
||||
|
||||
decodedKey, err := utils.ParsePEMPrivateKey(pem.EncodeToMemory(block), "")
|
||||
if err != nil {
|
||||
logrus.Warn("failed to import key to store: Invalid key generated, key may be encrypted and does not contain path header")
|
||||
return "", errors.New("invalid key pem block")
|
||||
}
|
||||
loc = decodedKey.ID()
|
||||
}
|
||||
return loc, nil
|
||||
}
|
||||
|
||||
func importToStores(to []Importer, path string, bytes []byte) error {
|
||||
var err error
|
||||
for _, i := range to {
|
||||
if err = i.Set(path, bytes); err != nil {
|
||||
logrus.Errorf("failed to import key to store: %s", err.Error())
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
262
vendor/github.com/theupdateframework/notary/trustmanager/keystore.go
generated
vendored
Normal file
262
vendor/github.com/theupdateframework/notary/trustmanager/keystore.go
generated
vendored
Normal file
@ -0,0 +1,262 @@
|
||||
package trustmanager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/theupdateframework/notary"
|
||||
store "github.com/theupdateframework/notary/storage"
|
||||
"github.com/theupdateframework/notary/tuf/data"
|
||||
"github.com/theupdateframework/notary/tuf/utils"
|
||||
)
|
||||
|
||||
type keyInfoMap map[string]KeyInfo
|
||||
|
||||
type cachedKey struct {
|
||||
role data.RoleName
|
||||
key data.PrivateKey
|
||||
}
|
||||
|
||||
// GenericKeyStore is a wrapper for Storage instances that provides
|
||||
// translation between the []byte form and Public/PrivateKey objects
|
||||
type GenericKeyStore struct {
|
||||
store Storage
|
||||
sync.Mutex
|
||||
notary.PassRetriever
|
||||
cachedKeys map[string]*cachedKey
|
||||
keyInfoMap
|
||||
}
|
||||
|
||||
// NewKeyFileStore returns a new KeyFileStore creating a private directory to
|
||||
// hold the keys.
|
||||
func NewKeyFileStore(baseDir string, p notary.PassRetriever) (*GenericKeyStore, error) {
|
||||
fileStore, err := store.NewPrivateKeyFileStorage(baseDir, notary.KeyExtension)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewGenericKeyStore(fileStore, p), nil
|
||||
}
|
||||
|
||||
// NewKeyMemoryStore returns a new KeyMemoryStore which holds keys in memory
|
||||
func NewKeyMemoryStore(p notary.PassRetriever) *GenericKeyStore {
|
||||
memStore := store.NewMemoryStore(nil)
|
||||
return NewGenericKeyStore(memStore, p)
|
||||
}
|
||||
|
||||
// NewGenericKeyStore creates a GenericKeyStore wrapping the provided
|
||||
// Storage instance, using the PassRetriever to enc/decrypt keys
|
||||
func NewGenericKeyStore(s Storage, p notary.PassRetriever) *GenericKeyStore {
|
||||
ks := GenericKeyStore{
|
||||
store: s,
|
||||
PassRetriever: p,
|
||||
cachedKeys: make(map[string]*cachedKey),
|
||||
keyInfoMap: make(keyInfoMap),
|
||||
}
|
||||
ks.loadKeyInfo()
|
||||
return &ks
|
||||
}
|
||||
|
||||
func generateKeyInfoMap(s Storage) map[string]KeyInfo {
|
||||
keyInfoMap := make(map[string]KeyInfo)
|
||||
for _, keyPath := range s.ListFiles() {
|
||||
d, err := s.Get(keyPath)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
continue
|
||||
}
|
||||
keyID, keyInfo, err := KeyInfoFromPEM(d, keyPath)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
continue
|
||||
}
|
||||
keyInfoMap[keyID] = keyInfo
|
||||
}
|
||||
return keyInfoMap
|
||||
}
|
||||
|
||||
func (s *GenericKeyStore) loadKeyInfo() {
|
||||
s.keyInfoMap = generateKeyInfoMap(s.store)
|
||||
}
|
||||
|
||||
// GetKeyInfo returns the corresponding gun and role key info for a keyID
|
||||
func (s *GenericKeyStore) GetKeyInfo(keyID string) (KeyInfo, error) {
|
||||
if info, ok := s.keyInfoMap[keyID]; ok {
|
||||
return info, nil
|
||||
}
|
||||
return KeyInfo{}, fmt.Errorf("Could not find info for keyID %s", keyID)
|
||||
}
|
||||
|
||||
// AddKey stores the contents of a PEM-encoded private key as a PEM block
|
||||
func (s *GenericKeyStore) AddKey(keyInfo KeyInfo, privKey data.PrivateKey) error {
|
||||
var (
|
||||
chosenPassphrase string
|
||||
giveup bool
|
||||
err error
|
||||
pemPrivKey []byte
|
||||
)
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
if keyInfo.Role == data.CanonicalRootRole || data.IsDelegation(keyInfo.Role) || !data.ValidRole(keyInfo.Role) {
|
||||
keyInfo.Gun = ""
|
||||
}
|
||||
keyID := privKey.ID()
|
||||
for attempts := 0; ; attempts++ {
|
||||
chosenPassphrase, giveup, err = s.PassRetriever(keyID, keyInfo.Role.String(), true, attempts)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if giveup || attempts > 10 {
|
||||
return ErrAttemptsExceeded{}
|
||||
}
|
||||
}
|
||||
|
||||
pemPrivKey, err = utils.ConvertPrivateKeyToPKCS8(privKey, keyInfo.Role, keyInfo.Gun, chosenPassphrase)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.cachedKeys[keyID] = &cachedKey{role: keyInfo.Role, key: privKey}
|
||||
err = s.store.Set(keyID, pemPrivKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.keyInfoMap[privKey.ID()] = keyInfo
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetKey returns the PrivateKey given a KeyID
|
||||
func (s *GenericKeyStore) GetKey(keyID string) (data.PrivateKey, data.RoleName, error) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
cachedKeyEntry, ok := s.cachedKeys[keyID]
|
||||
if ok {
|
||||
return cachedKeyEntry.key, cachedKeyEntry.role, nil
|
||||
}
|
||||
|
||||
role, err := getKeyRole(s.store, keyID)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
keyBytes, err := s.store.Get(keyID)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// See if the key is encrypted. If its encrypted we'll fail to parse the private key
|
||||
privKey, err := utils.ParsePEMPrivateKey(keyBytes, "")
|
||||
if err != nil {
|
||||
privKey, _, err = GetPasswdDecryptBytes(s.PassRetriever, keyBytes, keyID, string(role))
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
}
|
||||
s.cachedKeys[keyID] = &cachedKey{role: role, key: privKey}
|
||||
return privKey, role, nil
|
||||
}
|
||||
|
||||
// ListKeys returns a list of unique PublicKeys present on the KeyFileStore, by returning a copy of the keyInfoMap
|
||||
func (s *GenericKeyStore) ListKeys() map[string]KeyInfo {
|
||||
return copyKeyInfoMap(s.keyInfoMap)
|
||||
}
|
||||
|
||||
// RemoveKey removes the key from the keyfilestore
|
||||
func (s *GenericKeyStore) RemoveKey(keyID string) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
delete(s.cachedKeys, keyID)
|
||||
|
||||
err := s.store.Remove(keyID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(s.keyInfoMap, keyID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name returns a user friendly name for the location this store
|
||||
// keeps its data
|
||||
func (s *GenericKeyStore) Name() string {
|
||||
return s.store.Location()
|
||||
}
|
||||
|
||||
// copyKeyInfoMap returns a deep copy of the passed-in keyInfoMap
|
||||
func copyKeyInfoMap(keyInfoMap map[string]KeyInfo) map[string]KeyInfo {
|
||||
copyMap := make(map[string]KeyInfo)
|
||||
for keyID, keyInfo := range keyInfoMap {
|
||||
copyMap[keyID] = KeyInfo{Role: keyInfo.Role, Gun: keyInfo.Gun}
|
||||
}
|
||||
return copyMap
|
||||
}
|
||||
|
||||
// KeyInfoFromPEM attempts to get a keyID and KeyInfo from the filename and PEM bytes of a key
|
||||
func KeyInfoFromPEM(pemBytes []byte, filename string) (string, KeyInfo, error) {
|
||||
var keyID string
|
||||
keyID = filepath.Base(filename)
|
||||
role, gun, err := utils.ExtractPrivateKeyAttributes(pemBytes)
|
||||
if err != nil {
|
||||
return "", KeyInfo{}, err
|
||||
}
|
||||
return keyID, KeyInfo{Gun: gun, Role: role}, nil
|
||||
}
|
||||
|
||||
// getKeyRole finds the role for the given keyID. It attempts to look
|
||||
// both in the newer format PEM headers, and also in the legacy filename
|
||||
// format. It returns: the role, and an error
|
||||
func getKeyRole(s Storage, keyID string) (data.RoleName, error) {
|
||||
name := strings.TrimSpace(strings.TrimSuffix(filepath.Base(keyID), filepath.Ext(keyID)))
|
||||
|
||||
for _, file := range s.ListFiles() {
|
||||
filename := filepath.Base(file)
|
||||
if strings.HasPrefix(filename, name) {
|
||||
d, err := s.Get(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
role, _, err := utils.ExtractPrivateKeyAttributes(d)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return role, nil
|
||||
}
|
||||
}
|
||||
return "", ErrKeyNotFound{KeyID: keyID}
|
||||
}
|
||||
|
||||
// GetPasswdDecryptBytes gets the password to decrypt the given pem bytes.
|
||||
// Returns the password and private key
|
||||
func GetPasswdDecryptBytes(passphraseRetriever notary.PassRetriever, pemBytes []byte, name, alias string) (data.PrivateKey, string, error) {
|
||||
var (
|
||||
passwd string
|
||||
privKey data.PrivateKey
|
||||
)
|
||||
for attempts := 0; ; attempts++ {
|
||||
var (
|
||||
giveup bool
|
||||
err error
|
||||
)
|
||||
if attempts > 10 {
|
||||
return nil, "", ErrAttemptsExceeded{}
|
||||
}
|
||||
passwd, giveup, err = passphraseRetriever(name, alias, false, attempts)
|
||||
// Check if the passphrase retriever got an error or if it is telling us to give up
|
||||
if giveup || err != nil {
|
||||
return nil, "", ErrPasswordInvalid{}
|
||||
}
|
||||
|
||||
// Try to convert PEM encoded bytes back to a PrivateKey using the passphrase
|
||||
privKey, err = utils.ParsePEMPrivateKey(pemBytes, passwd)
|
||||
if err == nil {
|
||||
// We managed to parse the PrivateKey. We've succeeded!
|
||||
break
|
||||
}
|
||||
}
|
||||
return privKey, passwd, nil
|
||||
}
|
60
vendor/github.com/theupdateframework/notary/trustmanager/yubikey/import.go
generated
vendored
Normal file
60
vendor/github.com/theupdateframework/notary/trustmanager/yubikey/import.go
generated
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
//go:build pkcs11
|
||||
// +build pkcs11
|
||||
|
||||
package yubikey
|
||||
|
||||
import (
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
|
||||
"github.com/theupdateframework/notary"
|
||||
"github.com/theupdateframework/notary/trustmanager"
|
||||
"github.com/theupdateframework/notary/tuf/data"
|
||||
"github.com/theupdateframework/notary/tuf/utils"
|
||||
)
|
||||
|
||||
// YubiImport is a wrapper around the YubiStore that allows us to import private
|
||||
// keys to the yubikey
|
||||
type YubiImport struct {
|
||||
dest *YubiStore
|
||||
passRetriever notary.PassRetriever
|
||||
}
|
||||
|
||||
// NewImporter returns a wrapper for the YubiStore provided that enables importing
|
||||
// keys via the simple Set(string, []byte) interface
|
||||
func NewImporter(ys *YubiStore, ret notary.PassRetriever) *YubiImport {
|
||||
return &YubiImport{
|
||||
dest: ys,
|
||||
passRetriever: ret,
|
||||
}
|
||||
}
|
||||
|
||||
// Set determines if we are allowed to set the given key on the Yubikey and
|
||||
// calls through to YubiStore.AddKey if it's valid
|
||||
func (s *YubiImport) Set(name string, bytes []byte) error {
|
||||
block, _ := pem.Decode(bytes)
|
||||
if block == nil {
|
||||
return errors.New("invalid PEM data, could not parse")
|
||||
}
|
||||
role, ok := block.Headers["role"]
|
||||
if !ok {
|
||||
return errors.New("no role found for key")
|
||||
}
|
||||
ki := trustmanager.KeyInfo{
|
||||
// GUN is ignored by YubiStore
|
||||
Role: data.RoleName(role),
|
||||
}
|
||||
privKey, err := utils.ParsePEMPrivateKey(bytes, "")
|
||||
if err != nil {
|
||||
privKey, _, err = trustmanager.GetPasswdDecryptBytes(
|
||||
s.passRetriever,
|
||||
bytes,
|
||||
name,
|
||||
ki.Role.String(),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return s.dest.AddKey(ki, privKey)
|
||||
}
|
9
vendor/github.com/theupdateframework/notary/trustmanager/yubikey/non_pkcs11.go
generated
vendored
Normal file
9
vendor/github.com/theupdateframework/notary/trustmanager/yubikey/non_pkcs11.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
// go list ./... and go test ./... will not pick up this package without this
|
||||
// file, because go ? ./... does not honor build tags.
|
||||
|
||||
// e.g. "go list -tags pkcs11 ./..." will not list this package if all the
|
||||
// files in it have a build tag.
|
||||
|
||||
// See https://github.com/golang/go/issues/11246
|
||||
|
||||
package yubikey
|
10
vendor/github.com/theupdateframework/notary/trustmanager/yubikey/pkcs11_darwin.go
generated
vendored
Normal file
10
vendor/github.com/theupdateframework/notary/trustmanager/yubikey/pkcs11_darwin.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
//go:build pkcs11 && darwin
|
||||
// +build pkcs11,darwin
|
||||
|
||||
package yubikey
|
||||
|
||||
var possiblePkcs11Libs = []string{
|
||||
"/usr/local/lib/libykcs11.dylib",
|
||||
"/usr/local/docker/lib/libykcs11.dylib",
|
||||
"/usr/local/docker-experimental/lib/libykcs11.dylib",
|
||||
}
|
41
vendor/github.com/theupdateframework/notary/trustmanager/yubikey/pkcs11_interface.go
generated
vendored
Normal file
41
vendor/github.com/theupdateframework/notary/trustmanager/yubikey/pkcs11_interface.go
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
//go:build pkcs11
|
||||
// +build pkcs11
|
||||
|
||||
// an interface around the pkcs11 library, so that things can be mocked out
|
||||
// for testing
|
||||
|
||||
package yubikey
|
||||
|
||||
import "github.com/miekg/pkcs11"
|
||||
|
||||
// IPKCS11 is an interface for wrapping github.com/miekg/pkcs11
|
||||
type pkcs11LibLoader func(module string) IPKCS11Ctx
|
||||
|
||||
func defaultLoader(module string) IPKCS11Ctx {
|
||||
return pkcs11.New(module)
|
||||
}
|
||||
|
||||
// IPKCS11Ctx is an interface for wrapping the parts of
|
||||
// github.com/miekg/pkcs11.Ctx that yubikeystore requires
|
||||
type IPKCS11Ctx interface {
|
||||
Destroy()
|
||||
Initialize() error
|
||||
Finalize() error
|
||||
GetSlotList(tokenPresent bool) ([]uint, error)
|
||||
OpenSession(slotID uint, flags uint) (pkcs11.SessionHandle, error)
|
||||
CloseSession(sh pkcs11.SessionHandle) error
|
||||
Login(sh pkcs11.SessionHandle, userType uint, pin string) error
|
||||
Logout(sh pkcs11.SessionHandle) error
|
||||
CreateObject(sh pkcs11.SessionHandle, temp []*pkcs11.Attribute) (
|
||||
pkcs11.ObjectHandle, error)
|
||||
DestroyObject(sh pkcs11.SessionHandle, oh pkcs11.ObjectHandle) error
|
||||
GetAttributeValue(sh pkcs11.SessionHandle, o pkcs11.ObjectHandle,
|
||||
a []*pkcs11.Attribute) ([]*pkcs11.Attribute, error)
|
||||
FindObjectsInit(sh pkcs11.SessionHandle, temp []*pkcs11.Attribute) error
|
||||
FindObjects(sh pkcs11.SessionHandle, max int) (
|
||||
[]pkcs11.ObjectHandle, bool, error)
|
||||
FindObjectsFinal(sh pkcs11.SessionHandle) error
|
||||
SignInit(sh pkcs11.SessionHandle, m []*pkcs11.Mechanism,
|
||||
o pkcs11.ObjectHandle) error
|
||||
Sign(sh pkcs11.SessionHandle, message []byte) ([]byte, error)
|
||||
}
|
13
vendor/github.com/theupdateframework/notary/trustmanager/yubikey/pkcs11_linux.go
generated
vendored
Normal file
13
vendor/github.com/theupdateframework/notary/trustmanager/yubikey/pkcs11_linux.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
//go:build pkcs11 && linux
|
||||
// +build pkcs11,linux
|
||||
|
||||
package yubikey
|
||||
|
||||
var possiblePkcs11Libs = []string{
|
||||
"/usr/lib/libykcs11.so",
|
||||
"/usr/lib/libykcs11.so.1", // yubico-piv-tool on Fedora installs here
|
||||
"/usr/lib64/libykcs11.so",
|
||||
"/usr/lib64/libykcs11.so.1", // yubico-piv-tool on Fedora installs here
|
||||
"/usr/lib/x86_64-linux-gnu/libykcs11.so",
|
||||
"/usr/local/lib/libykcs11.so",
|
||||
}
|
925
vendor/github.com/theupdateframework/notary/trustmanager/yubikey/yubikeystore.go
generated
vendored
Normal file
925
vendor/github.com/theupdateframework/notary/trustmanager/yubikey/yubikeystore.go
generated
vendored
Normal file
@ -0,0 +1,925 @@
|
||||
//go:build pkcs11
|
||||
// +build pkcs11
|
||||
|
||||
package yubikey
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/pkcs11"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/theupdateframework/notary"
|
||||
"github.com/theupdateframework/notary/trustmanager"
|
||||
"github.com/theupdateframework/notary/tuf/data"
|
||||
"github.com/theupdateframework/notary/tuf/signed"
|
||||
"github.com/theupdateframework/notary/tuf/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
// UserPin is the user pin of a yubikey (in PIV parlance, is the PIN)
|
||||
UserPin = "123456"
|
||||
// SOUserPin is the "Security Officer" user pin - this is the PIV management
|
||||
// (MGM) key, which is different than the admin pin of the Yubikey PGP interface
|
||||
// (which in PIV parlance is the PUK, and defaults to 12345678)
|
||||
SOUserPin = "010203040506070801020304050607080102030405060708"
|
||||
numSlots = 4 // number of slots in the yubikey
|
||||
|
||||
// KeymodeNone means that no touch or PIN is required to sign with the yubikey
|
||||
KeymodeNone = 0
|
||||
// KeymodeTouch means that only touch is required to sign with the yubikey
|
||||
KeymodeTouch = 1
|
||||
// KeymodePinOnce means that the pin entry is required once the first time to sign with the yubikey
|
||||
KeymodePinOnce = 2
|
||||
// KeymodePinAlways means that pin entry is required every time to sign with the yubikey
|
||||
KeymodePinAlways = 4
|
||||
|
||||
// the key size, when importing a key into yubikey, MUST be 32 bytes
|
||||
ecdsaPrivateKeySize = 32
|
||||
|
||||
sigAttempts = 5
|
||||
)
|
||||
|
||||
// what key mode to use when generating keys
|
||||
var (
|
||||
yubikeyKeymode = KeymodeTouch | KeymodePinOnce
|
||||
// order in which to prefer token locations on the yubikey.
|
||||
// corresponds to: 9c, 9e, 9d, 9a
|
||||
slotIDs = []int{2, 1, 3, 0}
|
||||
)
|
||||
|
||||
// SetYubikeyKeyMode - sets the mode when generating yubikey keys.
|
||||
// This is to be used for testing. It does nothing if not building with tag
|
||||
// pkcs11.
|
||||
func SetYubikeyKeyMode(keyMode int) error {
|
||||
// technically 7 (1 | 2 | 4) is valid, but KeymodePinOnce +
|
||||
// KeymdoePinAlways don't really make sense together
|
||||
if keyMode < 0 || keyMode > 5 {
|
||||
return errors.New("Invalid key mode")
|
||||
}
|
||||
yubikeyKeymode = keyMode
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTouchToSignUI - allows configurable UX for notifying a user that they
|
||||
// need to touch the yubikey to sign. The callback may be used to provide a
|
||||
// mechanism for updating a GUI (such as removing a modal) after the touch
|
||||
// has been made
|
||||
func SetTouchToSignUI(notifier func(), callback func()) {
|
||||
touchToSignUI = notifier
|
||||
if callback != nil {
|
||||
touchDoneCallback = callback
|
||||
}
|
||||
}
|
||||
|
||||
var touchToSignUI = func() {
|
||||
fmt.Println("Please touch the attached Yubikey to perform signing.")
|
||||
}
|
||||
|
||||
var touchDoneCallback = func() {
|
||||
// noop
|
||||
}
|
||||
|
||||
var pkcs11Lib string
|
||||
|
||||
func init() {
|
||||
for _, loc := range possiblePkcs11Libs {
|
||||
_, err := os.Stat(loc)
|
||||
if err == nil {
|
||||
p := pkcs11.New(loc)
|
||||
if p != nil {
|
||||
pkcs11Lib = loc
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ErrBackupFailed is returned when a YubiStore fails to back up a key that
|
||||
// is added
|
||||
type ErrBackupFailed struct {
|
||||
err string
|
||||
}
|
||||
|
||||
func (err ErrBackupFailed) Error() string {
|
||||
return fmt.Sprintf("Failed to backup private key to: %s", err.err)
|
||||
}
|
||||
|
||||
// An error indicating that the HSM is not present (as opposed to failing),
|
||||
// i.e. that we can confidently claim that the key is not stored in the HSM
|
||||
// without notifying the user about a missing or failing HSM.
|
||||
type errHSMNotPresent struct {
|
||||
err string
|
||||
}
|
||||
|
||||
func (err errHSMNotPresent) Error() string {
|
||||
return err.err
|
||||
}
|
||||
|
||||
type yubiSlot struct {
|
||||
role data.RoleName
|
||||
slotID []byte
|
||||
}
|
||||
|
||||
// YubiPrivateKey represents a private key inside of a yubikey
|
||||
type YubiPrivateKey struct {
|
||||
data.ECDSAPublicKey
|
||||
passRetriever notary.PassRetriever
|
||||
slot []byte
|
||||
libLoader pkcs11LibLoader
|
||||
}
|
||||
|
||||
// yubikeySigner wraps a YubiPrivateKey and implements the crypto.Signer interface
|
||||
type yubikeySigner struct {
|
||||
YubiPrivateKey
|
||||
}
|
||||
|
||||
// NewYubiPrivateKey returns a YubiPrivateKey, which implements the data.PrivateKey
|
||||
// interface except that the private material is inaccessible
|
||||
func NewYubiPrivateKey(slot []byte, pubKey data.ECDSAPublicKey,
|
||||
passRetriever notary.PassRetriever) *YubiPrivateKey {
|
||||
|
||||
return &YubiPrivateKey{
|
||||
ECDSAPublicKey: pubKey,
|
||||
passRetriever: passRetriever,
|
||||
slot: slot,
|
||||
libLoader: defaultLoader,
|
||||
}
|
||||
}
|
||||
|
||||
// Public is a required method of the crypto.Signer interface
|
||||
func (ys *yubikeySigner) Public() crypto.PublicKey {
|
||||
publicKey, err := x509.ParsePKIXPublicKey(ys.YubiPrivateKey.Public())
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return publicKey
|
||||
}
|
||||
|
||||
func (y *YubiPrivateKey) setLibLoader(loader pkcs11LibLoader) {
|
||||
y.libLoader = loader
|
||||
}
|
||||
|
||||
// CryptoSigner returns a crypto.Signer tha wraps the YubiPrivateKey. Needed for
|
||||
// Certificate generation only
|
||||
func (y *YubiPrivateKey) CryptoSigner() crypto.Signer {
|
||||
return &yubikeySigner{YubiPrivateKey: *y}
|
||||
}
|
||||
|
||||
// Private is not implemented in hardware keys
|
||||
func (y *YubiPrivateKey) Private() []byte {
|
||||
// We cannot return the private material from a Yubikey
|
||||
// TODO(david): We probably want to return an error here
|
||||
return nil
|
||||
}
|
||||
|
||||
// SignatureAlgorithm returns which algorithm this key uses to sign - currently
|
||||
// hardcoded to ECDSA
|
||||
func (y YubiPrivateKey) SignatureAlgorithm() data.SigAlgorithm {
|
||||
return data.ECDSASignature
|
||||
}
|
||||
|
||||
// Sign is a required method of the crypto.Signer interface and the data.PrivateKey
|
||||
// interface
|
||||
func (y *YubiPrivateKey) Sign(rand io.Reader, msg []byte, opts crypto.SignerOpts) ([]byte, error) {
|
||||
ctx, session, err := SetupHSMEnv(pkcs11Lib, y.libLoader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cleanup(ctx, session)
|
||||
|
||||
v := signed.Verifiers[data.ECDSASignature]
|
||||
for i := 0; i < sigAttempts; i++ {
|
||||
sig, err := sign(ctx, session, y.slot, y.passRetriever, msg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign using Yubikey: %v", err)
|
||||
}
|
||||
if err := v.Verify(&y.ECDSAPublicKey, sig, msg); err == nil {
|
||||
return sig, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("failed to generate signature on Yubikey")
|
||||
}
|
||||
|
||||
// If a byte array is less than the number of bytes specified by
|
||||
// ecdsaPrivateKeySize, left-zero-pad the byte array until
|
||||
// it is the required size.
|
||||
func ensurePrivateKeySize(payload []byte) []byte {
|
||||
final := payload
|
||||
if len(payload) < ecdsaPrivateKeySize {
|
||||
final = make([]byte, ecdsaPrivateKeySize)
|
||||
copy(final[ecdsaPrivateKeySize-len(payload):], payload)
|
||||
}
|
||||
return final
|
||||
}
|
||||
|
||||
// addECDSAKey adds a key to the yubikey
|
||||
func addECDSAKey(
|
||||
ctx IPKCS11Ctx,
|
||||
session pkcs11.SessionHandle,
|
||||
privKey data.PrivateKey,
|
||||
pkcs11KeyID []byte,
|
||||
passRetriever notary.PassRetriever,
|
||||
role data.RoleName,
|
||||
) error {
|
||||
logrus.Debugf("Attempting to add key to yubikey with ID: %s", privKey.ID())
|
||||
|
||||
err := login(ctx, session, passRetriever, pkcs11.CKU_SO, SOUserPin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ctx.Logout(session)
|
||||
|
||||
// Create an ecdsa.PrivateKey out of the private key bytes
|
||||
ecdsaPrivKey, err := x509.ParseECPrivateKey(privKey.Private())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ecdsaPrivKeyD := ensurePrivateKeySize(ecdsaPrivKey.D.Bytes())
|
||||
|
||||
// Hard-coded policy: the generated certificate expires in 10 years.
|
||||
startTime := time.Now()
|
||||
template, err := utils.NewCertificate(role.String(), startTime, startTime.AddDate(10, 0, 0))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create the certificate template: %v", err)
|
||||
}
|
||||
|
||||
certBytes, err := x509.CreateCertificate(rand.Reader, template, template, ecdsaPrivKey.Public(), ecdsaPrivKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create the certificate: %v", err)
|
||||
}
|
||||
|
||||
certTemplate := []*pkcs11.Attribute{
|
||||
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_VALUE, certBytes),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID),
|
||||
}
|
||||
|
||||
privateKeyTemplate := []*pkcs11.Attribute{
|
||||
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_ECDSA),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07}),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_VALUE, ecdsaPrivKeyD),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_VENDOR_DEFINED, yubikeyKeymode),
|
||||
}
|
||||
|
||||
_, err = ctx.CreateObject(session, certTemplate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error importing: %v", err)
|
||||
}
|
||||
|
||||
_, err = ctx.CreateObject(session, privateKeyTemplate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error importing: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getECDSAKey(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte) (*data.ECDSAPublicKey, data.RoleName, error) {
|
||||
findTemplate := []*pkcs11.Attribute{
|
||||
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY),
|
||||
}
|
||||
|
||||
attrTemplate := []*pkcs11.Attribute{
|
||||
pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, []byte{0}),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_EC_POINT, []byte{0}),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_EC_PARAMS, []byte{0}),
|
||||
}
|
||||
|
||||
if err := ctx.FindObjectsInit(session, findTemplate); err != nil {
|
||||
logrus.Debugf("Failed to init: %s", err.Error())
|
||||
return nil, "", err
|
||||
}
|
||||
obj, _, err := ctx.FindObjects(session, 1)
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to find objects: %v", err)
|
||||
return nil, "", err
|
||||
}
|
||||
if err := ctx.FindObjectsFinal(session); err != nil {
|
||||
logrus.Debugf("Failed to finalize: %s", err.Error())
|
||||
return nil, "", err
|
||||
}
|
||||
if len(obj) != 1 {
|
||||
logrus.Debugf("should have found one object")
|
||||
return nil, "", errors.New("no matching keys found inside of yubikey")
|
||||
}
|
||||
|
||||
// Retrieve the public-key material to be able to create a new ECSAKey
|
||||
attr, err := ctx.GetAttributeValue(session, obj[0], attrTemplate)
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to get Attribute for: %v", obj[0])
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// Iterate through all the attributes of this key and saves CKA_PUBLIC_EXPONENT and CKA_MODULUS. Removes ordering specific issues.
|
||||
var rawPubKey []byte
|
||||
for _, a := range attr {
|
||||
if a.Type == pkcs11.CKA_EC_POINT {
|
||||
rawPubKey = a.Value
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ecdsaPubKey := ecdsa.PublicKey{Curve: elliptic.P256(), X: new(big.Int).SetBytes(rawPubKey[3:35]), Y: new(big.Int).SetBytes(rawPubKey[35:])}
|
||||
pubBytes, err := x509.MarshalPKIXPublicKey(&ecdsaPubKey)
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to Marshal public key")
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return data.NewECDSAPublicKey(pubBytes), data.CanonicalRootRole, nil
|
||||
}
|
||||
|
||||
// sign returns a signature for a given signature request
|
||||
func sign(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, passRetriever notary.PassRetriever, payload []byte) ([]byte, error) {
|
||||
err := login(ctx, session, passRetriever, pkcs11.CKU_USER, UserPin)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error logging in: %v", err)
|
||||
}
|
||||
defer ctx.Logout(session)
|
||||
|
||||
// Define the ECDSA Private key template
|
||||
class := pkcs11.CKO_PRIVATE_KEY
|
||||
privateKeyTemplate := []*pkcs11.Attribute{
|
||||
pkcs11.NewAttribute(pkcs11.CKA_CLASS, class),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_ECDSA),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID),
|
||||
}
|
||||
|
||||
if err := ctx.FindObjectsInit(session, privateKeyTemplate); err != nil {
|
||||
logrus.Debugf("Failed to init find objects: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
obj, _, err := ctx.FindObjects(session, 1)
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to find objects: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
if err = ctx.FindObjectsFinal(session); err != nil {
|
||||
logrus.Debugf("Failed to finalize find objects: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
if len(obj) != 1 {
|
||||
return nil, errors.New("length of objects found not 1")
|
||||
}
|
||||
|
||||
var sig []byte
|
||||
err = ctx.SignInit(
|
||||
session, []*pkcs11.Mechanism{pkcs11.NewMechanism(pkcs11.CKM_ECDSA, nil)}, obj[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the SHA256 of the payload
|
||||
digest := sha256.Sum256(payload)
|
||||
|
||||
if (yubikeyKeymode & KeymodeTouch) > 0 {
|
||||
touchToSignUI()
|
||||
defer touchDoneCallback()
|
||||
}
|
||||
// a call to Sign, whether or not Sign fails, will clear the SignInit
|
||||
sig, err = ctx.Sign(session, digest[:])
|
||||
if err != nil {
|
||||
logrus.Debugf("Error while signing: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sig == nil {
|
||||
return nil, errors.New("Failed to create signature")
|
||||
}
|
||||
return sig[:], nil
|
||||
}
|
||||
|
||||
func yubiRemoveKey(ctx IPKCS11Ctx, session pkcs11.SessionHandle, pkcs11KeyID []byte, passRetriever notary.PassRetriever, keyID string) error {
|
||||
err := login(ctx, session, passRetriever, pkcs11.CKU_SO, SOUserPin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ctx.Logout(session)
|
||||
|
||||
template := []*pkcs11.Attribute{
|
||||
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_ID, pkcs11KeyID),
|
||||
//pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE),
|
||||
}
|
||||
|
||||
if err := ctx.FindObjectsInit(session, template); err != nil {
|
||||
logrus.Debugf("Failed to init find objects: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
obj, b, err := ctx.FindObjects(session, 1)
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to find objects: %s %v", err.Error(), b)
|
||||
return err
|
||||
}
|
||||
if err := ctx.FindObjectsFinal(session); err != nil {
|
||||
logrus.Debugf("Failed to finalize find objects: %s", err.Error())
|
||||
return err
|
||||
}
|
||||
if len(obj) != 1 {
|
||||
logrus.Debugf("should have found exactly one object")
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete the certificate
|
||||
err = ctx.DestroyObject(session, obj[0])
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to delete cert")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func yubiListKeys(ctx IPKCS11Ctx, session pkcs11.SessionHandle) (keys map[string]yubiSlot, err error) {
|
||||
keys = make(map[string]yubiSlot)
|
||||
|
||||
attrTemplate := []*pkcs11.Attribute{
|
||||
pkcs11.NewAttribute(pkcs11.CKA_ID, []byte{0}),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_VALUE, []byte{0}),
|
||||
}
|
||||
|
||||
objs, err := listObjects(ctx, session)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(objs) == 0 {
|
||||
return nil, errors.New("no keys found in yubikey")
|
||||
}
|
||||
logrus.Debugf("Found %d objects matching list filters", len(objs))
|
||||
for _, obj := range objs {
|
||||
var (
|
||||
cert *x509.Certificate
|
||||
slot []byte
|
||||
)
|
||||
// Retrieve the public-key material to be able to create a new ECDSA
|
||||
attr, err := ctx.GetAttributeValue(session, obj, attrTemplate)
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to get Attribute for: %v", obj)
|
||||
continue
|
||||
}
|
||||
|
||||
// Iterate through all the attributes of this key and saves CKA_PUBLIC_EXPONENT and CKA_MODULUS. Removes ordering specific issues.
|
||||
for _, a := range attr {
|
||||
if a.Type == pkcs11.CKA_ID {
|
||||
slot = a.Value
|
||||
}
|
||||
if a.Type == pkcs11.CKA_VALUE {
|
||||
cert, err = x509.ParseCertificate(a.Value)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if !data.ValidRole(data.RoleName(cert.Subject.CommonName)) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we found nothing
|
||||
if cert == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var ecdsaPubKey *ecdsa.PublicKey
|
||||
switch cert.PublicKeyAlgorithm {
|
||||
case x509.ECDSA:
|
||||
ecdsaPubKey = cert.PublicKey.(*ecdsa.PublicKey)
|
||||
default:
|
||||
logrus.Infof("Unsupported x509 PublicKeyAlgorithm: %d", cert.PublicKeyAlgorithm)
|
||||
continue
|
||||
}
|
||||
|
||||
pubBytes, err := x509.MarshalPKIXPublicKey(ecdsaPubKey)
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to Marshal public key")
|
||||
continue
|
||||
}
|
||||
|
||||
keys[data.NewECDSAPublicKey(pubBytes).ID()] = yubiSlot{
|
||||
role: data.RoleName(cert.Subject.CommonName),
|
||||
slotID: slot,
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func listObjects(ctx IPKCS11Ctx, session pkcs11.SessionHandle) ([]pkcs11.ObjectHandle, error) {
|
||||
findTemplate := []*pkcs11.Attribute{
|
||||
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
|
||||
pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_CERTIFICATE),
|
||||
}
|
||||
|
||||
if err := ctx.FindObjectsInit(session, findTemplate); err != nil {
|
||||
logrus.Debugf("Failed to init: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objs, b, err := ctx.FindObjects(session, numSlots)
|
||||
for err == nil {
|
||||
var o []pkcs11.ObjectHandle
|
||||
o, b, err = ctx.FindObjects(session, numSlots)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if len(o) == 0 {
|
||||
break
|
||||
}
|
||||
objs = append(objs, o...)
|
||||
}
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to find: %s %v", err.Error(), b)
|
||||
if len(objs) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := ctx.FindObjectsFinal(session); err != nil {
|
||||
logrus.Debugf("Failed to finalize: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
return objs, nil
|
||||
}
|
||||
|
||||
func getNextEmptySlot(ctx IPKCS11Ctx, session pkcs11.SessionHandle) ([]byte, error) {
|
||||
findTemplate := []*pkcs11.Attribute{
|
||||
pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
|
||||
}
|
||||
attrTemplate := []*pkcs11.Attribute{
|
||||
pkcs11.NewAttribute(pkcs11.CKA_ID, []byte{0}),
|
||||
}
|
||||
|
||||
if err := ctx.FindObjectsInit(session, findTemplate); err != nil {
|
||||
logrus.Debugf("Failed to init: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
objs, b, err := ctx.FindObjects(session, numSlots)
|
||||
// if there are more objects than `numSlots`, get all of them until
|
||||
// there are no more to get
|
||||
for err == nil {
|
||||
var o []pkcs11.ObjectHandle
|
||||
o, b, err = ctx.FindObjects(session, numSlots)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if len(o) == 0 {
|
||||
break
|
||||
}
|
||||
objs = append(objs, o...)
|
||||
}
|
||||
taken := make(map[int]bool)
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to find: %s %v", err.Error(), b)
|
||||
return nil, err
|
||||
}
|
||||
if err = ctx.FindObjectsFinal(session); err != nil {
|
||||
logrus.Debugf("Failed to finalize: %s\n", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
for _, obj := range objs {
|
||||
// Retrieve the slot ID
|
||||
attr, err := ctx.GetAttributeValue(session, obj, attrTemplate)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Iterate through attributes. If an ID attr was found, mark it as taken
|
||||
for _, a := range attr {
|
||||
if a.Type == pkcs11.CKA_ID {
|
||||
if len(a.Value) < 1 {
|
||||
continue
|
||||
}
|
||||
// a byte will always be capable of representing all slot IDs
|
||||
// for the Yubikeys
|
||||
slotNum := int(a.Value[0])
|
||||
if slotNum >= numSlots {
|
||||
// defensive
|
||||
continue
|
||||
}
|
||||
taken[slotNum] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
// iterate the token locations in our preferred order and use the first
|
||||
// available one. Otherwise exit the loop and return an error.
|
||||
for _, loc := range slotIDs {
|
||||
if !taken[loc] {
|
||||
return []byte{byte(loc)}, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("yubikey has no available slots")
|
||||
}
|
||||
|
||||
// YubiStore is a KeyStore for private keys inside a Yubikey
|
||||
type YubiStore struct {
|
||||
passRetriever notary.PassRetriever
|
||||
keys map[string]yubiSlot
|
||||
backupStore trustmanager.KeyStore
|
||||
libLoader pkcs11LibLoader
|
||||
}
|
||||
|
||||
// NewYubiStore returns a YubiStore, given a backup key store to write any
|
||||
// generated keys to (usually a KeyFileStore)
|
||||
func NewYubiStore(backupStore trustmanager.KeyStore, passphraseRetriever notary.PassRetriever) (
|
||||
*YubiStore, error) {
|
||||
|
||||
s := &YubiStore{
|
||||
passRetriever: passphraseRetriever,
|
||||
keys: make(map[string]yubiSlot),
|
||||
backupStore: backupStore,
|
||||
libLoader: defaultLoader,
|
||||
}
|
||||
s.ListKeys() // populate keys field
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Name returns a user friendly name for the location this store
|
||||
// keeps its data
|
||||
func (s YubiStore) Name() string {
|
||||
return "yubikey"
|
||||
}
|
||||
|
||||
func (s *YubiStore) setLibLoader(loader pkcs11LibLoader) {
|
||||
s.libLoader = loader
|
||||
}
|
||||
|
||||
// ListKeys returns a list of keys in the yubikey store
|
||||
func (s *YubiStore) ListKeys() map[string]trustmanager.KeyInfo {
|
||||
if len(s.keys) > 0 {
|
||||
return buildKeyMap(s.keys)
|
||||
}
|
||||
ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
|
||||
if err != nil {
|
||||
logrus.Debugf("No yubikey found, using alternative key storage: %s", err.Error())
|
||||
return nil
|
||||
}
|
||||
defer cleanup(ctx, session)
|
||||
|
||||
keys, err := yubiListKeys(ctx, session)
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to list key from the yubikey: %s", err.Error())
|
||||
return nil
|
||||
}
|
||||
s.keys = keys
|
||||
|
||||
return buildKeyMap(keys)
|
||||
}
|
||||
|
||||
// AddKey puts a key inside the Yubikey, as well as writing it to the backup store
|
||||
func (s *YubiStore) AddKey(keyInfo trustmanager.KeyInfo, privKey data.PrivateKey) error {
|
||||
added, err := s.addKey(privKey.ID(), keyInfo.Role, privKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if added && s.backupStore != nil {
|
||||
err = s.backupStore.AddKey(keyInfo, privKey)
|
||||
if err != nil {
|
||||
defer s.RemoveKey(privKey.ID())
|
||||
return ErrBackupFailed{err: err.Error()}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Only add if we haven't seen the key already. Return whether the key was
|
||||
// added.
|
||||
func (s *YubiStore) addKey(keyID string, role data.RoleName, privKey data.PrivateKey) (
|
||||
bool, error) {
|
||||
|
||||
// We only allow adding root keys for now
|
||||
if role != data.CanonicalRootRole {
|
||||
return false, fmt.Errorf(
|
||||
"yubikey only supports storing root keys, got %s for key: %s", role, keyID)
|
||||
}
|
||||
|
||||
ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
|
||||
if err != nil {
|
||||
logrus.Debugf("No yubikey found, using alternative key storage: %s", err.Error())
|
||||
return false, err
|
||||
}
|
||||
defer cleanup(ctx, session)
|
||||
|
||||
if k, ok := s.keys[keyID]; ok {
|
||||
if k.role == role {
|
||||
// already have the key and it's associated with the correct role
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
slot, err := getNextEmptySlot(ctx, session)
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to get an empty yubikey slot: %s", err.Error())
|
||||
return false, err
|
||||
}
|
||||
logrus.Debugf("Attempting to store key using yubikey slot %v", slot)
|
||||
|
||||
err = addECDSAKey(
|
||||
ctx, session, privKey, slot, s.passRetriever, role)
|
||||
if err == nil {
|
||||
s.keys[privKey.ID()] = yubiSlot{
|
||||
role: role,
|
||||
slotID: slot,
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
logrus.Debugf("Failed to add key to yubikey: %v", err)
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
// GetKey retrieves a key from the Yubikey only (it does not look inside the
|
||||
// backup store)
|
||||
func (s *YubiStore) GetKey(keyID string) (data.PrivateKey, data.RoleName, error) {
|
||||
ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
|
||||
if err != nil {
|
||||
logrus.Debugf("No yubikey found, using alternative key storage: %s", err.Error())
|
||||
if _, ok := err.(errHSMNotPresent); ok {
|
||||
err = trustmanager.ErrKeyNotFound{KeyID: keyID}
|
||||
}
|
||||
return nil, "", err
|
||||
}
|
||||
defer cleanup(ctx, session)
|
||||
|
||||
key, ok := s.keys[keyID]
|
||||
if !ok {
|
||||
return nil, "", trustmanager.ErrKeyNotFound{KeyID: keyID}
|
||||
}
|
||||
|
||||
pubKey, alias, err := getECDSAKey(ctx, session, key.slotID)
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to get key from slot %s: %s", key.slotID, err.Error())
|
||||
return nil, "", err
|
||||
}
|
||||
// Check to see if we're returning the intended keyID
|
||||
if pubKey.ID() != keyID {
|
||||
return nil, "", fmt.Errorf("expected root key: %s, but found: %s", keyID, pubKey.ID())
|
||||
}
|
||||
privKey := NewYubiPrivateKey(key.slotID, *pubKey, s.passRetriever)
|
||||
if privKey == nil {
|
||||
return nil, "", errors.New("could not initialize new YubiPrivateKey")
|
||||
}
|
||||
|
||||
return privKey, alias, err
|
||||
}
|
||||
|
||||
// RemoveKey deletes a key from the Yubikey only (it does not remove it from the
|
||||
// backup store)
|
||||
func (s *YubiStore) RemoveKey(keyID string) error {
|
||||
ctx, session, err := SetupHSMEnv(pkcs11Lib, s.libLoader)
|
||||
if err != nil {
|
||||
logrus.Debugf("No yubikey found, using alternative key storage: %s", err.Error())
|
||||
return nil
|
||||
}
|
||||
defer cleanup(ctx, session)
|
||||
|
||||
key, ok := s.keys[keyID]
|
||||
if !ok {
|
||||
return errors.New("Key not present in yubikey")
|
||||
}
|
||||
err = yubiRemoveKey(ctx, session, key.slotID, s.passRetriever, keyID)
|
||||
if err == nil {
|
||||
delete(s.keys, keyID)
|
||||
} else {
|
||||
logrus.Debugf("Failed to remove from the yubikey KeyID %s: %v", keyID, err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// GetKeyInfo is not yet implemented
|
||||
func (s *YubiStore) GetKeyInfo(keyID string) (trustmanager.KeyInfo, error) {
|
||||
return trustmanager.KeyInfo{}, fmt.Errorf("Not yet implemented")
|
||||
}
|
||||
|
||||
func cleanup(ctx IPKCS11Ctx, session pkcs11.SessionHandle) {
|
||||
err := ctx.CloseSession(session)
|
||||
if err != nil {
|
||||
logrus.Debugf("Error closing session: %s", err.Error())
|
||||
}
|
||||
finalizeAndDestroy(ctx)
|
||||
}
|
||||
|
||||
func finalizeAndDestroy(ctx IPKCS11Ctx) {
|
||||
err := ctx.Finalize()
|
||||
if err != nil {
|
||||
logrus.Debugf("Error finalizing: %s", err.Error())
|
||||
}
|
||||
ctx.Destroy()
|
||||
}
|
||||
|
||||
// SetupHSMEnv is a method that depends on the existences
|
||||
func SetupHSMEnv(libraryPath string, libLoader pkcs11LibLoader) (
|
||||
IPKCS11Ctx, pkcs11.SessionHandle, error) {
|
||||
|
||||
if libraryPath == "" {
|
||||
return nil, 0, errHSMNotPresent{err: "no library found"}
|
||||
}
|
||||
p := libLoader(libraryPath)
|
||||
|
||||
if p == nil {
|
||||
return nil, 0, fmt.Errorf("failed to load library %s", libraryPath)
|
||||
}
|
||||
|
||||
if err := p.Initialize(); err != nil {
|
||||
defer finalizeAndDestroy(p)
|
||||
return nil, 0, fmt.Errorf("found library %s, but initialize error %s", libraryPath, err.Error())
|
||||
}
|
||||
|
||||
slots, err := p.GetSlotList(true)
|
||||
if err != nil {
|
||||
defer finalizeAndDestroy(p)
|
||||
return nil, 0, fmt.Errorf(
|
||||
"loaded library %s, but failed to list HSM slots %s", libraryPath, err)
|
||||
}
|
||||
// Check to see if we got any slots from the HSM.
|
||||
if len(slots) < 1 {
|
||||
defer finalizeAndDestroy(p)
|
||||
return nil, 0, fmt.Errorf(
|
||||
"loaded library %s, but no HSM slots found", libraryPath)
|
||||
}
|
||||
|
||||
// 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.
|
||||
// CKF_RW_SESSION: TRUE if the session is read/write; FALSE if the session is read-only
|
||||
session, err := p.OpenSession(slots[0], pkcs11.CKF_SERIAL_SESSION|pkcs11.CKF_RW_SESSION)
|
||||
if err != nil {
|
||||
defer cleanup(p, session)
|
||||
return nil, 0, fmt.Errorf(
|
||||
"loaded library %s, but failed to start session with HSM %s",
|
||||
libraryPath, err)
|
||||
}
|
||||
|
||||
logrus.Debugf("Initialized PKCS11 library %s and started HSM session", libraryPath)
|
||||
return p, session, nil
|
||||
}
|
||||
|
||||
// IsAccessible returns true if a Yubikey can be accessed
|
||||
func IsAccessible() bool {
|
||||
if pkcs11Lib == "" {
|
||||
return false
|
||||
}
|
||||
ctx, session, err := SetupHSMEnv(pkcs11Lib, defaultLoader)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer cleanup(ctx, session)
|
||||
return true
|
||||
}
|
||||
|
||||
func login(ctx IPKCS11Ctx, session pkcs11.SessionHandle, passRetriever notary.PassRetriever, userFlag uint, defaultPassw string) error {
|
||||
// try default password
|
||||
err := ctx.Login(session, userFlag, defaultPassw)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// default failed, ask user for password
|
||||
for attempts := 0; ; attempts++ {
|
||||
var (
|
||||
giveup bool
|
||||
err error
|
||||
user string
|
||||
)
|
||||
if userFlag == pkcs11.CKU_SO {
|
||||
user = "SO Pin"
|
||||
} else {
|
||||
user = "User Pin"
|
||||
}
|
||||
passwd, giveup, err := passRetriever(user, "yubikey", false, attempts)
|
||||
// Check if the passphrase retriever got an error or if it is telling us to give up
|
||||
if giveup || err != nil {
|
||||
return trustmanager.ErrPasswordInvalid{}
|
||||
}
|
||||
if attempts > 2 {
|
||||
return trustmanager.ErrAttemptsExceeded{}
|
||||
}
|
||||
|
||||
// attempt to login. Loop if failed
|
||||
err = ctx.Login(session, userFlag, passwd)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func buildKeyMap(keys map[string]yubiSlot) map[string]trustmanager.KeyInfo {
|
||||
res := make(map[string]trustmanager.KeyInfo)
|
||||
for k, v := range keys {
|
||||
res[k] = trustmanager.KeyInfo{Role: v.role, Gun: ""}
|
||||
}
|
||||
return res
|
||||
}
|
Reference in New Issue
Block a user