diff options
-rw-r--r-- | cmd/swarm/access.go | 47 | ||||
-rw-r--r-- | cmd/swarm/access_test.go | 176 | ||||
-rw-r--r-- | cmd/swarm/main.go | 1 | ||||
-rw-r--r-- | cmd/utils/flags.go | 4 | ||||
-rw-r--r-- | consensus/clique/clique.go | 2 | ||||
-rw-r--r-- | core/vm/evm.go | 21 | ||||
-rw-r--r-- | core/vm/interpreter.go | 25 | ||||
-rw-r--r-- | eth/tracers/tracer_test.go | 2 | ||||
-rw-r--r-- | rlp/typecache.go | 2 | ||||
-rw-r--r-- | swarm/api/act.go | 149 | ||||
-rw-r--r-- | swarm/api/encrypt.go | 22 | ||||
-rw-r--r-- | swarm/api/http/server.go | 2 | ||||
-rw-r--r-- | swarm/api/http/server_test.go | 2 | ||||
-rw-r--r-- | swarm/storage/encryption/encryption.go | 126 | ||||
-rw-r--r-- | swarm/storage/encryption/encryption_test.go | 47 | ||||
-rw-r--r-- | swarm/storage/hasherstore.go | 100 | ||||
-rw-r--r-- | swarm/testutil/file.go | 44 |
17 files changed, 442 insertions, 330 deletions
diff --git a/cmd/swarm/access.go b/cmd/swarm/access.go index df1edf046..67e852dde 100644 --- a/cmd/swarm/access.go +++ b/cmd/swarm/access.go @@ -51,7 +51,7 @@ func accessNewPass(ctx *cli.Context) { password = getPassPhrase("", 0, makePasswordList(ctx)) dryRun = ctx.Bool(SwarmDryRunFlag.Name) ) - accessKey, ae, err = api.DoPasswordNew(ctx, password, salt) + accessKey, ae, err = api.DoPassword(ctx, password, salt) if err != nil { utils.Fatalf("error getting session key: %v", err) } @@ -84,7 +84,7 @@ func accessNewPK(ctx *cli.Context) { granteePublicKey = ctx.String(SwarmAccessGrantKeyFlag.Name) dryRun = ctx.Bool(SwarmDryRunFlag.Name) ) - sessionKey, ae, err = api.DoPKNew(ctx, privateKey, granteePublicKey, salt) + sessionKey, ae, err = api.DoPK(ctx, privateKey, granteePublicKey, salt) if err != nil { utils.Fatalf("error getting session key: %v", err) } @@ -109,23 +109,38 @@ func accessNewACT(ctx *cli.Context) { } var ( - ae *api.AccessEntry - actManifest *api.Manifest - accessKey []byte - err error - ref = args[0] - grantees = []string{} - actFilename = ctx.String(SwarmAccessGrantKeysFlag.Name) - privateKey = getPrivKey(ctx) - dryRun = ctx.Bool(SwarmDryRunFlag.Name) + ae *api.AccessEntry + actManifest *api.Manifest + accessKey []byte + err error + ref = args[0] + pkGrantees = []string{} + passGrantees = []string{} + pkGranteesFilename = ctx.String(SwarmAccessGrantKeysFlag.Name) + passGranteesFilename = ctx.String(utils.PasswordFileFlag.Name) + privateKey = getPrivKey(ctx) + dryRun = ctx.Bool(SwarmDryRunFlag.Name) ) + if pkGranteesFilename == "" && passGranteesFilename == "" { + utils.Fatalf("you have to provide either a grantee public-keys file or an encryption passwords file (or both)") + } - bytes, err := ioutil.ReadFile(actFilename) - if err != nil { - utils.Fatalf("had an error reading the grantee public key list") + if pkGranteesFilename != "" { + bytes, err := ioutil.ReadFile(pkGranteesFilename) + if err != nil { + utils.Fatalf("had an error reading the grantee public key list") + } + pkGrantees = strings.Split(string(bytes), "\n") + } + + if passGranteesFilename != "" { + bytes, err := ioutil.ReadFile(passGranteesFilename) + if err != nil { + utils.Fatalf("could not read password filename: %v", err) + } + passGrantees = strings.Split(string(bytes), "\n") } - grantees = strings.Split(string(bytes), "\n") - accessKey, ae, actManifest, err = api.DoACTNew(ctx, privateKey, salt, grantees) + accessKey, ae, actManifest, err = api.DoACT(ctx, privateKey, salt, pkGrantees, passGrantees) if err != nil { utils.Fatalf("error generating ACT manifest: %v", err) } diff --git a/cmd/swarm/access_test.go b/cmd/swarm/access_test.go index 384d25630..106e6dd91 100644 --- a/cmd/swarm/access_test.go +++ b/cmd/swarm/access_test.go @@ -28,7 +28,6 @@ import ( gorand "math/rand" "net/http" "os" - "path/filepath" "strings" "testing" "time" @@ -39,6 +38,12 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/swarm/api" swarm "github.com/ethereum/go-ethereum/swarm/api/client" + "github.com/ethereum/go-ethereum/swarm/testutil" +) + +const ( + hashRegexp = `[a-f\d]{128}` + data = "notsorandomdata" ) var DefaultCurve = crypto.S256() @@ -53,23 +58,8 @@ func TestAccessPassword(t *testing.T) { defer cluster.Shutdown() proxyNode := cluster.Nodes[0] - // create a tmp file - tmp, err := ioutil.TempDir("", "swarm-test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmp) - - // write data to file - data := "notsorandomdata" - dataFilename := filepath.Join(tmp, "data.txt") - - err = ioutil.WriteFile(dataFilename, []byte(data), 0666) - if err != nil { - t.Fatal(err) - } - - hashRegexp := `[a-f\d]{128}` + dataFilename := testutil.TempFileWithContent(t, data) + defer os.RemoveAll(dataFilename) // upload the file with 'swarm up' and expect a hash up := runSwarm(t, @@ -86,14 +76,14 @@ func TestAccessPassword(t *testing.T) { } ref := matches[0] - - password := "smth" - passwordFilename := filepath.Join(tmp, "password.txt") - - err = ioutil.WriteFile(passwordFilename, []byte(password), 0666) + tmp, err := ioutil.TempDir("", "swarm-test") if err != nil { t.Fatal(err) } + defer os.RemoveAll(tmp) + password := "smth" + passwordFilename := testutil.TempFileWithContent(t, "smth") + defer os.RemoveAll(passwordFilename) up = runSwarm(t, "access", @@ -193,12 +183,8 @@ func TestAccessPassword(t *testing.T) { t.Errorf("expected decrypted data %q, got %q", data, string(d)) } - wrongPasswordFilename := filepath.Join(tmp, "password-wrong.txt") - - err = ioutil.WriteFile(wrongPasswordFilename, []byte("just wr0ng"), 0666) - if err != nil { - t.Fatal(err) - } + wrongPasswordFilename := testutil.TempFileWithContent(t, "just wr0ng") + defer os.RemoveAll(wrongPasswordFilename) //download file with 'swarm down' with wrong password up = runSwarm(t, @@ -227,22 +213,8 @@ func TestAccessPK(t *testing.T) { cluster := newTestCluster(t, 2) defer cluster.Shutdown() - // create a tmp file - tmp, err := ioutil.TempFile("", "swarm-test") - if err != nil { - t.Fatal(err) - } - defer tmp.Close() - defer os.Remove(tmp.Name()) - - // write data to file - data := "notsorandomdata" - _, err = io.WriteString(tmp, data) - if err != nil { - t.Fatal(err) - } - - hashRegexp := `[a-f\d]{128}` + dataFilename := testutil.TempFileWithContent(t, data) + defer os.RemoveAll(dataFilename) // upload the file with 'swarm up' and expect a hash up := runSwarm(t, @@ -250,7 +222,7 @@ func TestAccessPK(t *testing.T) { cluster.Nodes[0].URL, "up", "--encrypt", - tmp.Name()) + dataFilename) _, matches := up.ExpectRegexp(hashRegexp) up.ExpectExit() @@ -259,7 +231,6 @@ func TestAccessPK(t *testing.T) { } ref := matches[0] - pk := cluster.Nodes[0].PrivateKey granteePubKey := crypto.CompressPubkey(&pk.PublicKey) @@ -268,22 +239,15 @@ func TestAccessPK(t *testing.T) { t.Fatal(err) } - passFile, err := ioutil.TempFile("", "swarm-test") - if err != nil { - t.Fatal(err) - } - defer passFile.Close() - defer os.Remove(passFile.Name()) - _, err = io.WriteString(passFile, testPassphrase) - if err != nil { - t.Fatal(err) - } + passwordFilename := testutil.TempFileWithContent(t, testPassphrase) + defer os.RemoveAll(passwordFilename) + _, publisherAccount := getTestAccount(t, publisherDir) up = runSwarm(t, "--bzzaccount", publisherAccount.Address.String(), "--password", - passFile.Name(), + passwordFilename, "--datadir", publisherDir, "--bzzapi", @@ -309,7 +273,7 @@ func TestAccessPK(t *testing.T) { "--bzzaccount", publisherAccount.Address.String(), "--password", - passFile.Name(), + passwordFilename, "--datadir", publisherDir, "print-keys", @@ -390,37 +354,24 @@ func TestAccessACTScale(t *testing.T) { testAccessACT(t, 1000) } -// TestAccessACT tests the e2e creation, uploading and downloading of an ACT type access control +// TestAccessACT tests the e2e creation, uploading and downloading of an ACT access control with both EC keys AND password protection // the test fires up a 3 node cluster, then randomly picks 2 nodes which will be acting as grantees to the data -// set. the third node should fail decoding the reference as it will not be granted access. the publisher uploads through -// one of the nodes then disappears. If `bogusEntries` is bigger than 0, the test will generate the number of bogus act entries -// to test what happens at scale +// set and also protects the ACT with a password. the third node should fail decoding the reference as it will not be granted access. +// the third node then then tries to download using a correct password (and succeeds) then uses a wrong password and fails. +// the publisher uploads through one of the nodes then disappears. func testAccessACT(t *testing.T, bogusEntries int) { // Setup Swarm and upload a test file to it - cluster := newTestCluster(t, 3) + const clusterSize = 3 + cluster := newTestCluster(t, clusterSize) defer cluster.Shutdown() var uploadThroughNode = cluster.Nodes[0] client := swarm.NewClient(uploadThroughNode.URL) r1 := gorand.New(gorand.NewSource(time.Now().UnixNano())) - nodeToSkip := r1.Intn(3) // a number between 0 and 2 (node indices in `cluster`) - // create a tmp file - tmp, err := ioutil.TempFile("", "swarm-test") - if err != nil { - t.Fatal(err) - } - defer tmp.Close() - defer os.Remove(tmp.Name()) - - // write data to file - data := "notsorandomdata" - _, err = io.WriteString(tmp, data) - if err != nil { - t.Fatal(err) - } - - hashRegexp := `[a-f\d]{128}` + nodeToSkip := r1.Intn(clusterSize) // a number between 0 and 2 (node indices in `cluster`) + dataFilename := testutil.TempFileWithContent(t, data) + defer os.RemoveAll(dataFilename) // upload the file with 'swarm up' and expect a hash up := runSwarm(t, @@ -428,7 +379,7 @@ func testAccessACT(t *testing.T, bogusEntries int) { cluster.Nodes[0].URL, "up", "--encrypt", - tmp.Name()) + dataFilename) _, matches := up.ExpectRegexp(hashRegexp) up.ExpectExit() @@ -464,41 +415,25 @@ func testAccessACT(t *testing.T, bogusEntries int) { } grantees = bogusGrantees } - - granteesPubkeyListFile, err := ioutil.TempFile("", "grantees-pubkey-list") - if err != nil { - t.Fatal(err) - } - defer granteesPubkeyListFile.Close() - defer os.Remove(granteesPubkeyListFile.Name()) - - _, err = granteesPubkeyListFile.WriteString(strings.Join(grantees, "\n")) - if err != nil { - t.Fatal(err) - } + granteesPubkeyListFile := testutil.TempFileWithContent(t, strings.Join(grantees, "\n")) + defer os.RemoveAll(granteesPubkeyListFile) publisherDir, err := ioutil.TempDir("", "swarm-account-dir-temp") if err != nil { t.Fatal(err) } + defer os.RemoveAll(publisherDir) - passFile, err := ioutil.TempFile("", "swarm-test") - if err != nil { - t.Fatal(err) - } - defer passFile.Close() - defer os.Remove(passFile.Name()) - _, err = io.WriteString(passFile, testPassphrase) - if err != nil { - t.Fatal(err) - } - + passwordFilename := testutil.TempFileWithContent(t, testPassphrase) + defer os.RemoveAll(passwordFilename) + actPasswordFilename := testutil.TempFileWithContent(t, "smth") + defer os.RemoveAll(actPasswordFilename) _, publisherAccount := getTestAccount(t, publisherDir) up = runSwarm(t, "--bzzaccount", publisherAccount.Address.String(), "--password", - passFile.Name(), + passwordFilename, "--datadir", publisherDir, "--bzzapi", @@ -507,7 +442,9 @@ func testAccessACT(t *testing.T, bogusEntries int) { "new", "act", "--grant-keys", - granteesPubkeyListFile.Name(), + granteesPubkeyListFile, + "--password", + actPasswordFilename, ref, ) @@ -523,7 +460,7 @@ func testAccessACT(t *testing.T, bogusEntries int) { "--bzzaccount", publisherAccount.Address.String(), "--password", - passFile.Name(), + passwordFilename, "--datadir", publisherDir, "print-keys", @@ -562,9 +499,7 @@ func testAccessACT(t *testing.T, bogusEntries int) { if len(a.Salt) < 32 { t.Fatalf(`got salt with length %v, expected not less the 32 bytes`, len(a.Salt)) } - if a.KdfParams != nil { - t.Fatal("manifest access kdf params should be nil") - } + if a.Publisher != pkComp { t.Fatal("publisher key did not match") } @@ -588,6 +523,25 @@ func testAccessACT(t *testing.T, bogusEntries int) { t.Fatalf("should be a 401") } + // try downloading using a password instead, using the unauthorized node + passwordUrl := strings.Replace(url, "http://", "http://:smth@", -1) + response, err = httpClient.Get(passwordUrl) + if err != nil { + t.Fatal(err) + } + if response.StatusCode != http.StatusOK { + t.Fatal("should be a 200") + } + + // now try with the wrong password, expect 401 + passwordUrl = strings.Replace(url, "http://", "http://:smthWrong@", -1) + response, err = httpClient.Get(passwordUrl) + if err != nil { + t.Fatal(err) + } + if response.StatusCode != http.StatusUnauthorized { + t.Fatal("should be a 401") + } continue } diff --git a/cmd/swarm/main.go b/cmd/swarm/main.go index e65440937..c93344c42 100644 --- a/cmd/swarm/main.go +++ b/cmd/swarm/main.go @@ -319,6 +319,7 @@ func init() { Flags: []cli.Flag{ SwarmAccessGrantKeysFlag, SwarmDryRunFlag, + utils.PasswordFileFlag, }, Name: "act", Usage: "encrypts a reference with the node's private key and a given grantee's public key and embeds it into a root manifest", diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index f431621ba..0fecae9aa 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -343,12 +343,12 @@ var ( } MinerGasPriceFlag = BigFlag{ Name: "miner.gasprice", - Usage: "Minimal gas price for mining a transactions", + Usage: "Minimum gas price for mining a transaction", Value: eth.DefaultConfig.MinerGasPrice, } MinerLegacyGasPriceFlag = BigFlag{ Name: "gasprice", - Usage: "Minimal gas price for mining a transactions (deprecated, use --miner.gasprice)", + Usage: "Minimum gas price for mining a transaction (deprecated, use --miner.gasprice)", Value: eth.DefaultConfig.MinerGasPrice, } MinerEtherbaseFlag = cli.StringFlag{ diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 547290984..0ff72e55c 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -388,7 +388,7 @@ func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash commo } } // If we're at an checkpoint block, make a snapshot if it's known - if number%c.config.Epoch == 0 { + if number == 0 || (number%c.config.Epoch == 0 && chain.GetHeaderByNumber(number-1) == nil) { checkpoint := chain.GetHeaderByNumber(number) if checkpoint != nil { hash := checkpoint.Hash() diff --git a/core/vm/evm.go b/core/vm/evm.go index a24f6f386..58618f811 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -41,7 +41,7 @@ type ( ) // run runs the given contract and takes care of running precompiles with a fallback to the byte code interpreter. -func run(evm *EVM, contract *Contract, input []byte) ([]byte, error) { +func run(evm *EVM, contract *Contract, input []byte, readOnly bool) ([]byte, error) { if contract.CodeAddr != nil { precompiles := PrecompiledContractsHomestead if evm.ChainConfig().IsByzantium(evm.BlockNumber) { @@ -61,7 +61,7 @@ func run(evm *EVM, contract *Contract, input []byte) ([]byte, error) { }(evm.interpreter) evm.interpreter = interpreter } - return interpreter.Run(contract, input) + return interpreter.Run(contract, input, readOnly) } } return nil, ErrNoCompatibleInterpreter @@ -210,7 +210,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err) }() } - ret, err = run(evm, contract, input) + ret, err = run(evm, contract, input, false) // When an error was returned by the EVM or when setting the creation code // above we revert to the snapshot and consume any gas remaining. Additionally @@ -255,7 +255,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, contract := NewContract(caller, to, value, gas) contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) - ret, err = run(evm, contract, input) + ret, err = run(evm, contract, input, false) if err != nil { evm.StateDB.RevertToSnapshot(snapshot) if err != errExecutionReverted { @@ -288,7 +288,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by contract := NewContract(caller, to, nil, gas).AsDelegate() contract.SetCallCode(&addr, evm.StateDB.GetCodeHash(addr), evm.StateDB.GetCode(addr)) - ret, err = run(evm, contract, input) + ret, err = run(evm, contract, input, false) if err != nil { evm.StateDB.RevertToSnapshot(snapshot) if err != errExecutionReverted { @@ -310,13 +310,6 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth } - // Make sure the readonly is only set if we aren't in readonly yet - // this makes also sure that the readonly flag isn't removed for - // child calls. - if !evm.interpreter.IsReadOnly() { - evm.interpreter.SetReadOnly(true) - defer func() { evm.interpreter.SetReadOnly(false) }() - } var ( to = AccountRef(addr) @@ -331,7 +324,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte // When an error was returned by the EVM or when setting the creation code // above we revert to the snapshot and consume any gas remaining. Additionally // when we're in Homestead this also counts for code storage gas errors. - ret, err = run(evm, contract, input) + ret, err = run(evm, contract, input, true) if err != nil { evm.StateDB.RevertToSnapshot(snapshot) if err != errExecutionReverted { @@ -382,7 +375,7 @@ func (evm *EVM) create(caller ContractRef, code []byte, gas uint64, value *big.I } start := time.Now() - ret, err := run(evm, contract, nil) + ret, err := run(evm, contract, nil, false) // check whether the max code size has been exceeded maxCodeSizeExceeded := evm.ChainConfig().IsEIP158(evm.BlockNumber) && len(ret) > params.MaxCodeSize diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 1e9202424..0f1b07342 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -48,7 +48,7 @@ type Config struct { type Interpreter interface { // Run loops and evaluates the contract's code with the given input data and returns // the return byte-slice and an error if one occurred. - Run(contract *Contract, input []byte) ([]byte, error) + Run(contract *Contract, input []byte, static bool) ([]byte, error) // CanRun tells if the contract, passed as an argument, can be // run by the current interpreter. This is meant so that the // caller can do something like: @@ -61,10 +61,6 @@ type Interpreter interface { // } // ``` CanRun([]byte) bool - // IsReadOnly reports if the interpreter is in read only mode. - IsReadOnly() bool - // SetReadOnly sets (or unsets) read only mode in the interpreter. - SetReadOnly(bool) } // EVMInterpreter represents an EVM interpreter @@ -125,7 +121,7 @@ func (in *EVMInterpreter) enforceRestrictions(op OpCode, operation operation, st // It's important to note that any errors returned by the interpreter should be // considered a revert-and-consume-all-gas operation except for // errExecutionReverted which means revert-and-keep-gas-left. -func (in *EVMInterpreter) Run(contract *Contract, input []byte) (ret []byte, err error) { +func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) { if in.intPool == nil { in.intPool = poolOfIntPools.get() defer func() { @@ -138,6 +134,13 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte) (ret []byte, err in.evm.depth++ defer func() { in.evm.depth-- }() + // Make sure the readOnly is only set if we aren't in readOnly yet. + // This makes also sure that the readOnly flag isn't removed for child calls. + if readOnly && !in.readOnly { + in.readOnly = true + defer func() { in.readOnly = false }() + } + // Reset the previous call's return data. It's unimportant to preserve the old buffer // as every returning call will return new data anyway. in.returnData = nil @@ -263,13 +266,3 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte) (ret []byte, err func (in *EVMInterpreter) CanRun(code []byte) bool { return true } - -// IsReadOnly reports if the interpreter is in read only mode. -func (in *EVMInterpreter) IsReadOnly() bool { - return in.readOnly -} - -// SetReadOnly sets (or unsets) read only mode in the interpreter. -func (in *EVMInterpreter) SetReadOnly(ro bool) { - in.readOnly = ro -} diff --git a/eth/tracers/tracer_test.go b/eth/tracers/tracer_test.go index 117c376b8..58b624724 100644 --- a/eth/tracers/tracer_test.go +++ b/eth/tracers/tracer_test.go @@ -49,7 +49,7 @@ func runTrace(tracer *Tracer) (json.RawMessage, error) { contract := vm.NewContract(account{}, account{}, big.NewInt(0), 10000) contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} - _, err := env.Interpreter().Run(contract, []byte{}) + _, err := env.Interpreter().Run(contract, []byte{}, false) if err != nil { return nil, err } diff --git a/rlp/typecache.go b/rlp/typecache.go index 3df799e1e..8c2dd518e 100644 --- a/rlp/typecache.go +++ b/rlp/typecache.go @@ -76,7 +76,7 @@ func cachedTypeInfo1(typ reflect.Type, tags tags) (*typeinfo, error) { // another goroutine got the write lock first return info, nil } - // put a dummmy value into the cache before generating. + // put a dummy value into the cache before generating. // if the generator tries to lookup itself, it will get // the dummy value and won't call itself recursively. typeCache[key] = new(typeinfo) diff --git a/swarm/api/act.go b/swarm/api/act.go index b1a594783..52d909827 100644 --- a/swarm/api/act.go +++ b/swarm/api/act.go @@ -102,6 +102,7 @@ const AccessTypePass = AccessType("pass") const AccessTypePK = AccessType("pk") const AccessTypeACT = AccessType("act") +// NewAccessEntryPassword creates a manifest AccessEntry in order to create an ACT protected by a password func NewAccessEntryPassword(salt []byte, kdfParams *KdfParams) (*AccessEntry, error) { if len(salt) != 32 { return nil, fmt.Errorf("salt should be 32 bytes long") @@ -113,6 +114,7 @@ func NewAccessEntryPassword(salt []byte, kdfParams *KdfParams) (*AccessEntry, er }, nil } +// NewAccessEntryPK creates a manifest AccessEntry in order to create an ACT protected by a pair of Elliptic Curve keys func NewAccessEntryPK(publisher string, salt []byte) (*AccessEntry, error) { if len(publisher) != 66 { return nil, fmt.Errorf("publisher should be 66 characters long, got %d", len(publisher)) @@ -127,6 +129,7 @@ func NewAccessEntryPK(publisher string, salt []byte) (*AccessEntry, error) { }, nil } +// NewAccessEntryACT creates a manifest AccessEntry in order to create an ACT protected by a combination of EC keys and passwords func NewAccessEntryACT(publisher string, salt []byte, act string) (*AccessEntry, error) { if len(salt) != 32 { return nil, fmt.Errorf("salt should be 32 bytes long") @@ -140,15 +143,19 @@ func NewAccessEntryACT(publisher string, salt []byte, act string) (*AccessEntry, Publisher: publisher, Salt: salt, Act: act, + KdfParams: DefaultKdfParams, }, nil } +// NOOPDecrypt is a generic decrypt function that is passed into the API in places where real ACT decryption capabilities are +// either unwanted, or alternatively, cannot be implemented in the immediate scope func NOOPDecrypt(*ManifestEntry) error { return nil } var DefaultKdfParams = NewKdfParams(262144, 1, 8) +// NewKdfParams returns a KdfParams struct with the given scrypt params func NewKdfParams(n, p, r int) *KdfParams { return &KdfParams{ @@ -161,15 +168,20 @@ func NewKdfParams(n, p, r int) *KdfParams { // NewSessionKeyPassword creates a session key based on a shared secret (password) and the given salt // and kdf parameters in the access entry func NewSessionKeyPassword(password string, accessEntry *AccessEntry) ([]byte, error) { - if accessEntry.Type != AccessTypePass { + if accessEntry.Type != AccessTypePass && accessEntry.Type != AccessTypeACT { return nil, errors.New("incorrect access entry type") + } + return sessionKeyPassword(password, accessEntry.Salt, accessEntry.KdfParams) +} + +func sessionKeyPassword(password string, salt []byte, kdfParams *KdfParams) ([]byte, error) { return scrypt.Key( []byte(password), - accessEntry.Salt, - accessEntry.KdfParams.N, - accessEntry.KdfParams.R, - accessEntry.KdfParams.P, + salt, + kdfParams.N, + kdfParams.R, + kdfParams.P, 32, ) } @@ -188,9 +200,6 @@ func NewSessionKeyPK(private *ecdsa.PrivateKey, public *ecdsa.PublicKey, salt [] return sessionKey, nil } -func (a *API) NodeSessionKey(privateKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey, salt []byte) ([]byte, error) { - return NewSessionKeyPK(privateKey, publicKey, salt) -} func (a *API) doDecrypt(ctx context.Context, credentials string, pk *ecdsa.PrivateKey) DecryptFunc { return func(m *ManifestEntry) error { if m.Access == nil { @@ -242,7 +251,7 @@ func (a *API) doDecrypt(ctx context.Context, credentials string, pk *ecdsa.Priva if err != nil { return ErrDecrypt } - key, err := a.NodeSessionKey(pk, publisher, m.Access.Salt) + key, err := NewSessionKeyPK(pk, publisher, m.Access.Salt) if err != nil { return ErrDecrypt } @@ -261,6 +270,11 @@ func (a *API) doDecrypt(ctx context.Context, credentials string, pk *ecdsa.Priva m.Access = nil return nil case "act": + var ( + sessionKey []byte + err error + ) + publisherBytes, err := hex.DecodeString(m.Access.Publisher) if err != nil { return ErrDecrypt @@ -270,40 +284,35 @@ func (a *API) doDecrypt(ctx context.Context, credentials string, pk *ecdsa.Priva return ErrDecrypt } - sessionKey, err := a.NodeSessionKey(pk, publisher, m.Access.Salt) + sessionKey, err = NewSessionKeyPK(pk, publisher, m.Access.Salt) if err != nil { return ErrDecrypt } - hasher := sha3.NewKeccak256() - hasher.Write(append(sessionKey, 0)) - lookupKey := hasher.Sum(nil) - - hasher.Reset() - - hasher.Write(append(sessionKey, 1)) - accessKeyDecryptionKey := hasher.Sum(nil) - - lk := hex.EncodeToString(lookupKey) - list, err := a.GetManifestList(ctx, NOOPDecrypt, storage.Address(common.Hex2Bytes(m.Access.Act)), lk) - - found := "" - for _, v := range list.Entries { - if v.Path == lk { - found = v.Hash - } - } - - if found == "" { - return ErrDecrypt - } - - v, err := hex.DecodeString(found) + found, ciphertext, decryptionKey, err := a.getACTDecryptionKey(ctx, storage.Address(common.Hex2Bytes(m.Access.Act)), sessionKey) if err != nil { return err } - enc := NewRefEncryption(len(v) - 8) - decodedRef, err := enc.Decrypt(v, accessKeyDecryptionKey) + if !found { + // try to fall back to password + if credentials != "" { + sessionKey, err = NewSessionKeyPassword(credentials, m.Access) + if err != nil { + return err + } + found, ciphertext, decryptionKey, err = a.getACTDecryptionKey(ctx, storage.Address(common.Hex2Bytes(m.Access.Act)), sessionKey) + if err != nil { + return err + } + if !found { + return ErrDecrypt + } + } else { + return ErrDecrypt + } + } + enc := NewRefEncryption(len(ciphertext) - 8) + decodedRef, err := enc.Decrypt(ciphertext, decryptionKey) if err != nil { return ErrDecrypt } @@ -326,6 +335,33 @@ func (a *API) doDecrypt(ctx context.Context, credentials string, pk *ecdsa.Priva } } +func (a *API) getACTDecryptionKey(ctx context.Context, actManifestAddress storage.Address, sessionKey []byte) (found bool, ciphertext, decryptionKey []byte, err error) { + hasher := sha3.NewKeccak256() + hasher.Write(append(sessionKey, 0)) + lookupKey := hasher.Sum(nil) + hasher.Reset() + + hasher.Write(append(sessionKey, 1)) + accessKeyDecryptionKey := hasher.Sum(nil) + hasher.Reset() + + lk := hex.EncodeToString(lookupKey) + list, err := a.GetManifestList(ctx, NOOPDecrypt, actManifestAddress, lk) + if err != nil { + return false, nil, nil, err + } + for _, v := range list.Entries { + if v.Path == lk { + cipherTextBytes, err := hex.DecodeString(v.Hash) + if err != nil { + return false, nil, nil, err + } + return true, cipherTextBytes, accessKeyDecryptionKey, nil + } + } + return false, nil, nil, nil +} + func GenerateAccessControlManifest(ctx *cli.Context, ref string, accessKey []byte, ae *AccessEntry) (*Manifest, error) { refBytes, err := hex.DecodeString(ref) if err != nil { @@ -352,7 +388,9 @@ func GenerateAccessControlManifest(ctx *cli.Context, ref string, accessKey []byt return m, nil } -func DoPKNew(ctx *cli.Context, privateKey *ecdsa.PrivateKey, granteePublicKey string, salt []byte) (sessionKey []byte, ae *AccessEntry, err error) { +// DoPK is a helper function to the CLI API that handles the entire business logic for +// creating a session key and access entry given the cli context, ec keys and salt +func DoPK(ctx *cli.Context, privateKey *ecdsa.PrivateKey, granteePublicKey string, salt []byte) (sessionKey []byte, ae *AccessEntry, err error) { if granteePublicKey == "" { return nil, nil, errors.New("need a grantee Public Key") } @@ -383,9 +421,11 @@ func DoPKNew(ctx *cli.Context, privateKey *ecdsa.PrivateKey, granteePublicKey st return sessionKey, ae, nil } -func DoACTNew(ctx *cli.Context, privateKey *ecdsa.PrivateKey, salt []byte, grantees []string) (accessKey []byte, ae *AccessEntry, actManifest *Manifest, err error) { - if len(grantees) == 0 { - return nil, nil, nil, errors.New("did not get any grantee public keys") +// DoACT is a helper function to the CLI API that handles the entire business logic for +// creating a access key, access entry and ACT manifest (including uploading it) given the cli context, ec keys, password grantees and salt +func DoACT(ctx *cli.Context, privateKey *ecdsa.PrivateKey, salt []byte, grantees []string, encryptPasswords []string) (accessKey []byte, ae *AccessEntry, actManifest *Manifest, err error) { + if len(grantees) == 0 && len(encryptPasswords) == 0 { + return nil, nil, nil, errors.New("did not get any grantee public keys or any encryption passwords") } publisherPub := hex.EncodeToString(crypto.CompressPubkey(&privateKey.PublicKey)) @@ -430,7 +470,31 @@ func DoACTNew(ctx *cli.Context, privateKey *ecdsa.PrivateKey, salt []byte, grant enc := NewRefEncryption(len(accessKey)) encryptedAccessKey, err := enc.Encrypt(accessKey, accessKeyEncryptionKey) + if err != nil { + return nil, nil, nil, err + } + lookupPathEncryptedAccessKeyMap[hex.EncodeToString(lookupKey)] = hex.EncodeToString(encryptedAccessKey) + } + for _, pass := range encryptPasswords { + sessionKey, err := sessionKeyPassword(pass, salt, DefaultKdfParams) + if err != nil { + return nil, nil, nil, err + } + hasher := sha3.NewKeccak256() + hasher.Write(append(sessionKey, 0)) + lookupKey := hasher.Sum(nil) + + hasher.Reset() + hasher.Write(append(sessionKey, 1)) + + accessKeyEncryptionKey := hasher.Sum(nil) + + enc := NewRefEncryption(len(accessKey)) + encryptedAccessKey, err := enc.Encrypt(accessKey, accessKeyEncryptionKey) + if err != nil { + return nil, nil, nil, err + } lookupPathEncryptedAccessKeyMap[hex.EncodeToString(lookupKey)] = hex.EncodeToString(encryptedAccessKey) } @@ -454,7 +518,10 @@ func DoACTNew(ctx *cli.Context, privateKey *ecdsa.PrivateKey, salt []byte, grant return accessKey, ae, m, nil } -func DoPasswordNew(ctx *cli.Context, password string, salt []byte) (sessionKey []byte, ae *AccessEntry, err error) { +// DoPassword is a helper function to the CLI API that handles the entire business logic for +// creating a session key and an access entry given the cli context, password and salt. +// By default - DefaultKdfParams are used as the scrypt params +func DoPassword(ctx *cli.Context, password string, salt []byte) (sessionKey []byte, ae *AccessEntry, err error) { ae, err = NewAccessEntryPassword(salt, DefaultKdfParams) if err != nil { return nil, nil, err diff --git a/swarm/api/encrypt.go b/swarm/api/encrypt.go index 9a2e36914..ffe6c16d2 100644 --- a/swarm/api/encrypt.go +++ b/swarm/api/encrypt.go @@ -25,27 +25,27 @@ import ( ) type RefEncryption struct { - spanEncryption encryption.Encryption - dataEncryption encryption.Encryption - span []byte + refSize int + span []byte } func NewRefEncryption(refSize int) *RefEncryption { span := make([]byte, 8) binary.LittleEndian.PutUint64(span, uint64(refSize)) return &RefEncryption{ - spanEncryption: encryption.New(0, uint32(refSize/32), sha3.NewKeccak256), - dataEncryption: encryption.New(refSize, 0, sha3.NewKeccak256), - span: span, + refSize: refSize, + span: span, } } func (re *RefEncryption) Encrypt(ref []byte, key []byte) ([]byte, error) { - encryptedSpan, err := re.spanEncryption.Encrypt(re.span, key) + spanEncryption := encryption.New(key, 0, uint32(re.refSize/32), sha3.NewKeccak256) + encryptedSpan, err := spanEncryption.Encrypt(re.span) if err != nil { return nil, err } - encryptedData, err := re.dataEncryption.Encrypt(ref, key) + dataEncryption := encryption.New(key, re.refSize, 0, sha3.NewKeccak256) + encryptedData, err := dataEncryption.Encrypt(ref) if err != nil { return nil, err } @@ -57,7 +57,8 @@ func (re *RefEncryption) Encrypt(ref []byte, key []byte) ([]byte, error) { } func (re *RefEncryption) Decrypt(ref []byte, key []byte) ([]byte, error) { - decryptedSpan, err := re.spanEncryption.Decrypt(ref[:8], key) + spanEncryption := encryption.New(key, 0, uint32(re.refSize/32), sha3.NewKeccak256) + decryptedSpan, err := spanEncryption.Decrypt(ref[:8]) if err != nil { return nil, err } @@ -67,7 +68,8 @@ func (re *RefEncryption) Decrypt(ref []byte, key []byte) ([]byte, error) { return nil, errors.New("invalid span in encrypted reference") } - decryptedRef, err := re.dataEncryption.Decrypt(ref[8:], key) + dataEncryption := encryption.New(key, re.refSize, 0, sha3.NewKeccak256) + decryptedRef, err := dataEncryption.Decrypt(ref[8:]) if err != nil { return nil, err } diff --git a/swarm/api/http/server.go b/swarm/api/http/server.go index 2aa196396..af1269b93 100644 --- a/swarm/api/http/server.go +++ b/swarm/api/http/server.go @@ -129,7 +129,7 @@ func NewServer(api *api.API, corsString string) *Server { }) mux.Handle("/bzz-immutable:/", methodHandler{ "GET": Adapt( - http.HandlerFunc(server.HandleGet), + http.HandlerFunc(server.HandleBzzGet), defaultMiddlewares..., ), }) diff --git a/swarm/api/http/server_test.go b/swarm/api/http/server_test.go index 2b6a97ebd..efefa9fae 100644 --- a/swarm/api/http/server_test.go +++ b/swarm/api/http/server_test.go @@ -672,7 +672,7 @@ func testBzzGetPath(encrypted bool, t *testing.T) { nonhashresponses := []string{ `cannot resolve name: no DNS to resolve name: "name"`, - `cannot resolve nonhash: immutable address not a content hash: "nonhash"`, + `cannot resolve nonhash: no DNS to resolve name: "nonhash"`, `cannot resolve nonhash: no DNS to resolve name: "nonhash"`, `cannot resolve nonhash: no DNS to resolve name: "nonhash"`, `cannot resolve nonhash: no DNS to resolve name: "nonhash"`, diff --git a/swarm/storage/encryption/encryption.go b/swarm/storage/encryption/encryption.go index e50f2163d..6fbdab062 100644 --- a/swarm/storage/encryption/encryption.go +++ b/swarm/storage/encryption/encryption.go @@ -21,6 +21,7 @@ import ( "encoding/binary" "fmt" "hash" + "sync" ) const KeyLength = 32 @@ -28,84 +29,119 @@ const KeyLength = 32 type Key []byte type Encryption interface { - Encrypt(data []byte, key Key) ([]byte, error) - Decrypt(data []byte, key Key) ([]byte, error) + Encrypt(data []byte) ([]byte, error) + Decrypt(data []byte) ([]byte, error) } type encryption struct { - padding int - initCtr uint32 - hashFunc func() hash.Hash + key Key // the encryption key (hashSize bytes long) + keyLen int // length of the key = length of blockcipher block + padding int // encryption will pad the data upto this if > 0 + initCtr uint32 // initial counter used for counter mode blockcipher + hashFunc func() hash.Hash // hasher constructor function } -func New(padding int, initCtr uint32, hashFunc func() hash.Hash) *encryption { +// New constructs a new encryptor/decryptor +func New(key Key, padding int, initCtr uint32, hashFunc func() hash.Hash) *encryption { return &encryption{ + key: key, + keyLen: len(key), padding: padding, initCtr: initCtr, hashFunc: hashFunc, } } -func (e *encryption) Encrypt(data []byte, key Key) ([]byte, error) { +// Encrypt encrypts the data and does padding if specified +func (e *encryption) Encrypt(data []byte) ([]byte, error) { length := len(data) + outLength := length isFixedPadding := e.padding > 0 - if isFixedPadding && length > e.padding { - return nil, fmt.Errorf("Data length longer than padding, data length %v padding %v", length, e.padding) - } - - paddedData := data - if isFixedPadding && length < e.padding { - paddedData = make([]byte, e.padding) - copy(paddedData[:length], data) - rand.Read(paddedData[length:]) + if isFixedPadding { + if length > e.padding { + return nil, fmt.Errorf("Data length longer than padding, data length %v padding %v", length, e.padding) + } + outLength = e.padding } - return e.transform(paddedData, key), nil + out := make([]byte, outLength) + e.transform(data, out) + return out, nil } -func (e *encryption) Decrypt(data []byte, key Key) ([]byte, error) { +// Decrypt decrypts the data, if padding was used caller must know original length and truncate +func (e *encryption) Decrypt(data []byte) ([]byte, error) { length := len(data) if e.padding > 0 && length != e.padding { return nil, fmt.Errorf("Data length different than padding, data length %v padding %v", length, e.padding) } + out := make([]byte, length) + e.transform(data, out) + return out, nil +} - return e.transform(data, key), nil +// +func (e *encryption) transform(in, out []byte) { + inLength := len(in) + wg := sync.WaitGroup{} + wg.Add((inLength-1)/e.keyLen + 1) + for i := 0; i < inLength; i += e.keyLen { + l := min(e.keyLen, inLength-i) + // call transformations per segment (asyncronously) + go func(i int, x, y []byte) { + defer wg.Done() + e.Transcrypt(i, x, y) + }(i/e.keyLen, in[i:i+l], out[i:i+l]) + } + // pad the rest if out is longer + pad(out[inLength:]) + wg.Wait() } -func (e *encryption) transform(data []byte, key Key) []byte { - dataLength := len(data) - transformedData := make([]byte, dataLength) +// used for segmentwise transformation +// if in is shorter than out, padding is used +func (e *encryption) Transcrypt(i int, in []byte, out []byte) { + // first hash key with counter (initial counter + i) hasher := e.hashFunc() - ctr := e.initCtr - hashSize := hasher.Size() - for i := 0; i < dataLength; i += hashSize { - hasher.Write(key) + hasher.Write(e.key) - ctrBytes := make([]byte, 4) - binary.LittleEndian.PutUint32(ctrBytes, ctr) + ctrBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(ctrBytes, uint32(i)+e.initCtr) + hasher.Write(ctrBytes) - hasher.Write(ctrBytes) + ctrHash := hasher.Sum(nil) + hasher.Reset() - ctrHash := hasher.Sum(nil) - hasher.Reset() - hasher.Write(ctrHash) + // second round of hashing for selective disclosure + hasher.Write(ctrHash) + segmentKey := hasher.Sum(nil) + hasher.Reset() - segmentKey := hasher.Sum(nil) - - hasher.Reset() + // XOR bytes uptil length of in (out must be at least as long) + inLength := len(in) + for j := 0; j < inLength; j++ { + out[j] = in[j] ^ segmentKey[j] + } + // insert padding if out is longer + pad(out[inLength:]) +} - segmentSize := min(hashSize, dataLength-i) - for j := 0; j < segmentSize; j++ { - transformedData[i+j] = data[i+j] ^ segmentKey[j] - } - ctr++ +func pad(b []byte) { + l := len(b) + for total := 0; total < l; { + read, _ := rand.Read(b[total:]) + total += read } - return transformedData } -func GenerateRandomKey() (Key, error) { - key := make([]byte, KeyLength) - _, err := rand.Read(key) - return key, err +// GenerateRandomKey generates a random key of length l +func GenerateRandomKey(l int) Key { + key := make([]byte, l) + var total int + for total < l { + read, _ := rand.Read(key[total:]) + total += read + } + return key } func min(x, y int) int { diff --git a/swarm/storage/encryption/encryption_test.go b/swarm/storage/encryption/encryption_test.go index 5ea546d6b..c3abefdce 100644 --- a/swarm/storage/encryption/encryption_test.go +++ b/swarm/storage/encryption/encryption_test.go @@ -22,34 +22,42 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto/sha3" ) -var expectedTransformedHex = "470c6c67ba1820d5cb4c23ccef22aa2417a323dc97e5f5dced930d74f2932fd178df80ddf129f4f8a4ccec0225c1c2e6765cbbd92bbad8413c50d93d53f7b2fec975d6f29468eccdaf6458f7a3306a7bc207211ea7f9ee5e6951ce6874aef09eb7ff9bed0aa00920b4dcc105e5f1f8dfbdb0564751311d6ceaca1e4e6d988097582638106404c03cfaef8db0e46674d9e8192c1b62d1cd952389cab3a8dee9329fdb059bafd9bae3df3a6b5a9a10961a0333016c99ee3d65cf18ea8ea7b4c386d59dcbf317460d517d5b55504b5992ffb9c3d7c49ebe1fe3ab7b00ad84b1d76d95f8165141260e6980b2ffe8a0000c36dd77ace4c7a781887f831f740d92d8b4848f1b9e237877b988b5a3e23d7b07b2c8eda0ea9fa748ab10bb6bfd7447b66f44e528d24eff31defdecddaf2bb5e6b8e2aa3d2f1a83de44e0ee3789b75dadba1375b7f3a8f7f1eb2d9b78ad1844d425fb76ea6ba45eabe88f1e062d7552d8c7d44c9c66c2753c41892cb675fd1d564b5f4746e76aa4b24ad121a907b8786715483067f46f1cd4a3dad5125f58c3d348db776e99e8a562ebe603e0e98509c72118ee81259b43f1f770dc9220dddbe4d12d2ffabdad76663d1cf30304eacbb43e9bbe0e4f95aae8609bf8a0786e56e0d20d4875d17cec644ca16824424c6c0700e5082ad5288d1a9fec637b75d24c270086a3606b97cc3240314ac123c04ff4d67ef547504f0eeea5e6252f9b5b75e47d32e583c4dee95ac84b6f03fef412e24c09697d6f7bc8408c2118c524f8e277b82658a5869d6d69d7fde469be27ea33fa22b47f3276f60580f2a05b0fbceb67949d2c8e6d8b097ceef6781d962866b23bc71b54597dd05caafbd533ff4e0013f0fc7e202782ba1e6ccd11242a5bc823af9ad0d5bcb8b78d0ce150fc6f8b97c66f9337adaf3878cbb70733044fd14f318d8e389940d07e0593e35f46dc84e7a9a75dadda153bbe5755af0683cfd262ce3797b1bc648a4e5cef5d9aa47d08cf78c6c9dd872096deb856680d5d932abfd5f2024f908ab84d19b762fdcd31209b1d9adf9cbab904bc58f5b00d5a3e0eb2ade52c7c9a678aadedb1b9257263e6145d34ebcc250d0867e2fdc2ed23b4a8e8aa580424308b543b76595f7e09127e8c301d3f6c228e656df2b5e63a89d634db2f87249025ad3b53d2703d6b93c4ab6b1dee4f9b4d2a0b383ed87458e0e1b2a3c04f9a08b885d856ea7ca9abda9a9c8bd28ecce78675b16829493dbeb66963a3665a8386bc4b97d13cfacacfa80b12e36b598d4f09ab7847481a95b35a937bd4f0107598844fc518c4134e8ca55cec024773dcf0a17c71f602406363dcc03912f23cffad9c611e34605fb03fea3d84aac33886efed6c53d455c786231436c01524cd41e2b6c35b339feaa94d7df0b3572db5595c05a1d4a96e1a17e814cea965be81786784af5697b5c7909fd7701d0f5d991f469e20fbc39536239c7a53278c0b1189253d3d978d54b51d33e0c8ac03ca1f08cfd440130944ae9d353b0b7f263a3a27d15290f35c0ad7cf62e8429ebfa10f5471987c53c5d574c9aebb935bdb8339fee9e1030b3f2845c36272642233afba71f81f10322057b51a33b434a4bc45530581c4a636895fc773e20cf6b8a6cef060920310f4cb79f735fc14e758f646b0cf3bf0ce1f1479788f5b73507203d54022c7afd805d46f45e7ede96b2660869939440d7d575fbf372a7f63421728343dfd88c5fa29ea3f15b68fe360f54afa0681093f2c82888d6babf9558159383c1a76264deec2d7fccb20dbd3ab4b3f137fb3dcf253fb6953df2f663d08e8b39909c9dbff98d49377ce0ce03c580b5e1bca6fcf58aceda42b00b1f478cff2995d47c82f2d5a3dc0e9718a0a46d8330353f2039e68d9565d29e77efea91e058ae03140a4b58b297ae664c9f0e7af78f3633f4aeabd95fd367ac5c67eb63fa2bd7035f4adfe3ab8502952d1675b47118b2006b4a5a1b2a3d03aa670862adc5f1ad1b39f0e4a08e2412128e0ef5fa84366b4b50992cc139e7837f8d65f8eae3c8dc2730f2ef1e1585ce95b0fe354c6c853526558a1732171b6017254ba0f0c22273b55caa2ee680791fb34e22bf897a0998156083c83d03310956b1f947ef474c90b2d7e20beae27f5d33b79ad6d9b1b0188e3cf850108f3a02f9386a314b97fb6c946e572e40024da3e4784c523cf2a70e45f6c0f8f35e9b279aecee183ccb30477ee4f5d57c246ab9918495159307935340a92bd46d6519bcc51af4a785a7eb7fa6eae9def89e2411efd2cb2c33f4d605b77af1ae298967fd846453e8b0a55c57706e79d7badd93268d93be27790bbb051496552b094f37bf96843cdf7604dc7f696976bebbe3561c7bd4b3f2843bd07e34a3c3acf0d6c755014a3e9d922dabdbc892511b6af3214958b3531baceab9082c2b4e19ef802b99db2cfa4076ba681efae8a9546582e4cbf07000fb3903727261b93d1bd08809ddc41b2a61b0bfa6b9210b8731394fd9084bc1406bcdd0992410e90f749a087edafb76f6617209855269bb5c89711d2830ea99f2a0e548991ef8dd3e62ced239dff4e9d8c1e834fb57078dc2a4322acc1ea1dbe64064db4cc2a7cdc32884cb7c31aa3c95634356b4d32f1c92fa039e5ed18c1af6605e2f66e5383fa1fcc610a3cc1eef5fe6296ac378f2440d0bd3c77d458af0bfa6b64cedd0301b116efafbbb8f9e88d4bedf56d11cfe0967fc06932e1f232c7daf2c73b58c52daa82dc22b2290147e358348f991f1473c4e63ac943cabc429f5689da8aee6801fd941778966da87fe137b033a0231a90a2ac759efbcb7c52de8e8f27bcc4e5bcb560524d17b0345727f8092e22b6a0e03ceee355085d4fe81568b5b8b84b69870dd333c9b3bdca45db7e43b876a217819928fec31f3ca4c44c4f03a91d578f9ed883bf1247a10eefe42279484af6f70719e193b07a14fc7d93fd6cee9e883a81cc51b53044fa2f1b2525c10e23fb988b31559e1fae4c082d905486d984d2f1e87c40976a571f92e4fdf09e23cad561d9cd3d1ca94e778d08b82852dbe9bab2c7f147c6078504c30fc3fe749fca95ed23791dcf6b935ab0ed08645c481cd2b7969258405f183593617c0d24ebe79bc6c87df65ddc25f933b12a641cc95cf321c1b2edf5db5813a98d99fd1760b5fac19c2b47c2a750e96da49c97276cf31cbd73e1e93ef6990fdf1b08a3e7963dffcf65aa6496727f6be5d9225cfbbadfd6a3a06a454acbb80e761699be97b9caeb71dc3c20a2c253f349ee190b9d8c1ff95751a281ed88b098e4a78fae8249232d4280daf46580ca6c14411f8c4d0e8fa44f8808a5a2dda36d35f79aa77c1c4c615216ec309aa5f3caff3a449b9740baad11e525a5651416a12dd4688897b5ee5e1e8361a87269e9be0789ea7564f64752788f3ac57bda4aa45582d8dabc4d44759639984514d9b151004bf0d9d3140becde41603ffd9d412ec08a1b2f7262862a761d7ce5b91a239d68cdfe7c615b62217422c5a224e652cffdbbe7b41064c9375bbf2c23b63130afedd93987c1f1e4a1623a77d3ef4960fd232afc7d12159c8f1245984e4bd390685668f10da77bf5f6130a5d405a42fb02d0048d63ca48cd4901f3704975b0493714c78de33c1a969993529bac13e310ee098a7b1e8e93b4fcb269773a1521defef98f403f79a8e24e1594b8dc6055223b79b4d7009498c02790a37dd3a9ac7168b11568877ee852395165f87d881355327e57ba48be948b1325824244a552ca80c2ecb79a04a199f0b6a58b455327d998a67ebae414394928161a311de9d3d7f1fc99c0bc35b0a814e7821d2d6958129775c56fdd9b28b6e43a3132a95eb64805f9941b0c5ca2078c37bc7a4d43ddcbc6e8c66de2ef51c714a81913bebeb8f9c6aa14da7e71854870fba9f093aa77abeb23e8d37f3a50135a3f894d1cdf647d3dceb3f16ad4b4f01dffe99d74cd1f18d37735ac69287df0a8b49b2fc3773bf426c881b64a1241c1143e39b6dca59e28c116947aac39585dd81f6fe3832f97d873884738982976f9f29aa6463662cc81c2abbd5eafbb3d9800ba811410a521065d61ad01cfde2c2d98f5fa0ec63e8b01ec6a2d738a1f6cf8c99725079aa51ed3dc7630deeea0b27ee8edbfa19bdb8504cf80a1ca74ea6777fa8786fd76d4ab019c00da35278bf49be5a42aff264875b69bab9e7f3f9299f2ea6a883fc5fd77e34debf414ce8af37a3e9a7de05f33bc547899d25a13389d9048be8b2b7c9b836b401eac9c53ba3d44a14e683a6b378248fdedcbfbc408e5ec83a6c3f769533ae90e339222d80d433ae820fca43d80bca7fbd9d6e5eb760271c74a95a35a853285f7dcfe10182af922508501d269cc53ed38756e8d202b25782ec208a39981ef6c9e9342eb6cee2142471574ab39ebdbbf140f64e305d6f6aa73d49d5bb765347a27ebd0ba1bdcaa8a383526b41f3361faddafec726e185cc223f8465472d7a53ad2d74717960ba5684bc692f773e680d0247f9de1650c046ea56fa18c1c3fb6636327f863f37dca86867c57cc643ff6b7973f97c91716502c33f0dc66176357315f09064c70df7db026413567ac9ec7605acd80606d962f7463359998b92f8a33ccffc80f6e162c1b5a65404e0cc4e37688ded910e6ae5f774e3238a1248b05c53486d149734dd1e38073603df0aa4a4f01fd0c1a0e82b6a513f7d22d1b09ff107923462fbfffbf5fb1f2c043e305917eee1f7f18f62dc604a1d39e5bf675dca67da52f95d312b31be7720efd391bf4794f0555b299325b5dfea9eb00e695861634ad28717cffff266e30c864ba844b0c3fd88bd0cee4d929530c2bb3663c5170a15a66c412ff2fed2d962dd0ff145f19f8085931cbd6bde4c11c9c2debfbdb6748d1a6dabf1404762343d468944a0495026091bc44c69dad971890b7221e1eac2e985097d344a4e2375938aa93806ad1715be8d05f4068ec67ef411e704b01851b7427c4137e6dadedfbb25378ecf1c6b749214f2655cf43fdbb72cb842aaab3074cd792f30f96e874d92f2ef62667d81ccd663dfd969c18dff790b6b7b261f0a03baa85bfd7db72baffe2ddc4cc3985183301a77daa826b1f76f6b6f2ac075c6a2b86609e4d26eb08f3ed6f341d7946966e654ed2c30a629ff81a57014a84105a9ad36a525033f16e3e60d639bf8f89ae6cfdbcdb41e93859354957dcfe9a847757c3cd946d8994cda126f146b77119bbc87c49ce79ad844715cd9bb0c7a4f800fce14c81d175aed59fce0377e62c6e597ab5acf1b3b7100403f371c19dd0f131c4c572e57e3e13743f9bac24a6a177d71f03c5d185bb7e163ec5866dda629340a964bc442d423ad7bc5187f3da68d1498dfe9f1815b31bae11df3585ece230cc3521f7a0b4b3360ddf898984b528afff75f229915f1f2d5c4491c65e2f38d26c0de7dc483860da7bf52859fdbc21946badec0bbf00ba8143b8b7289cfb7096d3405e3183e56f1cbb8c48f25d058530894d0302cdce7c43ea31768b5b610820c97f6e9a8e31a8e9c4624117d213a03ef7d655513cffd8fb5606fc8790692c99828a47382e3e37b9c1317028f5c9d196ff3c2f09435df7614fb37ea20b2371c52b6c4667799922010edeb28001de2a137889db4d3dbf0a406ba90f3631be485ec5e4110b01502f557f15026716030fb6384499adee3cfc44015638e05b206ce3cbc3cbf21b7dd1dcb3b932629e7cd4f4f6b148f37f976803644e5ce792583daf39608a3cd02ff2f47e9bc6" +var expectedTransformedHex = "352187af3a843decc63ceca6cb01ea39dbcf77caf0a8f705f5c30d557044ceec9392b94a79376f1e5c10cd0c0f2a98e5353bf22b3ea4fdac6677ee553dec192e3db64e179d0474e96088fb4abd2babd67de123fb398bdf84d818f7bda2c1ab60b3ea0e0569ae54aa969658eb4844e6960d2ff44d7c087ee3aaffa1c0ee5df7e50b615f7ad90190f022934ad5300c7d1809bfe71a11cc04cece5274eb97a5f20350630522c1dbb7cebaf4f97f84e03f5cfd88f2b48880b25d12f4d5e75c150f704ef6b46c72e07db2b705ac3644569dccd22fd8f964f6ef787fda63c46759af334e6f665f70eac775a7017acea49f3c7696151cb1b9434fa4ac27fb803921ffb5ec58dafa168098d7d5b97e384be3384cf5bc235c3d887fef89fe76c0065f9b8d6ad837b442340d9e797b46ef5709ea3358bc415df11e4830de986ef0f1c418ffdcc80e9a3cda9bea0ab5676c0d4240465c43ba527e3b4ea50b4f6255b510e5d25774a75449b0bd71e56c537ade4fcf0f4d63c99ae1dbb5a844971e2c19941b8facfcfc8ee3056e7cb3c7114c5357e845b52f7103cb6e00d2308c37b12baa5b769e1cc7b00fc06f2d16e70cc27a82cb9c1a4e40cb0d43907f73df2c9db44f1b51a6b0bc6d09f77ac3be14041fae3f9df2da42df43ae110904f9ecee278030185254d7c6e918a5512024d047f77a992088cb3190a6587aa54d0c7231c1cd2e455e0d4c07f74bece68e29cd8ba0190c0bcfb26d24634af5d91a81ef5d4dd3d614836ce942ddbf7bb1399317f4c03faa675f325f18324bf9433844bfe5c4cc04130c8d5c329562b7cd66e72f7355de8f5375a72202971613c32bd7f3fcdcd51080758cd1d0a46dbe8f0374381dbc359f5864250c63dde8131cbd7c98ae2b0147d6ea4bf65d1443d511b18e6d608bbb46ac036353b4c51df306a10a6f6939c38629a5c18aaf89cac04bd3ad5156e6b92011c88341cb08551bab0a89e6a46538f5af33b86121dba17e3a434c273f385cd2e8cb90bdd32747d8425d929ccbd9b0815c73325988855549a8489dfd047daf777aaa3099e54cf997175a5d9e1edfe363e3b68c70e02f6bf4fcde6a0f3f7d0e7e98bde1a72ae8b6cd27b32990680cc4a04fc467f41c5adcaddabfc71928a3f6872c360c1d765260690dd28b269864c8e380d9c92ef6b89b0094c8f9bb22608b4156381b19b920e9583c9616ce5693b4d2a6c689f02e6a91584a8e501e107403d2689dd0045269dd9946c0e969fb656a3b39d84a798831f5f9290f163eb2f97d3ae25071324e95e2256d9c1e56eb83c26397855323edc202d56ad05894333b7f0ed3c1e4734782eb8bd5477242fd80d7a89b12866f85cfae476322f032465d6b1253993033fccd4723530630ab97a1566460af9c90c9da843c229406e65f3fa578bd6bf04dee9b6153807ddadb8ceefc5c601a8ab26023c67b1ab1e8e0f29ce94c78c308005a781853e7a2e0e51738939a657c987b5e611f32f47b5ff461c52e63e0ea390515a8e1f5393dae54ea526934b5f310b76e3fa050e40718cb4c8a20e58946d6ee1879f08c52764422fe542b3240e75eccb7aa75b1f8a651e37a3bc56b0932cdae0e985948468db1f98eb4b77b82081ea25d8a762db00f7898864984bd80e2f3f35f236bf57291dec28f550769943bcfb6f884b7687589b673642ef7fe5d7d5a87d3eca5017f83ccb9a3310520474479464cb3f433440e7e2f1e28c0aef700a45848573409e7ab66e0cfd4fe5d2147ace81bc65fd8891f6245cd69246bbf5c27830e5ab882dd1d02aba34ff6ca9af88df00fd602892f02fedbdc65dedec203faf3f8ff4a97314e0ddb58b9ab756a61a562597f4088b445fcc3b28a708ca7b1485dcd791b779fbf2b3ef1ec5c6205f595fbe45a02105034147e5a146089c200a49dae33ae051a08ea5f974a21540aaeffa7f9d9e3d35478016fb27b871036eb27217a5b834b461f535752fb5f1c8dded3ae14ce3a2ef6639e2fe41939e3509e46e347a95d50b2080f1ba42c804b290ddc912c952d1cec3f2661369f738feacc0dbf1ea27429c644e45f9e26f30c341acd34c7519b2a1663e334621691e810767e9918c2c547b2e23cce915f97d26aac8d0d2fcd3edb7986ad4e2b8a852edebad534cb6c0e9f0797d3563e5409d7e068e48356c67ce519246cd9c560e881453df97cbba562018811e6cf8c327f399d1d1253ab47a19f4a0ccc7c6d86a9603e0551da310ea595d71305c4aad96819120a92cdbaf1f77ec8df9cc7c838c0d4de1e8692dd81da38268d1d71324bcffdafbe5122e4b81828e021e936d83ae8021eac592aa52cd296b5ce392c7173d622f8e07d18f59bb1b08ba15211af6703463b09b593af3c37735296816d9f2e7a369354a5374ea3955e14ca8ac56d5bfe4aef7a21bd825d6ae85530bee5d2aaaa4914981b3dfdb2e92ec2a27c83d74b59e84ff5c056f7d8945745f2efc3dcf28f288c6cd8383700fb2312f7001f24dd40015e436ae23e052fe9070ea9535b9c989898a9bda3d5382cf10e432fae6ccf0c825b3e6436edd3a9f8846e5606f8563931b5f29ba407c5236e5730225dda211a8504ec1817bc935e1fd9a532b648c502df302ed2063aed008fd5676131ac9e95998e9447b02bd29d77e38fcfd2959f2de929b31970335eb2a74348cc6918bc35b9bf749eab0fe304c946cd9e1ca284e6853c42646e60b6b39e0d3fb3c260abfc5c1b4ca3c3770f344118ca7c7f5c1ad1f123f8f369cd60afc3cdb3e9e81968c5c9fa7c8b014ffe0508dd4f0a2a976d5d1ca8fc9ad7a237d92cfe7b41413d934d6e142824b252699397e48e4bac4e91ebc10602720684bd0863773c548f9a2f9724245e47b129ecf65afd7252aac48c8a8d6fd3d888af592a01fb02dc71ed7538a700d3d16243e4621e0fcf9f8ed2b4e11c9fa9a95338bb1dac74a7d9bc4eb8cbf900b634a2a56469c00f5994e4f0934bdb947640e6d67e47d0b621aacd632bfd3c800bd7d93bd329f494a90e06ed51535831bd6e07ac1b4b11434ef3918fa9511813a002913f33f836454798b8d1787fea9a4c4743ba091ed192ed92f4d33e43a226bf9503e1a83a16dd340b3cbbf38af6db0d99201da8de529b4225f3d2fa2aad6621afc6c79ef3537720591edfc681ae6d00ede53ed724fc71b23b90d2e9b7158aaee98d626a4fe029107df2cb5f90147e07ebe423b1519d848af18af365c71bfd0665db46be493bbe99b79a188de0cf3594aef2299f0324075bdce9eb0b87bc29d62401ba4fd6ae48b1ba33261b5b845279becf38ee03e3dc5c45303321c5fac96fd02a3ad8c9e3b02127b320501333c9e6360440d1ad5e64a6239501502dde1a49c9abe33b66098458eee3d611bb06ffcd234a1b9aef4af5021cd61f0de6789f822ee116b5078aae8c129e8391d8987500d322b58edd1595dc570b57341f2df221b94a96ab7fbcf32a8ca9684196455694024623d7ed49f7d66e8dd453c0bae50e0d8b34377b22d0ece059e2c385dfc70b9089fcd27577c51f4d870b5738ee2b68c361a67809c105c7848b68860a829f29930857a9f9d40b14fd2384ac43bafdf43c0661103794c4bd07d1cfdd4681b6aeaefad53d4c1473359bcc5a83b09189352e5bb9a7498dd0effb89c35aad26954551f8b0621374b449bf515630bd3974dca982279733470fdd059aa9c3df403d8f22b38c4709c82d8f12b888e22990350490e16179caf406293cc9e65f116bafcbe96af132f679877061107a2f690a82a8cb46eea57a90abd23798c5937c6fe6b17be3f9bfa01ce117d2c268181b9095bf49f395fea07ca03838de0588c5e2db633e836d64488c1421e653ea52d810d096048c092d0da6e02fa6613890219f51a76148c8588c2487b171a28f17b7a299204874af0131725d793481333be5f08e86ca837a226850b0c1060891603bfecf9e55cddd22c0dbb28d495342d9cc3de8409f72e52a0115141cffe755c74f061c1a770428ccb0ae59536ee6fc074fbfc6cacb51a549d327527e20f8407477e60355863f1153f9ce95641198663c968874e7fdb29407bd771d94fdda8180cbb0358f5874738db705924b8cbe0cd5e1484aeb64542fe8f38667b7c34baf818c63b1e18440e9fba575254d063fd49f24ef26432f4eb323f3836972dca87473e3e9bb26dc3be236c3aae6bc8a6da567442309da0e8450e242fc9db836e2964f2c76a3b80a2c677979882dda7d7ebf62c93664018bcf4ec431fe6b403d49b3b36618b9c07c2d0d4569cb8d52223903debc72ec113955b206c34f1ae5300990ccfc0180f47d91afdb542b6312d12aeff7e19c645dc0b9fe6e3288e9539f6d5870f99882df187bfa6d24d179dfd1dac22212c8b5339f7171a3efc15b760fed8f68538bc5cbd845c2d1ab41f3a6c692820653eaef7930c02fbe6061d93805d73decdbb945572a7c44ed0241982a6e4d2d730898f82b3d9877cb7bca41cc6dcee67aa0c3d6db76f0b0a708ace0031113e48429de5d886c10e9200f68f32263a2fbf44a5992c2459fda7b8796ba796e3a0804fc25992ed2c9a5fe0580a6b809200ecde6caa0364b58be11564dcb9a616766dd7906db5636ee708b0204f38d309466d8d4a162965dd727e29f5a6c133e9b4ed5bafe803e479f9b2a7640c942c4a40b14ac7dc9828546052761a070f6404008f1ec3605836339c3da95a00b4fd81b2cabf88b51d2087d5b83e8c5b69bf96d8c72cbd278dad3bbb42b404b436f84ad688a22948adf60a81090f1e904291503c16e9f54b05fc76c881a5f95f0e732949e95d3f1bae2d3652a14fe0dda2d68879604657171856ef72637def2a96ac47d7b3fe86eb3198f5e0e626f06be86232305f2ae79ffcd2725e48208f9d8d63523f81915acc957563ab627cd6bc68c2a37d59fb0ed77a90aa9d085d6914a8ebada22a2c2d471b5163aeddd799d90fbb10ed6851ace2c4af504b7d572686700a59d6db46d5e42bb83f8e0c0ffe1dfa6582cc0b34c921ff6e85e83188d24906d5c08bb90069639e713051b3102b53e6f703e8210017878add5df68e6f2b108de279c5490e9eef5590185c4a1c744d4e00d244e1245a8805bd30407b1bc488db44870ccfd75a8af104df78efa2fb7ba31f048a263efdb3b63271fff4922bece9a71187108f65744a24f4947dc556b7440cb4fa45d296bb7f724588d1f245125b21ea063500029bd49650237f53899daf1312809552c81c5827341263cc807a29fe84746170cdfa1ff3838399a5645319bcaff674bb70efccdd88b3d3bb2f2d98111413585dc5d5bd5168f43b3f55e58972a5b2b9b3733febf02f931bd436648cb617c3794841aab961fe41277ab07812e1d3bc4ff6f4350a3e615bfba08c3b9480ef57904d3a16f7e916345202e3f93d11f7a7305170cb8c4eb9ac88ace8bbd1f377bdd5855d3162d6723d4435e84ce529b8f276a8927915ac759a0d04e5ca4a9d3da6291f0333b475df527e99fe38f7a4082662e8125936640c26dd1d17cf284ce6e2b17777a05aa0574f7793a6a062cc6f7263f7ab126b4528a17becfdec49ac0f7d8705aa1704af97fb861faa8a466161b2b5c08a5bacc79fe8500b913d65c8d3c52d1fd52d2ab2c9f52196e712455619c1cd3e0f391b274487944240e2ed8858dd0823c801094310024ae3fe4dd1cf5a2b6487b42cc5937bbafb193ee331d87e378258963d49b9da90899bbb4b88e79f78e866b0213f4719f67da7bcc2fce073c01e87c62ea3cdbcd589cfc41281f2f4c757c742d6d1e" var hashFunc = sha3.NewKeccak256 +var testKey Key + +func init() { + var err error + testKey, err = hexutil.Decode("0x8abf1502f557f15026716030fb6384792583daf39608a3cd02ff2f47e9bc6e49") + if err != nil { + panic(err.Error()) + } +} func TestEncryptDataLongerThanPadding(t *testing.T) { - enc := New(4095, uint32(0), hashFunc) + enc := New(testKey, 4095, uint32(0), hashFunc) data := make([]byte, 4096) - key := make([]byte, 32) expectedError := "Data length longer than padding, data length 4096 padding 4095" - _, err := enc.Encrypt(data, key) + _, err := enc.Encrypt(data) if err == nil || err.Error() != expectedError { t.Fatalf("Expected error \"%v\" got \"%v\"", expectedError, err) } } func TestEncryptDataZeroPadding(t *testing.T) { - enc := New(0, uint32(0), hashFunc) + enc := New(testKey, 0, uint32(0), hashFunc) data := make([]byte, 2048) - key := make([]byte, 32) - encrypted, err := enc.Encrypt(data, key) + encrypted, err := enc.Encrypt(data) if err != nil { t.Fatalf("Expected no error got %v", err) } @@ -59,12 +67,11 @@ func TestEncryptDataZeroPadding(t *testing.T) { } func TestEncryptDataLengthEqualsPadding(t *testing.T) { - enc := New(4096, uint32(0), hashFunc) + enc := New(testKey, 4096, uint32(0), hashFunc) data := make([]byte, 4096) - key := make([]byte, 32) - encrypted, err := enc.Encrypt(data, key) + encrypted, err := enc.Encrypt(data) if err != nil { t.Fatalf("Expected no error got %v", err) } @@ -77,12 +84,11 @@ func TestEncryptDataLengthEqualsPadding(t *testing.T) { } func TestEncryptDataLengthSmallerThanPadding(t *testing.T) { - enc := New(4096, uint32(0), hashFunc) + enc := New(testKey, 4096, uint32(0), hashFunc) data := make([]byte, 4080) - key := make([]byte, 32) - encrypted, err := enc.Encrypt(data, key) + encrypted, err := enc.Encrypt(data) if err != nil { t.Fatalf("Expected no error got %v", err) } @@ -96,14 +102,13 @@ func TestEncryptDataCounterNonZero(t *testing.T) { } func TestDecryptDataLengthNotEqualsPadding(t *testing.T) { - enc := New(4096, uint32(0), hashFunc) + enc := New(testKey, 4096, uint32(0), hashFunc) data := make([]byte, 4097) - key := make([]byte, 32) expectedError := "Data length different than padding, data length 4097 padding 4096" - _, err := enc.Decrypt(data, key) + _, err := enc.Decrypt(data) if err == nil || err.Error() != expectedError { t.Fatalf("Expected error \"%v\" got \"%v\"", expectedError, err) } @@ -117,20 +122,18 @@ func TestEncryptDecryptIsIdentity(t *testing.T) { } func testEncryptDecryptIsIdentity(t *testing.T, padding int, initCtr uint32, dataLength int, keyLength int) { - enc := New(padding, initCtr, hashFunc) + key := GenerateRandomKey(keyLength) + enc := New(key, padding, initCtr, hashFunc) data := make([]byte, dataLength) rand.Read(data) - key := make([]byte, keyLength) - rand.Read(key) - - encrypted, err := enc.Encrypt(data, key) + encrypted, err := enc.Encrypt(data) if err != nil { t.Fatalf("Expected no error got %v", err) } - decrypted, err := enc.Decrypt(encrypted, key) + decrypted, err := enc.Decrypt(encrypted) if err != nil { t.Fatalf("Expected no error got %v", err) } diff --git a/swarm/storage/hasherstore.go b/swarm/storage/hasherstore.go index bc23077c1..766207eae 100644 --- a/swarm/storage/hasherstore.go +++ b/swarm/storage/hasherstore.go @@ -26,49 +26,34 @@ import ( "github.com/ethereum/go-ethereum/swarm/storage/encryption" ) -type chunkEncryption struct { - spanEncryption encryption.Encryption - dataEncryption encryption.Encryption -} - type hasherStore struct { - store ChunkStore - hashFunc SwarmHasher - chunkEncryption *chunkEncryption - hashSize int // content hash size - refSize int64 // reference size (content hash + possibly encryption key) - wg *sync.WaitGroup - closed chan struct{} -} - -func newChunkEncryption(chunkSize, refSize int64) *chunkEncryption { - return &chunkEncryption{ - spanEncryption: encryption.New(0, uint32(chunkSize/refSize), sha3.NewKeccak256), - dataEncryption: encryption.New(int(chunkSize), 0, sha3.NewKeccak256), - } + store ChunkStore + toEncrypt bool + hashFunc SwarmHasher + hashSize int // content hash size + refSize int64 // reference size (content hash + possibly encryption key) + wg *sync.WaitGroup + closed chan struct{} } // NewHasherStore creates a hasherStore object, which implements Putter and Getter interfaces. // With the HasherStore you can put and get chunk data (which is just []byte) into a ChunkStore // and the hasherStore will take core of encryption/decryption of data if necessary func NewHasherStore(chunkStore ChunkStore, hashFunc SwarmHasher, toEncrypt bool) *hasherStore { - var chunkEncryption *chunkEncryption - hashSize := hashFunc().Size() refSize := int64(hashSize) if toEncrypt { refSize += encryption.KeyLength - chunkEncryption = newChunkEncryption(chunk.DefaultSize, refSize) } return &hasherStore{ - store: chunkStore, - hashFunc: hashFunc, - chunkEncryption: chunkEncryption, - hashSize: hashSize, - refSize: refSize, - wg: &sync.WaitGroup{}, - closed: make(chan struct{}), + store: chunkStore, + toEncrypt: toEncrypt, + hashFunc: hashFunc, + hashSize: hashSize, + refSize: refSize, + wg: &sync.WaitGroup{}, + closed: make(chan struct{}), } } @@ -79,7 +64,7 @@ func (h *hasherStore) Put(ctx context.Context, chunkData ChunkData) (Reference, c := chunkData size := chunkData.Size() var encryptionKey encryption.Key - if h.chunkEncryption != nil { + if h.toEncrypt { var err error c, encryptionKey, err = h.encryptChunkData(chunkData) if err != nil { @@ -155,23 +140,14 @@ func (h *hasherStore) encryptChunkData(chunkData ChunkData) (ChunkData, encrypti return nil, nil, fmt.Errorf("Invalid ChunkData, min length 8 got %v", len(chunkData)) } - encryptionKey, err := encryption.GenerateRandomKey() - if err != nil { - return nil, nil, err - } - - encryptedSpan, err := h.chunkEncryption.spanEncryption.Encrypt(chunkData[:8], encryptionKey) - if err != nil { - return nil, nil, err - } - encryptedData, err := h.chunkEncryption.dataEncryption.Encrypt(chunkData[8:], encryptionKey) + key, encryptedSpan, encryptedData, err := h.encrypt(chunkData) if err != nil { return nil, nil, err } c := make(ChunkData, len(encryptedSpan)+len(encryptedData)) copy(c[:8], encryptedSpan) copy(c[8:], encryptedData) - return c, encryptionKey, nil + return c, key, nil } func (h *hasherStore) decryptChunkData(chunkData ChunkData, encryptionKey encryption.Key) (ChunkData, error) { @@ -179,12 +155,7 @@ func (h *hasherStore) decryptChunkData(chunkData ChunkData, encryptionKey encryp return nil, fmt.Errorf("Invalid ChunkData, min length 8 got %v", len(chunkData)) } - decryptedSpan, err := h.chunkEncryption.spanEncryption.Decrypt(chunkData[:8], encryptionKey) - if err != nil { - return nil, err - } - - decryptedData, err := h.chunkEncryption.dataEncryption.Decrypt(chunkData[8:], encryptionKey) + decryptedSpan, decryptedData, err := h.decrypt(chunkData, encryptionKey) if err != nil { return nil, err } @@ -201,13 +172,46 @@ func (h *hasherStore) decryptChunkData(chunkData ChunkData, encryptionKey encryp copy(c[:8], decryptedSpan) copy(c[8:], decryptedData[:length]) - return c[:length+8], nil + return c, nil } func (h *hasherStore) RefSize() int64 { return h.refSize } +func (h *hasherStore) encrypt(chunkData ChunkData) (encryption.Key, []byte, []byte, error) { + key := encryption.GenerateRandomKey(encryption.KeyLength) + encryptedSpan, err := h.newSpanEncryption(key).Encrypt(chunkData[:8]) + if err != nil { + return nil, nil, nil, err + } + encryptedData, err := h.newDataEncryption(key).Encrypt(chunkData[8:]) + if err != nil { + return nil, nil, nil, err + } + return key, encryptedSpan, encryptedData, nil +} + +func (h *hasherStore) decrypt(chunkData ChunkData, key encryption.Key) ([]byte, []byte, error) { + encryptedSpan, err := h.newSpanEncryption(key).Encrypt(chunkData[:8]) + if err != nil { + return nil, nil, err + } + encryptedData, err := h.newDataEncryption(key).Encrypt(chunkData[8:]) + if err != nil { + return nil, nil, err + } + return encryptedSpan, encryptedData, nil +} + +func (h *hasherStore) newSpanEncryption(key encryption.Key) encryption.Encryption { + return encryption.New(key, 0, uint32(chunk.DefaultSize/h.refSize), sha3.NewKeccak256) +} + +func (h *hasherStore) newDataEncryption(key encryption.Key) encryption.Encryption { + return encryption.New(key, int(chunk.DefaultSize), 0, sha3.NewKeccak256) +} + func (h *hasherStore) storeChunk(ctx context.Context, chunk *Chunk) { h.wg.Add(1) go func() { diff --git a/swarm/testutil/file.go b/swarm/testutil/file.go new file mode 100644 index 000000000..ecb0d971e --- /dev/null +++ b/swarm/testutil/file.go @@ -0,0 +1,44 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. + +package testutil + +import ( + "io" + "io/ioutil" + "os" + "strings" + "testing" +) + +// TempFileWithContent is a helper function that creates a temp file that contains the following string content then closes the file handle +// it returns the complete file path +func TempFileWithContent(t *testing.T, content string) string { + tempFile, err := ioutil.TempFile("", "swarm-temp-file") + if err != nil { + t.Fatal(err) + } + + _, err = io.Copy(tempFile, strings.NewReader(content)) + if err != nil { + os.RemoveAll(tempFile.Name()) + t.Fatal(err) + } + if err = tempFile.Close(); err != nil { + t.Fatal(err) + } + return tempFile.Name() +} |