Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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