mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 18:32:23 +00:00
clef: utilize keystore encryption, check flags correctly
This commit is contained in:
parent
797b44c24a
commit
e696b4e7d4
8 changed files with 50 additions and 201 deletions
|
|
@ -135,7 +135,7 @@ func (ks keyStorePassphrase) JoinPath(filename string) string {
|
||||||
return filepath.Join(ks.keysDirPath, filename)
|
return filepath.Join(ks.keysDirPath, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encryptdata encrypts the data given as 'data with the password 'auth'.
|
// Encryptdata encrypts the data given as 'data' with the password 'auth'.
|
||||||
func EncryptDataV3(data, auth []byte, scryptN, scryptP int) (CryptoJSON, error) {
|
func EncryptDataV3(data, auth []byte, scryptN, scryptP int) (CryptoJSON, error) {
|
||||||
|
|
||||||
salt := make([]byte, 32)
|
salt := make([]byte, 32)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
224
cmd/clef/main.go
224
cmd/clef/main.go
|
|
@ -20,10 +20,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"crypto/aes"
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
|
@ -49,16 +46,14 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/signer/core"
|
"github.com/ethereum/go-ethereum/signer/core"
|
||||||
"github.com/ethereum/go-ethereum/signer/rules"
|
"github.com/ethereum/go-ethereum/signer/rules"
|
||||||
"github.com/ethereum/go-ethereum/signer/storage"
|
"github.com/ethereum/go-ethereum/signer/storage"
|
||||||
"golang.org/x/crypto/pbkdf2"
|
|
||||||
"golang.org/x/crypto/scrypt"
|
|
||||||
"gopkg.in/urfave/cli.v1"
|
"gopkg.in/urfave/cli.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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!
|
||||||
|
|
@ -70,12 +65,6 @@ unless you agree to take full responsibility for doing so, and know what you are
|
||||||
TLDR; THIS IS NOT PRODUCTION-READY SOFTWARE!
|
TLDR; THIS IS NOT PRODUCTION-READY SOFTWARE!
|
||||||
|
|
||||||
`
|
`
|
||||||
const (
|
|
||||||
keyHeaderKDF = "scrypt"
|
|
||||||
|
|
||||||
scryptR = 8
|
|
||||||
scryptDKLen = 32
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
logLevelFlag = cli.IntFlag{
|
logLevelFlag = cli.IntFlag{
|
||||||
|
|
@ -104,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",
|
||||||
|
|
@ -225,7 +214,7 @@ 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)
|
||||||
num, err := io.ReadFull(rand.Reader, masterSeed)
|
num, err := io.ReadFull(rand.Reader, masterSeed)
|
||||||
|
|
@ -237,7 +226,7 @@ func initializeSecrets(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
n, p := keystore.StandardScryptN, keystore.StandardScryptP
|
n, p := keystore.StandardScryptN, keystore.StandardScryptP
|
||||||
if c.Bool(utils.LightKDFFlag.Name) {
|
if c.GlobalBool(utils.LightKDFFlag.Name) {
|
||||||
n, p = keystore.LightScryptN, keystore.LightScryptP
|
n, p = keystore.LightScryptN, keystore.LightScryptP
|
||||||
}
|
}
|
||||||
password := getPassPhrase("The master seed of clef is locked with a password. Please give a password. Do not forget this password.", true)
|
password := getPassPhrase("The master seed of clef is locked with a password. Please give a password. Do not forget this password.", true)
|
||||||
|
|
@ -250,7 +239,7 @@ func initializeSecrets(c *cli.Context) error {
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
@ -283,7 +272,7 @@ func attestFile(ctx *cli.Context) error {
|
||||||
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)
|
||||||
|
|
||||||
|
|
@ -307,7 +296,7 @@ func addCredential(ctx *cli.Context) error {
|
||||||
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)
|
||||||
|
|
||||||
|
|
@ -326,7 +315,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)
|
||||||
|
|
@ -347,24 +336,26 @@ 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, ui); 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")
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -385,7 +376,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 {
|
||||||
|
|
@ -409,17 +400,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())
|
||||||
|
|
@ -438,13 +427,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)
|
||||||
|
|
@ -458,9 +447,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")
|
||||||
}
|
}
|
||||||
|
|
@ -477,7 +466,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)
|
||||||
}
|
}
|
||||||
|
|
@ -539,12 +528,12 @@ func homeDir() string {
|
||||||
func readMasterKey(ctx *cli.Context, ui core.SignerUI) ([]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
|
||||||
|
|
@ -557,15 +546,18 @@ func readMasterKey(ctx *cli.Context, ui core.SignerUI) ([]byte, error) {
|
||||||
var password string
|
var password string
|
||||||
// If ui is not nil, get the password from ui.
|
// If ui is not nil, get the password from ui.
|
||||||
if ui != nil {
|
if ui != nil {
|
||||||
resp, err := ui.OnMasterPassword(&core.PasswordRequest{Prompt: "password to decrypt master seed"})
|
resp, err := ui.OnInputRequired(core.UserInputRequest{
|
||||||
|
Title: "Master Password",
|
||||||
|
Prompt: "Please enter the password to decrypt the master seed",
|
||||||
|
IsPassword: true})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
password = resp.Password
|
password = resp.Text
|
||||||
} else {
|
} else {
|
||||||
password = getPassPhrase("Decrypt master seed of clef", false)
|
password = getPassPhrase("Decrypt master seed of clef", false)
|
||||||
}
|
}
|
||||||
masterSeed, err := decryptSeed(cipherKey, []byte(password))
|
masterSeed, err := decryptSeed(cipherKey, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to decrypt the master seed of clef")
|
return nil, fmt.Errorf("failed to decrypt the master seed of clef")
|
||||||
}
|
}
|
||||||
|
|
@ -579,9 +571,6 @@ func readMasterKey(ctx *cli.Context, ui core.SignerUI) ([]byte, error) {
|
||||||
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
|
|
||||||
// stretched_key := stretch_key(master_key)
|
|
||||||
|
|
||||||
return masterSeed, nil
|
return masterSeed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -678,156 +667,33 @@ func getPassPhrase(prompt string, confirmation bool) string {
|
||||||
utils.Fatalf("Passphrases do not match")
|
utils.Fatalf("Passphrases do not match")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//TODO add validation of password (exists already in a separate PR)
|
||||||
return password
|
return password
|
||||||
}
|
}
|
||||||
|
|
||||||
type cryptoJSON struct {
|
// encryptSeed uses a similar scheme as the keystore uses, but with a different wrapping,
|
||||||
Cipher string `json:"cipher"`
|
// to encrypt the master seed
|
||||||
CipherText string `json:"ciphertext"`
|
|
||||||
CipherParams cipherparamsJSON `json:"cipherparams"`
|
|
||||||
KDF string `json:"kdf"`
|
|
||||||
KDFParams map[string]interface{} `json:"kdfparams"`
|
|
||||||
MAC string `json:"mac"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type cipherparamsJSON struct {
|
|
||||||
IV string `json:"iv"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func encryptSeed(seed []byte, auth []byte, scryptN, scryptP int) ([]byte, error) {
|
func encryptSeed(seed []byte, auth []byte, scryptN, scryptP int) ([]byte, error) {
|
||||||
salt := make([]byte, 32)
|
cryptoStruct, err := keystore.EncryptDataV3(seed, auth, scryptN, scryptP)
|
||||||
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
|
|
||||||
panic("reading from crypto/rand failed: " + err.Error())
|
|
||||||
}
|
|
||||||
derivedKey, err := scrypt.Key(auth, salt, scryptN, scryptR, scryptP, scryptDKLen)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
encryptKey := derivedKey[:16]
|
|
||||||
|
|
||||||
iv := make([]byte, aes.BlockSize) // 16
|
|
||||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
|
||||||
panic("reading from crypto/rand failed: " + err.Error())
|
|
||||||
}
|
|
||||||
cipherText, err := aesCTRXOR(encryptKey, seed, iv)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mac := crypto.Keccak256(derivedKey[16:32], cipherText)
|
|
||||||
|
|
||||||
scryptParamsJSON := make(map[string]interface{}, 5)
|
|
||||||
scryptParamsJSON["n"] = scryptN
|
|
||||||
scryptParamsJSON["r"] = scryptR
|
|
||||||
scryptParamsJSON["p"] = scryptP
|
|
||||||
scryptParamsJSON["dklen"] = scryptDKLen
|
|
||||||
scryptParamsJSON["salt"] = hex.EncodeToString(salt)
|
|
||||||
|
|
||||||
cipherParamsJSON := cipherparamsJSON{
|
|
||||||
IV: hex.EncodeToString(iv),
|
|
||||||
}
|
|
||||||
|
|
||||||
cryptoStruct := cryptoJSON{
|
|
||||||
Cipher: "aes-128-ctr",
|
|
||||||
CipherText: hex.EncodeToString(cipherText),
|
|
||||||
CipherParams: cipherParamsJSON,
|
|
||||||
KDF: keyHeaderKDF,
|
|
||||||
KDFParams: scryptParamsJSON,
|
|
||||||
MAC: hex.EncodeToString(mac),
|
|
||||||
}
|
|
||||||
return json.Marshal(cryptoStruct)
|
return json.Marshal(cryptoStruct)
|
||||||
}
|
}
|
||||||
|
|
||||||
func decryptSeed(keyjson []byte, auth []byte) ([]byte, error) {
|
// decryptSeed decrypts the master seed
|
||||||
var cryptoStruct cryptoJSON
|
func decryptSeed(keyjson []byte, auth string) ([]byte, error) {
|
||||||
|
var cryptoStruct keystore.CryptoJSON
|
||||||
if err := json.Unmarshal(keyjson, &cryptoStruct); err != nil {
|
if err := json.Unmarshal(keyjson, &cryptoStruct); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
seed, err := keystore.DecryptDataV3(cryptoStruct, auth)
|
||||||
if cryptoStruct.Cipher != "aes-128-ctr" {
|
|
||||||
return nil, fmt.Errorf("Cipher not supported: %v", cryptoStruct.Cipher)
|
|
||||||
}
|
|
||||||
|
|
||||||
mac, err := hex.DecodeString(cryptoStruct.MAC)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
iv, err := hex.DecodeString(cryptoStruct.CipherParams.IV)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cipherText, err := hex.DecodeString(cryptoStruct.CipherText)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
derivedKey, err := getKDFKey(cryptoStruct, auth)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
calculatedMAC := crypto.Keccak256(derivedKey[16:32], cipherText)
|
|
||||||
if !bytes.Equal(calculatedMAC, mac) {
|
|
||||||
return nil, fmt.Errorf("could not decrypt seed with given passphrase")
|
|
||||||
}
|
|
||||||
|
|
||||||
seed, err := aesCTRXOR(derivedKey[:16], cipherText, iv)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return seed, err
|
return seed, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func aesCTRXOR(key, inText, iv []byte) ([]byte, error) {
|
|
||||||
// AES-128 is selected due to size of encryptKey.
|
|
||||||
aesBlock, err := aes.NewCipher(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
stream := cipher.NewCTR(aesBlock, iv)
|
|
||||||
outText := make([]byte, len(inText))
|
|
||||||
stream.XORKeyStream(outText, inText)
|
|
||||||
return outText, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func getKDFKey(cryptoJSON cryptoJSON, auth []byte) ([]byte, error) {
|
|
||||||
salt, err := hex.DecodeString(cryptoJSON.KDFParams["salt"].(string))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
dkLen := ensureInt(cryptoJSON.KDFParams["dklen"])
|
|
||||||
|
|
||||||
if cryptoJSON.KDF == keyHeaderKDF {
|
|
||||||
n := ensureInt(cryptoJSON.KDFParams["n"])
|
|
||||||
r := ensureInt(cryptoJSON.KDFParams["r"])
|
|
||||||
p := ensureInt(cryptoJSON.KDFParams["p"])
|
|
||||||
return scrypt.Key(auth, salt, n, r, p, dkLen)
|
|
||||||
|
|
||||||
} else if cryptoJSON.KDF == "pbkdf2" {
|
|
||||||
c := ensureInt(cryptoJSON.KDFParams["c"])
|
|
||||||
prf := cryptoJSON.KDFParams["prf"].(string)
|
|
||||||
if prf != "hmac-sha256" {
|
|
||||||
return nil, fmt.Errorf("Unsupported PBKDF2 PRF: %s", prf)
|
|
||||||
}
|
|
||||||
key := pbkdf2.Key(auth, salt, c, dkLen, sha256.New)
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("Unsupported KDF: %s", cryptoJSON.KDF)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: can we do without this when unmarshalling dynamic JSON?
|
|
||||||
// why do integers in KDF params end up as float64 and not int after
|
|
||||||
// unmarshal?
|
|
||||||
func ensureInt(x interface{}) int {
|
|
||||||
res, ok := x.(int)
|
|
||||||
if !ok {
|
|
||||||
res = int(x.(float64))
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
//Create Account
|
//Create Account
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,8 +79,6 @@ type SignerUI interface {
|
||||||
// OnApprovedTx notifies the UI about a transaction having been successfully signed.
|
// OnApprovedTx notifies the UI about a transaction having been successfully signed.
|
||||||
// This method can be used by a UI to keep track of e.g. how much has been sent to a particular recipient.
|
// This method can be used by a UI to keep track of e.g. how much has been sent to a particular recipient.
|
||||||
OnApprovedTx(tx ethapi.SignTransactionResult)
|
OnApprovedTx(tx ethapi.SignTransactionResult)
|
||||||
// OnMasterPassword is invoked when the signer boots, and tells the UI to input the password for the master seed.
|
|
||||||
OnMasterPassword(request *PasswordRequest) (PasswordResponse, error)
|
|
||||||
// OnSignerStartup is invoked when the signer boots, and tells the UI info about external API location and version
|
// OnSignerStartup is invoked when the signer boots, and tells the UI info about external API location and version
|
||||||
// information
|
// information
|
||||||
OnSignerStartup(info StartupInfo)
|
OnSignerStartup(info StartupInfo)
|
||||||
|
|
|
||||||
|
|
@ -49,10 +49,6 @@ func (ui *HeadlessUI) OnInputRequired(info UserInputRequest) (UserInputResponse,
|
||||||
func (ui *HeadlessUI) OnSignerStartup(info StartupInfo) {
|
func (ui *HeadlessUI) OnSignerStartup(info StartupInfo) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *HeadlessUI) OnMasterPassword(request *PasswordRequest) (PasswordResponse, error) {
|
|
||||||
return PasswordResponse{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ui *HeadlessUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
|
func (ui *HeadlessUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
|
||||||
fmt.Printf("OnApproved()\n")
|
fmt.Printf("OnApproved()\n")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -264,10 +264,6 @@ func (ui *CommandlineUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
|
||||||
spew.Dump(tx.Tx)
|
spew.Dump(tx.Tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *CommandlineUI) OnMasterPassword(request *PasswordRequest) (PasswordResponse, error) {
|
|
||||||
return PasswordResponse{ui.readPasswordText(request.Prompt)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ui *CommandlineUI) OnSignerStartup(info StartupInfo) {
|
func (ui *CommandlineUI) OnSignerStartup(info StartupInfo) {
|
||||||
|
|
||||||
fmt.Printf("------- Signer info -------\n")
|
fmt.Printf("------- Signer info -------\n")
|
||||||
|
|
|
||||||
|
|
@ -105,12 +105,6 @@ func (ui *StdIOUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *StdIOUI) OnMasterPassword(request *PasswordRequest) (PasswordResponse, error) {
|
|
||||||
var result PasswordResponse
|
|
||||||
err := ui.dispatch("OnMasterPassword", request, &result)
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ui *StdIOUI) OnSignerStartup(info StartupInfo) {
|
func (ui *StdIOUI) OnSignerStartup(info StartupInfo) {
|
||||||
err := ui.dispatch("OnSignerStartup", info, nil)
|
err := ui.dispatch("OnSignerStartup", info, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,3 @@ func (r *rulesetUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
|
||||||
log.Info("error occurred during execution", "error", err)
|
log.Info("error occurred during execution", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rulesetUI) OnMasterPassword(request *core.PasswordRequest) (core.PasswordResponse, error) {
|
|
||||||
return core.PasswordResponse{}, nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue