Skip to content

Commit

Permalink
feat(api): returns and consumes token as string
Browse files Browse the repository at this point in the history
Signed-off-by: Thibault Normand <[email protected]>
  • Loading branch information
Zenithar committed Jan 16, 2023
1 parent 6734a81 commit c175540
Show file tree
Hide file tree
Showing 10 changed files with 124 additions and 109 deletions.
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,12 @@ More examples - [here](example_test.go)
goos: darwin
goarch: arm64
pkg: zntr.io/paseto/v3
Benchmark_Paseto_Encrypt-10 74648 14735 ns/op 8288 B/op 60 allocs/op
Benchmark_Paseto_Decrypt-10 84492 14211 ns/op 7762 B/op 58 allocs/op
Benchmark_Paseto_Sign-10 7495 157311 ns/op 9076 B/op 87 allocs/op
Benchmark_Paseto_Verify-10 1976 604464 ns/op 3433 B/op 51 allocs/op
Benchmark_Paseto_Encrypt-10 74833 14775 ns/op 8274 B/op 59 allocs/op
Benchmark_Paseto_Decrypt-10 84738 14189 ns/op 8050 B/op 59 allocs/op
Benchmark_Paseto_Sign-10 7467 157376 ns/op 9059 B/op 86 allocs/op
Benchmark_Paseto_Verify-10 1980 604653 ns/op 3754 B/op 52 allocs/op
PASS
ok zntr.io/paseto/v3 5.335s
ok zntr.io/paseto/v3 5.373s
```

### V4
Expand All @@ -71,12 +71,12 @@ ok zntr.io/paseto/v3 5.335s
goos: darwin
goarch: arm64
pkg: zntr.io/paseto/v4
Benchmark_Paseto_Encrypt-10 461188 2567 ns/op 2272 B/op 13 allocs/op
Benchmark_Paseto_Decrypt-10 570516 2086 ns/op 1776 B/op 11 allocs/op
Benchmark_Paseto_Sign-10 48141 24877 ns/op 912 B/op 5 allocs/op
Benchmark_Paseto_Verify-10 22591 52607 ns/op 416 B/op 3 allocs/op
Benchmark_Paseto_Encrypt-10 461580 2580 ns/op 2288 B/op 12 allocs/op
Benchmark_Paseto_Decrypt-10 554426 2139 ns/op 2064 B/op 12 allocs/op
Benchmark_Paseto_Sign-10 47422 24875 ns/op 928 B/op 4 allocs/op
Benchmark_Paseto_Verify-10 22990 52357 ns/op 704 B/op 4 allocs/op
PASS
ok zntr.io/paseto/v4 6.588s
ok zntr.io/paseto/v4 6.660s
```

## License
Expand Down
4 changes: 2 additions & 2 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func ExamplePasetoV4LocalDecrypt() {
}

// Encrypted token.
input := []byte("v4.local.dGVzdHMtMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTZ-qF7cj1LApZxpU5R2qdaX9Ox9NaKxnci6ObPVawSbAlqcRdmSDrklvbUqNGk61-tuOKJ0vkFQ.eyJraWQiOiIxMjM0NTY3ODkwIn0")
input := "v4.local.dGVzdHMtMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTZ-qF7cj1LApZxpU5R2qdaX9Ox9NaKxnci6ObPVawSbAlqcRdmSDrklvbUqNGk61-tuOKJ0vkFQ.eyJraWQiOiIxMjM0NTY3ODkwIn0"

// Expected footer value.
footer := []byte(`{"kid":"1234567890"}`)
Expand Down Expand Up @@ -169,7 +169,7 @@ func ExamplePasetoV4PublicVerify() {
}

// Prepare the message
input := []byte("v4.public.bXkgc3VwZXIgc2VjcmV0IG1lc3NhZ2UbOO-zu6XQbbhmDj0IUEjrmLS_TK1vM69D3pmdbUJmSa7A4c0qjEi9q-DQiMD6UUtbGEMXA1z9zdRskpGfStQH.eyJraWQiOiIxMjM0NTY3ODkwIn0")
input := "v4.public.bXkgc3VwZXIgc2VjcmV0IG1lc3NhZ2UbOO-zu6XQbbhmDj0IUEjrmLS_TK1vM69D3pmdbUJmSa7A4c0qjEi9q-DQiMD6UUtbGEMXA1z9zdRskpGfStQH.eyJraWQiOiIxMjM0NTY3ODkwIn0"
footer := []byte(`{"kid":"1234567890"}`)
assertions := []byte(`{"user_id":"1234567890"}`)

Expand Down
49 changes: 26 additions & 23 deletions v3/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,33 +56,33 @@ func LocalKeyFromSeed(seed []byte) (*LocalKey, error) {

// PASETO v3 symmetric encryption primitive.
// https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version3.md#encrypt
func Encrypt(r io.Reader, key *LocalKey, m, f, i []byte) ([]byte, error) {
func Encrypt(r io.Reader, key *LocalKey, m, f, i []byte) (string, error) {
// Check arguments
if key == nil {
return nil, errors.New("paseto: key is nil")
return "", errors.New("paseto: key is nil")
}
if len(key) != KeyLength {
return nil, fmt.Errorf("paseto: invalid key length, it must be %d bytes long", KeyLength)
return "", fmt.Errorf("paseto: invalid key length, it must be %d bytes long", KeyLength)
}

// Pre-allocate body
body := make([]byte, nonceLength+len(m), nonceLength+len(m)+macLength)

// Create random seed
if _, err := io.ReadFull(r, body[:nonceLength]); err != nil {
return nil, fmt.Errorf("paseto: unable to generate random seed: %w", err)
return "", fmt.Errorf("paseto: unable to generate random seed: %w", err)
}

// Derive keys from seed and secret key
ek, n2, ak, err := kdf(key, body[:nonceLength])
if err != nil {
return nil, fmt.Errorf("paseto: unable to derive keys from seed: %w", err)
return "", fmt.Errorf("paseto: unable to derive keys from seed: %w", err)
}

// Prepare an AES-256-CTR stream cipher
block, err := aes.NewCipher(ek)
if err != nil {
return nil, fmt.Errorf("paseto: unable to prepare block cipher: %w", err)
return "", fmt.Errorf("paseto: unable to prepare block cipher: %w", err)
}
ciph := cipher.NewCTR(block, n2)

Expand All @@ -92,7 +92,7 @@ func Encrypt(r io.Reader, key *LocalKey, m, f, i []byte) ([]byte, error) {
// Compute MAC
t, err := mac(ak, []byte(LocalPrefix), body[:nonceLength], body[nonceLength:], f, i)
if err != nil {
return nil, fmt.Errorf("paseto: unable to compute MAC: %w", err)
return "", fmt.Errorf("paseto: unable to compute MAC: %w", err)
}

// Serialize final token
Expand All @@ -106,53 +106,56 @@ func Encrypt(r io.Reader, key *LocalKey, m, f, i []byte) ([]byte, error) {
tokenLen += base64.RawURLEncoding.EncodedLen(len(f)) + 1
}

final := make([]byte, tokenLen)
base64.RawURLEncoding.Encode(final, body)
final := make([]byte, 9+tokenLen)
copy(final, []byte(LocalPrefix))
base64.RawURLEncoding.Encode(final[9:], body)

// Assemble final token
if len(f) > 0 {
final[tokenLen-footerLen] = '.'
final[9+tokenLen-footerLen] = '.'
// Encode footer as RawURLBase64
base64.RawURLEncoding.Encode(final[tokenLen-footerLen+1:], []byte(f))
base64.RawURLEncoding.Encode(final[9+tokenLen-footerLen+1:], []byte(f))
}

// No error
return append([]byte(LocalPrefix), final...), nil
return string(final), nil
}

// PASETO v3 symmetric decryption primitive
// https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version3.md#decrypt
func Decrypt(key *LocalKey, input, f, i []byte) ([]byte, error) {
func Decrypt(key *LocalKey, token string, f, i []byte) ([]byte, error) {
// Check arguments
if key == nil {
return nil, errors.New("paseto: key is nil")
}
if len(key) != KeyLength {
return nil, fmt.Errorf("paseto: invalid key length, it must be %d bytes long", KeyLength)
}
if input == nil {
return nil, errors.New("paseto: input is nil")
if token == "" {
return nil, errors.New("paseto: token is blank")
}

rawToken := []byte(token)

// Check token header
if !bytes.HasPrefix(input, []byte(LocalPrefix)) {
if !bytes.HasPrefix(rawToken, []byte(LocalPrefix)) {
return nil, errors.New("paseto: invalid token")
}

// Trim prefix
input = input[len(LocalPrefix):]
rawToken = rawToken[len(LocalPrefix):]

// Check footer usage
if len(f) > 0 {
// Split the footer and the body
footerIdx := bytes.Index(input, []byte("."))
footerIdx := bytes.Index(rawToken, []byte("."))
if footerIdx == 0 {
return nil, errors.New("paseto: invalid token, footer is missing but expected")
}

// Decode footer
footer := make([]byte, base64.RawURLEncoding.DecodedLen(len(input[footerIdx+1:])))
if _, err := base64.RawURLEncoding.Decode(footer, input[footerIdx+1:]); err != nil {
footer := make([]byte, base64.RawURLEncoding.DecodedLen(len(rawToken[footerIdx+1:])))
if _, err := base64.RawURLEncoding.Decode(footer, rawToken[footerIdx+1:]); err != nil {
return nil, fmt.Errorf("paseto: invalid token, footer has invalid encoding: %w", err)
}

Expand All @@ -162,12 +165,12 @@ func Decrypt(key *LocalKey, input, f, i []byte) ([]byte, error) {
}

// Continue without footer
input = input[:footerIdx]
rawToken = rawToken[:footerIdx]
}

// Decode token
raw := make([]byte, base64.RawURLEncoding.DecodedLen(len(input)))
if _, err := base64.RawURLEncoding.Decode(raw, input); err != nil {
raw := make([]byte, base64.RawURLEncoding.DecodedLen(len(rawToken)))
if _, err := base64.RawURLEncoding.Decode(raw, rawToken); err != nil {
return nil, fmt.Errorf("paseto: invalid token body: %w", err)
}

Expand Down
10 changes: 5 additions & 5 deletions v3/local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ func Test_Paseto_LocalVector(t *testing.T) {
assert.Equal(t, testCase.token, string(token))

// Decrypt
message, err := Decrypt(key, []byte(testCase.token), testCase.footer, testCase.implicitAssertion)
message, err := Decrypt(key, testCase.token, testCase.footer, testCase.implicitAssertion)
if (err != nil) != testCase.expectFail {
t.Errorf("error during the decrypt call, error = %v, wantErr %v", err, testCase.expectFail)
return
Expand Down Expand Up @@ -217,9 +217,9 @@ func Benchmark_Paseto_Encrypt(b *testing.B) {
benchmarkEncrypt(&key, m, f, i, b)
}

func benchmarkDecrypt(key *LocalKey, m, f, i []byte, b *testing.B) {
func benchmarkDecrypt(key *LocalKey, t string, f, i []byte, b *testing.B) {
for n := 0; n < b.N; n++ {
_, err := Decrypt(key, m, f, i)
_, err := Decrypt(key, t, f, i)
if err != nil {
b.Fatal(err)
}
Expand All @@ -232,12 +232,12 @@ func Benchmark_Paseto_Decrypt(b *testing.B) {
assert.NoError(b, err)
key := LocalKey(keyRaw)

m := []byte("v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0X-4P3EcxGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJmZHSSKYR6AnPYJV6gpHtx6dLakIG_AOPhu8vKexNyrv5_1qoom6_NaPGecoiz6fR8.eyJraWQiOiJVYmtLOFk2aXY0R1poRnA2VHgzSVdMV0xmTlhTRXZKY2RUM3pkUjY1WVp4byJ9")
t := "v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0X-4P3EcxGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJmZHSSKYR6AnPYJV6gpHtx6dLakIG_AOPhu8vKexNyrv5_1qoom6_NaPGecoiz6fR8.eyJraWQiOiJVYmtLOFk2aXY0R1poRnA2VHgzSVdMV0xmTlhTRXZKY2RUM3pkUjY1WVp4byJ9"
f := []byte("{\"kid\":\"UbkK8Y6iv4GZhFp6Tx3IWLWLfNXSEvJcdT3zdR65YZxo\"}")
i := []byte("{\"test-vector\":\"3-E-8\"}")

b.ReportAllocs()
b.ResetTimer()

benchmarkDecrypt(&key, m, f, i, b)
benchmarkDecrypt(&key, t, f, i, b)
}
35 changes: 19 additions & 16 deletions v3/public.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ import (
// Sign a message (m) with the private key (sk).
// PASETO v3 public signature primitive.
// https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version3.md#sign
func Sign(m []byte, sk *ecdsa.PrivateKey, f, i []byte) ([]byte, error) {
func Sign(m []byte, sk *ecdsa.PrivateKey, f, i []byte) (string, error) {
// Check arguments
if sk == nil {
return nil, errors.New("paseto: unable to sign with a nil private key")
return "", errors.New("paseto: unable to sign with a nil private key")
}

// Compress public key point
Expand Down Expand Up @@ -66,47 +66,50 @@ func Sign(m []byte, sk *ecdsa.PrivateKey, f, i []byte) ([]byte, error) {
tokenLen += base64.RawURLEncoding.EncodedLen(len(f)) + 1
}

final := make([]byte, tokenLen)
base64.RawURLEncoding.Encode(final, body)
final := make([]byte, 10+tokenLen)
copy(final, []byte(PublicPrefix))
base64.RawURLEncoding.Encode(final[10:], body)

// Assemble final token
if len(f) > 0 {
final[tokenLen-footerLen] = '.'
final[10+tokenLen-footerLen] = '.'
// Encode footer as RawURLBase64
base64.RawURLEncoding.Encode(final[tokenLen-footerLen+1:], []byte(f))
base64.RawURLEncoding.Encode(final[10+tokenLen-footerLen+1:], []byte(f))
}

// No error
return append([]byte(PublicPrefix), final...), nil
return string(final), nil
}

// PASETO v3 signature verification primitive.
// https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version3.md#verify
func Verify(sm []byte, pub *ecdsa.PublicKey, f, i []byte) ([]byte, error) {
func Verify(t string, pub *ecdsa.PublicKey, f, i []byte) ([]byte, error) {
// Check arguments
if pub == nil {
return nil, errors.New("paseto: public key is nil")
}

rawToken := []byte(t)

// Check token header
if !bytes.HasPrefix(sm, []byte(PublicPrefix)) {
if !bytes.HasPrefix(rawToken, []byte(PublicPrefix)) {
return nil, errors.New("paseto: invalid token")
}

// Trim prefix
sm = sm[len(PublicPrefix):]
rawToken = rawToken[len(PublicPrefix):]

// Check footer usage
if len(f) > 0 {
// Split the footer and the body
footerIdx := bytes.Index(sm, []byte("."))
footerIdx := bytes.Index(rawToken, []byte("."))
if footerIdx == 0 {
return nil, errors.New("paseto: invalid token, footer is missing but expected")
}

// Decode footer
footer := make([]byte, base64.RawURLEncoding.DecodedLen(len(sm[footerIdx+1:])))
if _, err := base64.RawURLEncoding.Decode(footer, sm[footerIdx+1:]); err != nil {
footer := make([]byte, base64.RawURLEncoding.DecodedLen(len(rawToken[footerIdx+1:])))
if _, err := base64.RawURLEncoding.Decode(footer, rawToken[footerIdx+1:]); err != nil {
return nil, fmt.Errorf("paseto: invalid token, footer has invalid encoding: %w", err)
}

Expand All @@ -116,12 +119,12 @@ func Verify(sm []byte, pub *ecdsa.PublicKey, f, i []byte) ([]byte, error) {
}

// Continue without footer
sm = sm[:footerIdx]
rawToken = rawToken[:footerIdx]
}

// Decode token
raw := make([]byte, base64.RawURLEncoding.DecodedLen(len(sm)))
if _, err := base64.RawURLEncoding.Decode(raw, sm); err != nil {
raw := make([]byte, base64.RawURLEncoding.DecodedLen(len(rawToken)))
if _, err := base64.RawURLEncoding.Decode(raw, rawToken); err != nil {
return nil, fmt.Errorf("paseto: invalid token body: %w", err)
}

Expand Down
8 changes: 4 additions & 4 deletions v3/public_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func Test_Paseto_PublicVector(t *testing.T) {
assert.Equal(t, testCase.token, string(token))

// Verify
message, err := Verify([]byte(testCase.token), &sk.PublicKey, []byte(testCase.footer), []byte(testCase.implicitAssertion))
message, err := Verify(testCase.token, &sk.PublicKey, []byte(testCase.footer), []byte(testCase.implicitAssertion))
if (err != nil) != testCase.expectFail {
t.Errorf("error during the verify call, error = %v, wantErr %v", err, testCase.expectFail)
return
Expand Down Expand Up @@ -134,9 +134,9 @@ func Benchmark_Paseto_Sign(b *testing.B) {
benchmarkSign(m, &sk, f, i, b)
}

func benchmarkVerify(m []byte, pk *ecdsa.PublicKey, f, i []byte, b *testing.B) {
func benchmarkVerify(t string, pk *ecdsa.PublicKey, f, i []byte, b *testing.B) {
for n := 0; n < b.N; n++ {
_, err := Verify(m, pk, f, i)
_, err := Verify(t, pk, f, i)
if err != nil {
b.Fatal(err)
}
Expand All @@ -149,7 +149,7 @@ func Benchmark_Paseto_Verify(b *testing.B) {
sk.PublicKey.Curve = elliptic.P384()
sk.PublicKey.X, sk.PublicKey.Y = sk.PublicKey.Curve.ScalarBaseMult(sk.D.Bytes())

token := []byte("v3.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAyMi0wMS0wMVQwMDowMDowMCswMDowMCJ94SjWIbjmS7715GjLSnHnpJrC9Z-cnwK45dmvnVvCRQDCCKAXaKEopTajX0DKYx1Xqr6gcTdfqscLCAbiB4eOW9jlt-oNqdG8TjsYEi6aloBfTzF1DXff_45tFlnBukEX.eyJraWQiOiJkWWtJU3lseFFlZWNFY0hFTGZ6Rjg4VVpyd2JMb2xOaUNkcHpVSEd3OVVxbiJ9")
token := "v3.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAyMi0wMS0wMVQwMDowMDowMCswMDowMCJ94SjWIbjmS7715GjLSnHnpJrC9Z-cnwK45dmvnVvCRQDCCKAXaKEopTajX0DKYx1Xqr6gcTdfqscLCAbiB4eOW9jlt-oNqdG8TjsYEi6aloBfTzF1DXff_45tFlnBukEX.eyJraWQiOiJkWWtJU3lseFFlZWNFY0hFTGZ6Rjg4VVpyd2JMb2xOaUNkcHpVSEd3OVVxbiJ9"
f := []byte("{\"kid\":\"dYkISylxQeecEcHELfzF88UZrwbLolNiCdpzUHGw9Uqn\"}")
i := []byte("{\"test-vector\":\"3-S-3\"}")

Expand Down
Loading

0 comments on commit c175540

Please sign in to comment.