Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement v2.local #6

Merged
merged 3 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
})
}
}
Loading