|
|
|
package blsgen
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
bls_core "github.com/harmony-one/bls/ffi/go/bls"
|
|
|
|
)
|
|
|
|
|
|
|
|
// PassSrcType is the type of passphrase provider source.
|
|
|
|
// Four options available:
|
|
|
|
// PassSrcNil - Do not use passphrase decryption
|
|
|
|
// PassSrcFile - Read the passphrase from files
|
|
|
|
// PassSrcPrompt - Read the passphrase from prompt
|
|
|
|
// PassSrcAuto - First try to unlock with passphrase from file, then read passphrase from prompt
|
|
|
|
type PassSrcType uint8
|
|
|
|
|
|
|
|
const (
|
|
|
|
// PassSrcNil is place holder for nil src
|
|
|
|
PassSrcNil PassSrcType = iota
|
|
|
|
// PassSrcFile provide the passphrase through pass files
|
|
|
|
PassSrcFile
|
|
|
|
// PassSrcPrompt provide the passphrase through prompt
|
|
|
|
PassSrcPrompt
|
|
|
|
// PassSrcAuto first try to unlock with pass from file, then look for prompt
|
|
|
|
PassSrcAuto
|
|
|
|
)
|
|
|
|
|
|
|
|
func (srcType PassSrcType) isValid() bool {
|
|
|
|
switch srcType {
|
|
|
|
case PassSrcAuto, PassSrcFile, PassSrcPrompt:
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// passDecrypterConfig is the data structure of passProviders config
|
|
|
|
type passDecrypterConfig struct {
|
|
|
|
passSrcType PassSrcType
|
|
|
|
passFile *string
|
|
|
|
persistPassphrase bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// passDecrypter decrypt the .key bls files with passphrase from a series
|
|
|
|
// of passProvider as passphrase source
|
|
|
|
type passDecrypter struct {
|
|
|
|
config passDecrypterConfig
|
|
|
|
|
|
|
|
pps []passProvider
|
|
|
|
}
|
|
|
|
|
|
|
|
func newPassDecrypter(cfg passDecrypterConfig) (*passDecrypter, error) {
|
|
|
|
pd := &passDecrypter{config: cfg}
|
|
|
|
if err := pd.validateConfig(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
pd.makePassProviders()
|
|
|
|
return pd, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pd *passDecrypter) extension() string {
|
|
|
|
return basicKeyExt
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pd *passDecrypter) decryptFile(keyFile string) (*bls_core.SecretKey, error) {
|
|
|
|
for _, pp := range pd.pps {
|
|
|
|
secretKey, err := loadBasicKeyWithProvider(keyFile, pp)
|
|
|
|
if err != nil {
|
|
|
|
console.println(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
return secretKey, nil
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("failed to load bls key %v", keyFile)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pd *passDecrypter) validateConfig() error {
|
|
|
|
config := pd.config
|
|
|
|
if !config.passSrcType.isValid() {
|
|
|
|
return errors.New("unknown PassSrcType")
|
|
|
|
}
|
|
|
|
if stringIsSet(config.passFile) {
|
|
|
|
if err := checkIsFile(*config.passFile); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pd *passDecrypter) makePassProviders() {
|
|
|
|
switch pd.config.passSrcType {
|
|
|
|
case PassSrcFile:
|
|
|
|
pd.pps = []passProvider{pd.getFilePassProvider()}
|
|
|
|
case PassSrcPrompt:
|
|
|
|
pd.pps = []passProvider{pd.getPromptPassProvider()}
|
|
|
|
case PassSrcAuto:
|
|
|
|
pd.pps = []passProvider{
|
|
|
|
pd.getFilePassProvider(),
|
|
|
|
pd.getPromptPassProvider(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pd *passDecrypter) getPromptPassProvider() passProvider {
|
|
|
|
return newPromptPassProvider(pd.config.persistPassphrase)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (pd *passDecrypter) getFilePassProvider() passProvider {
|
|
|
|
switch {
|
|
|
|
case stringIsSet(pd.config.passFile):
|
|
|
|
return newStaticPassProvider(*pd.config.passFile)
|
|
|
|
default:
|
|
|
|
return newDynamicPassProvider()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// passProvider is the interface to provide the passphrase of a bls keys.
|
|
|
|
// Implemented by
|
|
|
|
// promptPassProvider - provide passphrase through user-interactive prompt
|
|
|
|
// staticPassProvider - provide passphrase from a static .pass file
|
|
|
|
// dynamicPassProvider - provide the passphrase based on the given key file keyFile
|
|
|
|
// dirPassProvider - provide passphrase from .pass files in a directory
|
|
|
|
type passProvider interface {
|
|
|
|
getPassphrase(keyFile string) (string, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// promptPassProvider provides the bls passphrase through console prompt.
|
|
|
|
type promptPassProvider struct {
|
|
|
|
// if enablePersist is true, after user enter the passphrase, the
|
|
|
|
// passphrase is also persisted into .pass file under the same directory
|
|
|
|
// of the key file
|
|
|
|
enablePersist bool
|
|
|
|
}
|
|
|
|
|
|
|
|
const pwdPromptStr = "Enter passphrase for the BLS key file %s:"
|
|
|
|
|
|
|
|
func newPromptPassProvider(enablePersist bool) *promptPassProvider {
|
|
|
|
return &promptPassProvider{enablePersist: enablePersist}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (provider *promptPassProvider) getPassphrase(keyFile string) (string, error) {
|
|
|
|
prompt := fmt.Sprintf(pwdPromptStr, keyFile)
|
|
|
|
pass, err := promptGetPassword(prompt)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("unable to read from prompt: %v", err)
|
|
|
|
}
|
|
|
|
pass = strings.TrimSpace(pass)
|
|
|
|
// If user set to persist the pass file, persist to .pass file
|
|
|
|
if provider.enablePersist {
|
|
|
|
if err := provider.persistPassphrase(keyFile, pass); err != nil {
|
|
|
|
return "", fmt.Errorf("unable to save passphrase: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return pass, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (provider *promptPassProvider) persistPassphrase(keyFile string, passPhrase string) error {
|
|
|
|
passFile := keyFileToPassFileFull(keyFile)
|
|
|
|
if _, err := os.Stat(passFile); err == nil {
|
|
|
|
// File exist. Prompt user to overwrite pass file
|
|
|
|
overwrite, err := promptYesNo(fmt.Sprintf("pass file [%v] already exist. Overwrite? ", passFile))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !overwrite {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
} else if !os.IsNotExist(err) {
|
|
|
|
// Unknown error. Directly return
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return ioutil.WriteFile(passFile, []byte(passPhrase), defWritePassFileMode)
|
|
|
|
}
|
|
|
|
|
|
|
|
// staticPassProvider provide the bls passphrase from a static .pass file
|
|
|
|
type staticPassProvider struct {
|
|
|
|
fileName string
|
|
|
|
|
|
|
|
// cached field
|
|
|
|
pass string
|
|
|
|
err error
|
|
|
|
once sync.Once
|
|
|
|
}
|
|
|
|
|
|
|
|
func newStaticPassProvider(fileName string) *staticPassProvider {
|
|
|
|
return &staticPassProvider{fileName: fileName}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (provider *staticPassProvider) getPassphrase(keyFile string) (string, error) {
|
|
|
|
provider.once.Do(func() {
|
|
|
|
provider.pass, provider.err = readPassFromFile(provider.fileName)
|
|
|
|
})
|
|
|
|
return provider.pass, provider.err
|
|
|
|
}
|
|
|
|
|
|
|
|
// dynamicPassProvider provide the passphrase based on .pass file with the given
|
|
|
|
// key file keyFile. For example, looking for key file xxx.key will provide the
|
|
|
|
// passphrase from xxx.pass
|
|
|
|
type dynamicPassProvider struct{}
|
|
|
|
|
|
|
|
func newDynamicPassProvider() passProvider {
|
|
|
|
return &dynamicPassProvider{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (provider *dynamicPassProvider) getPassphrase(keyFile string) (string, error) {
|
|
|
|
passFile := keyFileToPassFileFull(keyFile)
|
|
|
|
return readPassFromFile(passFile)
|
|
|
|
}
|
|
|
|
|
|
|
|
func readPassFromFile(file string) (string, error) {
|
|
|
|
f, err := os.Open(file)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
b, err := ioutil.ReadAll(f)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return strings.TrimSpace(string(b)), nil
|
|
|
|
}
|