2018-09-05 15:36:14 +00:00
// Copyright 2018 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
// signer is a utility that can be used so sign transactions and
// arbitrary data.
package main
import (
"bufio"
"context"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"os/signal"
"os/user"
"path/filepath"
"runtime"
"strings"
2018-11-21 15:30:00 +00:00
"github.com/ethereum/go-ethereum/accounts/keystore"
2018-09-05 15:36:14 +00:00
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
2018-11-21 15:30:00 +00:00
"github.com/ethereum/go-ethereum/console"
2018-09-05 15:36:14 +00:00
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/signer/core"
"github.com/ethereum/go-ethereum/signer/rules"
"github.com/ethereum/go-ethereum/signer/storage"
"gopkg.in/urfave/cli.v1"
)
// ExternalAPIVersion -- see extapi_changelog.md
2018-11-21 15:30:00 +00:00
const ExternalAPIVersion = "4.0.0"
2018-09-05 15:36:14 +00:00
// InternalAPIVersion -- see intapi_changelog.md
2018-11-21 15:30:00 +00:00
const InternalAPIVersion = "3.0.0"
2018-09-05 15:36:14 +00:00
const legalWarning = `
WARNING !
Clef is alpha software , and not yet publically released . This software has _not_ been audited , and there
are no guarantees about the workings of this software . It may contain severe flaws . You should not use this software
unless you agree to take full responsibility for doing so , and know what you are doing .
TLDR ; THIS IS NOT PRODUCTION - READY SOFTWARE !
`
var (
logLevelFlag = cli . IntFlag {
Name : "loglevel" ,
Value : 4 ,
Usage : "log level to emit to the screen" ,
}
2018-11-21 15:30:00 +00:00
advancedMode = cli . BoolFlag {
Name : "advanced" ,
Usage : "If enabled, issues warnings instead of rejections for suspicious requests. Default off" ,
}
2018-09-05 15:36:14 +00:00
keystoreFlag = cli . StringFlag {
Name : "keystore" ,
Value : filepath . Join ( node . DefaultDataDir ( ) , "keystore" ) ,
Usage : "Directory for the keystore" ,
}
configdirFlag = cli . StringFlag {
Name : "configdir" ,
Value : DefaultConfigDir ( ) ,
Usage : "Directory for Clef configuration" ,
}
rpcPortFlag = cli . IntFlag {
Name : "rpcport" ,
Usage : "HTTP-RPC server listening port" ,
Value : node . DefaultHTTPPort + 5 ,
}
signerSecretFlag = cli . StringFlag {
Name : "signersecret" ,
2018-11-21 15:30:00 +00:00
Usage : "A file containing the (encrypted) master seed to encrypt Clef data, e.g. keystore credentials and ruleset hash" ,
2018-09-05 15:36:14 +00:00
}
dBFlag = cli . StringFlag {
Name : "4bytedb" ,
Usage : "File containing 4byte-identifiers" ,
Value : "./4byte.json" ,
}
customDBFlag = cli . StringFlag {
Name : "4bytedb-custom" ,
Usage : "File used for writing new 4byte-identifiers submitted via API" ,
Value : "./4byte-custom.json" ,
}
auditLogFlag = cli . StringFlag {
Name : "auditlog" ,
Usage : "File used to emit audit logs. Set to \"\" to disable" ,
Value : "audit.log" ,
}
ruleFlag = cli . StringFlag {
Name : "rules" ,
Usage : "Enable rule-engine" ,
Value : "rules.json" ,
}
stdiouiFlag = cli . BoolFlag {
Name : "stdio-ui" ,
Usage : "Use STDIN/STDOUT as a channel for an external UI. " +
"This means that an STDIN/STDOUT is used for RPC-communication with a e.g. a graphical user " +
"interface, and can be used when Clef is started by an external process." ,
}
testFlag = cli . BoolFlag {
Name : "stdio-ui-test" ,
Usage : "Mechanism to test interface between Clef and UI. Requires 'stdio-ui'." ,
}
app = cli . NewApp ( )
initCommand = cli . Command {
Action : utils . MigrateFlags ( initializeSecrets ) ,
Name : "init" ,
Usage : "Initialize the signer, generate secret storage" ,
ArgsUsage : "" ,
Flags : [ ] cli . Flag {
logLevelFlag ,
configdirFlag ,
} ,
Description : `
The init command generates a master seed which Clef can use to store credentials and data needed for
the rule - engine to work . ` ,
}
attestCommand = cli . Command {
Action : utils . MigrateFlags ( attestFile ) ,
Name : "attest" ,
Usage : "Attest that a js-file is to be used" ,
ArgsUsage : "<sha256sum>" ,
Flags : [ ] cli . Flag {
logLevelFlag ,
configdirFlag ,
signerSecretFlag ,
} ,
Description : `
The attest command stores the sha256 of the rule . js - file that you want to use for automatic processing of
incoming requests .
Whenever you make an edit to the rule file , you need to use attestation to tell
Clef that the file is ' safe ' to execute . ` ,
}
2018-11-21 15:30:00 +00:00
setCredentialCommand = cli . Command {
Action : utils . MigrateFlags ( setCredential ) ,
Name : "setpw" ,
2018-09-05 15:36:14 +00:00
Usage : "Store a credential for a keystore file" ,
2018-11-21 15:30:00 +00:00
ArgsUsage : "<address>" ,
2018-09-05 15:36:14 +00:00
Flags : [ ] cli . Flag {
logLevelFlag ,
configdirFlag ,
signerSecretFlag ,
} ,
Description : `
2018-11-21 15:30:00 +00:00
The setpw command stores a password for a given address ( keyfile ) . If you enter a blank passphrase , it will
2018-09-05 15:36:14 +00:00
remove any stored credential for that address ( keyfile )
` ,
}
)
func init ( ) {
app . Name = "Clef"
app . Usage = "Manage Ethereum account operations"
app . Flags = [ ] cli . Flag {
logLevelFlag ,
keystoreFlag ,
configdirFlag ,
utils . NetworkIdFlag ,
utils . LightKDFFlag ,
utils . NoUSBFlag ,
utils . RPCListenAddrFlag ,
utils . RPCVirtualHostsFlag ,
utils . IPCDisabledFlag ,
utils . IPCPathFlag ,
utils . RPCEnabledFlag ,
rpcPortFlag ,
signerSecretFlag ,
dBFlag ,
customDBFlag ,
auditLogFlag ,
ruleFlag ,
stdiouiFlag ,
testFlag ,
2018-11-21 15:30:00 +00:00
advancedMode ,
2018-09-05 15:36:14 +00:00
}
app . Action = signer
2018-11-21 15:30:00 +00:00
app . Commands = [ ] cli . Command { initCommand , attestCommand , setCredentialCommand }
2018-09-05 15:36:14 +00:00
}
func main ( ) {
if err := app . Run ( os . Args ) ; err != nil {
fmt . Fprintln ( os . Stderr , err )
os . Exit ( 1 )
}
}
func initializeSecrets ( c * cli . Context ) error {
if err := initialize ( c ) ; err != nil {
return err
}
2018-11-21 15:30:00 +00:00
configDir := c . GlobalString ( configdirFlag . Name )
2018-09-05 15:36:14 +00:00
masterSeed := make ( [ ] byte , 256 )
2018-11-21 15:30:00 +00:00
num , err := io . ReadFull ( rand . Reader , masterSeed )
2018-09-05 15:36:14 +00:00
if err != nil {
return err
}
2018-11-21 15:30:00 +00:00
if num != len ( masterSeed ) {
2018-09-05 15:36:14 +00:00
return fmt . Errorf ( "failed to read enough random" )
}
2018-11-21 15:30:00 +00:00
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 )
}
2018-09-05 15:36:14 +00:00
err = os . Mkdir ( configDir , 0700 )
if err != nil && ! os . IsExist ( err ) {
return err
}
2018-11-21 15:30:00 +00:00
location := filepath . Join ( configDir , "masterseed.json" )
2018-09-05 15:36:14 +00:00
if _ , err := os . Stat ( location ) ; err == nil {
return fmt . Errorf ( "file %v already exists, will not overwrite" , location )
}
2018-11-21 15:30:00 +00:00
err = ioutil . WriteFile ( location , cipherSeed , 0400 )
2018-09-05 15:36:14 +00:00
if err != nil {
return err
}
fmt . Printf ( "A master seed has been generated into %s\n" , location )
fmt . Printf ( `
This is required to be able to store credentials , such as :
* Passwords for keystores ( used by rule engine )
* Storage for javascript rules
* Hash of rule - file
You should treat that file with utmost secrecy , and make a backup of it .
NOTE : This file does not contain your accounts . Those need to be backed up separately !
` )
return nil
}
func attestFile ( ctx * cli . Context ) error {
if len ( ctx . Args ( ) ) < 1 {
utils . Fatalf ( "This command requires an argument." )
}
if err := initialize ( ctx ) ; err != nil {
return err
}
2018-11-21 15:30:00 +00:00
stretchedKey , err := readMasterKey ( ctx , nil )
2018-09-05 15:36:14 +00:00
if err != nil {
utils . Fatalf ( err . Error ( ) )
}
2018-11-21 15:30:00 +00:00
configDir := ctx . GlobalString ( configdirFlag . Name )
2018-09-05 15:36:14 +00:00
vaultLocation := filepath . Join ( configDir , common . Bytes2Hex ( crypto . Keccak256 ( [ ] byte ( "vault" ) , stretchedKey ) [ : 10 ] ) )
confKey := crypto . Keccak256 ( [ ] byte ( "config" ) , stretchedKey )
// Initialize the encrypted storages
configStorage := storage . NewAESEncryptedStorage ( filepath . Join ( vaultLocation , "config.json" ) , confKey )
val := ctx . Args ( ) . First ( )
configStorage . Put ( "ruleset_sha256" , val )
log . Info ( "Ruleset attestation updated" , "sha256" , val )
return nil
}
2018-11-21 15:30:00 +00:00
func setCredential ( ctx * cli . Context ) error {
2018-09-05 15:36:14 +00:00
if len ( ctx . Args ( ) ) < 1 {
2018-11-21 15:30:00 +00:00
utils . Fatalf ( "This command requires an address to be passed as an argument." )
2018-09-05 15:36:14 +00:00
}
if err := initialize ( ctx ) ; err != nil {
return err
}
2018-11-21 15:30:00 +00:00
address := ctx . Args ( ) . First ( )
password := getPassPhrase ( "Enter a passphrase to store with this address." , true )
stretchedKey , err := readMasterKey ( ctx , nil )
2018-09-05 15:36:14 +00:00
if err != nil {
utils . Fatalf ( err . Error ( ) )
}
2018-11-21 15:30:00 +00:00
configDir := ctx . GlobalString ( configdirFlag . Name )
2018-09-05 15:36:14 +00:00
vaultLocation := filepath . Join ( configDir , common . Bytes2Hex ( crypto . Keccak256 ( [ ] byte ( "vault" ) , stretchedKey ) [ : 10 ] ) )
pwkey := crypto . Keccak256 ( [ ] byte ( "credentials" ) , stretchedKey )
// Initialize the encrypted storages
pwStorage := storage . NewAESEncryptedStorage ( filepath . Join ( vaultLocation , "credentials.json" ) , pwkey )
2018-11-21 15:30:00 +00:00
pwStorage . Put ( address , password )
log . Info ( "Credential store updated" , "key" , address )
2018-09-05 15:36:14 +00:00
return nil
}
func initialize ( c * cli . Context ) error {
// Set up the logger to print everything
logOutput := os . Stdout
2018-11-21 15:30:00 +00:00
if c . GlobalBool ( stdiouiFlag . Name ) {
2018-09-05 15:36:14 +00:00
logOutput = os . Stderr
// If using the stdioui, we can't do the 'confirm'-flow
fmt . Fprintf ( logOutput , legalWarning )
} else {
if ! confirm ( legalWarning ) {
return fmt . Errorf ( "aborted by user" )
}
}
log . Root ( ) . SetHandler ( log . LvlFilterHandler ( log . Lvl ( c . Int ( logLevelFlag . Name ) ) , log . StreamHandler ( logOutput , log . TerminalFormat ( true ) ) ) )
return nil
}
func signer ( c * cli . Context ) error {
if err := initialize ( c ) ; err != nil {
return err
}
var (
ui core . SignerUI
)
2018-11-21 15:30:00 +00:00
if c . GlobalBool ( stdiouiFlag . Name ) {
2018-09-05 15:36:14 +00:00
log . Info ( "Using stdin/stdout as UI-channel" )
ui = core . NewStdIOUI ( )
} else {
log . Info ( "Using CLI as UI-channel" )
ui = core . NewCommandlineUI ( )
}
2018-11-21 15:30:00 +00:00
fourByteDb := c . GlobalString ( dBFlag . Name )
fourByteLocal := c . GlobalString ( customDBFlag . Name )
db , err := core . NewAbiDBFromFiles ( fourByteDb , fourByteLocal )
2018-09-05 15:36:14 +00:00
if err != nil {
utils . Fatalf ( err . Error ( ) )
}
2018-11-21 15:30:00 +00:00
log . Info ( "Loaded 4byte db" , "signatures" , db . Size ( ) , "file" , fourByteDb , "local" , fourByteLocal )
2018-09-05 15:36:14 +00:00
var (
api core . ExternalAPI
)
2018-11-21 15:30:00 +00:00
configDir := c . GlobalString ( configdirFlag . Name )
if stretchedKey , err := readMasterKey ( c , ui ) ; err != nil {
log . Info ( "No master seed provided, rules disabled" , "error" , err )
2018-09-05 15:36:14 +00:00
} else {
if err != nil {
utils . Fatalf ( err . Error ( ) )
}
vaultLocation := filepath . Join ( configDir , common . Bytes2Hex ( crypto . Keccak256 ( [ ] byte ( "vault" ) , stretchedKey ) [ : 10 ] ) )
// Generate domain specific keys
pwkey := crypto . Keccak256 ( [ ] byte ( "credentials" ) , stretchedKey )
jskey := crypto . Keccak256 ( [ ] byte ( "jsstorage" ) , stretchedKey )
confkey := crypto . Keccak256 ( [ ] byte ( "config" ) , stretchedKey )
// Initialize the encrypted storages
pwStorage := storage . NewAESEncryptedStorage ( filepath . Join ( vaultLocation , "credentials.json" ) , pwkey )
jsStorage := storage . NewAESEncryptedStorage ( filepath . Join ( vaultLocation , "jsstorage.json" ) , jskey )
configStorage := storage . NewAESEncryptedStorage ( filepath . Join ( vaultLocation , "config.json" ) , confkey )
//Do we have a rule-file?
2018-11-21 15:30:00 +00:00
ruleJS , err := ioutil . ReadFile ( c . GlobalString ( ruleFlag . Name ) )
2018-09-05 15:36:14 +00:00
if err != nil {
log . Info ( "Could not load rulefile, rules not enabled" , "file" , "rulefile" )
} else {
hasher := sha256 . New ( )
hasher . Write ( ruleJS )
shasum := hasher . Sum ( nil )
storedShasum := configStorage . Get ( "ruleset_sha256" )
if storedShasum != hex . EncodeToString ( shasum ) {
log . Info ( "Could not validate ruleset hash, rules not enabled" , "got" , hex . EncodeToString ( shasum ) , "expected" , storedShasum )
} else {
// Initialize rules
ruleEngine , err := rules . NewRuleEvaluator ( ui , jsStorage , pwStorage )
if err != nil {
utils . Fatalf ( err . Error ( ) )
}
ruleEngine . Init ( string ( ruleJS ) )
ui = ruleEngine
log . Info ( "Rule engine configured" , "file" , c . String ( ruleFlag . Name ) )
}
}
}
apiImpl := core . NewSignerAPI (
2018-11-21 15:30:00 +00:00
c . GlobalInt64 ( utils . NetworkIdFlag . Name ) ,
c . GlobalString ( keystoreFlag . Name ) ,
c . GlobalBool ( utils . NoUSBFlag . Name ) ,
2018-09-05 15:36:14 +00:00
ui , db ,
2018-11-21 15:30:00 +00:00
c . GlobalBool ( utils . LightKDFFlag . Name ) ,
c . GlobalBool ( advancedMode . Name ) )
2018-09-05 15:36:14 +00:00
api = apiImpl
// Audit logging
2018-11-21 15:30:00 +00:00
if logfile := c . GlobalString ( auditLogFlag . Name ) ; logfile != "" {
2018-09-05 15:36:14 +00:00
api , err = core . NewAuditLogger ( logfile , api )
if err != nil {
utils . Fatalf ( err . Error ( ) )
}
log . Info ( "Audit logs configured" , "file" , logfile )
}
// register signer API with server
var (
extapiURL = "n/a"
ipcapiURL = "n/a"
)
rpcAPI := [ ] rpc . API {
{
Namespace : "account" ,
Public : true ,
Service : api ,
Version : "1.0" } ,
}
2018-11-21 15:30:00 +00:00
if c . GlobalBool ( utils . RPCEnabledFlag . Name ) {
2018-09-05 15:36:14 +00:00
vhosts := splitAndTrim ( c . GlobalString ( utils . RPCVirtualHostsFlag . Name ) )
cors := splitAndTrim ( c . GlobalString ( utils . RPCCORSDomainFlag . Name ) )
// start http server
2018-11-21 15:30:00 +00:00
httpEndpoint := fmt . Sprintf ( "%s:%d" , c . GlobalString ( utils . RPCListenAddrFlag . Name ) , c . Int ( rpcPortFlag . Name ) )
2018-09-05 15:36:14 +00:00
listener , _ , err := rpc . StartHTTPEndpoint ( httpEndpoint , rpcAPI , [ ] string { "account" } , cors , vhosts , rpc . DefaultHTTPTimeouts )
if err != nil {
utils . Fatalf ( "Could not start RPC api: %v" , err )
}
extapiURL = fmt . Sprintf ( "http://%s" , httpEndpoint )
log . Info ( "HTTP endpoint opened" , "url" , extapiURL )
defer func ( ) {
listener . Close ( )
log . Info ( "HTTP endpoint closed" , "url" , httpEndpoint )
} ( )
}
2018-11-21 15:30:00 +00:00
if ! c . GlobalBool ( utils . IPCDisabledFlag . Name ) {
2018-09-05 15:36:14 +00:00
if c . IsSet ( utils . IPCPathFlag . Name ) {
2018-11-21 15:30:00 +00:00
ipcapiURL = c . GlobalString ( utils . IPCPathFlag . Name )
2018-09-05 15:36:14 +00:00
} else {
ipcapiURL = filepath . Join ( configDir , "clef.ipc" )
}
listener , _ , err := rpc . StartIPCEndpoint ( ipcapiURL , rpcAPI )
if err != nil {
utils . Fatalf ( "Could not start IPC api: %v" , err )
}
log . Info ( "IPC endpoint opened" , "url" , ipcapiURL )
defer func ( ) {
listener . Close ( )
log . Info ( "IPC endpoint closed" , "url" , ipcapiURL )
} ( )
}
2018-11-21 15:30:00 +00:00
if c . GlobalBool ( testFlag . Name ) {
2018-09-05 15:36:14 +00:00
log . Info ( "Performing UI test" )
go testExternalUI ( apiImpl )
}
ui . OnSignerStartup ( core . StartupInfo {
Info : map [ string ] interface { } {
"extapi_version" : ExternalAPIVersion ,
"intapi_version" : InternalAPIVersion ,
"extapi_http" : extapiURL ,
"extapi_ipc" : ipcapiURL ,
} ,
} )
abortChan := make ( chan os . Signal )
signal . Notify ( abortChan , os . Interrupt )
sig := <- abortChan
log . Info ( "Exiting..." , "signal" , sig )
return nil
}
// splitAndTrim splits input separated by a comma
// and trims excessive white space from the substrings.
func splitAndTrim ( input string ) [ ] string {
result := strings . Split ( input , "," )
for i , r := range result {
result [ i ] = strings . TrimSpace ( r )
}
return result
}
// DefaultConfigDir is the default config directory to use for the vaults and other
// persistence requirements.
func DefaultConfigDir ( ) string {
// Try to place the data folder in the user's home dir
home := homeDir ( )
if home != "" {
if runtime . GOOS == "darwin" {
return filepath . Join ( home , "Library" , "Signer" )
} else if runtime . GOOS == "windows" {
return filepath . Join ( home , "AppData" , "Roaming" , "Signer" )
} else {
return filepath . Join ( home , ".clef" )
}
}
// As we cannot guess a stable location, return empty and handle later
return ""
}
func homeDir ( ) string {
if home := os . Getenv ( "HOME" ) ; home != "" {
return home
}
if usr , err := user . Current ( ) ; err == nil {
return usr . HomeDir
}
return ""
}
2018-11-21 15:30:00 +00:00
func readMasterKey ( ctx * cli . Context , ui core . SignerUI ) ( [ ] byte , error ) {
2018-09-05 15:36:14 +00:00
var (
file string
2018-11-21 15:30:00 +00:00
configDir = ctx . GlobalString ( configdirFlag . Name )
2018-09-05 15:36:14 +00:00
)
2018-11-21 15:30:00 +00:00
if ctx . GlobalIsSet ( signerSecretFlag . Name ) {
file = ctx . GlobalString ( signerSecretFlag . Name )
2018-09-05 15:36:14 +00:00
} else {
2018-11-21 15:30:00 +00:00
file = filepath . Join ( configDir , "masterseed.json" )
2018-09-05 15:36:14 +00:00
}
if err := checkFile ( file ) ; err != nil {
return nil , err
}
2018-11-21 15:30:00 +00:00
cipherKey , err := ioutil . ReadFile ( file )
2018-09-05 15:36:14 +00:00
if err != nil {
return nil , err
}
2018-11-21 15:30:00 +00:00
var password string
// 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 )
2018-09-05 15:36:14 +00:00
}
2018-11-21 15:30:00 +00:00
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 ) )
}
2018-09-05 15:36:14 +00:00
// Create vault location
2018-11-21 15:30:00 +00:00
vaultLocation := filepath . Join ( configDir , common . Bytes2Hex ( crypto . Keccak256 ( [ ] byte ( "vault" ) , masterSeed ) [ : 10 ] ) )
2018-09-05 15:36:14 +00:00
err = os . Mkdir ( vaultLocation , 0700 )
if err != nil && ! os . IsExist ( err ) {
return nil , err
}
2018-11-21 15:30:00 +00:00
return masterSeed , nil
2018-09-05 15:36:14 +00:00
}
// checkFile is a convenience function to check if a file
// * exists
2018-11-21 15:30:00 +00:00
// * is mode 0400
2018-09-05 15:36:14 +00:00
func checkFile ( filename string ) error {
info , err := os . Stat ( filename )
if err != nil {
return fmt . Errorf ( "failed stat on %s: %v" , filename , err )
}
// Check the unix permission bits
2018-11-21 15:30:00 +00:00
if info . Mode ( ) . Perm ( ) & 0377 != 0 {
2018-09-05 15:36:14 +00:00
return fmt . Errorf ( "file (%v) has insecure file permissions (%v)" , filename , info . Mode ( ) . String ( ) )
}
return nil
}
// confirm displays a text and asks for user confirmation
func confirm ( text string ) bool {
fmt . Printf ( text )
fmt . Printf ( "\nEnter 'ok' to proceed:\n>" )
text , err := bufio . NewReader ( os . Stdin ) . ReadString ( '\n' )
if err != nil {
log . Crit ( "Failed to read user input" , "err" , err )
}
if text := strings . TrimSpace ( text ) ; text == "ok" {
return true
}
return false
}
func testExternalUI ( api * core . SignerAPI ) {
ctx := context . WithValue ( context . Background ( ) , "remote" , "clef binary" )
ctx = context . WithValue ( ctx , "scheme" , "in-proc" )
ctx = context . WithValue ( ctx , "local" , "main" )
errs := make ( [ ] string , 0 )
api . UI . ShowInfo ( "Testing 'ShowInfo'" )
api . UI . ShowError ( "Testing 'ShowError'" )
checkErr := func ( method string , err error ) {
if err != nil && err != core . ErrRequestDenied {
errs = append ( errs , fmt . Sprintf ( "%v: %v" , method , err . Error ( ) ) )
}
}
var err error
_ , err = api . SignTransaction ( ctx , core . SendTxArgs { From : common . MixedcaseAddress { } } , nil )
checkErr ( "SignTransaction" , err )
_ , err = api . Sign ( ctx , common . MixedcaseAddress { } , common . Hex2Bytes ( "01020304" ) )
checkErr ( "Sign" , err )
_ , err = api . List ( ctx )
checkErr ( "List" , err )
_ , err = api . New ( ctx )
checkErr ( "New" , err )
_ , err = api . Export ( ctx , common . Address { } )
checkErr ( "Export" , err )
_ , err = api . Import ( ctx , json . RawMessage { } )
checkErr ( "Import" , err )
api . UI . ShowInfo ( "Tests completed" )
if len ( errs ) > 0 {
log . Error ( "Got errors" )
for _ , e := range errs {
log . Error ( e )
}
} else {
log . Info ( "No errors" )
}
}
2018-11-21 15:30:00 +00:00
// 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
}
2018-09-05 15:36:14 +00:00
/ * *
//Create Account
curl - H "Content-Type: application/json" - X POST -- data ' { "jsonrpc" : "2.0" , "method" : "account_new" , "params" : [ "test" ] , "id" : 67 } ' localhost : 8550
// List accounts
curl - i - H "Content-Type: application/json" - X POST -- data ' { "jsonrpc" : "2.0" , "method" : "account_list" , "params" : [ "" ] , "id" : 67 } ' http : //localhost:8550/
// Make Transaction
// safeSend(0x12)
// 4401a6e40000000000000000000000000000000000000000000000000000000000000012
// supplied abi
curl - i - H "Content-Type: application/json" - X POST -- data ' { "jsonrpc" : "2.0" , "method" : "account_signTransaction" , "params" : [ { "from" : "0x82A2A876D39022B3019932D30Cd9c97ad5616813" , "gas" : "0x333" , "gasPrice" : "0x123" , "nonce" : "0x0" , "to" : "0x07a565b7ed7d7a678680a4c162885bedbb695fe0" , "value" : "0x10" , "data" : "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012" } , "test" ] , "id" : 67 } ' http : //localhost:8550/
// Not supplied
curl - i - H "Content-Type: application/json" - X POST -- data ' { "jsonrpc" : "2.0" , "method" : "account_signTransaction" , "params" : [ { "from" : "0x82A2A876D39022B3019932D30Cd9c97ad5616813" , "gas" : "0x333" , "gasPrice" : "0x123" , "nonce" : "0x0" , "to" : "0x07a565b7ed7d7a678680a4c162885bedbb695fe0" , "value" : "0x10" , "data" : "0x4401a6e40000000000000000000000000000000000000000000000000000000000000012" } ] , "id" : 67 } ' http : //localhost:8550/
// Sign data
curl - i - H "Content-Type: application/json" - X POST -- data ' { "jsonrpc" : "2.0" , "method" : "account_sign" , "params" : [ "0x694267f14675d7e1b9494fd8d72fefe1755710fa" , "bazonk gaz baz" ] , "id" : 67 } ' http : //localhost:8550/
* * /