The core protocol of WoopChain
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
woop/internal/blsgen/passphrase.go

230 lines
6.1 KiB

package blsgen
import (
"errors"
"fmt"
"io"
"os"
"strings"
"sync"
bls_core "github.com/woop-chain/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 os.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 := io.ReadAll(f)
if err != nil {
return "", err
}
return strings.TrimSpace(string(b)), nil
}