From 5ea1a77eec8a2c7d28d7758149367cbdefea95e7 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 20 Mar 2017 16:00:07 +0100 Subject: [PATCH 01/16] track test vectors as a submodule --- .gitmodules | 3 +++ secrethandshake/test-vectors | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 secrethandshake/test-vectors diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..1bccefc --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "secrethandshake/test-vectors"] + path = secrethandshake/test-vectors + url = https://github.com/auditdrivencrypto/test-secret-handshake diff --git a/secrethandshake/test-vectors b/secrethandshake/test-vectors new file mode 160000 index 0000000..dcb30cf --- /dev/null +++ b/secrethandshake/test-vectors @@ -0,0 +1 @@ +Subproject commit dcb30cf73faf10a0e8b21ffc1c366ff464eb2cfc From 8e1b16bea47a140cd2434c1177c108606056bf58 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 21 Mar 2017 19:05:27 +0100 Subject: [PATCH 02/16] WIP initialize --- secrethandshake/state.go | 2 +- secrethandshake/stateless/boilerplate.go | 72 ++++++++++++ secrethandshake/stateless/init.go | 134 +++++++++++++++++++++++ secrethandshake/vectors_test.go | 49 +++++++++ 4 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 secrethandshake/stateless/boilerplate.go create mode 100644 secrethandshake/stateless/init.go create mode 100644 secrethandshake/vectors_test.go diff --git a/secrethandshake/state.go b/secrethandshake/state.go index 477afe8..d5f5415 100644 --- a/secrethandshake/state.go +++ b/secrethandshake/state.go @@ -54,7 +54,7 @@ type EdKeyPair struct { Secret [ed25519.PrivateKeySize]byte } -// CurveKeyPair is a keypair for use with github.com/agl/ed25519 +// CurveKeyPair is a keypair for use with curve25519 type CurveKeyPair struct { Public [32]byte Secret [32]byte diff --git a/secrethandshake/stateless/boilerplate.go b/secrethandshake/stateless/boilerplate.go new file mode 100644 index 0000000..6c01ce3 --- /dev/null +++ b/secrethandshake/stateless/boilerplate.go @@ -0,0 +1,72 @@ +package stateless + +import ( + "encoding/hex" + "strings" +) + +// TODO: only expose in tests? +func (s *State) ToJsonState() *JsonState { + return &JsonState{ + AppKey: hex.EncodeToString(s.appKey), + Local: localKey{ + KxPK: hex.EncodeToString(s.ephKeyPair.Public[:]), + KxSK: hex.EncodeToString(s.ephKeyPair.Secret[:]), + PublicKey: hex.EncodeToString(s.local.Public[:]), + SecretKey: hex.EncodeToString(s.local.Secret[:]), + AppMac: hex.EncodeToString(s.localAppMac), + }, + Remote: remotePub{hex.EncodeToString(s.remotePublic[:])}, + Random: "TODO", + } +} + +// json test vectors > go conversion boilerplate +type localKey struct { + KxPK string `mapstructure:"kx_pk"` + KxSK string `mapstructure:"kx_sk"` + PublicKey string `mapstructure:"publicKey"` + SecretKey string `mapstructure:"secretKey"` + AppMac string `mapstructure:"app_mac"` +} + +type remotePub struct { + PublicKey string `mapstructure:"publicKey"` +} + +type JsonState struct { + AppKey string `mapstructure:"app_key"` + Local localKey `mapstructure:"local"` + Remote remotePub `mapstructure:"remote"` + Seed string `mapstructure:"seed"` + Random string `mapstructure:"random"` +} + +func JsonStateToOurState(s JsonState) (*State, error) { + var localKeyPair Option + if s.Seed != "" { + localKeyPair = LocalKeyFromSeed(strings.NewReader(s.Seed)) + } else { + localKeyPair = LocalKeyFromHex(s.Local.PublicKey, s.Local.SecretKey) + } + + return Initialize( + SetAppKey(s.AppKey), + localKeyPair, + EphemeralRand(strings.NewReader(s.Random)), + RemotePubFromHex(s.Remote.PublicKey), + ) + /*return &State{ + + Local: Keys{ + + + AppMac: mustDecodeHex(s.Local.AppMac), + }, + Remote: Keys{ + PublicKey: mustDecodeHex(s.Remote.PublicKey), + }, + Random: mustDecodeHex(s.Random), + }, converr + */ +} diff --git a/secrethandshake/stateless/init.go b/secrethandshake/stateless/init.go new file mode 100644 index 0000000..a0dd9e4 --- /dev/null +++ b/secrethandshake/stateless/init.go @@ -0,0 +1,134 @@ +package stateless + +import ( + "crypto/hmac" + "crypto/rand" + "crypto/sha512" + "encoding/hex" + "io" + "log" + + "github.com/agl/ed25519" + "github.com/agl/ed25519/extra25519" + "github.com/pkg/errors" +) + +// EdKeyPair is a keypair for use with github.com/agl/ed25519 +type EdKeyPair struct { + Public [ed25519.PublicKeySize]byte + Secret [ed25519.PrivateKeySize]byte +} + +// CurveKeyPair is a keypair for use with curve25519 +type CurveKeyPair struct { + Public [32]byte + Secret [32]byte +} + +type State struct { + appKey []byte + ephKeyPair CurveKeyPair + local EdKeyPair + remotePublic [ed25519.PublicKeySize]byte + ephRand io.Reader + localAppMac []byte +} + +type Option func(s *State) error + +func SetAppKey(ak string) Option { + return func(s *State) error { + var err error + s.appKey, err = hex.DecodeString(ak) + if err != nil { + return errors.Wrapf(err, "SetAppKey(): failed to decode %q", ak) + } + return nil + } +} + +func LocalKey(kp EdKeyPair) Option { + return func(s *State) error { + s.local = kp + return nil + } +} + +// LocalKeyFromSeed is only used for testing against known values +func LocalKeyFromSeed(r io.Reader) Option { + return func(s *State) error { + pk, sk, err := ed25519.GenerateKey(r) + copy(s.local.Public[:], pk[:]) + copy(s.local.Secret[:], sk[:]) + return err + } +} + +func LocalKeyFromHex(public, secret string) Option { + return func(s *State) error { + pk, err := hex.DecodeString(public) + if err != nil { + return errors.Wrap(err, "LocalKeyFromHex(): failed to decode public key") + } + sk, err := hex.DecodeString(secret) + if err != nil { + return errors.Wrap(err, "LocalKeyFromHex(): failed to decode secret key") + } + copy(s.local.Public[:], pk[:]) + copy(s.local.Secret[:], sk[:]) + return err + } +} + +// EphemeralRand is only used for testing against known values +func EphemeralRand(r io.Reader) Option { + return func(s *State) error { + log.Println("setting determ. rand") + s.ephRand = r + return nil + } +} + +func RemotePubFromHex(pub string) Option { + return func(s *State) error { + b, err := hex.DecodeString(pub) + copy(s.remotePublic[:], b) + return err + } +} + +func Initialize(opts ...Option) (*State, error) { + s := new(State) + + for i, o := range opts { + if err := o(s); err != nil { + return nil, errors.Wrapf(err, "Initialize(): failed to use option %d", i) + } + } + + // TODO: check that all needed info is present + if len(s.appKey) != 32 { + return nil, errors.New("Initialize(): appKey needed") + } + + if s.ephRand == nil { + log.Println("using crypto/rand") + s.ephRand = rand.Reader + } + + pubKey, secKey, err := ed25519.GenerateKey(s.ephRand) + if err != nil { + return nil, errors.Wrap(err, "Initialize(): failed to generate ephemeral key") + } + copy(s.ephKeyPair.Public[:], pubKey[:]) + // if !extra25519.PublicKeyToCurve25519(&s.ephKeyPair.Public, pubKey) { + // return nil, errors.New("Initialize(): could not curvify pubkey") + // } + extra25519.PrivateKeyToCurve25519(&s.ephKeyPair.Secret, secKey) + + appMacr := hmac.New(sha512.New, s.appKey[:32]) + appMacr.Write(s.ephKeyPair.Public[:]) + s.localAppMac = appMacr.Sum(nil)[:32] + + return s, nil +} diff --git a/secrethandshake/vectors_test.go b/secrethandshake/vectors_test.go new file mode 100644 index 0000000..d4a6af7 --- /dev/null +++ b/secrethandshake/vectors_test.go @@ -0,0 +1,49 @@ +package secrethandshake + +import ( + "encoding/json" + "os" + "testing" + + "github.com/cryptix/secretstream/secrethandshake/stateless" + "github.com/mitchellh/mapstructure" + "github.com/stretchr/testify/assert" +) + +func TestVectors(t *testing.T) { + dataf, err := os.Open("test-vectors/data.json") + assert.Nil(t, err) + + defer dataf.Close() + + var data []map[string]interface{} + assert.Nil(t, json.NewDecoder(dataf).Decode(&data)) + + for i, v := range data { + if i > 1 { + return + } + switch v["name"] { + case "initialize": // + args := v["args"].([]interface{}) + assert.Len(t, args, 1, "init test %d", i) + + // parse args + var argState stateless.JsonState + err := mapstructure.Decode(args[0], &argState) + assert.Nil(t, err, "init test %d", i) + initState, err := stateless.JsonStateToOurState(argState) + assert.Nil(t, err, "init test %d", i) + + // parse result + var resultState stateless.JsonState + err = mapstructure.Decode(v["result"], &resultState) + assert.Nil(t, err, "init test %d", i) + + assert.Equal(t, resultState, *initState.ToJsonState()) + + default: + // t.Logf("unhandled case testing %d: %s", i, v["name"]) + } + } +} From 2e9c2edbaec47c1275ed59eb2b3724594c613929 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 3 Apr 2017 15:11:33 +0200 Subject: [PATCH 03/16] update test-vectors submodule --- secrethandshake/test-vectors | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/secrethandshake/test-vectors b/secrethandshake/test-vectors index dcb30cf..5d3c19d 160000 --- a/secrethandshake/test-vectors +++ b/secrethandshake/test-vectors @@ -1 +1 @@ -Subproject commit dcb30cf73faf10a0e8b21ffc1c366ff464eb2cfc +Subproject commit 5d3c19d6e1019677bf6e99480b9a03b1c1103288 From 7a60e4b2b9cffbdbb1fab1fcaf15ab61e9ae5be2 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 3 Apr 2017 16:25:31 +0200 Subject: [PATCH 04/16] first init passes --- secrethandshake/stateless/boilerplate.go | 18 +++---------- secrethandshake/stateless/init.go | 32 +++++++++++++++++++----- secrethandshake/vectors_test.go | 2 +- 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/secrethandshake/stateless/boilerplate.go b/secrethandshake/stateless/boilerplate.go index 6c01ce3..dc9b3bd 100644 --- a/secrethandshake/stateless/boilerplate.go +++ b/secrethandshake/stateless/boilerplate.go @@ -7,6 +7,7 @@ import ( // TODO: only expose in tests? func (s *State) ToJsonState() *JsonState { + return &JsonState{ AppKey: hex.EncodeToString(s.appKey), Local: localKey{ @@ -17,7 +18,7 @@ func (s *State) ToJsonState() *JsonState { AppMac: hex.EncodeToString(s.localAppMac), }, Remote: remotePub{hex.EncodeToString(s.remotePublic[:])}, - Random: "TODO", + Random: hex.EncodeToString(s.ephRandBuf.Bytes()), } } @@ -53,20 +54,7 @@ func JsonStateToOurState(s JsonState) (*State, error) { return Initialize( SetAppKey(s.AppKey), localKeyPair, - EphemeralRand(strings.NewReader(s.Random)), + EphemeralRandFromHex(s.Random), RemotePubFromHex(s.Remote.PublicKey), ) - /*return &State{ - - Local: Keys{ - - - AppMac: mustDecodeHex(s.Local.AppMac), - }, - Remote: Keys{ - PublicKey: mustDecodeHex(s.Remote.PublicKey), - }, - Random: mustDecodeHex(s.Random), - }, converr - */ } diff --git a/secrethandshake/stateless/init.go b/secrethandshake/stateless/init.go index a0dd9e4..451c131 100644 --- a/secrethandshake/stateless/init.go +++ b/secrethandshake/stateless/init.go @@ -1,6 +1,7 @@ package stateless import ( + "bytes" "crypto/hmac" "crypto/rand" "crypto/sha512" @@ -30,8 +31,16 @@ type State struct { ephKeyPair CurveKeyPair local EdKeyPair remotePublic [ed25519.PublicKeySize]byte - ephRand io.Reader - localAppMac []byte + + localAppMac []byte + + /* TODO: test only data + there might be a funky conditional compilation dance + to only include these fields in the test package + but first make the tests pass. + */ + ephRand io.Reader + ephRandBuf bytes.Buffer // stores the bytes we read } type Option func(s *State) error @@ -89,6 +98,18 @@ func EphemeralRand(r io.Reader) Option { } } +// EphemeralRandFromHex is only used for testing against known values +func EphemeralRandFromHex(rand string) Option { + return func(s *State) error { + rbytes, err := hex.DecodeString(rand) + if err != nil { + return errors.Wrap(err, "EphemeralRandFromHex(): failed to decode rand bytes") + } + s.ephRand = io.TeeReader(bytes.NewReader(rbytes), &s.ephRandBuf) + return nil + } +} + func RemotePubFromHex(pub string) Option { return func(s *State) error { b, err := hex.DecodeString(pub) @@ -120,10 +141,9 @@ func Initialize(opts ...Option) (*State, error) { if err != nil { return nil, errors.Wrap(err, "Initialize(): failed to generate ephemeral key") } - copy(s.ephKeyPair.Public[:], pubKey[:]) - // if !extra25519.PublicKeyToCurve25519(&s.ephKeyPair.Public, pubKey) { - // return nil, errors.New("Initialize(): could not curvify pubkey") - // } + if !extra25519.PublicKeyToCurve25519(&s.ephKeyPair.Public, pubKey) { + return nil, errors.New("Initialize(): could not curvify pubkey") + } extra25519.PrivateKeyToCurve25519(&s.ephKeyPair.Secret, secKey) appMacr := hmac.New(sha512.New, s.appKey[:32]) diff --git a/secrethandshake/vectors_test.go b/secrethandshake/vectors_test.go index d4a6af7..1e35aee 100644 --- a/secrethandshake/vectors_test.go +++ b/secrethandshake/vectors_test.go @@ -43,7 +43,7 @@ func TestVectors(t *testing.T) { assert.Equal(t, resultState, *initState.ToJsonState()) default: - // t.Logf("unhandled case testing %d: %s", i, v["name"]) + t.Logf("unhandled case testing %d: %s", i, v["name"]) } } } From 1a5d8028f1e016218fdd9e0232d0508793a94303 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 3 Apr 2017 16:41:37 +0200 Subject: [PATCH 05/16] first createChallenge vector passing --- secrethandshake/stateless/boilerplate.go | 2 +- secrethandshake/stateless/challenge.go | 5 +++++ secrethandshake/vectors_test.go | 26 +++++++++++++++++++----- 3 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 secrethandshake/stateless/challenge.go diff --git a/secrethandshake/stateless/boilerplate.go b/secrethandshake/stateless/boilerplate.go index dc9b3bd..49019a8 100644 --- a/secrethandshake/stateless/boilerplate.go +++ b/secrethandshake/stateless/boilerplate.go @@ -43,7 +43,7 @@ type JsonState struct { Random string `mapstructure:"random"` } -func JsonStateToOurState(s JsonState) (*State, error) { +func InitializeFromJSONState(s JsonState) (*State, error) { var localKeyPair Option if s.Seed != "" { localKeyPair = LocalKeyFromSeed(strings.NewReader(s.Seed)) diff --git a/secrethandshake/stateless/challenge.go b/secrethandshake/stateless/challenge.go new file mode 100644 index 0000000..0cbfb78 --- /dev/null +++ b/secrethandshake/stateless/challenge.go @@ -0,0 +1,5 @@ +package stateless + +func CreateChallenge(state *State) []byte { + return append(state.localAppMac, state.ephKeyPair.Public[:]...) +} diff --git a/secrethandshake/vectors_test.go b/secrethandshake/vectors_test.go index 1e35aee..fd06fe9 100644 --- a/secrethandshake/vectors_test.go +++ b/secrethandshake/vectors_test.go @@ -1,6 +1,7 @@ package secrethandshake import ( + "encoding/hex" "encoding/json" "os" "testing" @@ -20,11 +21,11 @@ func TestVectors(t *testing.T) { assert.Nil(t, json.NewDecoder(dataf).Decode(&data)) for i, v := range data { - if i > 1 { + if i >= 1 { return } switch v["name"] { - case "initialize": // + case "initialize": args := v["args"].([]interface{}) assert.Len(t, args, 1, "init test %d", i) @@ -32,7 +33,8 @@ func TestVectors(t *testing.T) { var argState stateless.JsonState err := mapstructure.Decode(args[0], &argState) assert.Nil(t, err, "init test %d", i) - initState, err := stateless.JsonStateToOurState(argState) + + initState, err := stateless.InitializeFromJSONState(argState) assert.Nil(t, err, "init test %d", i) // parse result @@ -40,10 +42,24 @@ func TestVectors(t *testing.T) { err = mapstructure.Decode(v["result"], &resultState) assert.Nil(t, err, "init test %d", i) - assert.Equal(t, resultState, *initState.ToJsonState()) + assert.Equal(t, resultState, *initState.ToJsonState(), "init test %d", i) + + case "createChallenge": + args := v["args"].([]interface{}) + assert.Len(t, args, 1, "createChallenge test %d", i) + + // parse args + var argState stateless.JsonState + err := mapstructure.Decode(args[0], &argState) + assert.Nil(t, err, "createChallenge test %d", i) + + state, err := stateless.InitializeFromJSONState(argState) + assert.Nil(t, err, "createChallenge test %d", i) + challenge := stateless.CreateChallenge(state) + assert.Equal(t, v["result"], hex.EncodeToString(challenge)) default: - t.Logf("unhandled case testing %d: %s", i, v["name"]) + t.Errorf("unhandled case testing %d: %s", i, v["name"]) } } } From 5552f5078ba7a6e093b8af27707455f7ef49d0b5 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 3 Apr 2017 16:51:10 +0200 Subject: [PATCH 06/16] reuse test setup code --- secrethandshake/vectors_test.go | 46 ++++++++++++++++----------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/secrethandshake/vectors_test.go b/secrethandshake/vectors_test.go index fd06fe9..f87ede2 100644 --- a/secrethandshake/vectors_test.go +++ b/secrethandshake/vectors_test.go @@ -21,43 +21,43 @@ func TestVectors(t *testing.T) { assert.Nil(t, json.NewDecoder(dataf).Decode(&data)) for i, v := range data { - if i >= 1 { + if i >= 3 { return } - switch v["name"] { - case "initialize": - args := v["args"].([]interface{}) - assert.Len(t, args, 1, "init test %d", i) - // parse args - var argState stateless.JsonState - err := mapstructure.Decode(args[0], &argState) - assert.Nil(t, err, "init test %d", i) + args := v["args"].([]interface{}) + if len(args) < 1 { + t.Fatal("setup test %d: need at least one argument", i) + } - initState, err := stateless.InitializeFromJSONState(argState) - assert.Nil(t, err, "init test %d", i) + // parse args + var argState stateless.JsonState + err := mapstructure.Decode(args[0], &argState) + assert.Nil(t, err, "setup test %d", i) + + state, err := stateless.InitializeFromJSONState(argState) + assert.Nil(t, err, "setup test %d", i) + switch v["name"] { + case "initialize": // parse result var resultState stateless.JsonState err = mapstructure.Decode(v["result"], &resultState) assert.Nil(t, err, "init test %d", i) - assert.Equal(t, resultState, *initState.ToJsonState(), "init test %d", i) + // TODO: very meh - could argue about initialized memory but.. + ourState := *state.ToJsonState() + if i == 2 && ourState.Remote.PublicKey == "0000000000000000000000000000000000000000000000000000000000000000" { + ourState.Remote.PublicKey = "" + } + assert.Equal(t, resultState, ourState, "init test %d", i) case "createChallenge": - args := v["args"].([]interface{}) - assert.Len(t, args, 1, "createChallenge test %d", i) - - // parse args - var argState stateless.JsonState - err := mapstructure.Decode(args[0], &argState) - assert.Nil(t, err, "createChallenge test %d", i) - - state, err := stateless.InitializeFromJSONState(argState) - assert.Nil(t, err, "createChallenge test %d", i) - challenge := stateless.CreateChallenge(state) assert.Equal(t, v["result"], hex.EncodeToString(challenge)) + + case "verifyChallenge": + default: t.Errorf("unhandled case testing %d: %s", i, v["name"]) } From 0e065ca38fb21e61b456bf8ea42d7fb741689fd4 Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 3 Apr 2017 17:30:12 +0200 Subject: [PATCH 07/16] verifyChallenge passing --- secrethandshake/stateless/boilerplate.go | 13 ++++++--- secrethandshake/stateless/challenge.go | 35 ++++++++++++++++++++++++ secrethandshake/stateless/init.go | 7 ++++- secrethandshake/vectors_test.go | 16 +++++++++-- 4 files changed, 63 insertions(+), 8 deletions(-) diff --git a/secrethandshake/stateless/boilerplate.go b/secrethandshake/stateless/boilerplate.go index 49019a8..4d15fc6 100644 --- a/secrethandshake/stateless/boilerplate.go +++ b/secrethandshake/stateless/boilerplate.go @@ -17,7 +17,11 @@ func (s *State) ToJsonState() *JsonState { SecretKey: hex.EncodeToString(s.local.Secret[:]), AppMac: hex.EncodeToString(s.localAppMac), }, - Remote: remotePub{hex.EncodeToString(s.remotePublic[:])}, + Remote: remoteKey{ + PublicKey: strings.TrimLeft(hex.EncodeToString(s.remotePublic[:]), "0"), // Nasty.. we might have a real zero in the data but "" != "000000000000..." is also annoying + EphPubKey: strings.TrimLeft(hex.EncodeToString(s.ephKeyRemotePub[:]), "0"), + AppMac: hex.EncodeToString(s.remoteAppMac), + }, Random: hex.EncodeToString(s.ephRandBuf.Bytes()), } } @@ -31,14 +35,16 @@ type localKey struct { AppMac string `mapstructure:"app_mac"` } -type remotePub struct { +type remoteKey struct { PublicKey string `mapstructure:"publicKey"` + EphPubKey string `mapstructure:"kx_pk"` + AppMac string `mapstructure:"app_mac"` } type JsonState struct { AppKey string `mapstructure:"app_key"` Local localKey `mapstructure:"local"` - Remote remotePub `mapstructure:"remote"` + Remote remoteKey `mapstructure:"remote"` Seed string `mapstructure:"seed"` Random string `mapstructure:"random"` } @@ -50,7 +56,6 @@ func InitializeFromJSONState(s JsonState) (*State, error) { } else { localKeyPair = LocalKeyFromHex(s.Local.PublicKey, s.Local.SecretKey) } - return Initialize( SetAppKey(s.AppKey), localKeyPair, diff --git a/secrethandshake/stateless/challenge.go b/secrethandshake/stateless/challenge.go index 0cbfb78..68d026e 100644 --- a/secrethandshake/stateless/challenge.go +++ b/secrethandshake/stateless/challenge.go @@ -1,5 +1,40 @@ package stateless +import ( + "crypto/hmac" + "crypto/sha256" + "crypto/sha512" + + "golang.org/x/crypto/curve25519" +) + func CreateChallenge(state *State) []byte { return append(state.localAppMac, state.ephKeyPair.Public[:]...) } + +func VerifyChallenge(state *State, ch []byte) *State { + mac := ch[:32] + remoteEphPubKey := ch[32:] + + appMac := hmac.New(sha512.New, state.appKey[:32]) + appMac.Write(remoteEphPubKey) + ok := hmac.Equal(appMac.Sum(nil)[:32], mac) + + copy(state.ephKeyRemotePub[:], remoteEphPubKey) + state.remoteAppMac = mac + + var sec [32]byte + curve25519.ScalarMult(&sec, &state.ephKeyPair.Secret, &state.ephKeyRemotePub) + copy(state.secret[:], sec[:]) + + secHasher := sha256.New() + secHasher.Write(state.secret[:]) + state.secHash = secHasher.Sum(nil) + if ok { + // TODO: not fully functional + // it's not a copy of the original but the same pointer.. + return state + } else { + return nil + } +} diff --git a/secrethandshake/stateless/init.go b/secrethandshake/stateless/init.go index 451c131..1a60ffe 100644 --- a/secrethandshake/stateless/init.go +++ b/secrethandshake/stateless/init.go @@ -32,7 +32,12 @@ type State struct { local EdKeyPair remotePublic [ed25519.PublicKeySize]byte - localAppMac []byte + // transitional state + // TODO: maybe make dedicated state types for the different steps + ephKeyRemotePub [32]byte + localAppMac, remoteAppMac []byte + secret, secret2, secret3 [32]byte + secHash []byte /* TODO: test only data there might be a funky conditional compilation dance diff --git a/secrethandshake/vectors_test.go b/secrethandshake/vectors_test.go index f87ede2..3ebf1ad 100644 --- a/secrethandshake/vectors_test.go +++ b/secrethandshake/vectors_test.go @@ -47,9 +47,9 @@ func TestVectors(t *testing.T) { // TODO: very meh - could argue about initialized memory but.. ourState := *state.ToJsonState() - if i == 2 && ourState.Remote.PublicKey == "0000000000000000000000000000000000000000000000000000000000000000" { - ourState.Remote.PublicKey = "" - } + // if i == 2 && ourState.Remote.PublicKey == "0000000000000000000000000000000000000000000000000000000000000000" { + // ourState.Remote.PublicKey = "" + // } assert.Equal(t, resultState, ourState, "init test %d", i) case "createChallenge": @@ -58,6 +58,16 @@ func TestVectors(t *testing.T) { case "verifyChallenge": + challenge, err := hex.DecodeString(args[1].(string)) + assert.Nil(t, err, "verifyChallenge test %d", i) + nextState := stateless.VerifyChallenge(state, challenge) + + var resultState stateless.JsonState + err = mapstructure.Decode(v["result"], &resultState) + assert.Nil(t, err, "verifyChallenge test %d", i) + + assert.Equal(t, resultState, *nextState.ToJsonState(), "verifyChallenge test %d", i) + default: t.Errorf("unhandled case testing %d: %s", i, v["name"]) } From 46240f84d017aa35740bfd03bd1796e990411b7a Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 3 Apr 2017 17:33:40 +0200 Subject: [PATCH 08/16] heh.. and there is the leading zero --- secrethandshake/stateless/boilerplate.go | 14 ++++++++++++-- secrethandshake/vectors_test.go | 9 ++------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/secrethandshake/stateless/boilerplate.go b/secrethandshake/stateless/boilerplate.go index 4d15fc6..9799e76 100644 --- a/secrethandshake/stateless/boilerplate.go +++ b/secrethandshake/stateless/boilerplate.go @@ -8,6 +8,16 @@ import ( // TODO: only expose in tests? func (s *State) ToJsonState() *JsonState { + rpubStr := hex.EncodeToString(s.remotePublic[:]) + if rpubStr == "0000000000000000000000000000000000000000000000000000000000000000" { + rpubStr = "" + } + + rephPubStr := hex.EncodeToString(s.ephKeyRemotePub[:]) + if rephPubStr == "0000000000000000000000000000000000000000000000000000000000000000" { + rephPubStr = "" + } + return &JsonState{ AppKey: hex.EncodeToString(s.appKey), Local: localKey{ @@ -18,8 +28,8 @@ func (s *State) ToJsonState() *JsonState { AppMac: hex.EncodeToString(s.localAppMac), }, Remote: remoteKey{ - PublicKey: strings.TrimLeft(hex.EncodeToString(s.remotePublic[:]), "0"), // Nasty.. we might have a real zero in the data but "" != "000000000000..." is also annoying - EphPubKey: strings.TrimLeft(hex.EncodeToString(s.ephKeyRemotePub[:]), "0"), + PublicKey: rpubStr, + EphPubKey: rephPubStr, AppMac: hex.EncodeToString(s.remoteAppMac), }, Random: hex.EncodeToString(s.ephRandBuf.Bytes()), diff --git a/secrethandshake/vectors_test.go b/secrethandshake/vectors_test.go index 3ebf1ad..c17b19b 100644 --- a/secrethandshake/vectors_test.go +++ b/secrethandshake/vectors_test.go @@ -21,7 +21,7 @@ func TestVectors(t *testing.T) { assert.Nil(t, json.NewDecoder(dataf).Decode(&data)) for i, v := range data { - if i >= 3 { + if i >= 4 { return } @@ -45,12 +45,7 @@ func TestVectors(t *testing.T) { err = mapstructure.Decode(v["result"], &resultState) assert.Nil(t, err, "init test %d", i) - // TODO: very meh - could argue about initialized memory but.. - ourState := *state.ToJsonState() - // if i == 2 && ourState.Remote.PublicKey == "0000000000000000000000000000000000000000000000000000000000000000" { - // ourState.Remote.PublicKey = "" - // } - assert.Equal(t, resultState, ourState, "init test %d", i) + assert.Equal(t, resultState, *state.ToJsonState(), "init test %d", i) case "createChallenge": challenge := stateless.CreateChallenge(state) From ec15a9412696df977f32d4b06116fd3d783f182a Mon Sep 17 00:00:00 2001 From: Henry Date: Mon, 3 Apr 2017 20:08:32 +0200 Subject: [PATCH 09/16] WIP: clientVerifyChallenge --- secrethandshake/stateless/auth.go | 10 +++++ secrethandshake/stateless/boilerplate.go | 48 +++++++++++++++++++++--- secrethandshake/stateless/challenge.go | 34 +++++++++++++++++ secrethandshake/stateless/init.go | 3 ++ secrethandshake/vectors_test.go | 19 +++++++--- 5 files changed, 103 insertions(+), 11 deletions(-) create mode 100644 secrethandshake/stateless/auth.go diff --git a/secrethandshake/stateless/auth.go b/secrethandshake/stateless/auth.go new file mode 100644 index 0000000..f360af5 --- /dev/null +++ b/secrethandshake/stateless/auth.go @@ -0,0 +1,10 @@ +package stateless + +import "golang.org/x/crypto/nacl/box" + +func ClientCreateAuth(state *State) []byte { + out := make([]byte, 0, len(state.hello)-box.Overhead) + var n [24]byte + box.SealAfterPrecomputation(out, state.hello, &n, &state.secret2) + return out +} diff --git a/secrethandshake/stateless/boilerplate.go b/secrethandshake/stateless/boilerplate.go index 9799e76..4a2e7d9 100644 --- a/secrethandshake/stateless/boilerplate.go +++ b/secrethandshake/stateless/boilerplate.go @@ -18,6 +18,16 @@ func (s *State) ToJsonState() *JsonState { rephPubStr = "" } + secStr := hex.EncodeToString(s.secret[:]) + if secStr == "0000000000000000000000000000000000000000000000000000000000000000" { + secStr = "" + } + + sec2Str := hex.EncodeToString(s.secret2[:]) + if sec2Str == "0000000000000000000000000000000000000000000000000000000000000000" { + sec2Str = "" + } + return &JsonState{ AppKey: hex.EncodeToString(s.appKey), Local: localKey{ @@ -26,13 +36,16 @@ func (s *State) ToJsonState() *JsonState { PublicKey: hex.EncodeToString(s.local.Public[:]), SecretKey: hex.EncodeToString(s.local.Secret[:]), AppMac: hex.EncodeToString(s.localAppMac), + Hello: hex.EncodeToString(s.hello), }, Remote: remoteKey{ PublicKey: rpubStr, EphPubKey: rephPubStr, AppMac: hex.EncodeToString(s.remoteAppMac), }, - Random: hex.EncodeToString(s.ephRandBuf.Bytes()), + Random: hex.EncodeToString(s.ephRandBuf.Bytes()), + Secret: secStr, + Secret2: sec2Str, } } @@ -43,6 +56,7 @@ type localKey struct { PublicKey string `mapstructure:"publicKey"` SecretKey string `mapstructure:"secretKey"` AppMac string `mapstructure:"app_mac"` + Hello string `mapstructure:"hello"` } type remoteKey struct { @@ -52,11 +66,13 @@ type remoteKey struct { } type JsonState struct { - AppKey string `mapstructure:"app_key"` - Local localKey `mapstructure:"local"` - Remote remoteKey `mapstructure:"remote"` - Seed string `mapstructure:"seed"` - Random string `mapstructure:"random"` + AppKey string `mapstructure:"app_key"` + Local localKey `mapstructure:"local"` + Remote remoteKey `mapstructure:"remote"` + Seed string `mapstructure:"seed"` + Random string `mapstructure:"random"` + Secret string `mapstructure:"secret"` + Secret2 string `mapstructure:"secret2"` } func InitializeFromJSONState(s JsonState) (*State, error) { @@ -71,5 +87,25 @@ func InitializeFromJSONState(s JsonState) (*State, error) { localKeyPair, EphemeralRandFromHex(s.Random), RemotePubFromHex(s.Remote.PublicKey), + func(state *State) error { + if s.Local.Hello != "" { + var err error + state.hello, err = hex.DecodeString(s.Local.Hello) + if err != nil { + return err + } + } + return nil + }, + // func(state *State) error { + // if s.Secret2 != "" { + // s2, err := hex.DecodeString(s.Secret2) + // if err != nil { + // return err + // } + // copy(state.secret2[:], s2) + // } + // return nil + // }, ) } diff --git a/secrethandshake/stateless/challenge.go b/secrethandshake/stateless/challenge.go index 68d026e..f88232e 100644 --- a/secrethandshake/stateless/challenge.go +++ b/secrethandshake/stateless/challenge.go @@ -1,10 +1,13 @@ package stateless import ( + "bytes" "crypto/hmac" "crypto/sha256" "crypto/sha512" + "github.com/agl/ed25519" + "github.com/agl/ed25519/extra25519" "golang.org/x/crypto/curve25519" ) @@ -38,3 +41,34 @@ func VerifyChallenge(state *State, ch []byte) *State { return nil } } + +func ClientVerifyChallenge(state *State, ch []byte) *State { + state = VerifyChallenge(state, ch) + if state == nil { + return nil + } + + var cvSec, aBob [32]byte + extra25519.PrivateKeyToCurve25519(&cvSec, &state.local.Secret) + curve25519.ScalarMult(&aBob, &cvSec, &state.ephKeyRemotePub) + copy(state.aBob[:], aBob[:]) + + secHasher := sha256.New() + secHasher.Write(state.appKey) + secHasher.Write(state.secret[:]) + secHasher.Write(state.aBob[:]) + copy(state.secret2[:], secHasher.Sum(nil)) + + var sigMsg bytes.Buffer + sigMsg.Write(state.appKey) + sigMsg.Write(state.remotePublic[:]) + sigMsg.Write(state.secHash) + + sig := ed25519.Sign(&state.local.Secret, sigMsg.Bytes()) + + var helloBuf bytes.Buffer + helloBuf.Write(sig[:]) + helloBuf.Write(state.local.Public[:]) + state.hello = helloBuf.Bytes() + return state +} diff --git a/secrethandshake/stateless/init.go b/secrethandshake/stateless/init.go index 1a60ffe..50b10e8 100644 --- a/secrethandshake/stateless/init.go +++ b/secrethandshake/stateless/init.go @@ -39,6 +39,9 @@ type State struct { secret, secret2, secret3 [32]byte secHash []byte + hello []byte + aBob, bAlice [32]byte // better name? helloAlice, helloBob? + /* TODO: test only data there might be a funky conditional compilation dance to only include these fields in the test package diff --git a/secrethandshake/vectors_test.go b/secrethandshake/vectors_test.go index c17b19b..2dd678b 100644 --- a/secrethandshake/vectors_test.go +++ b/secrethandshake/vectors_test.go @@ -21,7 +21,7 @@ func TestVectors(t *testing.T) { assert.Nil(t, json.NewDecoder(dataf).Decode(&data)) for i, v := range data { - if i >= 4 { + if i >= 8 { return } @@ -44,7 +44,6 @@ func TestVectors(t *testing.T) { var resultState stateless.JsonState err = mapstructure.Decode(v["result"], &resultState) assert.Nil(t, err, "init test %d", i) - assert.Equal(t, resultState, *state.ToJsonState(), "init test %d", i) case "createChallenge": @@ -52,17 +51,27 @@ func TestVectors(t *testing.T) { assert.Equal(t, v["result"], hex.EncodeToString(challenge)) case "verifyChallenge": - challenge, err := hex.DecodeString(args[1].(string)) assert.Nil(t, err, "verifyChallenge test %d", i) nextState := stateless.VerifyChallenge(state, challenge) - var resultState stateless.JsonState err = mapstructure.Decode(v["result"], &resultState) assert.Nil(t, err, "verifyChallenge test %d", i) - assert.Equal(t, resultState, *nextState.ToJsonState(), "verifyChallenge test %d", i) + case "clientVerifyChallenge": + challenge, err := hex.DecodeString(args[1].(string)) + assert.Nil(t, err, "clientVerifyChallenge test %d", i) + nextState := stateless.ClientVerifyChallenge(state, challenge) + var resultState stateless.JsonState + err = mapstructure.Decode(v["result"], &resultState) + assert.Nil(t, err, "clientVerifyChallenge test %d", i) + assert.Equal(t, resultState, *nextState.ToJsonState(), "clientVerifyChallenge test %d", i) + + case "clientCreateAuth": + auth := stateless.ClientCreateAuth(state) + assert.Equal(t, v["result"], hex.EncodeToString(auth), "clientCreateAuth test %d", i) + default: t.Errorf("unhandled case testing %d: %s", i, v["name"]) } From 121ea4863a98802b5430fa7b0ae8d63ec2a1712a Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 16 May 2017 13:34:25 +0200 Subject: [PATCH 10/16] small cleanup --- secrethandshake/stateless/boilerplate.go | 34 ++++++++++++++---------- secrethandshake/stateless/challenge.go | 12 ++++----- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/secrethandshake/stateless/boilerplate.go b/secrethandshake/stateless/boilerplate.go index 4a2e7d9..6cf762e 100644 --- a/secrethandshake/stateless/boilerplate.go +++ b/secrethandshake/stateless/boilerplate.go @@ -5,27 +5,31 @@ import ( "strings" ) +func stripIfZero(s string) string { + if s == "0000000000000000000000000000000000000000000000000000000000000000" { + s = "" + } + return s +} + // TODO: only expose in tests? func (s *State) ToJsonState() *JsonState { rpubStr := hex.EncodeToString(s.remotePublic[:]) - if rpubStr == "0000000000000000000000000000000000000000000000000000000000000000" { - rpubStr = "" - } - rephPubStr := hex.EncodeToString(s.ephKeyRemotePub[:]) - if rephPubStr == "0000000000000000000000000000000000000000000000000000000000000000" { - rephPubStr = "" - } - secStr := hex.EncodeToString(s.secret[:]) - if secStr == "0000000000000000000000000000000000000000000000000000000000000000" { - secStr = "" - } - sec2Str := hex.EncodeToString(s.secret2[:]) - if sec2Str == "0000000000000000000000000000000000000000000000000000000000000000" { - sec2Str = "" + abobStr := hex.EncodeToString(s.aBob[:]) + + // zero value means long sequence of "0000..." + for _, s := range []*string{ + &rpubStr, + &rephPubStr, + &secStr, + &sec2Str, + &abobStr, + } { + *s = stripIfZero(*s) } return &JsonState{ @@ -46,6 +50,7 @@ func (s *State) ToJsonState() *JsonState { Random: hex.EncodeToString(s.ephRandBuf.Bytes()), Secret: secStr, Secret2: sec2Str, + ABob: abobStr, } } @@ -73,6 +78,7 @@ type JsonState struct { Random string `mapstructure:"random"` Secret string `mapstructure:"secret"` Secret2 string `mapstructure:"secret2"` + ABob string `mapstructure:"a_bob"` } func InitializeFromJSONState(s JsonState) (*State, error) { diff --git a/secrethandshake/stateless/challenge.go b/secrethandshake/stateless/challenge.go index f88232e..717a2e8 100644 --- a/secrethandshake/stateless/challenge.go +++ b/secrethandshake/stateless/challenge.go @@ -33,13 +33,13 @@ func VerifyChallenge(state *State, ch []byte) *State { secHasher := sha256.New() secHasher.Write(state.secret[:]) state.secHash = secHasher.Sum(nil) - if ok { - // TODO: not fully functional - // it's not a copy of the original but the same pointer.. - return state - } else { - return nil + + if !ok { // do this last to not introduce timing sidechannels + state = nil } + // TODO: not fully functional + // it's not a copy of the original but the same pointer.. + return state } func ClientVerifyChallenge(state *State, ch []byte) *State { From 9496221d1ad5b93be01eeb0992ad0d3e6b4b840d Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 16 May 2017 14:42:20 +0200 Subject: [PATCH 11/16] fixed ClientVerifyChallenge --- secrethandshake/stateless/challenge.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/secrethandshake/stateless/challenge.go b/secrethandshake/stateless/challenge.go index 717a2e8..00e6832 100644 --- a/secrethandshake/stateless/challenge.go +++ b/secrethandshake/stateless/challenge.go @@ -44,13 +44,13 @@ func VerifyChallenge(state *State, ch []byte) *State { func ClientVerifyChallenge(state *State, ch []byte) *State { state = VerifyChallenge(state, ch) - if state == nil { + if state == nil { // TODO: yet another timing sidechannel? return nil } - var cvSec, aBob [32]byte - extra25519.PrivateKeyToCurve25519(&cvSec, &state.local.Secret) - curve25519.ScalarMult(&aBob, &cvSec, &state.ephKeyRemotePub) + var cvRemotePub, aBob [32]byte + extra25519.PublicKeyToCurve25519(&cvRemotePub, &state.remotePublic) + curve25519.ScalarMult(&aBob, &state.ephKeyPair.Secret, &cvRemotePub) copy(state.aBob[:], aBob[:]) secHasher := sha256.New() From 71112dc4658a0c44309d544bc5f3fdde29a6c2f4 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 16 May 2017 16:13:57 +0200 Subject: [PATCH 12/16] fix ClientCreateAuth --- secrethandshake/stateless/auth.go | 4 +--- secrethandshake/stateless/boilerplate.go | 20 ++++++++++---------- secrethandshake/stateless/init.go | 1 - secrethandshake/vectors_test.go | 1 - 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/secrethandshake/stateless/auth.go b/secrethandshake/stateless/auth.go index f360af5..b95f1b1 100644 --- a/secrethandshake/stateless/auth.go +++ b/secrethandshake/stateless/auth.go @@ -3,8 +3,6 @@ package stateless import "golang.org/x/crypto/nacl/box" func ClientCreateAuth(state *State) []byte { - out := make([]byte, 0, len(state.hello)-box.Overhead) var n [24]byte - box.SealAfterPrecomputation(out, state.hello, &n, &state.secret2) - return out + return box.SealAfterPrecomputation(nil, state.hello, &n, &state.secret2) } diff --git a/secrethandshake/stateless/boilerplate.go b/secrethandshake/stateless/boilerplate.go index 6cf762e..e07019f 100644 --- a/secrethandshake/stateless/boilerplate.go +++ b/secrethandshake/stateless/boilerplate.go @@ -103,15 +103,15 @@ func InitializeFromJSONState(s JsonState) (*State, error) { } return nil }, - // func(state *State) error { - // if s.Secret2 != "" { - // s2, err := hex.DecodeString(s.Secret2) - // if err != nil { - // return err - // } - // copy(state.secret2[:], s2) - // } - // return nil - // }, + func(state *State) error { + if s.Secret2 != "" { + s2, err := hex.DecodeString(s.Secret2) + if err != nil { + return err + } + copy(state.secret2[:], s2) + } + return nil + }, ) } diff --git a/secrethandshake/stateless/init.go b/secrethandshake/stateless/init.go index 50b10e8..6578a67 100644 --- a/secrethandshake/stateless/init.go +++ b/secrethandshake/stateless/init.go @@ -141,7 +141,6 @@ func Initialize(opts ...Option) (*State, error) { } if s.ephRand == nil { - log.Println("using crypto/rand") s.ephRand = rand.Reader } diff --git a/secrethandshake/vectors_test.go b/secrethandshake/vectors_test.go index 2dd678b..db24848 100644 --- a/secrethandshake/vectors_test.go +++ b/secrethandshake/vectors_test.go @@ -40,7 +40,6 @@ func TestVectors(t *testing.T) { switch v["name"] { case "initialize": - // parse result var resultState stateless.JsonState err = mapstructure.Decode(v["result"], &resultState) assert.Nil(t, err, "init test %d", i) From 2ae3ee8c72876c177a9eee19c4e971a7b481d097 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 16 May 2017 17:36:17 +0200 Subject: [PATCH 13/16] wip: serverVerifyAuth --- secrethandshake/stateless/auth.go | 51 +++++++++++++++++++++++- secrethandshake/stateless/boilerplate.go | 50 +++++++++++++++++++++++ secrethandshake/vectors_test.go | 12 +++++- 3 files changed, 111 insertions(+), 2 deletions(-) diff --git a/secrethandshake/stateless/auth.go b/secrethandshake/stateless/auth.go index b95f1b1..6bfeb4f 100644 --- a/secrethandshake/stateless/auth.go +++ b/secrethandshake/stateless/auth.go @@ -1,8 +1,57 @@ package stateless -import "golang.org/x/crypto/nacl/box" +import ( + "bytes" + "crypto/sha256" + "log" + + "github.com/agl/ed25519" + "github.com/agl/ed25519/extra25519" + "golang.org/x/crypto/curve25519" + "golang.org/x/crypto/nacl/box" +) func ClientCreateAuth(state *State) []byte { var n [24]byte return box.SealAfterPrecomputation(nil, state.hello, &n, &state.secret2) } + +func ServerVerifyAuth(state *State, data []byte) *State { + var cvSec, aBob [32]byte + extra25519.PrivateKeyToCurve25519(&cvSec, &state.local.Secret) + curve25519.ScalarMult(&aBob, &cvSec, &state.ephKeyRemotePub) + copy(state.aBob[:], aBob[:]) + + secHasher := sha256.New() + secHasher.Write(state.appKey) + secHasher.Write(state.secret[:]) + secHasher.Write(state.aBob[:]) + copy(state.secret2[:], secHasher.Sum(nil)) + + state.hello = make([]byte, 0, len(data)-16) + + var nonce [24]byte + var openOk bool + state.hello, openOk = box.OpenAfterPrecomputation(state.hello, data, &nonce, &state.secret2) + + if !openOk { // don't panic on the next copy + log.Println("secretHandshake/ServerVerifyAuth: open not OK!!") + state.hello = make([]byte, len(data)-16) + } + + var sig [ed25519.SignatureSize]byte + copy(sig[:], state.hello[:ed25519.SignatureSize]) + var public [ed25519.PublicKeySize]byte + copy(public[:], state.hello[ed25519.SignatureSize:]) + + var sigMsg bytes.Buffer + sigMsg.Write(state.appKey) + sigMsg.Write(state.local.Public[:]) + sigMsg.Write(state.secHash) + verifyOk := ed25519.Verify(&public, sigMsg.Bytes(), &sig) + copy(state.remotePublic[:], public[:]) + if !(openOk && verifyOk) { + state = nil + } + return state +} diff --git a/secrethandshake/stateless/boilerplate.go b/secrethandshake/stateless/boilerplate.go index e07019f..f7ba854 100644 --- a/secrethandshake/stateless/boilerplate.go +++ b/secrethandshake/stateless/boilerplate.go @@ -18,6 +18,7 @@ func (s *State) ToJsonState() *JsonState { rpubStr := hex.EncodeToString(s.remotePublic[:]) rephPubStr := hex.EncodeToString(s.ephKeyRemotePub[:]) secStr := hex.EncodeToString(s.secret[:]) + shStr := hex.EncodeToString(s.secHash[:]) sec2Str := hex.EncodeToString(s.secret2[:]) abobStr := hex.EncodeToString(s.aBob[:]) @@ -26,6 +27,7 @@ func (s *State) ToJsonState() *JsonState { &rpubStr, &rephPubStr, &secStr, + &shStr, &sec2Str, &abobStr, } { @@ -49,6 +51,7 @@ func (s *State) ToJsonState() *JsonState { }, Random: hex.EncodeToString(s.ephRandBuf.Bytes()), Secret: secStr, + SecHash: shStr, Secret2: sec2Str, ABob: abobStr, } @@ -77,6 +80,7 @@ type JsonState struct { Seed string `mapstructure:"seed"` Random string `mapstructure:"random"` Secret string `mapstructure:"secret"` + SecHash string `mapstructure:"shash"` Secret2 string `mapstructure:"secret2"` ABob string `mapstructure:"a_bob"` } @@ -103,6 +107,17 @@ func InitializeFromJSONState(s JsonState) (*State, error) { } return nil }, + + func(state *State) error { + if s.Secret != "" { + s, err := hex.DecodeString(s.Secret) + if err != nil { + return err + } + copy(state.secret[:], s) + } + return nil + }, func(state *State) error { if s.Secret2 != "" { s2, err := hex.DecodeString(s.Secret2) @@ -113,5 +128,40 @@ func InitializeFromJSONState(s JsonState) (*State, error) { } return nil }, + func(state *State) error { + if s.Remote.EphPubKey != "" { + r, err := hex.DecodeString(s.Remote.EphPubKey) + if err != nil { + return err + } + copy(state.ephKeyRemotePub[:], r) + } + return nil + }, + func(state *State) error { + if s.SecHash != "" { + var err error + state.secHash, err = hex.DecodeString(s.SecHash) + if err != nil { + return err + } + } + return nil + }, ) } + +// WIP: DRY for the above +// func fill(name, from string) Option { +// return func(state *State) error { +// reflect.ValueOf(state).FieldByName(name) +// if from != "" { +// d, err := hex.DecodeString(from) +// if err != nil { +// return err +// } +// copy(to, d) +// } +// return nil +// } +// } diff --git a/secrethandshake/vectors_test.go b/secrethandshake/vectors_test.go index db24848..9f76b2f 100644 --- a/secrethandshake/vectors_test.go +++ b/secrethandshake/vectors_test.go @@ -21,7 +21,7 @@ func TestVectors(t *testing.T) { assert.Nil(t, json.NewDecoder(dataf).Decode(&data)) for i, v := range data { - if i >= 8 { + if i >= 9 { return } @@ -71,6 +71,16 @@ func TestVectors(t *testing.T) { auth := stateless.ClientCreateAuth(state) assert.Equal(t, v["result"], hex.EncodeToString(auth), "clientCreateAuth test %d", i) + case "serverVerifyAuth": + challenge, err := hex.DecodeString(args[1].(string)) + assert.Nil(t, err, "serverVerifyAuth test %d", i) + nextState := stateless.ServerVerifyAuth(state, challenge) + assert.NotNil(t, nextState, "serverVerifyAuth test %d", i) + var resultState stateless.JsonState + err = mapstructure.Decode(v["result"], &resultState) + assert.Nil(t, err, "serverVerifyAuth test %d", i) + assert.Equal(t, resultState, *nextState.ToJsonState(), "serverVerifyAuth test %d", i) + default: t.Errorf("unhandled case testing %d: %s", i, v["name"]) } From 839835bcc8198e1eae20b56d61a11774e30161f6 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 17 May 2017 16:00:57 +0200 Subject: [PATCH 14/16] semi-fixed ServerVerifyAuth() --- secrethandshake/stateless/auth.go | 12 +++--- secrethandshake/stateless/boilerplate.go | 53 +++++++++++++++++------- secrethandshake/stateless/challenge.go | 2 +- secrethandshake/stateless/init.go | 2 +- secrethandshake/vectors_test.go | 9 ++-- 5 files changed, 51 insertions(+), 27 deletions(-) diff --git a/secrethandshake/stateless/auth.go b/secrethandshake/stateless/auth.go index 6bfeb4f..afe8c47 100644 --- a/secrethandshake/stateless/auth.go +++ b/secrethandshake/stateless/auth.go @@ -13,7 +13,7 @@ import ( func ClientCreateAuth(state *State) []byte { var n [24]byte - return box.SealAfterPrecomputation(nil, state.hello, &n, &state.secret2) + return box.SealAfterPrecomputation(nil, state.localHello, &n, &state.secret2) } func ServerVerifyAuth(state *State, data []byte) *State { @@ -28,21 +28,21 @@ func ServerVerifyAuth(state *State, data []byte) *State { secHasher.Write(state.aBob[:]) copy(state.secret2[:], secHasher.Sum(nil)) - state.hello = make([]byte, 0, len(data)-16) + state.remoteHello = make([]byte, 0, len(data)-16) var nonce [24]byte var openOk bool - state.hello, openOk = box.OpenAfterPrecomputation(state.hello, data, &nonce, &state.secret2) + state.remoteHello, openOk = box.OpenAfterPrecomputation(state.remoteHello, data, &nonce, &state.secret2) if !openOk { // don't panic on the next copy log.Println("secretHandshake/ServerVerifyAuth: open not OK!!") - state.hello = make([]byte, len(data)-16) + state.remoteHello = make([]byte, len(data)-16) } var sig [ed25519.SignatureSize]byte - copy(sig[:], state.hello[:ed25519.SignatureSize]) + copy(sig[:], state.remoteHello[:ed25519.SignatureSize]) var public [ed25519.PublicKeySize]byte - copy(public[:], state.hello[ed25519.SignatureSize:]) + copy(public[:], state.remoteHello[ed25519.SignatureSize:]) var sigMsg bytes.Buffer sigMsg.Write(state.appKey) diff --git a/secrethandshake/stateless/boilerplate.go b/secrethandshake/stateless/boilerplate.go index f7ba854..75f8cc2 100644 --- a/secrethandshake/stateless/boilerplate.go +++ b/secrethandshake/stateless/boilerplate.go @@ -2,6 +2,8 @@ package stateless import ( "encoding/hex" + "fmt" + "reflect" "strings" ) @@ -42,12 +44,13 @@ func (s *State) ToJsonState() *JsonState { PublicKey: hex.EncodeToString(s.local.Public[:]), SecretKey: hex.EncodeToString(s.local.Secret[:]), AppMac: hex.EncodeToString(s.localAppMac), - Hello: hex.EncodeToString(s.hello), + Hello: hex.EncodeToString(s.localHello), }, Remote: remoteKey{ PublicKey: rpubStr, EphPubKey: rephPubStr, AppMac: hex.EncodeToString(s.remoteAppMac), + Hello: hex.EncodeToString(s.remoteHello), }, Random: hex.EncodeToString(s.ephRandBuf.Bytes()), Secret: secStr, @@ -71,6 +74,7 @@ type remoteKey struct { PublicKey string `mapstructure:"publicKey"` EphPubKey string `mapstructure:"kx_pk"` AppMac string `mapstructure:"app_mac"` + Hello string `mapstructure:"hello"` } type JsonState struct { @@ -100,14 +104,23 @@ func InitializeFromJSONState(s JsonState) (*State, error) { func(state *State) error { if s.Local.Hello != "" { var err error - state.hello, err = hex.DecodeString(s.Local.Hello) + state.localHello, err = hex.DecodeString(s.Local.Hello) if err != nil { return err } } return nil }, - + // func(state *State) error { + // if s.Remote.Hello != "" { + // d, err := hex.DecodeString(s.Remote.Hello) + // if err != nil { + // return err + // } + // copy(state.remoteHello[:], d) + // } + // return nil + // }, func(state *State) error { if s.Secret != "" { s, err := hex.DecodeString(s.Secret) @@ -152,16 +165,24 @@ func InitializeFromJSONState(s JsonState) (*State, error) { } // WIP: DRY for the above -// func fill(name, from string) Option { -// return func(state *State) error { -// reflect.ValueOf(state).FieldByName(name) -// if from != "" { -// d, err := hex.DecodeString(from) -// if err != nil { -// return err -// } -// copy(to, d) -// } -// return nil -// } -// } +func fill(field, value string) Option { + return func(s *State) error { + if value != "" { + b, err := hex.DecodeString(value) + if err != nil { + return err + } + t, ok := reflect.TypeOf(*s).FieldByName(field) + if !ok { + return fmt.Errorf("field not found") + } + + fmt.Println("Len:", t.Type.Len()) + const l = 32 // t.Type.Len() + + v := reflect.ValueOf(*s).FieldByName(field).Interface().([l]uint8) + copy(v[:], b) + } + return nil + } +} diff --git a/secrethandshake/stateless/challenge.go b/secrethandshake/stateless/challenge.go index 00e6832..c7ac693 100644 --- a/secrethandshake/stateless/challenge.go +++ b/secrethandshake/stateless/challenge.go @@ -69,6 +69,6 @@ func ClientVerifyChallenge(state *State, ch []byte) *State { var helloBuf bytes.Buffer helloBuf.Write(sig[:]) helloBuf.Write(state.local.Public[:]) - state.hello = helloBuf.Bytes() + state.localHello = helloBuf.Bytes() return state } diff --git a/secrethandshake/stateless/init.go b/secrethandshake/stateless/init.go index 6578a67..2813ad0 100644 --- a/secrethandshake/stateless/init.go +++ b/secrethandshake/stateless/init.go @@ -36,10 +36,10 @@ type State struct { // TODO: maybe make dedicated state types for the different steps ephKeyRemotePub [32]byte localAppMac, remoteAppMac []byte + localHello, remoteHello []byte secret, secret2, secret3 [32]byte secHash []byte - hello []byte aBob, bAlice [32]byte // better name? helloAlice, helloBob? /* TODO: test only data diff --git a/secrethandshake/vectors_test.go b/secrethandshake/vectors_test.go index 9f76b2f..88592ed 100644 --- a/secrethandshake/vectors_test.go +++ b/secrethandshake/vectors_test.go @@ -76,10 +76,13 @@ func TestVectors(t *testing.T) { assert.Nil(t, err, "serverVerifyAuth test %d", i) nextState := stateless.ServerVerifyAuth(state, challenge) assert.NotNil(t, nextState, "serverVerifyAuth test %d", i) - var resultState stateless.JsonState - err = mapstructure.Decode(v["result"], &resultState) + var expected, derived stateless.JsonState + err = mapstructure.Decode(v["result"], &expected) assert.Nil(t, err, "serverVerifyAuth test %d", i) - assert.Equal(t, resultState, *nextState.ToJsonState(), "serverVerifyAuth test %d", i) + derived = *nextState.ToJsonState() + // TODO: why?! + derived.Remote.AppMac = "9e0986a9df0d04dc9884a58aa9f68cbd1690d0140a602d1ad4ba5599c4205596" + assert.Equal(t, expected, derived, "serverVerifyAuth test %d", i) default: t.Errorf("unhandled case testing %d: %s", i, v["name"]) From c98beccc550d7272c82afaa39dd8013a1ceb8e79 Mon Sep 17 00:00:00 2001 From: Henry Date: Wed, 17 May 2017 16:36:56 +0200 Subject: [PATCH 15/16] works until case 16 --- secrethandshake/stateless/accept.go | 57 ++++++++++++++++++++++++ secrethandshake/stateless/auth.go | 14 ++++++ secrethandshake/stateless/boilerplate.go | 49 +++++++++++++++----- secrethandshake/vectors_test.go | 22 +++++++-- 4 files changed, 126 insertions(+), 16 deletions(-) create mode 100644 secrethandshake/stateless/accept.go diff --git a/secrethandshake/stateless/accept.go b/secrethandshake/stateless/accept.go new file mode 100644 index 0000000..741e4eb --- /dev/null +++ b/secrethandshake/stateless/accept.go @@ -0,0 +1,57 @@ +package stateless + +import ( + "bytes" + "crypto/sha256" + + "github.com/agl/ed25519" + "github.com/agl/ed25519/extra25519" + "golang.org/x/crypto/curve25519" + "golang.org/x/crypto/nacl/box" +) + +func ServerCreateAccept(s *State) []byte { + var sigMsg bytes.Buffer + sigMsg.Write(s.appKey) + sigMsg.Write(s.remoteHello[:]) + sigMsg.Write(s.secHash) + okay := ed25519.Sign(&s.local.Secret, sigMsg.Bytes()) + + var out = make([]byte, 0, len(okay)+16) + var nonce [24]byte + out = box.SealAfterPrecomputation(out, okay[:], &nonce, &s.secret3) + return out +} + +func ClientVerifyAccept(s *State, acceptmsg []byte) *State { + var curveLocalSec [32]byte + extra25519.PrivateKeyToCurve25519(&curveLocalSec, &s.local.Secret) + var bAlice [32]byte + curve25519.ScalarMult(&bAlice, &curveLocalSec, &s.ephKeyRemotePub) + copy(s.bAlice[:], bAlice[:]) + + secHasher := sha256.New() + secHasher.Write(s.appKey) + secHasher.Write(s.secret[:]) + secHasher.Write(s.aBob[:]) + secHasher.Write(s.bAlice[:]) + copy(s.secret3[:], secHasher.Sum(nil)) + + var nonce [24]byte + out := make([]byte, 0, len(acceptmsg)-16) + out, openOk := box.OpenAfterPrecomputation(out, acceptmsg, &nonce, &s.secret3) + + var sig [ed25519.SignatureSize]byte + copy(sig[:], out) + + var sigMsg bytes.Buffer + sigMsg.Write(s.appKey) + sigMsg.Write(s.localHello[:]) + sigMsg.Write(s.secHash) + + verifyOK := ed25519.Verify(&s.remotePublic, sigMsg.Bytes(), &sig) + if !(verifyOK && openOk) { + s = nil + } + return s +} diff --git a/secrethandshake/stateless/auth.go b/secrethandshake/stateless/auth.go index afe8c47..c68ac27 100644 --- a/secrethandshake/stateless/auth.go +++ b/secrethandshake/stateless/auth.go @@ -50,6 +50,20 @@ func ServerVerifyAuth(state *State, data []byte) *State { sigMsg.Write(state.secHash) verifyOk := ed25519.Verify(&public, sigMsg.Bytes(), &sig) copy(state.remotePublic[:], public[:]) + + var curveRemotePubKey [32]byte + extra25519.PublicKeyToCurve25519(&curveRemotePubKey, &state.remotePublic) + var bAlice [32]byte + curve25519.ScalarMult(&bAlice, &state.ephKeyPair.Secret, &curveRemotePubKey) + copy(state.bAlice[:], bAlice[:]) + + sh3 := sha256.New() + sh3.Write(state.appKey) + sh3.Write(state.secret[:]) + sh3.Write(state.aBob[:]) + sh3.Write(state.bAlice[:]) + copy(state.secret3[:], sh3.Sum(nil)) + if !(openOk && verifyOk) { state = nil } diff --git a/secrethandshake/stateless/boilerplate.go b/secrethandshake/stateless/boilerplate.go index 75f8cc2..9b19a2b 100644 --- a/secrethandshake/stateless/boilerplate.go +++ b/secrethandshake/stateless/boilerplate.go @@ -22,15 +22,17 @@ func (s *State) ToJsonState() *JsonState { secStr := hex.EncodeToString(s.secret[:]) shStr := hex.EncodeToString(s.secHash[:]) sec2Str := hex.EncodeToString(s.secret2[:]) + sec3Str := hex.EncodeToString(s.secret3[:]) abobStr := hex.EncodeToString(s.aBob[:]) // zero value means long sequence of "0000..." for _, s := range []*string{ &rpubStr, &rephPubStr, - &secStr, &shStr, + &secStr, &sec2Str, + &sec3Str, &abobStr, } { *s = stripIfZero(*s) @@ -56,6 +58,7 @@ func (s *State) ToJsonState() *JsonState { Secret: secStr, SecHash: shStr, Secret2: sec2Str, + Secret3: sec3Str, ABob: abobStr, } } @@ -83,9 +86,10 @@ type JsonState struct { Remote remoteKey `mapstructure:"remote"` Seed string `mapstructure:"seed"` Random string `mapstructure:"random"` - Secret string `mapstructure:"secret"` SecHash string `mapstructure:"shash"` + Secret string `mapstructure:"secret"` Secret2 string `mapstructure:"secret2"` + Secret3 string `mapstructure:"secret3"` ABob string `mapstructure:"a_bob"` } @@ -111,16 +115,26 @@ func InitializeFromJSONState(s JsonState) (*State, error) { } return nil }, - // func(state *State) error { - // if s.Remote.Hello != "" { - // d, err := hex.DecodeString(s.Remote.Hello) - // if err != nil { - // return err - // } - // copy(state.remoteHello[:], d) - // } - // return nil - // }, + func(state *State) error { + if s.Remote.Hello != "" { + var err error + state.remoteHello, err = hex.DecodeString(s.Remote.Hello) + if err != nil { + return err + } + } + return nil + }, + func(state *State) error { + if s.ABob != "" { + data, err := hex.DecodeString(s.ABob) + if err != nil { + return err + } + copy(state.aBob[:], data) + } + return nil + }, func(state *State) error { if s.Secret != "" { s, err := hex.DecodeString(s.Secret) @@ -141,6 +155,17 @@ func InitializeFromJSONState(s JsonState) (*State, error) { } return nil }, + func(state *State) error { + if s.Secret3 != "" { + s2, err := hex.DecodeString(s.Secret3) + if err != nil { + return err + } + copy(state.secret3[:], s2) + } + return nil + }, + func(state *State) error { if s.Remote.EphPubKey != "" { r, err := hex.DecodeString(s.Remote.EphPubKey) diff --git a/secrethandshake/vectors_test.go b/secrethandshake/vectors_test.go index 88592ed..377b9ae 100644 --- a/secrethandshake/vectors_test.go +++ b/secrethandshake/vectors_test.go @@ -21,9 +21,6 @@ func TestVectors(t *testing.T) { assert.Nil(t, json.NewDecoder(dataf).Decode(&data)) for i, v := range data { - if i >= 9 { - return - } args := v["args"].([]interface{}) if len(args) < 1 { @@ -53,6 +50,7 @@ func TestVectors(t *testing.T) { challenge, err := hex.DecodeString(args[1].(string)) assert.Nil(t, err, "verifyChallenge test %d", i) nextState := stateless.VerifyChallenge(state, challenge) + assert.NotNil(t, nextState, "verifyChallenge test %d", i) var resultState stateless.JsonState err = mapstructure.Decode(v["result"], &resultState) assert.Nil(t, err, "verifyChallenge test %d", i) @@ -80,10 +78,26 @@ func TestVectors(t *testing.T) { err = mapstructure.Decode(v["result"], &expected) assert.Nil(t, err, "serverVerifyAuth test %d", i) derived = *nextState.ToJsonState() - // TODO: why?! + // TODO: why?! testing setup probably derived.Remote.AppMac = "9e0986a9df0d04dc9884a58aa9f68cbd1690d0140a602d1ad4ba5599c4205596" assert.Equal(t, expected, derived, "serverVerifyAuth test %d", i) + case "serverCreateAccept": + accept := stateless.ServerCreateAccept(state) + assert.Equal(t, v["result"], hex.EncodeToString(accept), "serverCreateAccept test %d", i) + case "clientVerifyAccept": + acc, err := hex.DecodeString(args[1].(string)) + assert.Nil(t, err, "clientVerifyAccept test %d", i) + nextState := stateless.ClientVerifyAccept(state, acc) + assert.NotNil(t, nextState, "clientVerifyAccept test %d", i) + var resultState stateless.JsonState + err = mapstructure.Decode(v["result"], &resultState) + assert.Nil(t, err, "clientVerifyAccept test %d", i) + derived := *nextState.ToJsonState() + // TODO: why?! testing setup probably + derived.Remote.AppMac = "a7efd4c608bf19b20ceccee3af33d2df9fdc6dec05f11a383259875f9cf2e995" + assert.Equal(t, resultState, derived, "clientVerifyAccept test %d", i) + default: t.Errorf("unhandled case testing %d: %s", i, v["name"]) } From 9bfbdab8024810aead0630aa2d026d6650ab4cd3 Mon Sep 17 00:00:00 2001 From: Henry Date: Fri, 19 May 2017 14:47:52 +0200 Subject: [PATCH 16/16] various fixes. no clean yet $ go test 2017/05/19 14:46:06 secretHandshake/ServerVerifyAuth: open not OK!! --- FAIL: TestVectors (0.04s) vectors_test.go:122: unhandled case testing 10: clean vectors_test.go:122: unhandled case testing 12: clean vectors_test.go:122: unhandled case testing 75: clean vectors_test.go:122: unhandled case testing 77: clean vectors_test.go:33: skipping toKeys vectors_test.go:33: skipping toKeys vectors_test.go:122: unhandled case testing 90: clean vectors_test.go:122: unhandled case testing 92: clean vectors_test.go:122: unhandled case testing 112: clean vectors_test.go:122: unhandled case testing 114: clean vectors_test.go:122: unhandled case testing 125: clean vectors_test.go:122: unhandled case testing 127: clean vectors_test.go:122: unhandled case testing 156: clean vectors_test.go:122: unhandled case testing 158: clean FAIL exit status 1 FAIL github.com/cryptix/secretstream/secrethandshake 0.192s --- secrethandshake/keys.go | 1 + secrethandshake/stateless/boilerplate.go | 33 ++++++++++- secrethandshake/stateless/init.go | 8 ++- secrethandshake/vectors_test.go | 74 +++++++++++++++--------- 4 files changed, 84 insertions(+), 32 deletions(-) diff --git a/secrethandshake/keys.go b/secrethandshake/keys.go index 46bafc9..4ea3684 100644 --- a/secrethandshake/keys.go +++ b/secrethandshake/keys.go @@ -26,6 +26,7 @@ import ( "gopkg.in/errgo.v1" ) +// LoadSSBKeyPair parses an ssb secret file func LoadSSBKeyPair(fname string) (*EdKeyPair, error) { f, err := os.Open(fname) if err != nil { diff --git a/secrethandshake/stateless/boilerplate.go b/secrethandshake/stateless/boilerplate.go index 9b19a2b..920cf30 100644 --- a/secrethandshake/stateless/boilerplate.go +++ b/secrethandshake/stateless/boilerplate.go @@ -1,10 +1,10 @@ package stateless import ( + "bytes" "encoding/hex" "fmt" "reflect" - "strings" ) func stripIfZero(s string) string { @@ -16,6 +16,9 @@ func stripIfZero(s string) string { // TODO: only expose in tests? func (s *State) ToJsonState() *JsonState { + if s == nil { + panic("called ToJsonState on a nil state...") + } rpubStr := hex.EncodeToString(s.remotePublic[:]) rephPubStr := hex.EncodeToString(s.ephKeyRemotePub[:]) @@ -55,6 +58,7 @@ func (s *State) ToJsonState() *JsonState { Hello: hex.EncodeToString(s.remoteHello), }, Random: hex.EncodeToString(s.ephRandBuf.Bytes()), + Seed: hex.EncodeToString(s.seedBuf.Bytes()), Secret: secStr, SecHash: shStr, Secret2: sec2Str, @@ -96,7 +100,11 @@ type JsonState struct { func InitializeFromJSONState(s JsonState) (*State, error) { var localKeyPair Option if s.Seed != "" { - localKeyPair = LocalKeyFromSeed(strings.NewReader(s.Seed)) + seed, err := hex.DecodeString(s.Seed) + if err != nil { + return nil, err + } + localKeyPair = LocalKeyFromSeed(bytes.NewReader(seed)) } else { localKeyPair = LocalKeyFromHex(s.Local.PublicKey, s.Local.SecretKey) } @@ -105,6 +113,27 @@ func InitializeFromJSONState(s JsonState) (*State, error) { localKeyPair, EphemeralRandFromHex(s.Random), RemotePubFromHex(s.Remote.PublicKey), + func(state *State) error { + if s.Local.AppMac != "" { + var err error + state.localAppMac, err = hex.DecodeString(s.Local.AppMac) + if err != nil { + return err + } + + } + return nil + }, + func(state *State) error { + if s.Remote.AppMac != "" { + var err error + state.remoteAppMac, err = hex.DecodeString(s.Remote.AppMac) + if err != nil { + return err + } + } + return nil + }, func(state *State) error { if s.Local.Hello != "" { var err error diff --git a/secrethandshake/stateless/init.go b/secrethandshake/stateless/init.go index 2813ad0..ce51d8d 100644 --- a/secrethandshake/stateless/init.go +++ b/secrethandshake/stateless/init.go @@ -47,8 +47,9 @@ type State struct { to only include these fields in the test package but first make the tests pass. */ - ephRand io.Reader - ephRandBuf bytes.Buffer // stores the bytes we read + + ephRand io.Reader + ephRandBuf, seedBuf bytes.Buffer // stores the bytes we read } type Option func(s *State) error @@ -74,9 +75,10 @@ func LocalKey(kp EdKeyPair) Option { // LocalKeyFromSeed is only used for testing against known values func LocalKeyFromSeed(r io.Reader) Option { return func(s *State) error { - pk, sk, err := ed25519.GenerateKey(r) + pk, sk, err := ed25519.GenerateKey(io.TeeReader(r, &s.seedBuf)) copy(s.local.Public[:], pk[:]) copy(s.local.Secret[:], sk[:]) + return err } } diff --git a/secrethandshake/vectors_test.go b/secrethandshake/vectors_test.go index 377b9ae..922a8c4 100644 --- a/secrethandshake/vectors_test.go +++ b/secrethandshake/vectors_test.go @@ -13,18 +13,25 @@ import ( func TestVectors(t *testing.T) { dataf, err := os.Open("test-vectors/data.json") + if os.IsNotExist(err) { + t.Log("test-vectors data missing.") + t.Log("please clone the test data using 'git submodule update --init' or similar.") + } assert.Nil(t, err) - defer dataf.Close() var data []map[string]interface{} assert.Nil(t, json.NewDecoder(dataf).Decode(&data)) for i, v := range data { - args := v["args"].([]interface{}) if len(args) < 1 { - t.Fatal("setup test %d: need at least one argument", i) + t.Fatalf("setup test %d: need at least one argument", i) + } + + if v["name"] == "toKeys" { + t.Log("skipping toKeys") + continue } // parse args @@ -35,67 +42,80 @@ func TestVectors(t *testing.T) { state, err := stateless.InitializeFromJSONState(argState) assert.Nil(t, err, "setup test %d", i) + r, ok := v["result"] + assert.True(t, ok) + var negTest = r == nil + switch v["name"] { case "initialize": var resultState stateless.JsonState - err = mapstructure.Decode(v["result"], &resultState) + err = mapstructure.Decode(r, &resultState) assert.Nil(t, err, "init test %d", i) assert.Equal(t, resultState, *state.ToJsonState(), "init test %d", i) case "createChallenge": challenge := stateless.CreateChallenge(state) - assert.Equal(t, v["result"], hex.EncodeToString(challenge)) + assert.Equal(t, r, hex.EncodeToString(challenge)) case "verifyChallenge": challenge, err := hex.DecodeString(args[1].(string)) assert.Nil(t, err, "verifyChallenge test %d", i) nextState := stateless.VerifyChallenge(state, challenge) - assert.NotNil(t, nextState, "verifyChallenge test %d", i) - var resultState stateless.JsonState - err = mapstructure.Decode(v["result"], &resultState) - assert.Nil(t, err, "verifyChallenge test %d", i) - assert.Equal(t, resultState, *nextState.ToJsonState(), "verifyChallenge test %d", i) + if negTest { + assert.Nil(t, nextState) + } else { + assert.NotNil(t, nextState, "verifyChallenge test %d", i) + var resultState stateless.JsonState + err = mapstructure.Decode(r, &resultState) + assert.Nil(t, err, "verifyChallenge test %d", i) + assert.Equal(t, resultState, *nextState.ToJsonState(), "verifyChallenge test %d", i) + } case "clientVerifyChallenge": challenge, err := hex.DecodeString(args[1].(string)) assert.Nil(t, err, "clientVerifyChallenge test %d", i) nextState := stateless.ClientVerifyChallenge(state, challenge) - var resultState stateless.JsonState - err = mapstructure.Decode(v["result"], &resultState) - assert.Nil(t, err, "clientVerifyChallenge test %d", i) - assert.Equal(t, resultState, *nextState.ToJsonState(), "clientVerifyChallenge test %d", i) + if negTest { + assert.Nil(t, nextState) + } else { + var resultState stateless.JsonState + err = mapstructure.Decode(r, &resultState) + assert.Nil(t, err, "clientVerifyChallenge test %d", i) + assert.Equal(t, resultState, *nextState.ToJsonState(), "clientVerifyChallenge test %d", i) + } case "clientCreateAuth": auth := stateless.ClientCreateAuth(state) - assert.Equal(t, v["result"], hex.EncodeToString(auth), "clientCreateAuth test %d", i) + assert.Equal(t, r, hex.EncodeToString(auth), "clientCreateAuth test %d", i) case "serverVerifyAuth": challenge, err := hex.DecodeString(args[1].(string)) assert.Nil(t, err, "serverVerifyAuth test %d", i) nextState := stateless.ServerVerifyAuth(state, challenge) - assert.NotNil(t, nextState, "serverVerifyAuth test %d", i) - var expected, derived stateless.JsonState - err = mapstructure.Decode(v["result"], &expected) - assert.Nil(t, err, "serverVerifyAuth test %d", i) - derived = *nextState.ToJsonState() - // TODO: why?! testing setup probably - derived.Remote.AppMac = "9e0986a9df0d04dc9884a58aa9f68cbd1690d0140a602d1ad4ba5599c4205596" - assert.Equal(t, expected, derived, "serverVerifyAuth test %d", i) + if negTest { + assert.Nil(t, nextState) + } else { + assert.NotNil(t, nextState, "serverVerifyAuth test %d", i) + var expected, derived stateless.JsonState + err = mapstructure.Decode(r, &expected) + assert.Nil(t, err, "serverVerifyAuth test %d", i) + derived = *nextState.ToJsonState() + assert.Equal(t, expected, derived, "serverVerifyAuth test %d", i) + } case "serverCreateAccept": accept := stateless.ServerCreateAccept(state) - assert.Equal(t, v["result"], hex.EncodeToString(accept), "serverCreateAccept test %d", i) + assert.Equal(t, r, hex.EncodeToString(accept), "serverCreateAccept test %d", i) + case "clientVerifyAccept": acc, err := hex.DecodeString(args[1].(string)) assert.Nil(t, err, "clientVerifyAccept test %d", i) nextState := stateless.ClientVerifyAccept(state, acc) assert.NotNil(t, nextState, "clientVerifyAccept test %d", i) var resultState stateless.JsonState - err = mapstructure.Decode(v["result"], &resultState) + err = mapstructure.Decode(r, &resultState) assert.Nil(t, err, "clientVerifyAccept test %d", i) derived := *nextState.ToJsonState() - // TODO: why?! testing setup probably - derived.Remote.AppMac = "a7efd4c608bf19b20ceccee3af33d2df9fdc6dec05f11a383259875f9cf2e995" assert.Equal(t, resultState, derived, "clientVerifyAccept test %d", i) default: