cmd/clef: encrypt master seed of clef

Signed-off-by: YaoZengzeng <yaozengzeng@zju.edu.cn>
This commit is contained in:
YaoZengzeng 2018-09-17 11:08:50 +08:00 committed by Martin Holst Swende
parent 3c037d9059
commit 797b44c24a
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
7 changed files with 260 additions and 12 deletions

View file

@ -20,7 +20,10 @@ 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"
@ -35,8 +38,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"
@ -44,6 +49,8 @@ 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"
) )
@ -63,6 +70,12 @@ 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{
@ -215,13 +228,24 @@ func initializeSecrets(c *cli.Context) error {
configDir := c.String(configdirFlag.Name) configDir := c.String(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.Bool(utils.LightKDFFlag.Name) {
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)
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
@ -230,7 +254,7 @@ func initializeSecrets(c *cli.Context) error {
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,7 +279,7 @@ 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())
} }
@ -279,7 +303,7 @@ 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())
} }
@ -341,7 +365,7 @@ func signer(c *cli.Context) error {
) )
configDir := c.String(configdirFlag.Name) configDir := c.String(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")
} else { } else {
@ -512,7 +536,7 @@ 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.String(configdirFlag.Name)
@ -525,15 +549,32 @@ func readMasterKey(ctx *cli.Context) ([]byte, error) {
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 {
return nil, fmt.Errorf("master key of insufficient length, expected >255 bytes, got %d", len(masterKey)) var password string
// If ui is not nil, get the password from ui.
if ui != nil {
resp, err := ui.OnMasterPassword(&core.PasswordRequest{Prompt: "password to decrypt master seed"})
if err != nil {
return nil, err
}
password = resp.Password
} else {
password = getPassPhrase("Decrypt master seed of clef", false)
} }
masterSeed, err := decryptSeed(cipherKey, []byte(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
@ -541,7 +582,7 @@ func readMasterKey(ctx *cli.Context) ([]byte, error) {
//!TODO, use KDF to stretch the master key //!TODO, use KDF to stretch the master key
// stretched_key := stretch_key(master_key) // stretched_key := stretch_key(master_key)
return masterKey, nil return masterSeed, nil
} }
// checkFile is a convenience function to check if a file // checkFile is a convenience function to check if a file
@ -619,6 +660,174 @@ 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 cryptoJSON struct {
Cipher string `json:"cipher"`
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) {
salt := make([]byte, 32)
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 {
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)
}
func decryptSeed(keyjson []byte, auth []byte) ([]byte, error) {
var cryptoStruct cryptoJSON
if err := json.Unmarshal(keyjson, &cryptoStruct); err != nil {
return nil, err
}
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 {
return nil, 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

View file

@ -79,6 +79,8 @@ 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)
@ -197,6 +199,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

@ -49,6 +49,10 @@ 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")
} }

View file

@ -264,6 +264,10 @@ 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")

View file

@ -105,6 +105,12 @@ 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 {

View file

@ -252,3 +252,7 @@ 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
}

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