cmd/clef: encrypt the master seed on disk (#17704)

* cmd/clef: encrypt master seed of clef

Signed-off-by: YaoZengzeng <yaozengzeng@zju.edu.cn>

* keystore: refactor for external use of encryption

* clef: utilize keystore encryption, check flags correctly

* clef: validate master password

* clef: add json wrapping around encrypted master seed
This commit is contained in:
Martin Holst Swende 2018-10-09 11:05:41 +02:00 committed by GitHub
parent ff5538ad4c
commit d5c7a6056a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 218 additions and 93 deletions

View File

@ -66,19 +66,19 @@ type plainKeyJSON struct {
type encryptedKeyJSONV3 struct { type encryptedKeyJSONV3 struct {
Address string `json:"address"` Address string `json:"address"`
Crypto cryptoJSON `json:"crypto"` Crypto CryptoJSON `json:"crypto"`
Id string `json:"id"` Id string `json:"id"`
Version int `json:"version"` Version int `json:"version"`
} }
type encryptedKeyJSONV1 struct { type encryptedKeyJSONV1 struct {
Address string `json:"address"` Address string `json:"address"`
Crypto cryptoJSON `json:"crypto"` Crypto CryptoJSON `json:"crypto"`
Id string `json:"id"` Id string `json:"id"`
Version string `json:"version"` Version string `json:"version"`
} }
type cryptoJSON struct { type CryptoJSON struct {
Cipher string `json:"cipher"` Cipher string `json:"cipher"`
CipherText string `json:"ciphertext"` CipherText string `json:"ciphertext"`
CipherParams cipherparamsJSON `json:"cipherparams"` CipherParams cipherparamsJSON `json:"cipherparams"`

View File

@ -135,29 +135,26 @@ func (ks keyStorePassphrase) JoinPath(filename string) string {
return filepath.Join(ks.keysDirPath, filename) return filepath.Join(ks.keysDirPath, filename)
} }
// EncryptKey encrypts a key using the specified scrypt parameters into a json // Encryptdata encrypts the data given as 'data' with the password 'auth'.
// blob that can be decrypted later on. func EncryptDataV3(data, auth []byte, scryptN, scryptP int) (CryptoJSON, error) {
func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
authArray := []byte(auth)
salt := make([]byte, 32) salt := make([]byte, 32)
if _, err := io.ReadFull(rand.Reader, salt); err != nil { if _, err := io.ReadFull(rand.Reader, salt); err != nil {
panic("reading from crypto/rand failed: " + err.Error()) panic("reading from crypto/rand failed: " + err.Error())
} }
derivedKey, err := scrypt.Key(authArray, salt, scryptN, scryptR, scryptP, scryptDKLen) derivedKey, err := scrypt.Key(auth, salt, scryptN, scryptR, scryptP, scryptDKLen)
if err != nil { if err != nil {
return nil, err return CryptoJSON{}, err
} }
encryptKey := derivedKey[:16] encryptKey := derivedKey[:16]
keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32)
iv := make([]byte, aes.BlockSize) // 16 iv := make([]byte, aes.BlockSize) // 16
if _, err := io.ReadFull(rand.Reader, iv); err != nil { if _, err := io.ReadFull(rand.Reader, iv); err != nil {
panic("reading from crypto/rand failed: " + err.Error()) panic("reading from crypto/rand failed: " + err.Error())
} }
cipherText, err := aesCTRXOR(encryptKey, keyBytes, iv) cipherText, err := aesCTRXOR(encryptKey, data, iv)
if err != nil { if err != nil {
return nil, err return CryptoJSON{}, err
} }
mac := crypto.Keccak256(derivedKey[16:32], cipherText) mac := crypto.Keccak256(derivedKey[16:32], cipherText)
@ -167,12 +164,11 @@ func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
scryptParamsJSON["p"] = scryptP scryptParamsJSON["p"] = scryptP
scryptParamsJSON["dklen"] = scryptDKLen scryptParamsJSON["dklen"] = scryptDKLen
scryptParamsJSON["salt"] = hex.EncodeToString(salt) scryptParamsJSON["salt"] = hex.EncodeToString(salt)
cipherParamsJSON := cipherparamsJSON{ cipherParamsJSON := cipherparamsJSON{
IV: hex.EncodeToString(iv), IV: hex.EncodeToString(iv),
} }
cryptoStruct := cryptoJSON{ cryptoStruct := CryptoJSON{
Cipher: "aes-128-ctr", Cipher: "aes-128-ctr",
CipherText: hex.EncodeToString(cipherText), CipherText: hex.EncodeToString(cipherText),
CipherParams: cipherParamsJSON, CipherParams: cipherParamsJSON,
@ -180,6 +176,17 @@ func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
KDFParams: scryptParamsJSON, KDFParams: scryptParamsJSON,
MAC: hex.EncodeToString(mac), MAC: hex.EncodeToString(mac),
} }
return cryptoStruct, nil
}
// EncryptKey encrypts a key using the specified scrypt parameters into a json
// blob that can be decrypted later on.
func EncryptKey(key *Key, auth string, scryptN, scryptP int) ([]byte, error) {
keyBytes := math.PaddedBigBytes(key.PrivateKey.D, 32)
cryptoStruct, err := EncryptDataV3(keyBytes, []byte(auth), scryptN, scryptP)
if err != nil {
return nil, err
}
encryptedKeyJSONV3 := encryptedKeyJSONV3{ encryptedKeyJSONV3 := encryptedKeyJSONV3{
hex.EncodeToString(key.Address[:]), hex.EncodeToString(key.Address[:]),
cryptoStruct, cryptoStruct,
@ -226,43 +233,48 @@ func DecryptKey(keyjson []byte, auth string) (*Key, error) {
PrivateKey: key, PrivateKey: key,
}, nil }, nil
} }
func DecryptDataV3(cryptoJson CryptoJSON, auth string) ([]byte, error) {
if cryptoJson.Cipher != "aes-128-ctr" {
return nil, fmt.Errorf("Cipher not supported: %v", cryptoJson.Cipher)
}
mac, err := hex.DecodeString(cryptoJson.MAC)
if err != nil {
return nil, err
}
iv, err := hex.DecodeString(cryptoJson.CipherParams.IV)
if err != nil {
return nil, err
}
cipherText, err := hex.DecodeString(cryptoJson.CipherText)
if err != nil {
return nil, err
}
derivedKey, err := getKDFKey(cryptoJson, auth)
if err != nil {
return nil, err
}
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
if !bytes.Equal(calculatedMAC, mac) {
return nil, ErrDecrypt
}
plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv)
if err != nil {
return nil, err
}
return plainText, err
}
func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byte, keyId []byte, err error) { func decryptKeyV3(keyProtected *encryptedKeyJSONV3, auth string) (keyBytes []byte, keyId []byte, err error) {
if keyProtected.Version != version { if keyProtected.Version != version {
return nil, nil, fmt.Errorf("Version not supported: %v", keyProtected.Version) return nil, nil, fmt.Errorf("Version not supported: %v", keyProtected.Version)
} }
if keyProtected.Crypto.Cipher != "aes-128-ctr" {
return nil, nil, fmt.Errorf("Cipher not supported: %v", keyProtected.Crypto.Cipher)
}
keyId = uuid.Parse(keyProtected.Id) keyId = uuid.Parse(keyProtected.Id)
mac, err := hex.DecodeString(keyProtected.Crypto.MAC) plainText, err := DecryptDataV3(keyProtected.Crypto, auth)
if err != nil {
return nil, nil, err
}
iv, err := hex.DecodeString(keyProtected.Crypto.CipherParams.IV)
if err != nil {
return nil, nil, err
}
cipherText, err := hex.DecodeString(keyProtected.Crypto.CipherText)
if err != nil {
return nil, nil, err
}
derivedKey, err := getKDFKey(keyProtected.Crypto, auth)
if err != nil {
return nil, nil, err
}
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
if !bytes.Equal(calculatedMAC, mac) {
return nil, nil, ErrDecrypt
}
plainText, err := aesCTRXOR(derivedKey[:16], cipherText, iv)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -303,7 +315,7 @@ func decryptKeyV1(keyProtected *encryptedKeyJSONV1, auth string) (keyBytes []byt
return plainText, keyId, err return plainText, keyId, err
} }
func getKDFKey(cryptoJSON cryptoJSON, auth string) ([]byte, error) { func getKDFKey(cryptoJSON CryptoJSON, auth string) ([]byte, error) {
authArray := []byte(auth) authArray := []byte(auth)
salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string)) salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string))
if err != nil { if err != nil {

View File

@ -1,5 +1,9 @@
### Changelog for internal API (ui-api) ### Changelog for internal API (ui-api)
### 3.0.0
* Make use of `OnInputRequired(info UserInputRequest)` for obtaining master password during startup
### 2.1.0 ### 2.1.0
* Add `OnInputRequired(info UserInputRequest)` to internal API. This method is used when Clef needs user input, e.g. passwords. * Add `OnInputRequired(info UserInputRequest)` to internal API. This method is used when Clef needs user input, e.g. passwords.
@ -14,7 +18,6 @@ The following structures are used:
UserInputResponse struct { UserInputResponse struct {
Text string `json:"text"` Text string `json:"text"`
} }
```
### 2.0.0 ### 2.0.0

View File

@ -35,8 +35,10 @@ import (
"runtime" "runtime"
"strings" "strings"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/cmd/utils" "github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/console"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/node"
@ -48,10 +50,10 @@ import (
) )
// ExternalAPIVersion -- see extapi_changelog.md // ExternalAPIVersion -- see extapi_changelog.md
const ExternalAPIVersion = "3.0.0" const ExternalAPIVersion = "4.0.0"
// InternalAPIVersion -- see intapi_changelog.md // InternalAPIVersion -- see intapi_changelog.md
const InternalAPIVersion = "2.0.0" const InternalAPIVersion = "3.0.0"
const legalWarning = ` const legalWarning = `
WARNING! WARNING!
@ -91,7 +93,7 @@ var (
} }
signerSecretFlag = cli.StringFlag{ signerSecretFlag = cli.StringFlag{
Name: "signersecret", Name: "signersecret",
Usage: "A file containing the password used to encrypt Clef credentials, e.g. keystore credentials and ruleset hash", Usage: "A file containing the (encrypted) master seed to encrypt Clef data, e.g. keystore credentials and ruleset hash",
} }
dBFlag = cli.StringFlag{ dBFlag = cli.StringFlag{
Name: "4bytedb", Name: "4bytedb",
@ -212,25 +214,45 @@ func initializeSecrets(c *cli.Context) error {
if err := initialize(c); err != nil { if err := initialize(c); err != nil {
return err return err
} }
configDir := c.String(configdirFlag.Name) configDir := c.GlobalString(configdirFlag.Name)
masterSeed := make([]byte, 256) masterSeed := make([]byte, 256)
n, err := io.ReadFull(rand.Reader, masterSeed) num, err := io.ReadFull(rand.Reader, masterSeed)
if err != nil { if err != nil {
return err return err
} }
if n != len(masterSeed) { if num != len(masterSeed) {
return fmt.Errorf("failed to read enough random") return fmt.Errorf("failed to read enough random")
} }
n, p := keystore.StandardScryptN, keystore.StandardScryptP
if c.GlobalBool(utils.LightKDFFlag.Name) {
n, p = keystore.LightScryptN, keystore.LightScryptP
}
text := "The master seed of clef is locked with a password. Please give a password. Do not forget this password."
var password string
for {
password = getPassPhrase(text, true)
if err := core.ValidatePasswordFormat(password); err != nil {
fmt.Printf("invalid password: %v\n", err)
} else {
break
}
}
cipherSeed, err := encryptSeed(masterSeed, []byte(password), n, p)
if err != nil {
return fmt.Errorf("failed to encrypt master seed: %v", err)
}
err = os.Mkdir(configDir, 0700) err = os.Mkdir(configDir, 0700)
if err != nil && !os.IsExist(err) { if err != nil && !os.IsExist(err) {
return err return err
} }
location := filepath.Join(configDir, "secrets.dat") location := filepath.Join(configDir, "masterseed.json")
if _, err := os.Stat(location); err == nil { if _, err := os.Stat(location); err == nil {
return fmt.Errorf("file %v already exists, will not overwrite", location) return fmt.Errorf("file %v already exists, will not overwrite", location)
} }
err = ioutil.WriteFile(location, masterSeed, 0400) err = ioutil.WriteFile(location, cipherSeed, 0400)
if err != nil { if err != nil {
return err return err
} }
@ -255,11 +277,11 @@ func attestFile(ctx *cli.Context) error {
return err return err
} }
stretchedKey, err := readMasterKey(ctx) stretchedKey, err := readMasterKey(ctx, nil)
if err != nil { if err != nil {
utils.Fatalf(err.Error()) utils.Fatalf(err.Error())
} }
configDir := ctx.String(configdirFlag.Name) configDir := ctx.GlobalString(configdirFlag.Name)
vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10])) vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
confKey := crypto.Keccak256([]byte("config"), stretchedKey) confKey := crypto.Keccak256([]byte("config"), stretchedKey)
@ -279,11 +301,11 @@ func addCredential(ctx *cli.Context) error {
return err return err
} }
stretchedKey, err := readMasterKey(ctx) stretchedKey, err := readMasterKey(ctx, nil)
if err != nil { if err != nil {
utils.Fatalf(err.Error()) utils.Fatalf(err.Error())
} }
configDir := ctx.String(configdirFlag.Name) configDir := ctx.GlobalString(configdirFlag.Name)
vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10])) vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), stretchedKey)[:10]))
pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey) pwkey := crypto.Keccak256([]byte("credentials"), stretchedKey)
@ -302,7 +324,7 @@ func addCredential(ctx *cli.Context) error {
func initialize(c *cli.Context) error { func initialize(c *cli.Context) error {
// Set up the logger to print everything // Set up the logger to print everything
logOutput := os.Stdout logOutput := os.Stdout
if c.Bool(stdiouiFlag.Name) { if c.GlobalBool(stdiouiFlag.Name) {
logOutput = os.Stderr logOutput = os.Stderr
// If using the stdioui, we can't do the 'confirm'-flow // If using the stdioui, we can't do the 'confirm'-flow
fmt.Fprintf(logOutput, legalWarning) fmt.Fprintf(logOutput, legalWarning)
@ -323,26 +345,28 @@ func signer(c *cli.Context) error {
var ( var (
ui core.SignerUI ui core.SignerUI
) )
if c.Bool(stdiouiFlag.Name) { if c.GlobalBool(stdiouiFlag.Name) {
log.Info("Using stdin/stdout as UI-channel") log.Info("Using stdin/stdout as UI-channel")
ui = core.NewStdIOUI() ui = core.NewStdIOUI()
} else { } else {
log.Info("Using CLI as UI-channel") log.Info("Using CLI as UI-channel")
ui = core.NewCommandlineUI() ui = core.NewCommandlineUI()
} }
db, err := core.NewAbiDBFromFiles(c.String(dBFlag.Name), c.String(customDBFlag.Name)) fourByteDb := c.GlobalString(dBFlag.Name)
fourByteLocal := c.GlobalString(customDBFlag.Name)
db, err := core.NewAbiDBFromFiles(fourByteDb, fourByteLocal)
if err != nil { if err != nil {
utils.Fatalf(err.Error()) utils.Fatalf(err.Error())
} }
log.Info("Loaded 4byte db", "signatures", db.Size(), "file", c.String("4bytedb")) log.Info("Loaded 4byte db", "signatures", db.Size(), "file", fourByteDb, "local", fourByteLocal)
var ( var (
api core.ExternalAPI api core.ExternalAPI
) )
configDir := c.String(configdirFlag.Name) configDir := c.GlobalString(configdirFlag.Name)
if stretchedKey, err := readMasterKey(c); err != nil { if stretchedKey, err := readMasterKey(c, ui); err != nil {
log.Info("No master seed provided, rules disabled") log.Info("No master seed provided, rules disabled", "error", err)
} else { } else {
if err != nil { if err != nil {
@ -361,7 +385,7 @@ func signer(c *cli.Context) error {
configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confkey) configStorage := storage.NewAESEncryptedStorage(filepath.Join(vaultLocation, "config.json"), confkey)
//Do we have a rule-file? //Do we have a rule-file?
ruleJS, err := ioutil.ReadFile(c.String(ruleFlag.Name)) ruleJS, err := ioutil.ReadFile(c.GlobalString(ruleFlag.Name))
if err != nil { if err != nil {
log.Info("Could not load rulefile, rules not enabled", "file", "rulefile") log.Info("Could not load rulefile, rules not enabled", "file", "rulefile")
} else { } else {
@ -385,17 +409,15 @@ func signer(c *cli.Context) error {
} }
apiImpl := core.NewSignerAPI( apiImpl := core.NewSignerAPI(
c.Int64(utils.NetworkIdFlag.Name), c.GlobalInt64(utils.NetworkIdFlag.Name),
c.String(keystoreFlag.Name), c.GlobalString(keystoreFlag.Name),
c.Bool(utils.NoUSBFlag.Name), c.GlobalBool(utils.NoUSBFlag.Name),
ui, db, ui, db,
c.Bool(utils.LightKDFFlag.Name), c.GlobalBool(utils.LightKDFFlag.Name),
c.Bool(advancedMode.Name)) c.GlobalBool(advancedMode.Name))
api = apiImpl api = apiImpl
// Audit logging // Audit logging
if logfile := c.String(auditLogFlag.Name); logfile != "" { if logfile := c.GlobalString(auditLogFlag.Name); logfile != "" {
api, err = core.NewAuditLogger(logfile, api) api, err = core.NewAuditLogger(logfile, api)
if err != nil { if err != nil {
utils.Fatalf(err.Error()) utils.Fatalf(err.Error())
@ -414,13 +436,13 @@ func signer(c *cli.Context) error {
Service: api, Service: api,
Version: "1.0"}, Version: "1.0"},
} }
if c.Bool(utils.RPCEnabledFlag.Name) { if c.GlobalBool(utils.RPCEnabledFlag.Name) {
vhosts := splitAndTrim(c.GlobalString(utils.RPCVirtualHostsFlag.Name)) vhosts := splitAndTrim(c.GlobalString(utils.RPCVirtualHostsFlag.Name))
cors := splitAndTrim(c.GlobalString(utils.RPCCORSDomainFlag.Name)) cors := splitAndTrim(c.GlobalString(utils.RPCCORSDomainFlag.Name))
// start http server // start http server
httpEndpoint := fmt.Sprintf("%s:%d", c.String(utils.RPCListenAddrFlag.Name), c.Int(rpcPortFlag.Name)) httpEndpoint := fmt.Sprintf("%s:%d", c.GlobalString(utils.RPCListenAddrFlag.Name), c.Int(rpcPortFlag.Name))
listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"account"}, cors, vhosts, rpc.DefaultHTTPTimeouts) listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"account"}, cors, vhosts, rpc.DefaultHTTPTimeouts)
if err != nil { if err != nil {
utils.Fatalf("Could not start RPC api: %v", err) utils.Fatalf("Could not start RPC api: %v", err)
@ -434,9 +456,9 @@ func signer(c *cli.Context) error {
}() }()
} }
if !c.Bool(utils.IPCDisabledFlag.Name) { if !c.GlobalBool(utils.IPCDisabledFlag.Name) {
if c.IsSet(utils.IPCPathFlag.Name) { if c.IsSet(utils.IPCPathFlag.Name) {
ipcapiURL = c.String(utils.IPCPathFlag.Name) ipcapiURL = c.GlobalString(utils.IPCPathFlag.Name)
} else { } else {
ipcapiURL = filepath.Join(configDir, "clef.ipc") ipcapiURL = filepath.Join(configDir, "clef.ipc")
} }
@ -453,7 +475,7 @@ func signer(c *cli.Context) error {
} }
if c.Bool(testFlag.Name) { if c.GlobalBool(testFlag.Name) {
log.Info("Performing UI test") log.Info("Performing UI test")
go testExternalUI(apiImpl) go testExternalUI(apiImpl)
} }
@ -512,36 +534,52 @@ func homeDir() string {
} }
return "" return ""
} }
func readMasterKey(ctx *cli.Context) ([]byte, error) { func readMasterKey(ctx *cli.Context, ui core.SignerUI) ([]byte, error) {
var ( var (
file string file string
configDir = ctx.String(configdirFlag.Name) configDir = ctx.GlobalString(configdirFlag.Name)
) )
if ctx.IsSet(signerSecretFlag.Name) { if ctx.GlobalIsSet(signerSecretFlag.Name) {
file = ctx.String(signerSecretFlag.Name) file = ctx.GlobalString(signerSecretFlag.Name)
} else { } else {
file = filepath.Join(configDir, "secrets.dat") file = filepath.Join(configDir, "masterseed.json")
} }
if err := checkFile(file); err != nil { if err := checkFile(file); err != nil {
return nil, err return nil, err
} }
masterKey, err := ioutil.ReadFile(file) cipherKey, err := ioutil.ReadFile(file)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(masterKey) < 256 { var password string
return nil, fmt.Errorf("master key of insufficient length, expected >255 bytes, got %d", len(masterKey)) // If ui is not nil, get the password from ui.
if ui != nil {
resp, err := ui.OnInputRequired(core.UserInputRequest{
Title: "Master Password",
Prompt: "Please enter the password to decrypt the master seed",
IsPassword: true})
if err != nil {
return nil, err
}
password = resp.Text
} else {
password = getPassPhrase("Decrypt master seed of clef", false)
} }
masterSeed, err := decryptSeed(cipherKey, password)
if err != nil {
return nil, fmt.Errorf("failed to decrypt the master seed of clef")
}
if len(masterSeed) < 256 {
return nil, fmt.Errorf("master seed of insufficient length, expected >255 bytes, got %d", len(masterSeed))
}
// Create vault location // Create vault location
vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), masterKey)[:10])) vaultLocation := filepath.Join(configDir, common.Bytes2Hex(crypto.Keccak256([]byte("vault"), masterSeed)[:10]))
err = os.Mkdir(vaultLocation, 0700) err = os.Mkdir(vaultLocation, 0700)
if err != nil && !os.IsExist(err) { if err != nil && !os.IsExist(err) {
return nil, err return nil, err
} }
//!TODO, use KDF to stretch the master key return masterSeed, nil
// stretched_key := stretch_key(master_key)
return masterKey, nil
} }
// checkFile is a convenience function to check if a file // checkFile is a convenience function to check if a file
@ -619,6 +657,59 @@ func testExternalUI(api *core.SignerAPI) {
} }
// getPassPhrase retrieves the password associated with clef, either fetched
// from a list of preloaded passphrases, or requested interactively from the user.
// TODO: there are many `getPassPhrase` functions, it will be better to abstract them into one.
func getPassPhrase(prompt string, confirmation bool) string {
fmt.Println(prompt)
password, err := console.Stdin.PromptPassword("Passphrase: ")
if err != nil {
utils.Fatalf("Failed to read passphrase: %v", err)
}
if confirmation {
confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ")
if err != nil {
utils.Fatalf("Failed to read passphrase confirmation: %v", err)
}
if password != confirm {
utils.Fatalf("Passphrases do not match")
}
}
return password
}
type encryptedSeedStorage struct {
Description string `json:"description"`
Version int `json:"version"`
Params keystore.CryptoJSON `json:"params"`
}
// encryptSeed uses a similar scheme as the keystore uses, but with a different wrapping,
// to encrypt the master seed
func encryptSeed(seed []byte, auth []byte, scryptN, scryptP int) ([]byte, error) {
cryptoStruct, err := keystore.EncryptDataV3(seed, auth, scryptN, scryptP)
if err != nil {
return nil, err
}
return json.Marshal(&encryptedSeedStorage{"Clef seed", 1, cryptoStruct})
}
// decryptSeed decrypts the master seed
func decryptSeed(keyjson []byte, auth string) ([]byte, error) {
var encSeed encryptedSeedStorage
if err := json.Unmarshal(keyjson, &encSeed); err != nil {
return nil, err
}
if encSeed.Version != 1 {
log.Warn(fmt.Sprintf("unsupported encryption format of seed: %d, operation will likely fail", encSeed.Version))
}
seed, err := keystore.DecryptDataV3(encSeed.Params, auth)
if err != nil {
return nil, err
}
return seed, err
}
/** /**
//Create Account //Create Account

View File

@ -197,6 +197,12 @@ type (
Message struct { Message struct {
Text string `json:"text"` Text string `json:"text"`
} }
PasswordRequest struct {
Prompt string `json:"prompt"`
}
PasswordResponse struct {
Password string `json:"password"`
}
StartupInfo struct { StartupInfo struct {
Info map[string]interface{} `json:"info"` Info map[string]interface{} `json:"info"`
} }

View File

@ -81,6 +81,10 @@ func (alwaysDenyUI) OnInputRequired(info core.UserInputRequest) (core.UserInputR
func (alwaysDenyUI) OnSignerStartup(info core.StartupInfo) { func (alwaysDenyUI) OnSignerStartup(info core.StartupInfo) {
} }
func (alwaysDenyUI) OnMasterPassword(request *core.PasswordRequest) (core.PasswordResponse, error) {
return core.PasswordResponse{}, nil
}
func (alwaysDenyUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) { func (alwaysDenyUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) {
return core.SignTxResponse{Transaction: request.Transaction, Approved: false, Password: ""}, nil return core.SignTxResponse{Transaction: request.Transaction, Approved: false, Password: ""}, nil
} }
@ -250,6 +254,11 @@ func (d *dummyUI) ShowInfo(message string) {
func (d *dummyUI) OnApprovedTx(tx ethapi.SignTransactionResult) { func (d *dummyUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
d.calls = append(d.calls, "OnApprovedTx") d.calls = append(d.calls, "OnApprovedTx")
} }
func (d *dummyUI) OnMasterPassword(request *core.PasswordRequest) (core.PasswordResponse, error) {
return core.PasswordResponse{}, nil
}
func (d *dummyUI) OnSignerStartup(info core.StartupInfo) { func (d *dummyUI) OnSignerStartup(info core.StartupInfo) {
} }
@ -526,6 +535,10 @@ func (d *dontCallMe) OnInputRequired(info core.UserInputRequest) (core.UserInput
func (d *dontCallMe) OnSignerStartup(info core.StartupInfo) { func (d *dontCallMe) OnSignerStartup(info core.StartupInfo) {
} }
func (d *dontCallMe) OnMasterPassword(request *core.PasswordRequest) (core.PasswordResponse, error) {
return core.PasswordResponse{}, nil
}
func (d *dontCallMe) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) { func (d *dontCallMe) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) {
d.t.Fatalf("Did not expect next-handler to be called") d.t.Fatalf("Did not expect next-handler to be called")
return core.SignTxResponse{}, core.ErrRequestDenied return core.SignTxResponse{}, core.ErrRequestDenied