Skip to content

Commit

Permalink
Implement v2.local (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
cristaloleg authored Apr 12, 2024
1 parent 7ce1c04 commit 98ffb5a
Show file tree
Hide file tree
Showing 5 changed files with 320 additions and 0 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ module github.com/cristalhq/paseto
go 1.21

require golang.org/x/crypto v0.22.0

require golang.org/x/sys v0.19.0 // indirect
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
167 changes: 167 additions & 0 deletions testdata/v2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
{
"name": "PASETO v2 Test Vectors",
"tests": [
{
"name": "2-E-1",
"expect-fail": false,
"nonce": "000000000000000000000000000000000000000000000000",
"key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f",
"token": "v2.local.97TTOvgwIxNGvV80XKiGZg_kD3tsXM_-qB4dZGHOeN1cTkgQ4PnW8888l802W8d9AvEGnoNBY3BnqHORy8a5cC8aKpbA0En8XELw2yDk2f1sVODyfnDbi6rEGMY3pSfCbLWMM2oHJxvlEl2XbQ",
"payload": "{\"data\":\"this is a signed message\",\"exp\":\"2019-01-01T00:00:00+00:00\"}",
"footer": "",
"implicit-assertion": ""
},
{
"name": "2-E-2",
"expect-fail": false,
"nonce": "000000000000000000000000000000000000000000000000",
"key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f",
"token": "v2.local.CH50H-HM5tzdK4kOmQ8KbIvrzJfjYUGuu5Vy9ARSFHy9owVDMYg3-8rwtJZQjN9ABHb2njzFkvpr5cOYuRyt7CRXnHt42L5yZ7siD-4l-FoNsC7J2OlvLlIwlG06mzQVunrFNb7Z3_CHM0PK5w",
"payload": "{\"data\":\"this is a secret message\",\"exp\":\"2019-01-01T00:00:00+00:00\"}",
"footer": "",
"implicit-assertion": ""
},
{
"name": "2-E-3",
"expect-fail": false,
"nonce": "45742c976d684ff84ebdc0de59809a97cda2f64c84fda19b",
"key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f",
"token": "v2.local.5K4SCXNhItIhyNuVIZcwrdtaDKiyF81-eWHScuE0idiVqCo72bbjo07W05mqQkhLZdVbxEa5I_u5sgVk1QLkcWEcOSlLHwNpCkvmGGlbCdNExn6Qclw3qTKIIl5-O5xRBN076fSDPo5xUCPpBA",
"payload": "{\"data\":\"this is a signed message\",\"exp\":\"2019-01-01T00:00:00+00:00\"}",
"footer": "",
"implicit-assertion": ""
},
{
"name": "2-E-4",
"expect-fail": false,
"nonce": "45742c976d684ff84ebdc0de59809a97cda2f64c84fda19b",
"key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f",
"token": "v2.local.pvFdDeNtXxknVPsbBCZF6MGedVhPm40SneExdClOxa9HNR8wFv7cu1cB0B4WxDdT6oUc2toyLR6jA6sc-EUM5ll1EkeY47yYk6q8m1RCpqTIzUrIu3B6h232h62DPbIxtjGvNRAwsLK7LcV8oQ",
"payload": "{\"data\":\"this is a secret message\",\"exp\":\"2019-01-01T00:00:00+00:00\"}",
"footer": "",
"implicit-assertion": ""
},
{
"name": "2-E-5",
"expect-fail": false,
"nonce": "45742c976d684ff84ebdc0de59809a97cda2f64c84fda19b",
"key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f",
"token": "v2.local.5K4SCXNhItIhyNuVIZcwrdtaDKiyF81-eWHScuE0idiVqCo72bbjo07W05mqQkhLZdVbxEa5I_u5sgVk1QLkcWEcOSlLHwNpCkvmGGlbCdNExn6Qclw3qTKIIl5-zSLIrxZqOLwcFLYbVK1SrQ.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9",
"payload": "{\"data\":\"this is a signed message\",\"exp\":\"2019-01-01T00:00:00+00:00\"}",
"footer": "{\"kid\":\"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN\"}",
"implicit-assertion": ""
},
{
"name": "2-E-6",
"expect-fail": false,
"nonce": "45742c976d684ff84ebdc0de59809a97cda2f64c84fda19b",
"key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f",
"token": "v2.local.pvFdDeNtXxknVPsbBCZF6MGedVhPm40SneExdClOxa9HNR8wFv7cu1cB0B4WxDdT6oUc2toyLR6jA6sc-EUM5ll1EkeY47yYk6q8m1RCpqTIzUrIu3B6h232h62DnMXKdHn_Smp6L_NfaEnZ-A.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9",
"payload": "{\"data\":\"this is a secret message\",\"exp\":\"2019-01-01T00:00:00+00:00\"}",
"footer": "{\"kid\":\"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN\"}",
"implicit-assertion": ""
},
{
"name": "2-E-7",
"expect-fail": false,
"nonce": "45742c976d684ff84ebdc0de59809a97cda2f64c84fda19b",
"key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f",
"token": "v2.local.5K4SCXNhItIhyNuVIZcwrdtaDKiyF81-eWHScuE0idiVqCo72bbjo07W05mqQkhLZdVbxEa5I_u5sgVk1QLkcWEcOSlLHwNpCkvmGGlbCdNExn6Qclw3qTKIIl5-zSLIrxZqOLwcFLYbVK1SrQ.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9",
"payload": "{\"data\":\"this is a signed message\",\"exp\":\"2019-01-01T00:00:00+00:00\"}",
"footer": "{\"kid\":\"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN\"}",
"implicit-assertion": "discarded-anyway"
},
{
"name": "2-E-8",
"expect-fail": false,
"nonce": "45742c976d684ff84ebdc0de59809a97cda2f64c84fda19b",
"key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f",
"token": "v2.local.pvFdDeNtXxknVPsbBCZF6MGedVhPm40SneExdClOxa9HNR8wFv7cu1cB0B4WxDdT6oUc2toyLR6jA6sc-EUM5ll1EkeY47yYk6q8m1RCpqTIzUrIu3B6h232h62DnMXKdHn_Smp6L_NfaEnZ-A.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9",
"payload": "{\"data\":\"this is a secret message\",\"exp\":\"2019-01-01T00:00:00+00:00\"}",
"footer": "{\"kid\":\"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN\"}",
"implicit-assertion": "discarded-anyway"
},
{
"name": "2-E-9",
"expect-fail": false,
"nonce": "45742c976d684ff84ebdc0de59809a97cda2f64c84fda19b",
"key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f",
"token": "v2.local.pvFdDeNtXxknVPsbBCZF6MGedVhPm40SneExdClOxa9HNR8wFv7cu1cB0B4WxDdT6oUc2toyLR6jA6sc-EUM5ll1EkeY47yYk6q8m1RCpqTIzUrIu3B6h232h62DoOJbyKBGPZG50XDZ6mbPtw.YXJiaXRyYXJ5LXN0cmluZy10aGF0LWlzbid0LWpzb24",
"payload": "{\"data\":\"this is a secret message\",\"exp\":\"2019-01-01T00:00:00+00:00\"}",
"footer": "arbitrary-string-that-isn't-json",
"implicit-assertion": "discarded-anyway"
},
{
"name": "2-S-1",
"expect-fail": false,
"public-key": "1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2",
"secret-key": "b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2",
"secret-key-seed": "b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a3774",
"secret-key-pem": "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEILTL+0PfTOIQcn2VPkpxMwf6Gbt9n4UEFDjZ4RuUKjd0\n-----END PRIVATE KEY-----",
"public-key-pem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAHrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI=\n-----END PUBLIC KEY-----",
"token": "v2.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9HQr8URrGntTu7Dz9J2IF23d1M7-9lH9xiqdGyJNvzp4angPW5Esc7C5huy_M8I8_DjJK2ZXC2SUYuOFM-Q_5Cw",
"payload": "{\"data\":\"this is a signed message\",\"exp\":\"2019-01-01T00:00:00+00:00\"}",
"footer": "",
"implicit-assertion": ""
},
{
"name": "2-S-2",
"expect-fail": false,
"public-key": "1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2",
"secret-key": "b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2",
"secret-key-seed": "b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a3774",
"secret-key-pem": "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEILTL+0PfTOIQcn2VPkpxMwf6Gbt9n4UEFDjZ4RuUKjd0\n-----END PRIVATE KEY-----",
"public-key-pem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAHrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI=\n-----END PUBLIC KEY-----",
"token": "v2.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9flsZsx_gYCR0N_Ec2QxJFFpvQAs7h9HtKwbVK2n1MJ3Rz-hwe8KUqjnd8FAnIJZ601tp7lGkguU63oGbomhoBw.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9",
"payload": "{\"data\":\"this is a signed message\",\"exp\":\"2019-01-01T00:00:00+00:00\"}",
"footer": "{\"kid\":\"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN\"}",
"implicit-assertion": ""
},
{
"name": "2-S-3",
"expect-fail": false,
"public-key": "1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2",
"secret-key": "b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2",
"secret-key-seed": "b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a3774",
"secret-key-pem": "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEILTL+0PfTOIQcn2VPkpxMwf6Gbt9n4UEFDjZ4RuUKjd0\n-----END PRIVATE KEY-----",
"public-key-pem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAHrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI=\n-----END PUBLIC KEY-----",
"token": "v2.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9flsZsx_gYCR0N_Ec2QxJFFpvQAs7h9HtKwbVK2n1MJ3Rz-hwe8KUqjnd8FAnIJZ601tp7lGkguU63oGbomhoBw.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9",
"payload": "{\"data\":\"this is a signed message\",\"exp\":\"2019-01-01T00:00:00+00:00\"}",
"footer": "{\"kid\":\"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN\"}",
"implicit-assertion": "discarded-anyway"
},
{
"name": "2-F-1",
"expect-fail": true,
"public-key": "1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2",
"secret-key": "b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2",
"secret-key-seed": "b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a3774",
"secret-key-pem": "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEILTL+0PfTOIQcn2VPkpxMwf6Gbt9n4UEFDjZ4RuUKjd0\n-----END PRIVATE KEY-----",
"public-key-pem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAHrnbu7wEfAP9cGBOAHHwmH4Wsot1ciXBHwBBXQ4gsaI=\n-----END PUBLIC KEY-----",
"token": "v2.local.pN9Y9kTFKnCskKr7B13IoceBabSTMS0LkUg3SeAqONg6EJsq9h-CLWdWaA_rMZX4MhGsOQn5I0EsIgYeOA2NPJZU0uulsahH-k871PBq.YXJiaXRyYXJ5LXN0cmluZy10aGF0LWlzbid0LWpzb24",
"payload": null,
"footer": "arbitrary-string-that-isn't-json",
"implicit-assertion": "{\"test-vector\":\"2-F-1\"}"
},
{
"name": "2-F-2",
"expect-fail": true,
"nonce": "df654812bac492663825520ba2f6e67cf5ca5bdc13d4e7507a98cc4c2fcc3ad8",
"key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f",
"token": "v2.public.eyJpbnZhbGlkIjoidGhpcyBzaG91bGQgbmV2ZXIgZGVjb2RlIn1kgrdAMxcO3wFKXJrLa1cq-DB6V_b25KQ1hV_jpOS-uYBmsg8EMS4j6kl2g83iRsh73knLGr7Ik1AEOvUgyw0P.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9",
"payload": null,
"footer": "{\"kid\":\"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN\"}",
"implicit-assertion": "{\"test-vector\":\"2-F-2\"}"
},
{
"name": "2-F-3",
"expect-fail": true,
"nonce": "26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2",
"key": "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f",
"token": "v1.local.vXWMCh8nxf_RMqrLREJVOWyu01yRzb-miB6mkG1zQ8LS4_W5nQdTOpexZq482ReJ0sv5uFfAWRGpJaONiMqFaAAo-dsbWG2vo63xUmwFGxHNhu9plfFav2SaGDERFGn7IQ20gNQl87eOLaxf2GDsWdfu5hrFaQ.YXJiaXRyYXJ5LXN0cmluZy10aGF0LWlzbid0LWpzb24",
"payload": null,
"footer": "arbitrary-string-that-isn't-json",
"implicit-assertion": "{\"test-vector\":\"2-F-3\"}"
}
]
}
101 changes: 101 additions & 0 deletions v2loc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package paseto

import (
"crypto/rand"
"fmt"
"io"

"golang.org/x/crypto/blake2b"
"golang.org/x/crypto/chacha20poly1305"
)

const (
v2LocHeader = "v2.local."
v2NonceSize = chacha20poly1305.NonceSizeX
)

func V2Encrypt(key []byte, payload, footer any, randBytes []byte) (string, error) {
if randBytes == nil {
randBytes = make([]byte, v2NonceSize)
if _, err := io.ReadFull(rand.Reader, randBytes); err != nil {
return "", fmt.Errorf("read from crypto/rand.Reader: %w", err)
}
}

payloadBytes, err := toBytes(payload)
if err != nil {
return "", fmt.Errorf("encode payload: %w", err)
}

footerBytes, err := toBytes(footer)
if err != nil {
return "", fmt.Errorf("encode footer: %w", err)
}

hash, err := blake2b.New(v2NonceSize, randBytes)
if err != nil {
return "", fmt.Errorf("create blake2b hash: %w", err)
}
if _, err := hash.Write(payloadBytes); err != nil {
return "", fmt.Errorf("hash payload: %w", err)
}
nonce := hash.Sum(nil)

aead, err := chacha20poly1305.NewX(key)
if err != nil {
return "", fmt.Errorf("create chacha20poly1305 cipher: %w", err)
}

preAuth := pae([]byte(v2LocHeader), nonce, footerBytes)

encryptedPayload := aead.Seal(
payloadBytes[:0],
nonce,
payloadBytes,
preAuth,
)
body := append(nonce, encryptedPayload...)

return buildToken(v2LocHeader, body, footerBytes), nil
}

func V2Decrypt(token string, key []byte, payload, footer any) error {
body, footerBytes, err := splitToken(token, v2LocHeader)
if err != nil {
return fmt.Errorf("decode token: %w", err)
}
if len(body) < v2NonceSize {
return ErrIncorrectTokenFormat
}

aead, err := chacha20poly1305.NewX(key)
if err != nil {
return fmt.Errorf("create chacha20poly1305 cipher: %w", err)
}

nonce, encryptedPayload := body[:v2NonceSize], body[v2NonceSize:]
preAuth := pae([]byte(v2LocHeader), nonce, footerBytes)

decryptedPayload, err := aead.Open(
encryptedPayload[:0],
nonce,
encryptedPayload,
preAuth,
)
if err != nil {
return ErrInvalidTokenAuth
}

if payload != nil {
if err := fromBytes(decryptedPayload, payload); err != nil {
return fmt.Errorf("decode payload: %w", err)
}
}

if footer != nil {
if err := fromBytes(footerBytes, footer); err != nil {
return fmt.Errorf("decode footer: %w", err)
}
}
return nil
}
48 changes: 48 additions & 0 deletions v2loc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package paseto

import (
"encoding/hex"
"strings"
"testing"
)

func TestV2Loc_Encrypt(t *testing.T) {
testCases := loadGoldenFile("testdata/v2.json")

for _, tc := range testCases.Tests {
if tc.Key == "" || !strings.HasPrefix(tc.Token, v2LocHeader) {
continue
}

t.Run(tc.Name, func(t *testing.T) {
key := mustHex(tc.Key)
payload := mustJSON(tc.Payload)
footer := mustJSON(tc.Footer)
nonce := mustHex(tc.Nonce)

token, err := V2Encrypt(key, payload, footer, nonce)
if err != nil {
t.Fatal(err)
}
mustEqual(t, token, tc.Token)
})
}
}

func TestV2Loc_Decrypt(t *testing.T) {
testCases := loadGoldenFile("testdata/v2.json")

for _, tc := range testCases.Tests {
if tc.Key == "" || !strings.HasPrefix(tc.Token, v2LocHeader) {
continue
}

t.Run(tc.Name, func(t *testing.T) {
key := must(hex.DecodeString(tc.Key))
var payload, footer any

err := V2Decrypt(tc.Token, key, payload, footer)
mustOk(t, err)
})
}
}

0 comments on commit 98ffb5a

Please sign in to comment.