-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
216 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
package sunrise |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
}) | ||
} |