[node.sh] continue to refactor decrypter

pull/3219/head
Jacky Wang 4 years ago
parent 61577b803e
commit c05fa0ac85
No known key found for this signature in database
GPG Key ID: 1085CE5F4FF5842C
  1. 8
      cmd/harmony/blsloader/decrypter.go
  2. 228
      cmd/harmony/blsloader/helper.go
  3. 286
      cmd/harmony/blsloader/kms.go
  4. 249
      cmd/harmony/blsloader/kmsProvider.go
  5. 128
      cmd/harmony/blsloader/loader.go
  6. 22
      cmd/harmony/blsloader/params.go
  7. 11
      cmd/harmony/blsloader/passphrase.go
  8. 81
      cmd/harmony/blsloader/utils.go

@ -1,8 +0,0 @@
package blsloader
import bls_core "github.com/harmony-one/bls/ffi/go/bls"
type decrypter interface {
extension() string
decrypt(keyFile string) (*bls_core.SecretKey, error)
}

@ -1,7 +1,6 @@
package blsloader package blsloader
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
@ -10,201 +9,84 @@ import (
"github.com/harmony-one/harmony/multibls" "github.com/harmony-one/harmony/multibls"
) )
// loadHelper defines the interface to help load bls keys // loadHelper defines the helper interface to load bls keys. Implemented by
// multiKeyLoader - load key files with a slice of target key files
// blsDirLoader - load key files from a directory
type loadHelper interface { type loadHelper interface {
loadKeys() (multibls.PrivateKeys, error) loadKeys() (multibls.PrivateKeys, error)
} }
// basicSingleBlsLoader loads a single bls key file with passphrase // multiKeyLoader load keys from multiple bls key files
type basicSingleBlsLoader struct { type multiKeyLoader struct {
blsKeyFile string keyFiles []string
decrypters map[string]keyDecrypter
passDecrypterConfig loadedSecrets []*bls_core.SecretKey
} }
// loadKeys load bls keys from a single bls file func newMultiKeyLoader(keyFiles []string, decrypters []keyDecrypter) (*multiKeyLoader, error) {
func (loader *basicSingleBlsLoader) loadKeys() (multibls.PrivateKeys, error) { dm := make(map[string]keyDecrypter)
providers, err := loader.getPassProviders() for _, decrypter := range decrypters {
if err != nil { dm[decrypter.extension()] = decrypter
return multibls.PrivateKeys{}, err
} }
secretKey, err := loadBasicKey(loader.blsKeyFile, providers) for _, keyFile := range keyFiles {
if err != nil { ext := filepath.Ext(keyFile)
return multibls.PrivateKeys{}, err if _, supported := dm[ext]; !supported {
} return nil, fmt.Errorf("unsupported key extension: %v", ext)
return multibls.GetPrivateKeys(secretKey), nil }
}
func (loader *basicSingleBlsLoader) getPassProviders() ([]passProvider, error) {
switch loader.passSrcType {
case PassSrcFile:
return []passProvider{loader.getFilePassProvider()}, nil
case PassSrcPrompt:
return []passProvider{loader.getPromptPassProvider()}, nil
case PassSrcAuto:
return []passProvider{
loader.getFilePassProvider(),
loader.getPromptPassProvider(),
}, nil
default:
return nil, errors.New("unknown passphrase source type")
}
}
func (loader *basicSingleBlsLoader) getFilePassProvider() passProvider {
if stringIsSet(loader.passFile) {
return newFilePassProvider(*loader.passFile)
}
passFile := keyFileToPassFileFull(loader.blsKeyFile)
return newFilePassProvider(passFile)
}
func (loader *basicSingleBlsLoader) getPromptPassProvider() passProvider {
provider := newPromptPassProvider()
if loader.persistPassphrase {
provider.setPersist(filepath.Dir(loader.blsKeyFile))
} }
return provider return &multiKeyLoader{
} keyFiles: keyFiles,
decrypters: dm,
// kmsSingleBlsLoader loads a single kms bls key loadedSecrets: make([]*bls_core.SecretKey, 0, len(keyFiles)),
type kmsSingleBlsLoader struct { }, nil
blsKeyFile string
kmsProviderConfig
} }
func (loader *kmsSingleBlsLoader) loadKeys() (multibls.PrivateKeys, error) { func (loader *multiKeyLoader) loadKeys() (multibls.PrivateKeys, error) {
provider, err := loader.getKmsClientProvider() for _, keyFile := range loader.keyFiles {
if err != nil { decrypter := loader.decrypters[filepath.Ext(keyFile)]
return multibls.PrivateKeys{}, err secret, err := decrypter.decryptFile(keyFile)
} if err != nil {
secretKey, err := loadKmsKeyFromFile(loader.blsKeyFile, provider) return multibls.PrivateKeys{}, err
if err != nil { }
return multibls.PrivateKeys{}, err loader.loadedSecrets = append(loader.loadedSecrets, secret)
} }
return multibls.GetPrivateKeys(secretKey), nil return multibls.GetPrivateKeys(loader.loadedSecrets...), nil
}
func (loader *kmsSingleBlsLoader) getKmsClientProvider() (kmsProvider, error) {
return newLazyKmsProvider(loader.kmsProviderConfig)
} }
// blsDirLoader is the helper for loading bls keys in a directory
type blsDirLoader struct { type blsDirLoader struct {
// input fields keyDir string
dirPath string decrypters map[string]keyDecrypter
passDecrypterConfig
kmsProviderConfig
// providers in process
pps []passProvider
kcp kmsProvider
// result field
secretKeys []*bls_core.SecretKey
}
func (loader *blsDirLoader) loadKeys() (multibls.PrivateKeys, error) {
var err error
if loader.pps, err = loader.getPassProviders(); err != nil {
return multibls.PrivateKeys{}, err
}
if loader.kcp, err = loader.getKmsClientProvider(); err != nil {
return multibls.PrivateKeys{}, err
}
return loader.loadKeyFiles()
}
func (loader *blsDirLoader) getPassProviders() ([]passProvider, error) { loadedSecrets []*bls_core.SecretKey
switch loader.passSrcType {
case PassSrcFile:
return []passProvider{loader.getFilePassProvider()}, nil
case PassSrcPrompt:
return []passProvider{loader.getPromptPassProvider()}, nil
case PassSrcAuto:
return []passProvider{
loader.getFilePassProvider(),
loader.getPromptPassProvider(),
}, nil
default:
return nil, errors.New("unknown pass source type")
}
} }
func (loader *blsDirLoader) getFilePassProvider() passProvider { func newBlsDirLoader(keyDir string, decrypters []keyDecrypter) (*blsDirLoader, error) {
if stringIsSet(loader.passFile) { dm := make(map[string]keyDecrypter)
return newFilePassProvider(*loader.passFile) for _, decrypter := range decrypters {
dm[decrypter.extension()] = decrypter
} }
return newDirPassProvider(loader.dirPath) if err := checkIsDir(keyDir); err != nil {
} return nil, fmt.Errorf("bls dir %v: %v", keyDir, err)
func (loader *blsDirLoader) getPromptPassProvider() passProvider {
provider := newPromptPassProvider()
if loader.persistPassphrase {
provider.setPersist(loader.dirPath)
} }
return provider return &blsDirLoader{
keyDir: keyDir,
decrypters: dm,
}, nil
} }
func (loader *blsDirLoader) getKmsClientProvider() (kmsProvider, error) { func (loader *blsDirLoader) loadKeys() (multibls.PrivateKeys, error) {
return newLazyKmsProvider(loader.kmsProviderConfig) filepath.Walk(loader.keyDir, func(path string, info os.FileInfo, err error) error {
} if err != nil {
func (loader *blsDirLoader) loadKeyFiles() (multibls.PrivateKeys, error) {
err := filepath.Walk(loader.dirPath, loader.processFileWalk)
if err != nil {
return multibls.PrivateKeys{}, err
}
return multibls.GetPrivateKeys(loader.secretKeys...), nil
}
func (loader *blsDirLoader) processFileWalk(path string, info os.FileInfo, err error) error {
key, err := loader.loadKeyFromFile(path)
if err != nil {
if !errIsErrors(err, loader.skippingErrors()) {
// unexpected error, return the error and break the file walk loop
return err return err
} }
// errors to be skipped. Skipping these files decrypter := loader.decrypters[filepath.Ext(path)]
skipStr := fmt.Sprintf("Skipping [%s]: %v\n", path, err) secret, err := decrypter.decryptFile(path)
console.println(skipStr) if err != nil {
return nil return err
}
loader.secretKeys = append(loader.secretKeys, key)
return nil
}
// errors to be neglected for directory bls loading
func (loader *blsDirLoader) skippingErrors() []error {
return []error{
errUnknownExtension,
errNilPassProvider,
errNilKMSClientProvider,
}
}
//func (loader *blsDirLoader) loadKeyFromFile(path string) (*bls_core.SecretKey, error) {
// var (
// key *bls_core.SecretKey
// err error
// )
// switch {
// case isBasicKeyFile(path):
// key, err = loadBasicKey(path, loader.pps)
// case isKMSKeyFile(path):
// key, err = loadKmsKeyFromFile(path, loader.kcp)
// default:
// err = errUnknownExtension
// }
// return key, err
//}
// errIsErrors return whether the err is one of the errs
func errIsErrors(err error, errs []error) bool {
for _, targetErr := range errs {
if errors.Is(err, targetErr) {
return true
} }
} loader.loadedSecrets = append(loader.loadedSecrets, secret)
return false return nil
})
return multibls.GetPrivateKeys(loader.loadedSecrets...), nil
} }

@ -0,0 +1,286 @@
package blsloader
import (
"encoding/json"
"fmt"
"io/ioutil"
"sync"
"time"
"github.com/harmony-one/harmony/internal/blsgen"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/kms"
bls_core "github.com/harmony-one/bls/ffi/go/bls"
"github.com/pkg/errors"
)
// AwsConfigSrcType is the type of src to load aws config. Four options available:
// AwsCfgSrcNil - Disable kms decryption
// AwsCfgSrcFile - Provide the aws config through a file (json).
// AwsCfgSrcPrompt - Provide the aws config though prompt.
// AwsCfgSrcShared - Use the shard aws config (env -> default .aws directory)
type AwsCfgSrcType uint8
const (
AwsCfgSrcNil AwsCfgSrcType = iota // nil place holder.
AwsCfgSrcFile // through a config file (json)
AwsCfgSrcPrompt // through console prompt.
AwsCfgSrcShared // through shared aws config
)
func (srcType AwsCfgSrcType) isValid() bool {
switch srcType {
case AwsCfgSrcFile, AwsCfgSrcPrompt, AwsCfgSrcShared:
return true
default:
return false
}
}
// kmsDecrypterConfig is the data structure of kmsClientProvider config
type kmsDecrypterConfig struct {
awsCfgSrcType AwsCfgSrcType
awsConfigFile *string
}
// kmsDecrypter provide the kms client with singleton lazy initialization with config get
// from awsConfigProvider for aws credential and regions loading.
type kmsDecrypter struct {
config kmsDecrypterConfig
provider awsConfigProvider
client *kms.KMS
err error
once sync.Once
}
// newKmsDecrypter creates a kmsDecrypter with the given config
func newKmsDecrypter(config kmsDecrypterConfig) (*kmsDecrypter, error) {
kd := &kmsDecrypter{config: config}
if err := kd.validateConfig(); err != nil {
return nil, err
}
kd.makeACProvider()
return kd, nil
}
// extension returns the kms key file extension
func (kd *kmsDecrypter) extension() string {
return kmsKeyExt
}
// decryptFile decrypt a kms key file to a secret key
func (kd *kmsDecrypter) decryptFile(keyFile string) (*bls_core.SecretKey, error) {
kms, err := kd.getKMSClient()
if err != nil {
return nil, err
}
return blsgen.LoadAwsCMKEncryptedBLSKey(keyFile, kms)
}
func (kd *kmsDecrypter) validateConfig() error {
config := kd.config
if !config.awsCfgSrcType.isValid() {
return errors.New("unknown AwsCfgSrcType")
}
if config.awsCfgSrcType == AwsCfgSrcFile {
if !stringIsSet(config.awsConfigFile) {
return errors.New("config field AwsConfig file must set for AwsCfgSrcFile")
}
if err := checkIsFile(*config.awsConfigFile); err != nil {
return fmt.Errorf("aws config file %v: %v", *config.awsConfigFile, err)
}
}
return nil
}
func (kd *kmsDecrypter) makeACProvider() {
config := kd.config
switch config.awsCfgSrcType {
case AwsCfgSrcFile:
kd.provider = newFileACProvider(*config.awsConfigFile)
case AwsCfgSrcPrompt:
kd.provider = newPromptACProvider(defKmsPromptTimeout)
case AwsCfgSrcShared:
kd.provider = newSharedAwsConfigProvider()
}
}
func (kd *kmsDecrypter) getKMSClient() (*kms.KMS, error) {
kd.once.Do(func() {
cfg, err := kd.provider.getAwsConfig()
if err != nil {
kd.err = err
return
}
kd.client, kd.err = kmsClientWithConfig(cfg)
})
if kd.err != nil {
return nil, kd.err
}
return kd.client, nil
}
// AwsConfig is the config data structure for credentials and region. Used for AWS KMS
// decryption.
type AwsConfig struct {
AccessKey string `json:"aws-access-key-id"`
SecretKey string `json:"aws-secret-access-key"`
Region string `json:"aws-region"`
Token string `json:"aws-token,omitempty"`
}
func (cfg AwsConfig) toAws() *aws.Config {
cred := credentials.NewStaticCredentials(cfg.AccessKey, cfg.SecretKey, cfg.Token)
return &aws.Config{
Region: aws.String(cfg.Region),
Credentials: cred,
}
}
// awsConfigProvider provides the aws config. Implemented by
// sharedACProvider - provide the nil to use shared AWS configuration
// fileACProvider - provide the aws config with a json file
// promptACProvider - provide the config field from prompt with time out
type awsConfigProvider interface {
getAwsConfig() (*AwsConfig, error)
String() string
}
// sharedACProvider returns nil for getAwsConfig to use shared aws configurations
type sharedACProvider struct{}
func newSharedAwsConfigProvider() *sharedACProvider {
return &sharedACProvider{}
}
func (provider *sharedACProvider) getAwsConfig() (*AwsConfig, error) {
return nil, nil
}
func (provider *sharedACProvider) String() string {
return "shared aws config"
}
// fileACProvider get aws config through a customized json file
type fileACProvider struct {
file string
}
func newFileACProvider(file string) *fileACProvider {
return &fileACProvider{file}
}
func (provider *fileACProvider) getAwsConfig() (*AwsConfig, error) {
b, err := ioutil.ReadFile(provider.file)
if err != nil {
return nil, err
}
var cfg AwsConfig
if err := json.Unmarshal(b, &cfg); err != nil {
return nil, err
}
return &cfg, nil
}
func (provider *fileACProvider) String() string {
return fmt.Sprintf("file %v", provider.file)
}
// promptACProvider provide a user interactive console for AWS config.
// Four fields are asked:
// 1. AccessKey 2. SecretKey 3. Region
// Each field is asked with a timeout mechanism.
type promptACProvider struct {
timeout time.Duration
}
func newPromptACProvider(timeout time.Duration) *promptACProvider {
return &promptACProvider{
timeout: timeout,
}
}
func (provider *promptACProvider) getAwsConfig() (*AwsConfig, error) {
console.println("Please provide AWS configurations for KMS encoded BLS keys:")
accessKey, err := provider.prompt(" AccessKey:")
if err != nil {
return nil, fmt.Errorf("cannot get aws access key: %v", err)
}
secretKey, err := provider.prompt(" SecretKey:")
if err != nil {
return nil, fmt.Errorf("cannot get aws secret key: %v", err)
}
region, err := provider.prompt("Region:")
if err != nil {
return nil, fmt.Errorf("cannot get aws region: %v", err)
}
return &AwsConfig{
AccessKey: accessKey,
SecretKey: secretKey,
Region: region,
Token: "",
}, nil
}
// prompt prompt the user to input a string for a certain field with timeout.
func (provider *promptACProvider) prompt(hint string) (string, error) {
var (
res string
err error
finished = make(chan struct{})
timedOut = time.After(provider.timeout)
)
go func() {
res, err = provider.threadedPrompt(hint)
close(finished)
}()
for {
select {
case <-finished:
return res, err
case <-timedOut:
return "", errors.New("timed out")
}
}
}
func (provider *promptACProvider) threadedPrompt(hint string) (string, error) {
console.print(hint)
return console.readPassword()
}
func (provider *promptACProvider) String() string {
return "prompt"
}
func kmsClientWithConfig(config *AwsConfig) (*kms.KMS, error) {
if config == nil {
return getSharedKMSClient()
}
return getKMSClientFromConfig(*config)
}
func getSharedKMSClient() (*kms.KMS, error) {
sess, err := session.NewSessionWithOptions(session.Options{
SharedConfigState: session.SharedConfigEnable,
})
if err != nil {
return nil, errors.Wrapf(err, "failed to create aws session")
}
return kms.New(sess), err
}
func getKMSClientFromConfig(config AwsConfig) (*kms.KMS, error) {
sess, err := session.NewSession(config.toAws())
if err != nil {
return nil, err
}
return kms.New(sess), nil
}

@ -1,249 +0,0 @@
package blsloader
import (
"encoding/json"
"fmt"
"io/ioutil"
"sync"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/kms"
"github.com/pkg/errors"
)
// AwsConfig is the config data structure for credentials and region. Used for AWS KMS
// decryption.
type AwsConfig struct {
AccessKey string `json:"aws-access-key-id"`
SecretKey string `json:"aws-secret-access-key"`
Region string `json:"aws-region"`
Token string `json:"aws-token,omitempty"`
}
func (cfg AwsConfig) toAws() *aws.Config {
cred := credentials.NewStaticCredentials(cfg.AccessKey, cfg.SecretKey, cfg.Token)
return &aws.Config{
Region: aws.String(cfg.Region),
Credentials: cred,
}
}
// kmsProviderConfig is the data structure of kmsClientProvider config
type kmsProviderConfig struct {
awsCfgSrcType AwsCfgSrcType
awsConfigFile *string
}
func (config kmsProviderConfig) validate() error {
if !config.awsCfgSrcType.isValid() {
return errors.New("unknown AwsCfgSrcType")
}
if config.awsCfgSrcType == AwsCfgSrcFile {
if !stringIsSet(config.awsConfigFile) {
return errors.New("config field AwsConfig file must set for AwsCfgSrcFile")
}
if !isFile(*config.awsConfigFile) {
return fmt.Errorf("aws config file not exist %v", *config.awsConfigFile)
}
}
return nil
}
// kmsProvider provide the aws kms client
type kmsProvider interface {
getKMSClient() (*kms.KMS, error)
}
// lazyKmsProvider provide the kms client with singleton lazy initialization with config get
// from awsConfigGetter for aws credential and regions loading.
type lazyKmsProvider struct {
acGetter awsConfigGetter
client *kms.KMS
err error
once sync.Once
}
// newLazyKmsProvider creates a kmsProvider with the given config
func newLazyKmsProvider(config kmsProviderConfig) (*lazyKmsProvider, error) {
var acg awsConfigGetter
switch config.awsCfgSrcType {
case AwsCfgSrcFile:
if stringIsSet(config.awsConfigFile) {
acg = newFileACGetter(*config.awsConfigFile)
} else {
acg = newSharedAwsConfigGetter()
}
case AwsCfgSrcPrompt:
acg = newPromptACGetter(defKmsPromptTimeout)
case AwsCfgSrcShared:
acg = newSharedAwsConfigGetter()
default:
return nil, errors.New("unknown aws config source type")
}
return &lazyKmsProvider{
acGetter: acg,
}, nil
}
func (provider *lazyKmsProvider) getKMSClient() (*kms.KMS, error) {
provider.once.Do(func() {
cfg, err := provider.acGetter.getAwsConfig()
if err != nil {
provider.err = err
return
}
provider.client, provider.err = kmsClientWithConfig(cfg)
})
if provider.err != nil {
return nil, provider.err
}
return provider.client, nil
}
// awsConfigGetter provides the aws config. Implemented by
// sharedACGetter - provide the nil to use shared AWS configuration
// fileACGetter - provide the aws config with a json file
// promptACGetter - provide the config field from prompt with time out
type awsConfigGetter interface {
getAwsConfig() (*AwsConfig, error)
String() string
}
// sharedACGetter returns nil for getAwsConfig to use shared aws configurations
type sharedACGetter struct{}
func newSharedAwsConfigGetter() *sharedACGetter {
return &sharedACGetter{}
}
func (getter *sharedACGetter) getAwsConfig() (*AwsConfig, error) {
return nil, nil
}
func (getter *sharedACGetter) String() string {
return "shared aws config"
}
// fileACGetter get aws config through a customized json file
type fileACGetter struct {
file string
}
func newFileACGetter(file string) *fileACGetter {
return &fileACGetter{file}
}
func (getter *fileACGetter) getAwsConfig() (*AwsConfig, error) {
b, err := ioutil.ReadFile(getter.file)
if err != nil {
return nil, err
}
var cfg AwsConfig
if err := json.Unmarshal(b, &cfg); err != nil {
return nil, err
}
return &cfg, nil
}
func (getter *fileACGetter) String() string {
return fmt.Sprintf("file %v", getter.file)
}
// promptACGetter provide a user interactive console for AWS config.
// Four fields are asked:
// 1. AccessKey 2. SecretKey 3. Region
// Each field is asked with a timeout mechanism.
type promptACGetter struct {
timeout time.Duration
}
func newPromptACGetter(timeout time.Duration) *promptACGetter {
return &promptACGetter{
timeout: timeout,
}
}
func (getter *promptACGetter) getAwsConfig() (*AwsConfig, error) {
console.println("Please provide AWS configurations for KMS encoded BLS keys:")
accessKey, err := getter.prompt(" AccessKey:")
if err != nil {
return nil, fmt.Errorf("cannot get aws access key: %v", err)
}
secretKey, err := getter.prompt(" SecretKey:")
if err != nil {
return nil, fmt.Errorf("cannot get aws secret key: %v", err)
}
region, err := getter.prompt("Region:")
if err != nil {
return nil, fmt.Errorf("cannot get aws region: %v", err)
}
return &AwsConfig{
AccessKey: accessKey,
SecretKey: secretKey,
Region: region,
Token: "",
}, nil
}
// prompt prompt the user to input a string for a certain field with timeout.
func (getter *promptACGetter) prompt(hint string) (string, error) {
var (
res string
err error
finished = make(chan struct{})
timedOut = time.After(getter.timeout)
)
go func() {
res, err = getter.threadedPrompt(hint)
close(finished)
}()
for {
select {
case <-finished:
return res, err
case <-timedOut:
return "", errors.New("timed out")
}
}
}
func (getter *promptACGetter) threadedPrompt(hint string) (string, error) {
console.print(hint)
return console.readPassword()
}
func (getter *promptACGetter) String() string {
return "prompt"
}
func kmsClientWithConfig(config *AwsConfig) (*kms.KMS, error) {
if config == nil {
return getSharedKMSClient()
}
return getKMSClientFromConfig(*config)
}
func getSharedKMSClient() (*kms.KMS, error) {
sess, err := session.NewSessionWithOptions(session.Options{
SharedConfigState: session.SharedConfigEnable,
})
if err != nil {
return nil, errors.Wrapf(err, "failed to create aws session")
}
return kms.New(sess), err
}
func getKMSClientFromConfig(config AwsConfig) (*kms.KMS, error) {
sess, err := session.NewSession(config.toAws())
if err != nil {
return nil, err
}
return kms.New(sess), nil
}

@ -3,31 +3,72 @@ package blsloader
import ( import (
"errors" "errors"
"fmt" "fmt"
"path/filepath"
bls_core "github.com/harmony-one/bls/ffi/go/bls"
"github.com/harmony-one/harmony/multibls" "github.com/harmony-one/harmony/multibls"
) )
func LoadKeys(cfg Config) (multibls.PrivateKeys, error) { func LoadKeys(cfg Config) (multibls.PrivateKeys, error) {
cfg.applyDefault() decrypters, err := getKeyDecrypters(cfg)
if err := cfg.validate(); err != nil { if err != nil {
return multibls.PrivateKeys{}, err return nil, err
} }
helper, err := getHelper(cfg) helper, err := getHelper(cfg, decrypters)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return helper.loadKeys() return helper.loadKeys()
} }
// keyDecrypter is the interface to decrypt the bls key file. Currently, two
// implementations are supported:
// passDecrypter - decrypt with passphrase
// kmsDecrypter - decrypt with aws kms service
type keyDecrypter interface {
extension() string
decryptFile(keyFile string) (*bls_core.SecretKey, error)
}
func getKeyDecrypters(cfg Config) ([]keyDecrypter, error) {
var decrypters []keyDecrypter
if cfg.PassSrcType != PassSrcNil {
pd, err := newPassDecrypter(cfg.getPassProviderConfig())
if err != nil {
return nil, err
}
decrypters = append(decrypters, pd)
}
if cfg.AwsCfgSrcType != AwsCfgSrcNil {
kd, err := newKmsDecrypter(cfg.getKmsProviderConfig())
if err != nil {
return nil, err
}
decrypters = append(decrypters, kd)
}
if len(decrypters) == 0 {
return nil, fmt.Errorf("must provide at least one bls key decryption")
}
return decrypters, nil
}
func getHelper(cfg Config, decrypters []keyDecrypter) (loadHelper, error) {
switch {
case len(cfg.multiBlsKeys) != 0:
return newMultiKeyLoader(cfg.multiBlsKeys, decrypters)
case stringIsSet(cfg.BlsDir):
return newBlsDirLoader(*cfg.BlsDir, decrypters)
default:
return nil, errors.New("either multiBlsKeys or BlsDir must be set")
}
}
// Loader is the structure to load bls keys. // Loader is the structure to load bls keys.
type Config struct { type Config struct {
// source for bls key loading. At least one of the BlsKeyFile and BlsDir // source for bls key loading. At least one of the multiBlsKeys and BlsDir
// need to be provided. // need to be provided.
// //
// BlsKeyFile defines a single key file to load from. Based on the file // multiBlsKeys defines a slice of key files to load from.
// extension, decryption with either passphrase or aws kms will be used. multiBlsKeys []string
BlsKeyFile *string
// BlsDir defines a file directory to load keys from. // BlsDir defines a file directory to load keys from.
BlsDir *string BlsDir *string
@ -47,7 +88,7 @@ type Config struct {
// under the same directory as the key file. // under the same directory as the key file.
PersistPassphrase bool PersistPassphrase bool
// Aws configuration related settings, including AWS credentials and region info. // KMS related settings, including AWS credentials and region info.
// Used for KMS encrypted passphrase files. // Used for KMS encrypted passphrase files.
// //
// AwsCfgSrcType defines the source to get aws config. Three types available: // AwsCfgSrcType defines the source to get aws config. Three types available:
@ -60,38 +101,6 @@ type Config struct {
AwsConfigFile *string AwsConfigFile *string
} }
func (cfg *Config) applyDefault() {
if cfg.PassSrcType == PassSrcNil {
cfg.PassSrcType = PassSrcAuto
}
if cfg.AwsCfgSrcType == AwsCfgSrcNil {
cfg.AwsCfgSrcType = AwsCfgSrcShared
}
}
func (cfg *Config) validate() error {
if stringIsSet(cfg.BlsKeyFile) {
if !isFile(*cfg.BlsKeyFile) {
return fmt.Errorf("key file not exist %v", *cfg.BlsKeyFile)
}
switch ext := filepath.Ext(*cfg.BlsKeyFile); ext {
case basicKeyExt, kmsKeyExt:
default:
return fmt.Errorf("unknown key file extension %v", ext)
}
} else if stringIsSet(cfg.BlsDir) {
if !isDir(*cfg.BlsDir) {
return fmt.Errorf("dir not exist %v", *cfg.BlsDir)
}
} else {
return errors.New("either BlsKeyFile or BlsDir must be set")
}
if err := cfg.getPassProviderConfig().validate(); err != nil {
return err
}
return cfg.getKmsProviderConfig().validate()
}
func (cfg *Config) getPassProviderConfig() passDecrypterConfig { func (cfg *Config) getPassProviderConfig() passDecrypterConfig {
return passDecrypterConfig{ return passDecrypterConfig{
passSrcType: cfg.PassSrcType, passSrcType: cfg.PassSrcType,
@ -100,40 +109,9 @@ func (cfg *Config) getPassProviderConfig() passDecrypterConfig {
} }
} }
func (cfg *Config) getKmsProviderConfig() kmsProviderConfig { func (cfg *Config) getKmsProviderConfig() kmsDecrypterConfig {
return kmsProviderConfig{ return kmsDecrypterConfig{
awsCfgSrcType: cfg.AwsCfgSrcType, awsCfgSrcType: cfg.AwsCfgSrcType,
awsConfigFile: cfg.AwsConfigFile, awsConfigFile: cfg.AwsConfigFile,
} }
} }
func getHelper(cfg Config) (loadHelper, error) {
fmt.Println("getting helper")
switch {
case stringIsSet(cfg.BlsKeyFile):
switch filepath.Ext(*cfg.BlsKeyFile) {
case basicKeyExt:
fmt.Println("basic")
return &basicSingleBlsLoader{
blsKeyFile: *cfg.BlsKeyFile,
passDecrypterConfig: cfg.getPassProviderConfig(),
}, nil
case kmsKeyExt:
fmt.Println("kms")
return &kmsSingleBlsLoader{
blsKeyFile: *cfg.BlsKeyFile,
kmsProviderConfig: cfg.getKmsProviderConfig(),
}, nil
default:
return nil, errors.New("unknown extension")
}
case stringIsSet(cfg.BlsDir):
return &blsDirLoader{
dirPath: *cfg.BlsDir,
passDecrypterConfig: cfg.getPassProviderConfig(),
kmsProviderConfig: cfg.getKmsProviderConfig(),
}, nil
default:
return nil, errors.New("either BlsKeyFile or BlsDir must be set")
}
}

@ -19,25 +19,3 @@ const (
defWritePassDirMode = 0600 defWritePassDirMode = 0600
defWritePassFileMode = 0600 defWritePassFileMode = 0600
) )
// AwsConfigSrcType is the type of src to load aws config. Two options available
// AwsCfgSrcFile - Provide the aws config through a file (json).
// AwsCfgSrcPrompt - Provide the aws config though prompt.
// AwsCfgSrcShared - Use the shard aws config (env -> default .aws directory)
type AwsCfgSrcType uint8
const (
AwsCfgSrcNil AwsCfgSrcType = iota // nil place holder.
AwsCfgSrcFile // through a config file (json)
AwsCfgSrcPrompt // through console prompt.
AwsCfgSrcShared // through shared aws config
)
func (srcType AwsCfgSrcType) isValid() bool {
switch srcType {
case AwsCfgSrcFile, AwsCfgSrcPrompt, AwsCfgSrcShared:
return true
default:
return false
}
}

@ -12,7 +12,8 @@ import (
) )
// PassSrcType is the type of passphrase provider source. // PassSrcType is the type of passphrase provider source.
// Three options available: // Four options available:
// PassSrcNil - Do not use passphrase decryption
// PassSrcFile - Read the passphrase from files // PassSrcFile - Read the passphrase from files
// PassSrcPrompt - Read the passphrase from prompt // PassSrcPrompt - Read the passphrase from prompt
// PassSrcAuto - First try to unlock with passphrase from file, then read passphrase from prompt // PassSrcAuto - First try to unlock with passphrase from file, then read passphrase from prompt
@ -51,7 +52,7 @@ type passDecrypter struct {
func newPassDecrypter(cfg passDecrypterConfig) (*passDecrypter, error) { func newPassDecrypter(cfg passDecrypterConfig) (*passDecrypter, error) {
pd := &passDecrypter{config: cfg} pd := &passDecrypter{config: cfg}
if err := pd.validate(); err != nil { if err := pd.validateConfig(); err != nil {
return nil, err return nil, err
} }
pd.makePassProviders() pd.makePassProviders()
@ -62,13 +63,13 @@ func (pd *passDecrypter) extension() string {
return basicKeyExt return basicKeyExt
} }
func (pd *passDecrypter) validate() error { func (pd *passDecrypter) validateConfig() error {
config := pd.config config := pd.config
if !config.passSrcType.isValid() { if !config.passSrcType.isValid() {
return errors.New("unknown PassSrcType") return errors.New("unknown PassSrcType")
} }
if stringIsSet(config.passFile) { if stringIsSet(config.passFile) {
if err := isPassFile(*config.passFile); err != nil { if err := checkIsPassFile(*config.passFile); err != nil {
return fmt.Errorf("%v not a passphrase file: %v", *config.passFile, err) return fmt.Errorf("%v not a passphrase file: %v", *config.passFile, err)
} }
} }
@ -102,7 +103,7 @@ func (pd *passDecrypter) getFilePassProvider() passProvider {
} }
} }
func (pd *passDecrypter) decrypt(keyFile string) (*bls_core.SecretKey, error) { func (pd *passDecrypter) decryptFile(keyFile string) (*bls_core.SecretKey, error) {
for _, pp := range pd.pps { for _, pp := range pd.pps {
secretKey, err := loadBasicKeyWithProvider(keyFile, pp) secretKey, err := loadBasicKeyWithProvider(keyFile, pp)
if err != nil { if err != nil {

@ -1,71 +1,28 @@
package blsloader package blsloader
import ( import (
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/pkg/errors"
bls_core "github.com/harmony-one/bls/ffi/go/bls" bls_core "github.com/harmony-one/bls/ffi/go/bls"
"github.com/harmony-one/harmony/internal/blsgen" "github.com/harmony-one/harmony/internal/blsgen"
"github.com/pkg/errors"
) )
var (
errUnknownExtension = errors.New("unknown extension")
errUnableGetPubkey = errors.New("unable to get public key")
errNilPassProvider = errors.New("no source for password")
errNilKMSClientProvider = errors.New("no source for KMS provider")
)
// loadBasicKey loads a single bls key through a key file and passphrase combination.
// The passphrase is provided by a slice of passProviders.
func loadBasicKey(blsKeyFile string, pps []passProvider) (*bls_core.SecretKey, error) {
if len(pps) == 0 {
return nil, errNilPassProvider
}
for _, pp := range pps {
secretKey, err := loadBasicKeyWithProvider(blsKeyFile, pp)
if err != nil {
console.println(err)
continue
}
return secretKey, nil
}
return nil, fmt.Errorf("failed to load bls key %v", blsKeyFile)
}
func loadBasicKeyWithProvider(blsKeyFile string, pp passProvider) (*bls_core.SecretKey, error) { func loadBasicKeyWithProvider(blsKeyFile string, pp passProvider) (*bls_core.SecretKey, error) {
pass, err := pp.getPassphrase(blsKeyFile) pass, err := pp.getPassphrase(blsKeyFile)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "unable to get passphrase from %s", pp.toStr()) return nil, err
} }
fmt.Printf("password: %s\n", pass)
secretKey, err := blsgen.LoadBLSKeyWithPassPhrase(blsKeyFile, pass) secretKey, err := blsgen.LoadBLSKeyWithPassPhrase(blsKeyFile, pass)
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "unable to decrypt bls key with %s\n", pp.toStr()) return nil, err
} }
return secretKey, nil return secretKey, nil
} }
// loadKmsKeyFromFile loads a single KMS BLS key from file func checkIsFile(path string) error {
func loadKmsKeyFromFile(blsKeyFile string, kcp kmsProvider) (*bls_core.SecretKey, error) {
if kcp == nil {
return nil, errNilKMSClientProvider
}
client, err := kcp.getKMSClient()
if err != nil {
return nil, errors.Wrap(err, "failed to get KMS client")
}
secretKey, err := blsgen.LoadAwsCMKEncryptedBLSKey(blsKeyFile, client)
if err != nil {
return nil, errors.Wrap(err, "failed to load KMS BLS key")
}
return secretKey, nil
}
func isFile(path string) error {
info, err := os.Stat(path) info, err := os.Stat(path)
if err != nil { if err != nil {
return err return err
@ -76,7 +33,7 @@ func isFile(path string) error {
return nil return nil
} }
func isDir(path string) error { func checkIsDir(path string) error {
info, err := os.Stat(path) info, err := os.Stat(path)
if err != nil { if err != nil {
return err return err
@ -87,19 +44,8 @@ func isDir(path string) error {
return nil return nil
} }
func isBasicKeyFile(path string) error { func checkIsPassFile(path string) error {
err := isFile(path) err := checkIsFile(path)
if err != nil {
return err
}
if filepath.Ext(path) != basicKeyExt {
return errors.New("should have extension .key")
}
return nil
}
func isPassFile(path string) error {
err := isFile(path)
if err != nil { if err != nil {
return err return err
} }
@ -109,17 +55,6 @@ func isPassFile(path string) error {
return nil return nil
} }
func isKMSKeyFile(path string) error {
err := isFile(path)
if err != nil {
return err
}
if filepath.Ext(path) != kmsKeyExt {
return errors.New("should have extension .bls")
}
return nil
}
func keyFileToPassFileFull(keyFile string) string { func keyFileToPassFileFull(keyFile string) string {
return strings.Trim(keyFile, basicKeyExt) + passExt return strings.Trim(keyFile, basicKeyExt) + passExt
} }
@ -128,9 +63,7 @@ func promptGetPassword(prompt string) (string, error) {
if !strings.HasSuffix(prompt, ":") { if !strings.HasSuffix(prompt, ":") {
prompt += ":" prompt += ":"
} }
fmt.Println("before print prompt", prompt)
console.print(prompt) console.print(prompt)
fmt.Println("after print prompt", prompt)
return console.readPassword() return console.readPassword()
} }

Loading…
Cancel
Save