/* Copyright IBM Corp. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ package sw import ( "bytes" "crypto/ecdsa" "encoding/hex" "errors" "fmt" "io" "os" "path/filepath" "strings" "sync" "github.com/hyperledger/fabric/bccsp" ) // NewFileBasedKeyStore instantiated a file-based key store at a given position. // The key store can be encrypted if a non-empty password is specified. // It can be also be set as read only. In this case, any store operation // will be forbidden func NewFileBasedKeyStore(pwd []byte, path string, readOnly bool) (bccsp.KeyStore, error) { ks := &fileBasedKeyStore{} return ks, ks.Init(pwd, path, readOnly) } // fileBasedKeyStore is a folder-based KeyStore. // Each key is stored in a separated file whose name contains the key's SKI // and flags to identity the key's type. All the keys are stored in // a folder whose path is provided at initialization time. // The KeyStore can be initialized with a password, this password // is used to encrypt and decrypt the files storing the keys. // A KeyStore can be read only to avoid the overwriting of keys. type fileBasedKeyStore struct { path string readOnly bool isOpen bool pwd []byte // Sync m sync.Mutex } // Init initializes this KeyStore with a password, a path to a folder // where the keys are stored and a read only flag. // Each key is stored in a separated file whose name contains the key's SKI // and flags to identity the key's type. // If the KeyStore is initialized with a password, this password // is used to encrypt and decrypt the files storing the keys. // The pwd can be nil for non-encrypted KeyStores. If an encrypted // key-store is initialized without a password, then retrieving keys from the // KeyStore will fail. // A KeyStore can be read only to avoid the overwriting of keys. func (ks *fileBasedKeyStore) Init(pwd []byte, path string, readOnly bool) error { // Validate inputs // pwd can be nil if len(path) == 0 { return errors.New("an invalid KeyStore path provided. Path cannot be an empty string") } ks.m.Lock() defer ks.m.Unlock() if ks.isOpen { return errors.New("keystore is already initialized") } ks.path = path clone := make([]byte, len(pwd)) copy(clone, pwd) ks.pwd = clone ks.readOnly = readOnly exists, err := dirExists(path) if err != nil { return err } if !exists { err = ks.createKeyStore() if err != nil { return err } return ks.openKeyStore() } empty, err := dirEmpty(path) if err != nil { return err } if empty { err = ks.createKeyStore() if err != nil { return err } } return ks.openKeyStore() } // ReadOnly returns true if this KeyStore is read only, false otherwise. // If ReadOnly is true then StoreKey will fail. func (ks *fileBasedKeyStore) ReadOnly() bool { return ks.readOnly } // GetKey returns a key object whose SKI is the one passed. func (ks *fileBasedKeyStore) GetKey(ski []byte) (bccsp.Key, error) { // Validate arguments if len(ski) == 0 { return nil, errors.New("invalid SKI. Cannot be of zero length") } suffix := ks.getSuffix(hex.EncodeToString(ski)) switch suffix { case "key": // Load the key key, err := ks.loadKey(hex.EncodeToString(ski)) if err != nil { return nil, fmt.Errorf("failed loading key [%x] [%s]", ski, err) } return &aesPrivateKey{key, false}, nil case "sk": // Load the private key key, err := ks.loadPrivateKey(hex.EncodeToString(ski)) if err != nil { return nil, fmt.Errorf("failed loading secret key [%x] [%s]", ski, err) } switch k := key.(type) { case *ecdsa.PrivateKey: return &ecdsaPrivateKey{k}, nil default: return nil, errors.New("secret key type not recognized") } case "pk": // Load the public key key, err := ks.loadPublicKey(hex.EncodeToString(ski)) if err != nil { return nil, fmt.Errorf("failed loading public key [%x] [%s]", ski, err) } switch k := key.(type) { case *ecdsa.PublicKey: return &ecdsaPublicKey{k}, nil default: return nil, errors.New("public key type not recognized") } default: return ks.searchKeystoreForSKI(ski) } } // StoreKey stores the key k in this KeyStore. // If this KeyStore is read only then the method will fail. func (ks *fileBasedKeyStore) StoreKey(k bccsp.Key) (err error) { if ks.readOnly { return errors.New("read only KeyStore") } if k == nil { return errors.New("invalid key. It must be different from nil") } switch kk := k.(type) { case *ecdsaPrivateKey: err = ks.storePrivateKey(hex.EncodeToString(k.SKI()), kk.privKey) if err != nil { return fmt.Errorf("failed storing ECDSA private key [%s]", err) } case *ecdsaPublicKey: err = ks.storePublicKey(hex.EncodeToString(k.SKI()), kk.pubKey) if err != nil { return fmt.Errorf("failed storing ECDSA public key [%s]", err) } case *aesPrivateKey: err = ks.storeKey(hex.EncodeToString(k.SKI()), kk.privKey) if err != nil { return fmt.Errorf("failed storing AES key [%s]", err) } default: return fmt.Errorf("key type not reconigned [%s]", k) } return } func (ks *fileBasedKeyStore) searchKeystoreForSKI(ski []byte) (k bccsp.Key, err error) { files, _ := os.ReadDir(ks.path) for _, f := range files { if f.IsDir() { continue } fileInfo, err := f.Info() if err != nil { continue } if fileInfo.Size() > (1 << 16) { // 64k, somewhat arbitrary limit, considering even large keys continue } raw, err := os.ReadFile(filepath.Join(ks.path, f.Name())) if err != nil { continue } key, err := pemToPrivateKey(raw, ks.pwd) if err != nil { continue } switch kk := key.(type) { case *ecdsa.PrivateKey: k = &ecdsaPrivateKey{kk} default: continue } if !bytes.Equal(k.SKI(), ski) { continue } return k, nil } return nil, fmt.Errorf("key with SKI %x not found in %s", ski, ks.path) } func (ks *fileBasedKeyStore) getSuffix(alias string) string { files, _ := os.ReadDir(ks.path) for _, f := range files { if strings.HasPrefix(f.Name(), alias) { if strings.HasSuffix(f.Name(), "sk") { return "sk" } if strings.HasSuffix(f.Name(), "pk") { return "pk" } if strings.HasSuffix(f.Name(), "key") { return "key" } break } } return "" } func (ks *fileBasedKeyStore) storePrivateKey(alias string, privateKey interface{}) error { rawKey, err := privateKeyToPEM(privateKey, ks.pwd) if err != nil { logger.Errorf("Failed converting private key to PEM [%s]: [%s]", alias, err) return err } err = os.WriteFile(ks.getPathForAlias(alias, "sk"), rawKey, 0o600) if err != nil { logger.Errorf("Failed storing private key [%s]: [%s]", alias, err) return err } return nil } func (ks *fileBasedKeyStore) storePublicKey(alias string, publicKey interface{}) error { rawKey, err := publicKeyToPEM(publicKey, ks.pwd) if err != nil { logger.Errorf("Failed converting public key to PEM [%s]: [%s]", alias, err) return err } err = os.WriteFile(ks.getPathForAlias(alias, "pk"), rawKey, 0o600) if err != nil { logger.Errorf("Failed storing private key [%s]: [%s]", alias, err) return err } return nil } func (ks *fileBasedKeyStore) storeKey(alias string, key []byte) error { pem, err := aesToEncryptedPEM(key, ks.pwd) if err != nil { logger.Errorf("Failed converting key to PEM [%s]: [%s]", alias, err) return err } err = os.WriteFile(ks.getPathForAlias(alias, "key"), pem, 0o600) if err != nil { logger.Errorf("Failed storing key [%s]: [%s]", alias, err) return err } return nil } func (ks *fileBasedKeyStore) loadPrivateKey(alias string) (interface{}, error) { path := ks.getPathForAlias(alias, "sk") logger.Debugf("Loading private key [%s] at [%s]...", alias, path) raw, err := os.ReadFile(path) if err != nil { logger.Errorf("Failed loading private key [%s]: [%s].", alias, err.Error()) return nil, err } privateKey, err := pemToPrivateKey(raw, ks.pwd) if err != nil { logger.Errorf("Failed parsing private key [%s]: [%s].", alias, err.Error()) return nil, err } return privateKey, nil } func (ks *fileBasedKeyStore) loadPublicKey(alias string) (interface{}, error) { path := ks.getPathForAlias(alias, "pk") logger.Debugf("Loading public key [%s] at [%s]...", alias, path) raw, err := os.ReadFile(path) if err != nil { logger.Errorf("Failed loading public key [%s]: [%s].", alias, err.Error()) return nil, err } privateKey, err := pemToPublicKey(raw, ks.pwd) if err != nil { logger.Errorf("Failed parsing private key [%s]: [%s].", alias, err.Error()) return nil, err } return privateKey, nil } func (ks *fileBasedKeyStore) loadKey(alias string) ([]byte, error) { path := ks.getPathForAlias(alias, "key") logger.Debugf("Loading key [%s] at [%s]...", alias, path) pem, err := os.ReadFile(path) if err != nil { logger.Errorf("Failed loading key [%s]: [%s].", alias, err.Error()) return nil, err } key, err := pemToAES(pem, ks.pwd) if err != nil { logger.Errorf("Failed parsing key [%s]: [%s]", alias, err) return nil, err } return key, nil } func (ks *fileBasedKeyStore) createKeyStore() error { // Create keystore directory root if it doesn't exist yet ksPath := ks.path logger.Debugf("Creating KeyStore at [%s]...", ksPath) err := os.MkdirAll(ksPath, 0o755) if err != nil { return err } logger.Debugf("KeyStore created at [%s].", ksPath) return nil } func (ks *fileBasedKeyStore) openKeyStore() error { if ks.isOpen { return nil } ks.isOpen = true logger.Debugf("KeyStore opened at [%s]...done", ks.path) return nil } func (ks *fileBasedKeyStore) getPathForAlias(alias, suffix string) string { return filepath.Join(ks.path, alias+"_"+suffix) } func dirExists(path string) (bool, error) { _, err := os.Stat(path) if err == nil { return true, nil } if os.IsNotExist(err) { return false, nil } return false, err } func dirEmpty(path string) (bool, error) { f, err := os.Open(path) if err != nil { return false, err } defer f.Close() _, err = f.Readdir(1) if err == io.EOF { return true, nil } return false, err }