Skip to content

Commit

Permalink
Sunrise Verification Token
Browse files Browse the repository at this point in the history
  • Loading branch information
bbengfort committed Nov 6, 2024
1 parent 057eb58 commit 6ad5b48
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 0 deletions.
11 changes: 11 additions & 0 deletions pkg/sunrise/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package sunrise

import "errors"

var (
ErrDecode = errors.New("sunrise: could not decode token")
ErrSize = errors.New("sunrise: invalid size for token")
ErrInvalidEnvelopeID = errors.New("invalid sunrise token: no envelope id")
ErrInvalidExpiration = errors.New("invalid sunrise token: no expiration timestamp")
ErrInvalidNonce = errors.New("invalid sunrise token: incorrect nonce")
)
1 change: 1 addition & 0 deletions pkg/sunrise/sunrise.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package sunrise
97 changes: 97 additions & 0 deletions pkg/sunrise/verify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package sunrise

import (
"bytes"
"crypto/rand"
"encoding/binary"
"errors"
"fmt"
"time"

"github.com/google/uuid"
)

const (
nonceLength = 64
keyLength = 64
minTokenLength = 16 + nonceLength + 1
maxTokenLength = 16 + nonceLength + binary.MaxVarintLen64
)

type Token struct {
EnvelopeID uuid.UUID
Expiration time.Time
nonce []byte
}

func NewToken(envelopeID uuid.UUID, expiration time.Time) *Token {
token := &Token{
EnvelopeID: envelopeID,
Expiration: expiration,
nonce: make([]byte, nonceLength),
}

if _, err := rand.Read(token.nonce); err != nil {
panic(fmt.Errorf("no crypto random generator available: %w", err))
}

return token
}

func (t *Token) MarshalBinary() ([]byte, error) {
if err := t.Validate(); err != nil {
return nil, err
}

data := make([]byte, maxTokenLength)
copy(data[:16], t.EnvelopeID[:])

i := binary.PutVarint(data[16:], t.Expiration.UnixNano())
copy(data[16+i:], t.nonce)

return data[:16+i+nonceLength], nil
}

func (t *Token) UnmarshalBinary(data []byte) error {
if len(data) > maxTokenLength || len(data) < minTokenLength {
return ErrSize
}

// Parse envelope ID
t.EnvelopeID = uuid.UUID(data[:16])

// Parse expiration time
exp, i := binary.Varint(data[16:])
if i <= 0 {
return ErrDecode
}
t.Expiration = time.Unix(0, exp)

// Read the nonce data
t.nonce = data[16+i:]

// Validate the binary data
return t.Validate()
}

func (t *Token) Validate() (err error) {
if t.EnvelopeID == uuid.Nil {
err = errors.Join(err, ErrInvalidEnvelopeID)
}

if t.Expiration.IsZero() {
err = errors.Join(err, ErrInvalidExpiration)
}

if len(t.nonce) != nonceLength {
err = errors.Join(err, ErrInvalidNonce)
}

return err
}

func (t *Token) Equal(o *Token) bool {
return bytes.Equal(t.EnvelopeID[:], o.EnvelopeID[:]) &&
t.Expiration.Equal(o.Expiration) &&
bytes.Equal(t.nonce, o.nonce)
}
107 changes: 107 additions & 0 deletions pkg/sunrise/verify_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package sunrise_test

import (
"bytes"
"testing"
"time"

"github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/trisacrypto/envoy/pkg/sunrise"
)

func TestTokenBinary(t *testing.T) {
t.Run("Valid", func(t *testing.T) {
testCases := []*sunrise.Token{
sunrise.NewToken(uuid.MustParse("24035c84-ff3d-4da2-aef7-8683d9c00978"), time.Date(1994, 12, 20, 15, 21, 1, 3213, time.UTC)),
sunrise.NewToken(uuid.New(), time.Now()),
sunrise.NewToken(uuid.New(), time.Now().Add(312391*time.Hour)),
}

for i, token := range testCases {
data, err := token.MarshalBinary()
require.NotNil(t, data, "test case %d returned nil data", i)
require.NoError(t, err, "test case %d errored on marshall", i)

cmpt := &sunrise.Token{}
err = cmpt.UnmarshalBinary(data)
require.NoError(t, err, "test case %d errored on unmarshall", i)

require.True(t, token.Equal(cmpt), "deserialization mismatch for test case %d", i)
}
})

t.Run("BadMarshal", func(t *testing.T) {
testCases := []struct {
token *sunrise.Token
err error
}{
{
sunrise.NewToken(uuid.Nil, time.Now()),
sunrise.ErrInvalidEnvelopeID,
},
{
sunrise.NewToken(uuid.New(), time.Time{}),
sunrise.ErrInvalidExpiration,
},
}

for i, tc := range testCases {
data, err := tc.token.MarshalBinary()
require.Nil(t, data, "test case %d returned non-nil data", i)
require.ErrorIs(t, err, tc.err, "test case %d return the wrong error", i)
}
})

t.Run("BadUnmarshal", func(t *testing.T) {
testCases := []struct {
data []byte
err error
}{
{
nil,
sunrise.ErrSize,
},
{
[]byte{},
sunrise.ErrSize,
},
{
[]byte{0x1, 0x2, 0x3, 0x4, 0xf, 0xfe},
sunrise.ErrSize,
},
{
bytes.Repeat([]byte{0x1, 0x2, 0x3, 0x4, 0xf, 0xfe}, 64),
sunrise.ErrSize,
},
{
[]byte{
0x22, 0x1, 0x33, 0x41, 0xd3, 0x7a, 0x12, 0xc2, 0xab, 0x41, 0x0, 0xfc, 0xe1, 0x7b, 0x7d, 0x15,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x22, 0x1, 0x33, 0x41, 0xd3, 0x7a, 0x12, 0xc2, 0xab, 0x41, 0x0, 0xfc, 0xe1, 0x7b, 0x7d, 0x15,
0x22, 0x1, 0x33, 0x41, 0xd3, 0x7a, 0x12, 0xc2, 0xab, 0x41, 0x0, 0xfc, 0xe1, 0x7b, 0x7d, 0x15,
0x22, 0x1, 0x33, 0x41, 0xd3, 0x7a, 0x12, 0xc2, 0xab, 0x41, 0x0, 0xfc, 0xe1, 0x7b, 0x7d, 0x15,
0x22, 0x1, 0x33, 0x41, 0xd3, 0x7a, 0x12, 0xc2, 0xab, 0x41, 0x0, 0xfc, 0xe1, 0x7b, 0x7d, 0x15,
},
sunrise.ErrDecode,
},
{
[]byte{
0x22, 0x1, 0x33, 0x41, 0xd3, 0x7a, 0x12, 0xc2, 0xab, 0x41, 0x0, 0xfc, 0xe1, 0x7b, 0x7d, 0x15,
0xff, 0x00, 0xff,
0x22, 0x1, 0x33, 0x41, 0xd3, 0x7a, 0x12, 0xc2, 0xab, 0x41, 0x0, 0xfc, 0xe1, 0x7b, 0x7d, 0x15,
0x22, 0x1, 0x33, 0x41, 0xd3, 0x7a, 0x12, 0xc2, 0xab, 0x41, 0x0, 0xfc, 0xe1, 0x7b, 0x7d, 0x15,
0x22, 0x1, 0x33, 0x41, 0xd3, 0x7a, 0x12, 0xc2, 0xab, 0x41, 0x0, 0xfc, 0xe1, 0x7b, 0x7d, 0x15,
0x22, 0x1, 0x33, 0x41, 0xd3, 0x7a, 0x12, 0xc2, 0xab, 0x41, 0x0, 0xfc, 0xe1, 0x7b, 0x7d, 0x15,
},
sunrise.ErrInvalidNonce,
},
}

for i, tc := range testCases {
token := &sunrise.Token{}
err := token.UnmarshalBinary(tc.data)
require.ErrorIs(t, err, tc.err, "test case %d return the wrong error", i)
}
})
}

0 comments on commit 6ad5b48

Please sign in to comment.