Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wip] test vectors #5

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "secrethandshake/test-vectors"]
path = secrethandshake/test-vectors
url = https://github.com/auditdrivencrypto/test-secret-handshake
1 change: 1 addition & 0 deletions secrethandshake/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion secrethandshake/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
57 changes: 57 additions & 0 deletions secrethandshake/stateless/accept.go
Original file line number Diff line number Diff line change
@@ -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
}
71 changes: 71 additions & 0 deletions secrethandshake/stateless/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package stateless

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.localHello, &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.remoteHello = make([]byte, 0, len(data)-16)

var nonce [24]byte
var openOk bool
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.remoteHello = make([]byte, len(data)-16)
}

var sig [ed25519.SignatureSize]byte
copy(sig[:], state.remoteHello[:ed25519.SignatureSize])
var public [ed25519.PublicKeySize]byte
copy(public[:], state.remoteHello[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[:])

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
}
return state
}
242 changes: 242 additions & 0 deletions secrethandshake/stateless/boilerplate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
package stateless

import (
"bytes"
"encoding/hex"
"fmt"
"reflect"
)

func stripIfZero(s string) string {
if s == "0000000000000000000000000000000000000000000000000000000000000000" {
s = ""
}
return s
}

// 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[:])
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,
&shStr,
&secStr,
&sec2Str,
&sec3Str,
&abobStr,
} {
*s = stripIfZero(*s)
}

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),
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()),
Seed: hex.EncodeToString(s.seedBuf.Bytes()),
Secret: secStr,
SecHash: shStr,
Secret2: sec2Str,
Secret3: sec3Str,
ABob: abobStr,
}
}

// 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"`
Hello string `mapstructure:"hello"`
}

type remoteKey struct {
PublicKey string `mapstructure:"publicKey"`
EphPubKey string `mapstructure:"kx_pk"`
AppMac string `mapstructure:"app_mac"`
Hello string `mapstructure:"hello"`
}

type JsonState struct {
AppKey string `mapstructure:"app_key"`
Local localKey `mapstructure:"local"`
Remote remoteKey `mapstructure:"remote"`
Seed string `mapstructure:"seed"`
Random string `mapstructure:"random"`
SecHash string `mapstructure:"shash"`
Secret string `mapstructure:"secret"`
Secret2 string `mapstructure:"secret2"`
Secret3 string `mapstructure:"secret3"`
ABob string `mapstructure:"a_bob"`
}

func InitializeFromJSONState(s JsonState) (*State, error) {
var localKeyPair Option
if 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)
}
return Initialize(
SetAppKey(s.AppKey),
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
state.localHello, err = hex.DecodeString(s.Local.Hello)
if err != nil {
return err
}
}
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)
if err != nil {
return err
}
copy(state.secret[:], s)
}
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.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)
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(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
}
}
Loading