Skip to content
This repository has been archived by the owner on May 12, 2018. It is now read-only.

Parameterize max hops and payload size - WIP #1

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
94 changes: 43 additions & 51 deletions client.go
Expand Up @@ -16,37 +16,27 @@ import (
"github.com/david415/go-lioness"
)

const (
// The number of bytes produced by our CSPRG for the key stream
// implementing our stream cipher to encrypt/decrypt the mix header. The
// last 2 * securityParameter bytes are only used in order to generate/check
// the MAC over the header.
numStreamBytes = (2*NumMaxHops + 3) * securityParameter

// NumMaxHops is the maximum path length.
NumMaxHops = 5

// Fixed size of the the routing info. This consists of a 16
// byte address and a 16 byte HMAC for each hop of the route,
// the first pair in cleartext and the following pairs
// increasingly obfuscated. In case fewer than numMaxHops are
// used, then the remainder is padded with null-bytes, also
// obfuscated.
routingInfoSize = pubKeyLen + (2*NumMaxHops-1)*securityParameter

// HopPayloadSize is the per-hop payload size in the header
HopPayloadSize = 32
type SphinxParams struct {
// PayloadSize is the packet payload size
PayloadSize = 1024
)
PayloadSize int
// NumMaxHops is the maximum path length.
MaxHops int
}

func NewSphinxParams(maxHops, payloadSize int) *SphinxParams {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needed? can't clients directly just &SphinxParams{payloadSize, maxHops}?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indeed. perhaps that's better.

return &SphinxParams{
PayloadSize: payloadSize,
MaxHops: maxHops,
}
}

// MixHeader contains the sphinx header but not the payload.
// A version number is also included; TODO: make the version
// number do something useful.
type MixHeader struct {
Version byte
EphemeralKey [32]byte // alpha
RoutingInfo [routingInfoSize]byte // beta
RoutingInfo []byte // beta
HeaderMAC [securityParameter]byte // gamma
}

Expand All @@ -65,6 +55,7 @@ func EncodeDestination(destination []byte) []byte {

// MixHeaderFactory builds mix headers
type MixHeaderFactory struct {
params *SphinxParams
group *GroupCurve25519
blockCipher BlockCipher
streamCipher StreamCipher
Expand All @@ -74,8 +65,9 @@ type MixHeaderFactory struct {
}

// NewMixHeaderFactory creates a new mix header factory
func NewMixHeaderFactory(pki SphinxPKI, randReader io.Reader) *MixHeaderFactory {
func NewMixHeaderFactory(params *SphinxParams, pki SphinxPKI, randReader io.Reader) *MixHeaderFactory {
factory := MixHeaderFactory{
params: params,
group: NewGroupCurve25519(),
blockCipher: NewLionessBlockCipher(),
streamCipher: &Chacha20Stream{},
Expand All @@ -92,8 +84,8 @@ func NewMixHeaderFactory(pki SphinxPKI, randReader io.Reader) *MixHeaderFactory
// a slice of 32byte shared secrets for each mix hop.
func (f *MixHeaderFactory) BuildHeader(route [][16]byte, destination []byte, messageID [16]byte) (*MixHeader, [][32]byte, error) {
routeLen := len(route)
if routeLen > NumMaxHops {
return nil, nil, fmt.Errorf("route length %d exceeds max hops %d", routeLen, NumMaxHops)
if routeLen > f.params.MaxHops {
return nil, nil, fmt.Errorf("route length %d exceeds max hops %d", routeLen, f.params.MaxHops)
}
var secretPoint [32]byte
var err error
Expand All @@ -102,7 +94,7 @@ func (f *MixHeaderFactory) BuildHeader(route [][16]byte, destination []byte, mes
return nil, nil, fmt.Errorf("faileed to generate curve25519 secret: %s", err)
}

paddingLen := (2*(NumMaxHops-routeLen)+2)*securityParameter - len(destination)
paddingLen := (2*(f.params.MaxHops-routeLen)+2)*securityParameter - len(destination)
padding := make([]byte, paddingLen)
_, err = f.randReader.Read(padding)
if err != nil {
Expand Down Expand Up @@ -131,8 +123,9 @@ func (f *MixHeaderFactory) BuildHeader(route [][16]byte, destination []byte, mes
// compute the filler strings
hopSize := 2 * securityParameter
filler := make([]byte, (numHops-1)*hopSize)
numStreamBytes := uint((2*f.params.MaxHops + 3) * securityParameter)
for i := 1; i < numHops; i++ {
min := (2*(NumMaxHops-i) + 3) * securityParameter
min := (2*(f.params.MaxHops-i) + 3) * securityParameter
streamKey := f.digest.DeriveStreamCipherKey(hopSharedSecrets[i-1])
streamBytes, err := f.streamCipher.GenerateStream(streamKey, numStreamBytes)
if err != nil {
Expand All @@ -147,7 +140,7 @@ func (f *MixHeaderFactory) BuildHeader(route [][16]byte, destination []byte, mes
copy(beta[len(destination):], messageID[:])
copy(beta[len(destination)+len(messageID):], padding)

betaLen := uint((2*(NumMaxHops-routeLen) + 3) * securityParameter)
betaLen := uint((2*(f.params.MaxHops-routeLen) + 3) * securityParameter)
rhoKey := f.digest.DeriveStreamCipherKey(hopSharedSecrets[routeLen-1])
cipherStream, err := f.streamCipher.GenerateStream(rhoKey, betaLen)
if err != nil {
Expand Down Expand Up @@ -175,10 +168,10 @@ func (f *MixHeaderFactory) BuildHeader(route [][16]byte, destination []byte, mes
newBeta = []byte{}
newBeta = append(newBeta, mixID[:]...)
newBeta = append(newBeta, gamma[:]...)
betaSlice := uint((2*NumMaxHops - 1) * securityParameter)
betaSlice := uint((2*f.params.MaxHops - 1) * securityParameter)
newBeta = append(newBeta, prevBeta[:betaSlice]...)
rhoKey := f.digest.DeriveStreamCipherKey(hopSharedSecrets[i])
streamSlice := uint((2*NumMaxHops + 1) * securityParameter)
streamSlice := uint((2*f.params.MaxHops + 1) * securityParameter)
cipherStream, err := f.streamCipher.GenerateStream(rhoKey, streamSlice)
if err != nil {
return nil, nil, fmt.Errorf("stream cipher failure: %s", err)
Expand All @@ -194,12 +187,10 @@ func (f *MixHeaderFactory) BuildHeader(route [][16]byte, destination []byte, mes
}
prevBeta = newBeta
}
finalBeta := [routingInfoSize]byte{}
copy(finalBeta[:], newBeta[:])
header := &MixHeader{
Version: 0x01,
EphemeralKey: hopEphemeralPubKeys[0],
RoutingInfo: finalBeta,
RoutingInfo: newBeta,
HeaderMAC: gamma,
}
return header, hopSharedSecrets, nil
Expand All @@ -210,12 +201,12 @@ func (f *MixHeaderFactory) BuildHeader(route [][16]byte, destination []byte, mes
// addressed to the final destination.
type SphinxPacket struct {
Header *MixHeader
Payload [PayloadSize]byte // delta
Payload []byte // delta
}

// NewOnionReply is used to create an SphinxPacket with a specified header and payload.
// This is used by the WrapReply to create Single Use Reply Blocks
func NewOnionReply(header *MixHeader, payload [PayloadSize]byte) *SphinxPacket {
func NewOnionReply(header *MixHeader, payload []byte) *SphinxPacket {
return &SphinxPacket{
Header: header,
Payload: payload,
Expand All @@ -224,6 +215,7 @@ func NewOnionReply(header *MixHeader, payload [PayloadSize]byte) *SphinxPacket {

// SphinxPacketFactory builds onion packets
type SphinxPacketFactory struct {
params *SphinxParams
group *GroupCurve25519
blockCipher BlockCipher
pki SphinxPKI
Expand All @@ -232,29 +224,31 @@ type SphinxPacketFactory struct {
}

// NewSphinxPacketFactory creates a new onion packet factory
func NewSphinxPacketFactory(pki SphinxPKI, randReader io.Reader) *SphinxPacketFactory {
func NewSphinxPacketFactory(params *SphinxParams, pki SphinxPKI, randReader io.Reader) *SphinxPacketFactory {
factory := SphinxPacketFactory{
params: params,
group: NewGroupCurve25519(),
blockCipher: NewLionessBlockCipher(),
pki: pki,
randReader: randReader,
mixHeaderFactory: NewMixHeaderFactory(pki, randReader),
mixHeaderFactory: NewMixHeaderFactory(params, pki, randReader),
}
return &factory
}

// BuildForwardSphinxPacket builds a forward oniion packet
func (f *SphinxPacketFactory) BuildForwardSphinxPacket(route [][16]byte, destination [16]byte, payload []byte) (*SphinxPacket, error) {

if len(payload)+1+len(destination) > PayloadSize-2 { // XXX AddPadding has a 2 byte overhead
return nil, fmt.Errorf("wrong sized payload %d > %d", len(payload), PayloadSize)
// AddPadding has a 2 byte overhead
if len(payload)+1+len(destination) > f.params.PayloadSize-2 {
return nil, fmt.Errorf("wrong sized payload %d > %d", len(payload), f.params.PayloadSize)
}
addrPayload := []byte{}
addrPayload = append(addrPayload, bytes.Repeat([]byte{0}, 16)...)
encodedDest := EncodeDestination(destination[:])
addrPayload = append(addrPayload, encodedDest...)
addrPayload = append(addrPayload, payload[:]...)
paddedPayload, err := AddPadding(addrPayload, PayloadSize)
paddedPayload, err := AddPadding(addrPayload, f.params.PayloadSize)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -287,11 +281,9 @@ func (f *SphinxPacketFactory) BuildForwardSphinxPacket(route [][16]byte, destina
return nil, err
}
}
newPayload := [PayloadSize]byte{}
copy(newPayload[:], delta)
return &SphinxPacket{
Header: mixHeader,
Payload: newPayload,
Payload: delta,
}, nil
}

Expand All @@ -307,6 +299,7 @@ type ReplyBlock struct {

// SphinxClient is used for sending and receiving messages
type SphinxClient struct {
params *SphinxParams
id []byte
keysmap map[[16]byte][][]byte
pki SphinxPKI
Expand All @@ -316,7 +309,7 @@ type SphinxClient struct {
}

// NewSphinxClient creates a new SphinxClient
func NewSphinxClient(pki SphinxPKI, id []byte, randReader io.Reader) (*SphinxClient, error) {
func NewSphinxClient(params *SphinxParams, pki SphinxPKI, id []byte, randReader io.Reader) (*SphinxClient, error) {
var newID [4]byte
if id == nil {
_, err := randReader.Read(newID[:])
Expand All @@ -326,12 +319,13 @@ func NewSphinxClient(pki SphinxPKI, id []byte, randReader io.Reader) (*SphinxCli
id = []byte(fmt.Sprintf("Client %x", newID))
}
return &SphinxClient{
params: params,
id: id,
keysmap: make(map[[16]byte][][]byte),
pki: pki,
randReader: randReader,
blockCipher: NewLionessBlockCipher(),
mixHeaderFactory: NewMixHeaderFactory(pki, randReader),
mixHeaderFactory: NewMixHeaderFactory(params, pki, randReader),
}, nil
}

Expand Down Expand Up @@ -422,16 +416,14 @@ func (c *SphinxClient) WrapReply(surb *ReplyBlock, message []byte) ([]byte, *Sph
}
prefixedMessage := make([]byte, securityParameter)
prefixedMessage = append(prefixedMessage, message...)
paddedPayload, err := AddPadding(prefixedMessage, PayloadSize)
paddedPayload, err := AddPadding(prefixedMessage, c.params.PayloadSize)
if err != nil {
return nil, nil, fmt.Errorf("WrapReply failed to add padding: %v", err)
}
ciphertextPayload, err := c.blockCipher.Encrypt(key, paddedPayload)
if err != nil {
return nil, nil, fmt.Errorf("WrapReply failed to encrypt payload: %v", err)
}
var payload [PayloadSize]byte
copy(payload[:], ciphertextPayload)
onionPacket := NewOnionReply(surb.Header, payload)
return surb.FirstHop[:], onionPacket, nil
sphinxPacket := NewOnionReply(surb.Header, ciphertextPayload)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth keeping an assertion that the payload is the expected PayloadSize?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm yeah... somewhere there should be such an assertion

return surb.FirstHop[:], sphinxPacket, nil
}
9 changes: 5 additions & 4 deletions client_test.go
Expand Up @@ -17,8 +17,9 @@ func TestBuildHeaderErrors(t *testing.T) {
if err != nil {
t.Fatal("unexpected NewFixedNoiseReader err")
}
headerFactory := NewMixHeaderFactory(pki, randReader)
badRoute := make([][16]byte, NumMaxHops+1)
params := NewSphinxParams(5, 1024)
headerFactory := NewMixHeaderFactory(params, pki, randReader)
badRoute := make([][16]byte, params.MaxHops+1)
var messageID [16]byte
_, _, err = headerFactory.BuildHeader(badRoute, route[len(route)-1][:], messageID)
if err == nil {
Expand All @@ -34,7 +35,7 @@ func TestBuildHeaderErrors(t *testing.T) {
if err != nil {
t.Fatal("unexpected NewFixedNoiseReader err")
}
headerFactory = NewMixHeaderFactory(pki, randReader)
headerFactory = NewMixHeaderFactory(params, pki, randReader)
_, _, err = headerFactory.BuildHeader(route, route[len(route)-1][:], messageID)
if err == nil {
t.Fatal("expected headerFactory error")
Expand All @@ -45,7 +46,7 @@ func TestBuildHeaderErrors(t *testing.T) {
if err != nil {
t.Fatal("unexpected NewFixedNoiseReader err")
}
headerFactory = NewMixHeaderFactory(pki, randReader)
headerFactory = NewMixHeaderFactory(params, pki, randReader)
var fakeDest [16]byte
route[0] = fakeDest
_, _, err = headerFactory.BuildHeader(route, fakeDest[:], messageID)
Expand Down
2 changes: 1 addition & 1 deletion crypto_primitives.go
Expand Up @@ -85,7 +85,7 @@ func NewLionessBlockCipher() *LionessBlockCipher {

// Decrypt decrypts a block of data with the given key.
func (l *LionessBlockCipher) Decrypt(key [lioness.KeyLen]byte, block []byte) ([]byte, error) {
cipher, err := lioness.NewCipher(key, PayloadSize)
cipher, err := lioness.NewCipher(key, len(block))
if err != nil {
return nil, err
}
Expand Down