aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.adoc10
-rw-r--r--lib/paseto/paseto.go330
-rw-r--r--lib/paseto/paseto_test.go269
-rw-r--r--lib/paseto/v2/example_local_mode_test.go (renamed from lib/paseto/example_local_mode_test.go)2
-rw-r--r--lib/paseto/v2/example_public_mode_test.go (renamed from lib/paseto/example_public_mode_test.go)5
-rw-r--r--lib/paseto/v2/json_footer.go (renamed from lib/paseto/json_footer.go)5
-rw-r--r--lib/paseto/v2/json_token.go (renamed from lib/paseto/json_token.go)2
-rw-r--r--lib/paseto/v2/json_token_test.go (renamed from lib/paseto/json_token_test.go)2
-rw-r--r--lib/paseto/v2/key.go (renamed from lib/paseto/key.go)2
-rw-r--r--lib/paseto/v2/keys.go (renamed from lib/paseto/keys.go)2
-rw-r--r--lib/paseto/v2/local_mode.go (renamed from lib/paseto/local_mode.go)2
-rw-r--r--lib/paseto/v2/paseto.go305
-rw-r--r--lib/paseto/v2/paseto_test.go264
-rw-r--r--lib/paseto/v2/public_mode.go (renamed from lib/paseto/public_mode.go)2
-rw-r--r--lib/paseto/v2/public_mode_test.go (renamed from lib/paseto/public_mode_test.go)2
-rw-r--r--lib/paseto/v2/public_token.go (renamed from lib/paseto/public_token.go)2
16 files changed, 615 insertions, 591 deletions
diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc
index 43b4c85f..2270a2f2 100644
--- a/CHANGELOG.adoc
+++ b/CHANGELOG.adoc
@@ -84,6 +84,16 @@ the worker for keeping the connection alive also call Write at the same
time, which cause the data race.
+[#v0_62_0__lib_paseto]
+=== lib/paseto
+
+==== 🪵 Move paseto v2 under sub directory "v2"
+
+There are new versions of paseto standard: version 3 and version 4.
+To minimize conflicts in the future, we move the old implementation of
+paseto v2 to sub directory "v2" with package name "pasetov2".
+
+
[#v0_62_0__lib_uuidv7]
=== lib/uuidv7
diff --git a/lib/paseto/paseto.go b/lib/paseto/paseto.go
index e37bcb2e..f903fd79 100644
--- a/lib/paseto/paseto.go
+++ b/lib/paseto/paseto.go
@@ -1,342 +1,44 @@
-// SPDX-FileCopyrightText: 2020 M. Shulhan <ms@kilabit.info>
-//
// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020 M. Shulhan <ms@kilabit.info>
// Package paseto provide a simple, ready to use, opinionated implementation
-// of Platform-Agnostic SEcurity TOkens (PASETOs) v2 as defined in
-// [paseto-rfc-01].
-//
-// See the examples below for quick reference.
-//
-// # Limitation
-//
-// This implementation only support PASETO Protocol v2.
-//
-// # Local mode
-//
-// The local mode use crypto/rand package to generate random nonce and hashed
-// with blake2b.
-//
-// # Public mode
-//
-// The public mode focus on signing and verifing data, everything else is
-// handled and filled automatically.
-//
-// Steps for sender when generating new token, the Pack() method,
-//
-// (1) Prepare the JSON token claims, set
-//
-// - Issuer "iss" to PublicMode.our.ID
-// - Subject "sub" to subject value from parameter
-// - Audience "aud" to audience value from parameter
-// - IssuedAt to current time
-// - NotBefore to current time
-// - ExpiredAt to current time + 60 seconds
-// - Data field to base64 encoded of data value from parameter
-//
-// (2) Prepare the JSON footer, set
-//
-// - Key ID "kid" to PublicMode.our.ID
-//
-// The user's claims data is stored using key "data" inside the JSON token,
-// encoded using base64 (with padding).
-// Additional footer data can be added on the Data field.
+// of Platform-Agnostic SEcurity TOkens (PASETO) version 2 and version 4 as
+// defined in [paseto-v2] and [paseto-v4].
//
-// Overall, the following JSONToken and JSONFooter is generated for each
-// token,
+// The paseto version 2 is available under sub packet
+// [git.sr.ht/~shulhan/pakakeh.go/lib/paseto/v2] and the paseto version 4
+// is available under sub packet
+// [git.sr.ht/~shulhan/pakakeh.go/lib/paseto/v4].
//
-// JSONToken:{
-// "iss": <Key.ID>,
-// "sub": <Subject parameter>,
-// "aud": <Audience parameter>
-// "exp": <time.Now() + TTL>,
-// "iat": <time.Now()>,
-// "nbf": <time.Now()>,
-// "data": <base64.StdEncoding.EncodeToString(userData)>,
-// }
-// JSONFooter:{
-// "kid": <Key.ID>,
-// "data": {}
-// }
-//
-// On the receiver side, they will have list of registered peers Key (include
-// ID, public Key, and list of allowed subject).
-//
-// PublicMode:{
-// peers: map[Key.ID]Key{
-// Public: <ed25519.PublicKey>,
-// AllowedSubjects: map[string]struct{}{
-// "/api/x": struct{}{},
-// "/api/y:read": struct{}{},
-// "/api/z:write": struct{}{},
-// ...
-// },
-// },
-// }
-//
-// Step for receiver to process the token, the Unpack() method,
-//
-// (1) Decode the token footer
-//
-// (2) Get the registered public key based on "kid" value in token footer.
-// If no peers key exist matched with "kid" value, reject the token.
-//
-// (3) Verify the token using the peer public key.
-// If verification failed, reject the token.
-//
-// (4) Validate the token.
-// - The Issuer must equal to peer ID.
-// - The Audience must equal to receiver ID.
-// - If the peer AllowedSubjects is not empty, the Subject must be in
-// one of them.
-// - The current time must be after IssuedAt.
-// - The current time must be after NotBefore.
-// - The current time must be before ExpiredAt.
-// - If one of the above condition is not passed, it will return an error.
-//
-// # References
-//
-// - [paseto-rfc-01]
-//
-// [paseto-rfc-01]: https://github.com/paragonie/paseto/blob/master/docs/RFC/draft-paragon-paseto-rfc-01.txt
+// [paseto-v2]: https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version2.md
+// [paseto-v4]: https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version4.md
package paseto
import (
"bytes"
- "crypto/cipher"
- "crypto/ed25519"
- "crypto/rand"
- "encoding/base64"
"encoding/binary"
- "errors"
- "strings"
-
- "golang.org/x/crypto/blake2b"
-)
-
-const (
- randNonceSize = 24
-)
-
-var (
- headerModePublic = []byte("v2.public.")
- headerModeLocal = []byte("v2.local.")
+ "fmt"
)
-// Encrypt given the shared key, encrypt the plain message and generate the
-// "local" token with optional footer.
-func Encrypt(aead cipher.AEAD, plain, footer []byte) (token string, err error) {
- nonce := make([]byte, randNonceSize)
- _, err = rand.Read(nonce)
- if err != nil {
- return "", err
- }
-
- return encrypt(aead, nonce, plain, footer)
-}
-
-func encrypt(aead cipher.AEAD, nonce, plain, footer []byte) (token string, err error) {
- b2b, err := blake2b.New(randNonceSize, nonce)
- if err != nil {
- return "", err
- }
-
- _, err = b2b.Write(plain)
- if err != nil {
- return "", err
- }
-
- nonce = b2b.Sum(nil)
-
- pieces := [][]byte{headerModeLocal, nonce, footer}
-
- m2, err := pae(pieces)
- if err != nil {
- return "", err
- }
-
- cipher := aead.Seal(nil, nonce, plain, m2)
-
- var buf bytes.Buffer
-
- _, err = buf.Write(headerModeLocal)
- if err != nil {
- return "", err
- }
-
- sc := make([]byte, 0, len(nonce)+len(cipher))
- sc = append(sc, nonce...)
- sc = append(sc, cipher...)
-
- n := base64.RawURLEncoding.EncodedLen(len(sc))
- dst := make([]byte, n)
- base64.RawURLEncoding.Encode(dst, sc)
- _, err = buf.Write(dst)
- if err != nil {
- return ``, err
- }
-
- if len(footer) > 0 {
- buf.WriteByte('.')
-
- n = base64.RawURLEncoding.EncodedLen(len(footer))
- dst = make([]byte, n)
- base64.RawURLEncoding.Encode(dst, footer)
- _, err = buf.Write(dst)
- if err != nil {
- return ``, err
- }
- }
-
- return buf.String(), nil
-}
-
-// Decrypt given a shared key and encrypted token, decrypt the token to get
-// the message.
-func Decrypt(aead cipher.AEAD, token string) (plain, footer []byte, err error) {
- pieces := strings.Split(token, ".")
- if len(pieces) < 3 || len(pieces) > 4 {
- return nil, nil, errors.New("invalid token format")
- }
- if pieces[0] != "v2" {
- return nil, nil, errors.New(`unsupported protocol version ` + pieces[0])
- }
- if pieces[1] != "local" {
- return nil, nil, errors.New(`expecting local mode, got ` + pieces[1])
- }
-
- if len(pieces) == 4 {
- footer, err = base64.RawURLEncoding.DecodeString(pieces[3])
- if err != nil {
- return nil, nil, err
- }
- }
-
- src, err := base64.RawURLEncoding.DecodeString(pieces[2])
- if err != nil {
- return nil, nil, err
- }
-
- nonce := src[:randNonceSize]
- cipher := src[randNonceSize:]
-
- if len(cipher) < aead.NonceSize() {
- return nil, nil, errors.New("ciphertext too short")
- }
-
- m2, err := pae([][]byte{headerModeLocal, nonce, footer})
- if err != nil {
- return nil, nil, err
- }
-
- plain, err = aead.Open(nil, nonce, cipher, m2)
- if err != nil {
- return nil, nil, err
- }
-
- return plain, footer, nil
-}
-
-// Sign given an Ed25519 secret key "sk", a message "m", and optional footer
-// "f" (which defaults to empty string); sign the message "m" and generate the
-// public token.
-func Sign(sk ed25519.PrivateKey, m, f []byte) (token string, err error) {
- pieces := [][]byte{headerModePublic, m, f}
-
- m2, err := pae(pieces)
- if err != nil {
- return "", err
- }
-
- sig := ed25519.Sign(sk, m2)
-
- var buf bytes.Buffer
-
- _, err = buf.Write(headerModePublic)
- if err != nil {
- return "", err
- }
-
- sm := make([]byte, 0, len(m)+len(sig))
- sm = append(sm, m...)
- sm = append(sm, sig...)
-
- n := base64.RawURLEncoding.EncodedLen(len(sm))
- dst := make([]byte, n)
- base64.RawURLEncoding.Encode(dst, sm)
-
- _, err = buf.Write(dst)
- if err != nil {
- return "", err
- }
-
- if len(f) > 0 {
- _ = buf.WriteByte('.')
-
- n = base64.RawURLEncoding.EncodedLen(len(f))
- dst = make([]byte, n)
- base64.RawURLEncoding.Encode(dst, f)
-
- _, err = buf.Write(dst)
- if err != nil {
- return "", err
- }
- }
-
- return buf.String(), nil
-}
-
-// Verify given a public key "pk", a signed message "sm" (that has been
-// decoded from base64), and optional footer "f" (also that has been decoded
-// from base64 string); verify that the signature is valid for the message.
-func Verify(pk ed25519.PublicKey, sm, f []byte) (msg []byte, err error) {
- if len(sm) <= 64 {
- return nil, errors.New(`invalid signed message length`)
- }
-
- msg = sm[:len(sm)-64]
- sig := sm[len(sm)-64:]
- pieces := [][]byte{headerModePublic, msg, f}
-
- msg2, err := pae(pieces)
- if err != nil {
- return nil, err
- }
-
- if !ed25519.Verify(pk, msg2, sig) {
- return nil, errors.New(`invalid message signature`)
- }
-
- return msg, nil
-}
-
-func pae(pieces [][]byte) (b []byte, err error) {
+// PreAuthEncode encodes each piece into single block.
+func PreAuthEncode(pieces ...[]byte) (b []byte, err error) {
+ logp := `PreAuthEncode`
var buf bytes.Buffer
err = binary.Write(&buf, binary.LittleEndian, uint64(len(pieces)))
if err != nil {
- return nil, err
- }
-
- _, err = buf.Write(b)
- if err != nil {
- return nil, err
+ return nil, fmt.Errorf(`%s: %w`, logp, err)
}
for x := range len(pieces) {
err = binary.Write(&buf, binary.LittleEndian, uint64(len(pieces[x])))
if err != nil {
- return nil, err
- }
-
- _, err = buf.Write(b)
- if err != nil {
- return nil, err
+ return nil, fmt.Errorf(`%s: %w`, logp, err)
}
_, err = buf.Write(pieces[x])
if err != nil {
- return nil, err
+ return nil, fmt.Errorf(`%s: %w`, logp, err)
}
}
return buf.Bytes(), nil
diff --git a/lib/paseto/paseto_test.go b/lib/paseto/paseto_test.go
index 5c267bc8..4a096dfd 100644
--- a/lib/paseto/paseto_test.go
+++ b/lib/paseto/paseto_test.go
@@ -1,294 +1,39 @@
// SPDX-License-Identifier: BSD-3-Clause
-// SPDX-FileCopyrightText: 2020 Shulhan <ms@kilabit.info>
+// SPDX-FileCopyrightText: 2020 M. Shulhan <ms@kilabit.info>
package paseto
import (
- "encoding/base64"
- "encoding/hex"
- "strings"
"testing"
"git.sr.ht/~shulhan/pakakeh.go/lib/test"
- "golang.org/x/crypto/chacha20poly1305"
)
-func TestPae(t *testing.T) {
+func TestPreAuthEncode(t *testing.T) {
cases := []struct {
pieces [][]byte
exp []byte
}{{
exp: []byte("\x00\x00\x00\x00\x00\x00\x00\x00"),
}, {
- pieces: [][]byte{[]byte{}},
+ pieces: [][]byte{[]byte(``)},
exp: []byte("\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"),
}, {
pieces: [][]byte{{}, {}},
exp: []byte("\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"),
}, {
- pieces: [][]byte{[]byte("test")},
+ pieces: [][]byte{[]byte(`test`)},
exp: []byte("\x01\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00test"),
}, {
- pieces: [][]byte{[]byte("Paragon")},
+ pieces: [][]byte{[]byte(`Paragon`)},
exp: []byte("\x01\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x50\x61\x72\x61\x67\x6f\x6e"),
}}
for _, c := range cases {
- got, err := pae(c.pieces)
+ got, err := PreAuthEncode(c.pieces...)
if err != nil {
t.Fatal(err)
}
-
- test.Assert(t, "pae", c.exp, got)
- }
-}
-
-func TestEncrypt(t *testing.T) {
- hexKey := "70717273" + "74757677" + "78797a7b" + "7c7d7e7f" +
- "80818283" + "84858687" + "88898a8b" + "8c8d8e8f"
-
- key, err := hex.DecodeString(hexKey)
- if err != nil {
- t.Fatal(err)
- }
-
- aead, err := chacha20poly1305.NewX(key)
- if err != nil {
- t.Fatal(err)
- }
-
- cases := []struct {
- desc string
- nonce string
- exp string
-
- msg []byte
- footer []byte
- }{{
- desc: "Encrypt with zero nonce, without footer",
- msg: []byte(`{"data":"this is a signed message","exp":"2019-01-01T00:00:00+00:00"}`),
- nonce: "00000000" + "00000000" + "00000000" + "00000000" +
- "00000000" + "00000000",
- exp: "v2.local.97TTOvgwIxNGvV80XKiGZg_kD3tsXM_-qB4dZGHOeN1cTkgQ4Pn" +
- "W8888l802W8d9AvEGnoNBY3BnqHORy8a5cC8aKpbA0En8XELw2yDk2f1sVOD" +
- "yfnDbi6rEGMY3pSfCbLWMM2oHJxvlEl2XbQ",
- }, {
- desc: "Encrypt with zero nonce, without footer (2)",
- msg: []byte(`{"data":"this is a secret message","exp":"2019-01-01T00:00:00+00:00"}`),
- nonce: "00000000" + "00000000" + "00000000" + "00000000" +
- "00000000" + "00000000",
- exp: "v2.local.CH50H-HM5tzdK4kOmQ8KbIvrzJfjYUGuu5Vy9ARSFHy9owVDMYg" +
- "3-8rwtJZQjN9ABHb2njzFkvpr5cOYuRyt7CRXnHt42L5yZ7siD-4l-FoNsC7" +
- "J2OlvLlIwlG06mzQVunrFNb7Z3_CHM0PK5w",
- }, {
- desc: "Encrypt with nonce, without footer",
- msg: []byte(`{"data":"this is a signed message","exp":"2019-01-01T00:00:00+00:00"}`),
- nonce: "45742c97" + "6d684ff8" + "4ebdc0de" + "59809a97" +
- "cda2f64c" + "84fda19b",
- exp: "v2.local.5K4SCXNhItIhyNuVIZcwrdtaDKiyF81-eWHScuE0idiVqCo72bb" +
- "jo07W05mqQkhLZdVbxEa5I_u5sgVk1QLkcWEcOSlLHwNpCkvmGGlbCdNExn6" +
- "Qclw3qTKIIl5-O5xRBN076fSDPo5xUCPpBA",
- }, {
- desc: "Encrypt with nonce, with footer",
- msg: []byte(`{"data":"this is a signed message","exp":"2019-01-01T00:00:00+00:00"}`),
- nonce: "45742c97" + "6d684ff8" + "4ebdc0de" + "59809a97" +
- "cda2f64c" + "84fda19b",
- footer: []byte(`{"kid":"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN"}`),
- exp: "v2.local.5K4SCXNhItIhyNuVIZcwrdtaDKiyF81-eWHScuE0idiVqCo72bb" +
- "jo07W05mqQkhLZdVbxEa5I_u5sgVk1QLkcWEcOSlLHwNpCkvmGGlbCdNExn6" +
- "Qclw3qTKIIl5-zSLIrxZqOLwcFLYbVK1SrQ.eyJraWQiOiJ6VmhNaVBCUDlm" +
- "UmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9",
- }, {
- desc: "Encrypt with nonce, with footer (2)",
- msg: []byte(`{"data":"this is a secret message","exp":"2019-01-01T00:00:00+00:00"}`),
- nonce: "45742c97" + "6d684ff8" + "4ebdc0de" + "59809a97" +
- "cda2f64c" + "84fda19b",
- footer: []byte(`{"kid":"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN"}`),
- exp: "v2.local.pvFdDeNtXxknVPsbBCZF6MGedVhPm40SneExdClOxa9HNR8wFv7" +
- "cu1cB0B4WxDdT6oUc2toyLR6jA6sc-EUM5ll1EkeY47yYk6q8m1RCpqTIzUr" +
- "Iu3B6h232h62DnMXKdHn_Smp6L_NfaEnZ-A.eyJraWQiOiJ6VmhNaVBCUDlm" +
- "UmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9",
- }}
-
- for _, c := range cases {
- nonce, err := hex.DecodeString(c.nonce)
- if err != nil {
- t.Fatal(err)
- }
-
- got, err := encrypt(aead, nonce, c.msg, c.footer)
- if err != nil {
- t.Fatal(err)
- }
-
- test.Assert(t, c.desc, c.exp, got)
- }
-}
-
-func TestDecrypt(t *testing.T) {
- hexKey := "70717273" + "74757677" + "78797a7b" + "7c7d7e7f" +
- "80818283" + "84858687" + "88898a8b" + "8c8d8e8f"
-
- key, err := hex.DecodeString(hexKey)
- if err != nil {
- t.Fatal(err)
- }
-
- aead, err := chacha20poly1305.NewX(key)
- if err != nil {
- t.Fatal(err)
- }
-
- cases := []struct {
- desc string
- token string
- exp []byte
- expFooter []byte
- }{{
- desc: "Decrypt without nonce and footer",
- token: "v2.local.97TTOvgwIxNGvV80XKiGZg_kD3tsXM_-qB4dZGHOeN1cTkgQ4Pn" +
- "W8888l802W8d9AvEGnoNBY3BnqHORy8a5cC8aKpbA0En8XELw2yDk2f1sVOD" +
- "yfnDbi6rEGMY3pSfCbLWMM2oHJxvlEl2XbQ",
- exp: []byte(`{"data":"this is a signed message","exp":"2019-01-01T00:00:00+00:00"}`),
- }, {
- desc: "Decrypt without nonce and footer (2)",
- token: "v2.local.CH50H-HM5tzdK4kOmQ8KbIvrzJfjYUGuu5Vy9ARSFHy9owVDMYg" +
- "3-8rwtJZQjN9ABHb2njzFkvpr5cOYuRyt7CRXnHt42L5yZ7siD-4l-FoNsC7" +
- "J2OlvLlIwlG06mzQVunrFNb7Z3_CHM0PK5w",
- exp: []byte(`{"data":"this is a secret message","exp":"2019-01-01T00:00:00+00:00"}`),
- }, {
- desc: "Decrypt with nonce, without footer",
- token: "v2.local.5K4SCXNhItIhyNuVIZcwrdtaDKiyF81-eWHScuE0idiVqCo72bb" +
- "jo07W05mqQkhLZdVbxEa5I_u5sgVk1QLkcWEcOSlLHwNpCkvmGGlbCdNExn6" +
- "Qclw3qTKIIl5-O5xRBN076fSDPo5xUCPpBA",
- exp: []byte(`{"data":"this is a signed message","exp":"2019-01-01T00:00:00+00:00"}`),
- }, {
- desc: "Decrypt with nonce, with footer",
- token: "v2.local.5K4SCXNhItIhyNuVIZcwrdtaDKiyF81-eWHScuE0idiVqCo72bb" +
- "jo07W05mqQkhLZdVbxEa5I_u5sgVk1QLkcWEcOSlLHwNpCkvmGGlbCdNExn6" +
- "Qclw3qTKIIl5-zSLIrxZqOLwcFLYbVK1SrQ.eyJraWQiOiJ6VmhNaVBCUDlm" +
- "UmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9",
- exp: []byte(`{"data":"this is a signed message","exp":"2019-01-01T00:00:00+00:00"}`),
- expFooter: []byte(`{"kid":"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN"}`),
- }, {
- desc: "Decrypt with nonce, with footer (2)",
- token: "v2.local.pvFdDeNtXxknVPsbBCZF6MGedVhPm40SneExdClOxa9HNR8wFv7" +
- "cu1cB0B4WxDdT6oUc2toyLR6jA6sc-EUM5ll1EkeY47yYk6q8m1RCpqTIzUr" +
- "Iu3B6h232h62DnMXKdHn_Smp6L_NfaEnZ-A.eyJraWQiOiJ6VmhNaVBCUDlm" +
- "UmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9",
- exp: []byte(`{"data":"this is a secret message","exp":"2019-01-01T00:00:00+00:00"}`),
- expFooter: []byte(`{"kid":"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN"}`),
- }}
-
- for _, c := range cases {
- got, gotFooter, err := Decrypt(aead, c.token)
- if err != nil {
- t.Fatal(err)
- }
-
- test.Assert(t, c.desc, c.exp, got)
- test.Assert(t, c.desc, c.expFooter, gotFooter)
- }
-}
-
-func TestSign(t *testing.T) {
- hexPrivate := "b4cbfb43" + "df4ce210" + "727d953e" + "4a713307" +
- "fa19bb7d" + "9f850414" + "38d9e11b" + "942a3774" +
- "1eb9dbbb" + "bc047c03" + "fd70604e" + "0071f098" +
- "7e16b28b" + "757225c1" + "1f00415d" + "0e20b1a2"
-
- sk, err := hex.DecodeString(hexPrivate)
- if err != nil {
- t.Fatal()
- }
-
- m := []byte(`{"data":"this is a signed message","exp":"2019-01-01T00:00:00+00:00"}`)
-
- cases := []struct {
- desc string
- exp string
-
- m []byte
- f []byte
- }{{
- desc: "Sign",
- m: m,
- exp: "v2.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIi" +
- "wiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9HQr8URrGnt" +
- "Tu7Dz9J2IF23d1M7-9lH9xiqdGyJNvzp4angPW5Esc7C5huy_M8I8_Dj" +
- "JK2ZXC2SUYuOFM-Q_5Cw",
- }, {
- desc: "Sign with footer",
- m: m,
- f: []byte(`{"kid":"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN"}`),
- exp: "v2.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIi" +
- "wiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9flsZsx_gYC" +
- "R0N_Ec2QxJFFpvQAs7h9HtKwbVK2n1MJ3Rz-hwe8KUqjnd8FAnIJZ601" +
- "tp7lGkguU63oGbomhoBw.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q" +
- "3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9",
- }}
-
- for _, c := range cases {
- got, err := Sign(sk, c.m, c.f)
- if err != nil {
- t.Fatal(err)
- }
-
- test.Assert(t, c.desc, c.exp, got)
- }
-}
-
-func TestVerify(t *testing.T) {
- hexPublic := "1eb9dbbb" + "bc047c03" + "fd70604e" + "0071f098" +
- "7e16b28b" + "757225c1" + "1f00415d" + "0e20b1a2"
-
- public, err := hex.DecodeString(hexPublic)
- if err != nil {
- t.Fatal()
- }
-
- cases := []struct {
- desc string
- token string
- exp string
- }{{
- desc: "Verify",
- token: "v2.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIi" +
- "wiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9HQr8URrGnt" +
- "Tu7Dz9J2IF23d1M7-9lH9xiqdGyJNvzp4angPW5Esc7C5huy_M8I8_Dj" +
- "JK2ZXC2SUYuOFM-Q_5Cw",
- exp: `{"data":"this is a signed message","exp":"2019-01-01T00:00:00+00:00"}`,
- }, {
- desc: "Verify with footer",
- token: "v2.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIi" +
- "wiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9flsZsx_gYC" +
- "R0N_Ec2QxJFFpvQAs7h9HtKwbVK2n1MJ3Rz-hwe8KUqjnd8FAnIJZ601" +
- "tp7lGkguU63oGbomhoBw.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q" +
- "3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9",
- exp: `{"data":"this is a signed message","exp":"2019-01-01T00:00:00+00:00"}`,
- }}
-
- for _, c := range cases {
- var footer []byte
-
- pieces := strings.Split(c.token, ".")
-
- sm, err := base64.RawURLEncoding.DecodeString(pieces[2])
- if err != nil {
- t.Fatal(err)
- }
- if len(pieces) == 4 {
- footer, err = base64.RawURLEncoding.DecodeString(pieces[3])
- if err != nil {
- t.Fatal(err)
- }
- }
-
- got, err := Verify(public, sm, footer)
- if err != nil {
- t.Fatal(err)
- }
-
- test.Assert(t, c.desc, c.exp, string(got))
+ test.Assert(t, `PreAuthEncode`, c.exp, got)
}
}
diff --git a/lib/paseto/example_local_mode_test.go b/lib/paseto/v2/example_local_mode_test.go
index 3046f8c6..15960e4a 100644
--- a/lib/paseto/example_local_mode_test.go
+++ b/lib/paseto/v2/example_local_mode_test.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020 M. Shulhan <ms@kilabit.info>
-package paseto
+package pasetov2
import (
"encoding/hex"
diff --git a/lib/paseto/example_public_mode_test.go b/lib/paseto/v2/example_public_mode_test.go
index a821c93b..0930aebf 100644
--- a/lib/paseto/example_public_mode_test.go
+++ b/lib/paseto/v2/example_public_mode_test.go
@@ -1,8 +1,7 @@
-// SPDX-FileCopyrightText: 2020 M. Shulhan <ms@kilabit.info>
-//
// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020 M. Shulhan <ms@kilabit.info>
-package paseto
+package pasetov2
import (
"crypto/ed25519"
diff --git a/lib/paseto/json_footer.go b/lib/paseto/v2/json_footer.go
index 990ee688..67a97444 100644
--- a/lib/paseto/json_footer.go
+++ b/lib/paseto/v2/json_footer.go
@@ -1,8 +1,7 @@
-// SPDX-FileCopyrightText: 2020 M. Shulhan <ms@kilabit.info>
-//
// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020 M. Shulhan <ms@kilabit.info>
-package paseto
+package pasetov2
// JSONFooter define the optional metadata and data at the footer of the
// token that are not included in signature.
diff --git a/lib/paseto/json_token.go b/lib/paseto/v2/json_token.go
index f5ab4979..a3b6006d 100644
--- a/lib/paseto/json_token.go
+++ b/lib/paseto/v2/json_token.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020 Shulhan <ms@kilabit.info>
-package paseto
+package pasetov2
import (
"fmt"
diff --git a/lib/paseto/json_token_test.go b/lib/paseto/v2/json_token_test.go
index 1d43e058..5081ebf4 100644
--- a/lib/paseto/json_token_test.go
+++ b/lib/paseto/v2/json_token_test.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020 Shulhan <ms@kilabit.info>
-package paseto
+package pasetov2
import (
"fmt"
diff --git a/lib/paseto/key.go b/lib/paseto/v2/key.go
index b5a2a083..fdd51988 100644
--- a/lib/paseto/key.go
+++ b/lib/paseto/v2/key.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020 Shulhan <ms@kilabit.info>
-package paseto
+package pasetov2
import "crypto/ed25519"
diff --git a/lib/paseto/keys.go b/lib/paseto/v2/keys.go
index 170efab0..bfe7e97f 100644
--- a/lib/paseto/keys.go
+++ b/lib/paseto/v2/keys.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020 Shulhan <ms@kilabit.info>
-package paseto
+package pasetov2
import "sync"
diff --git a/lib/paseto/local_mode.go b/lib/paseto/v2/local_mode.go
index cfab3b3d..0cc29aef 100644
--- a/lib/paseto/local_mode.go
+++ b/lib/paseto/v2/local_mode.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020 Shulhan <ms@kilabit.info>
-package paseto
+package pasetov2
import (
"crypto/cipher"
diff --git a/lib/paseto/v2/paseto.go b/lib/paseto/v2/paseto.go
new file mode 100644
index 00000000..a7fb7f77
--- /dev/null
+++ b/lib/paseto/v2/paseto.go
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020 M. Shulhan <ms@kilabit.info>
+
+// Package paseto provide a simple, ready to use, opinionated implementation
+// of Platform-Agnostic SEcurity TOkens (PASETOs) v2 as defined in
+// [paseto-rfc-01].
+//
+// See the examples below for quick reference.
+//
+// # Limitation
+//
+// This implementation only support PASETO Protocol v2.
+//
+// # Local mode
+//
+// The local mode use crypto/rand package to generate random nonce and hashed
+// with blake2b.
+//
+// # Public mode
+//
+// The public mode focus on signing and verifing data, everything else is
+// handled and filled automatically.
+//
+// Steps for sender when generating new token, the Pack() method,
+//
+// (1) Prepare the JSON token claims, set
+//
+// - Issuer "iss" to PublicMode.our.ID
+// - Subject "sub" to subject value from parameter
+// - Audience "aud" to audience value from parameter
+// - IssuedAt to current time
+// - NotBefore to current time
+// - ExpiredAt to current time + 60 seconds
+// - Data field to base64 encoded of data value from parameter
+//
+// (2) Prepare the JSON footer, set
+//
+// - Key ID "kid" to PublicMode.our.ID
+//
+// The user's claims data is stored using key "data" inside the JSON token,
+// encoded using base64 (with padding).
+// Additional footer data can be added on the Data field.
+//
+// Overall, the following JSONToken and JSONFooter is generated for each
+// token,
+//
+// JSONToken:{
+// "iss": <Key.ID>,
+// "sub": <Subject parameter>,
+// "aud": <Audience parameter>
+// "exp": <time.Now() + TTL>,
+// "iat": <time.Now()>,
+// "nbf": <time.Now()>,
+// "data": <base64.StdEncoding.EncodeToString(userData)>,
+// }
+// JSONFooter:{
+// "kid": <Key.ID>,
+// "data": {}
+// }
+//
+// On the receiver side, they will have list of registered peers Key (include
+// ID, public Key, and list of allowed subject).
+//
+// PublicMode:{
+// peers: map[Key.ID]Key{
+// Public: <ed25519.PublicKey>,
+// AllowedSubjects: map[string]struct{}{
+// "/api/x": struct{}{},
+// "/api/y:read": struct{}{},
+// "/api/z:write": struct{}{},
+// ...
+// },
+// },
+// }
+//
+// Step for receiver to process the token, the Unpack() method,
+//
+// (1) Decode the token footer
+//
+// (2) Get the registered public key based on "kid" value in token footer.
+// If no peers key exist matched with "kid" value, reject the token.
+//
+// (3) Verify the token using the peer public key.
+// If verification failed, reject the token.
+//
+// (4) Validate the token.
+// - The Issuer must equal to peer ID.
+// - The Audience must equal to receiver ID.
+// - If the peer AllowedSubjects is not empty, the Subject must be in
+// one of them.
+// - The current time must be after IssuedAt.
+// - The current time must be after NotBefore.
+// - The current time must be before ExpiredAt.
+// - If one of the above condition is not passed, it will return an error.
+//
+// # References
+//
+// - [paseto-rfc-01]
+//
+// [paseto-rfc-01]: https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version2.md
+package pasetov2
+
+import (
+ "bytes"
+ "crypto/cipher"
+ "crypto/ed25519"
+ "crypto/rand"
+ "encoding/base64"
+ "errors"
+ "strings"
+
+ "git.sr.ht/~shulhan/pakakeh.go/lib/paseto"
+ "golang.org/x/crypto/blake2b"
+)
+
+const (
+ randNonceSize = 24
+)
+
+var (
+ headerModePublic = []byte("v2.public.")
+ headerModeLocal = []byte("v2.local.")
+)
+
+// Encrypt given the shared key, encrypt the plain message and generate the
+// "local" token with optional footer.
+func Encrypt(aead cipher.AEAD, plain, footer []byte) (token string, err error) {
+ nonce := make([]byte, randNonceSize)
+ _, err = rand.Read(nonce)
+ if err != nil {
+ return "", err
+ }
+
+ return encrypt(aead, nonce, plain, footer)
+}
+
+func encrypt(aead cipher.AEAD, nonce, plain, footer []byte) (token string, err error) {
+ b2b, err := blake2b.New(randNonceSize, nonce)
+ if err != nil {
+ return "", err
+ }
+
+ _, err = b2b.Write(plain)
+ if err != nil {
+ return "", err
+ }
+
+ nonce = b2b.Sum(nil)
+
+ m2, err := paseto.PreAuthEncode(headerModeLocal, nonce, footer)
+ if err != nil {
+ return "", err
+ }
+
+ cipher := aead.Seal(nil, nonce, plain, m2)
+
+ var buf bytes.Buffer
+
+ _, err = buf.Write(headerModeLocal)
+ if err != nil {
+ return "", err
+ }
+
+ sc := make([]byte, 0, len(nonce)+len(cipher))
+ sc = append(sc, nonce...)
+ sc = append(sc, cipher...)
+
+ n := base64.RawURLEncoding.EncodedLen(len(sc))
+ dst := make([]byte, n)
+ base64.RawURLEncoding.Encode(dst, sc)
+ _, err = buf.Write(dst)
+ if err != nil {
+ return ``, err
+ }
+
+ if len(footer) > 0 {
+ buf.WriteByte('.')
+
+ n = base64.RawURLEncoding.EncodedLen(len(footer))
+ dst = make([]byte, n)
+ base64.RawURLEncoding.Encode(dst, footer)
+ _, err = buf.Write(dst)
+ if err != nil {
+ return ``, err
+ }
+ }
+
+ return buf.String(), nil
+}
+
+// Decrypt given a shared key and encrypted token, decrypt the token to get
+// the message.
+func Decrypt(aead cipher.AEAD, token string) (plain, footer []byte, err error) {
+ pieces := strings.Split(token, ".")
+ if len(pieces) < 3 || len(pieces) > 4 {
+ return nil, nil, errors.New("invalid token format")
+ }
+ if pieces[0] != "v2" {
+ return nil, nil, errors.New(`unsupported protocol version ` + pieces[0])
+ }
+ if pieces[1] != "local" {
+ return nil, nil, errors.New(`expecting local mode, got ` + pieces[1])
+ }
+
+ if len(pieces) == 4 {
+ footer, err = base64.RawURLEncoding.DecodeString(pieces[3])
+ if err != nil {
+ return nil, nil, err
+ }
+ }
+
+ src, err := base64.RawURLEncoding.DecodeString(pieces[2])
+ if err != nil {
+ return nil, nil, err
+ }
+
+ nonce := src[:randNonceSize]
+ cipher := src[randNonceSize:]
+
+ if len(cipher) < aead.NonceSize() {
+ return nil, nil, errors.New("ciphertext too short")
+ }
+
+ m2, err := paseto.PreAuthEncode(headerModeLocal, nonce, footer)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ plain, err = aead.Open(nil, nonce, cipher, m2)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ return plain, footer, nil
+}
+
+// Sign given an Ed25519 secret key "sk", a message "m", and optional footer
+// "f" (which defaults to empty string); sign the message "m" and generate the
+// public token.
+func Sign(sk ed25519.PrivateKey, m, f []byte) (token string, err error) {
+ m2, err := paseto.PreAuthEncode(headerModePublic, m, f)
+ if err != nil {
+ return "", err
+ }
+
+ sig := ed25519.Sign(sk, m2)
+
+ var buf bytes.Buffer
+
+ _, err = buf.Write(headerModePublic)
+ if err != nil {
+ return "", err
+ }
+
+ sm := make([]byte, 0, len(m)+len(sig))
+ sm = append(sm, m...)
+ sm = append(sm, sig...)
+
+ n := base64.RawURLEncoding.EncodedLen(len(sm))
+ dst := make([]byte, n)
+ base64.RawURLEncoding.Encode(dst, sm)
+
+ _, err = buf.Write(dst)
+ if err != nil {
+ return "", err
+ }
+
+ if len(f) > 0 {
+ _ = buf.WriteByte('.')
+
+ n = base64.RawURLEncoding.EncodedLen(len(f))
+ dst = make([]byte, n)
+ base64.RawURLEncoding.Encode(dst, f)
+
+ _, err = buf.Write(dst)
+ if err != nil {
+ return "", err
+ }
+ }
+
+ return buf.String(), nil
+}
+
+// Verify given a public key "pk", a signed message "sm" (that has been
+// decoded from base64), and optional footer "f" (also that has been decoded
+// from base64 string); verify that the signature is valid for the message.
+func Verify(pk ed25519.PublicKey, sm, f []byte) (msg []byte, err error) {
+ if len(sm) <= 64 {
+ return nil, errors.New(`invalid signed message length`)
+ }
+
+ msg = sm[:len(sm)-64]
+ sig := sm[len(sm)-64:]
+
+ msg2, err := paseto.PreAuthEncode(headerModePublic, msg, f)
+ if err != nil {
+ return nil, err
+ }
+
+ if !ed25519.Verify(pk, msg2, sig) {
+ return nil, errors.New(`invalid message signature`)
+ }
+
+ return msg, nil
+}
diff --git a/lib/paseto/v2/paseto_test.go b/lib/paseto/v2/paseto_test.go
new file mode 100644
index 00000000..7e066456
--- /dev/null
+++ b/lib/paseto/v2/paseto_test.go
@@ -0,0 +1,264 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// SPDX-FileCopyrightText: 2020 Shulhan <ms@kilabit.info>
+
+package pasetov2
+
+import (
+ "encoding/base64"
+ "encoding/hex"
+ "strings"
+ "testing"
+
+ "git.sr.ht/~shulhan/pakakeh.go/lib/test"
+ "golang.org/x/crypto/chacha20poly1305"
+)
+
+func TestEncrypt(t *testing.T) {
+ hexKey := "70717273" + "74757677" + "78797a7b" + "7c7d7e7f" +
+ "80818283" + "84858687" + "88898a8b" + "8c8d8e8f"
+
+ key, err := hex.DecodeString(hexKey)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ aead, err := chacha20poly1305.NewX(key)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ cases := []struct {
+ desc string
+ nonce string
+ exp string
+
+ msg []byte
+ footer []byte
+ }{{
+ desc: "Encrypt with zero nonce, without footer",
+ msg: []byte(`{"data":"this is a signed message","exp":"2019-01-01T00:00:00+00:00"}`),
+ nonce: "00000000" + "00000000" + "00000000" + "00000000" +
+ "00000000" + "00000000",
+ exp: "v2.local.97TTOvgwIxNGvV80XKiGZg_kD3tsXM_-qB4dZGHOeN1cTkgQ4Pn" +
+ "W8888l802W8d9AvEGnoNBY3BnqHORy8a5cC8aKpbA0En8XELw2yDk2f1sVOD" +
+ "yfnDbi6rEGMY3pSfCbLWMM2oHJxvlEl2XbQ",
+ }, {
+ desc: "Encrypt with zero nonce, without footer (2)",
+ msg: []byte(`{"data":"this is a secret message","exp":"2019-01-01T00:00:00+00:00"}`),
+ nonce: "00000000" + "00000000" + "00000000" + "00000000" +
+ "00000000" + "00000000",
+ exp: "v2.local.CH50H-HM5tzdK4kOmQ8KbIvrzJfjYUGuu5Vy9ARSFHy9owVDMYg" +
+ "3-8rwtJZQjN9ABHb2njzFkvpr5cOYuRyt7CRXnHt42L5yZ7siD-4l-FoNsC7" +
+ "J2OlvLlIwlG06mzQVunrFNb7Z3_CHM0PK5w",
+ }, {
+ desc: "Encrypt with nonce, without footer",
+ msg: []byte(`{"data":"this is a signed message","exp":"2019-01-01T00:00:00+00:00"}`),
+ nonce: "45742c97" + "6d684ff8" + "4ebdc0de" + "59809a97" +
+ "cda2f64c" + "84fda19b",
+ exp: "v2.local.5K4SCXNhItIhyNuVIZcwrdtaDKiyF81-eWHScuE0idiVqCo72bb" +
+ "jo07W05mqQkhLZdVbxEa5I_u5sgVk1QLkcWEcOSlLHwNpCkvmGGlbCdNExn6" +
+ "Qclw3qTKIIl5-O5xRBN076fSDPo5xUCPpBA",
+ }, {
+ desc: "Encrypt with nonce, with footer",
+ msg: []byte(`{"data":"this is a signed message","exp":"2019-01-01T00:00:00+00:00"}`),
+ nonce: "45742c97" + "6d684ff8" + "4ebdc0de" + "59809a97" +
+ "cda2f64c" + "84fda19b",
+ footer: []byte(`{"kid":"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN"}`),
+ exp: "v2.local.5K4SCXNhItIhyNuVIZcwrdtaDKiyF81-eWHScuE0idiVqCo72bb" +
+ "jo07W05mqQkhLZdVbxEa5I_u5sgVk1QLkcWEcOSlLHwNpCkvmGGlbCdNExn6" +
+ "Qclw3qTKIIl5-zSLIrxZqOLwcFLYbVK1SrQ.eyJraWQiOiJ6VmhNaVBCUDlm" +
+ "UmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9",
+ }, {
+ desc: "Encrypt with nonce, with footer (2)",
+ msg: []byte(`{"data":"this is a secret message","exp":"2019-01-01T00:00:00+00:00"}`),
+ nonce: "45742c97" + "6d684ff8" + "4ebdc0de" + "59809a97" +
+ "cda2f64c" + "84fda19b",
+ footer: []byte(`{"kid":"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN"}`),
+ exp: "v2.local.pvFdDeNtXxknVPsbBCZF6MGedVhPm40SneExdClOxa9HNR8wFv7" +
+ "cu1cB0B4WxDdT6oUc2toyLR6jA6sc-EUM5ll1EkeY47yYk6q8m1RCpqTIzUr" +
+ "Iu3B6h232h62DnMXKdHn_Smp6L_NfaEnZ-A.eyJraWQiOiJ6VmhNaVBCUDlm" +
+ "UmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9",
+ }}
+
+ for _, c := range cases {
+ nonce, err := hex.DecodeString(c.nonce)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ got, err := encrypt(aead, nonce, c.msg, c.footer)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ test.Assert(t, c.desc, c.exp, got)
+ }
+}
+
+func TestDecrypt(t *testing.T) {
+ hexKey := "70717273" + "74757677" + "78797a7b" + "7c7d7e7f" +
+ "80818283" + "84858687" + "88898a8b" + "8c8d8e8f"
+
+ key, err := hex.DecodeString(hexKey)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ aead, err := chacha20poly1305.NewX(key)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ cases := []struct {
+ desc string
+ token string
+ exp []byte
+ expFooter []byte
+ }{{
+ desc: "Decrypt without nonce and footer",
+ token: "v2.local.97TTOvgwIxNGvV80XKiGZg_kD3tsXM_-qB4dZGHOeN1cTkgQ4Pn" +
+ "W8888l802W8d9AvEGnoNBY3BnqHORy8a5cC8aKpbA0En8XELw2yDk2f1sVOD" +
+ "yfnDbi6rEGMY3pSfCbLWMM2oHJxvlEl2XbQ",
+ exp: []byte(`{"data":"this is a signed message","exp":"2019-01-01T00:00:00+00:00"}`),
+ }, {
+ desc: "Decrypt without nonce and footer (2)",
+ token: "v2.local.CH50H-HM5tzdK4kOmQ8KbIvrzJfjYUGuu5Vy9ARSFHy9owVDMYg" +
+ "3-8rwtJZQjN9ABHb2njzFkvpr5cOYuRyt7CRXnHt42L5yZ7siD-4l-FoNsC7" +
+ "J2OlvLlIwlG06mzQVunrFNb7Z3_CHM0PK5w",
+ exp: []byte(`{"data":"this is a secret message","exp":"2019-01-01T00:00:00+00:00"}`),
+ }, {
+ desc: "Decrypt with nonce, without footer",
+ token: "v2.local.5K4SCXNhItIhyNuVIZcwrdtaDKiyF81-eWHScuE0idiVqCo72bb" +
+ "jo07W05mqQkhLZdVbxEa5I_u5sgVk1QLkcWEcOSlLHwNpCkvmGGlbCdNExn6" +
+ "Qclw3qTKIIl5-O5xRBN076fSDPo5xUCPpBA",
+ exp: []byte(`{"data":"this is a signed message","exp":"2019-01-01T00:00:00+00:00"}`),
+ }, {
+ desc: "Decrypt with nonce, with footer",
+ token: "v2.local.5K4SCXNhItIhyNuVIZcwrdtaDKiyF81-eWHScuE0idiVqCo72bb" +
+ "jo07W05mqQkhLZdVbxEa5I_u5sgVk1QLkcWEcOSlLHwNpCkvmGGlbCdNExn6" +
+ "Qclw3qTKIIl5-zSLIrxZqOLwcFLYbVK1SrQ.eyJraWQiOiJ6VmhNaVBCUDlm" +
+ "UmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9",
+ exp: []byte(`{"data":"this is a signed message","exp":"2019-01-01T00:00:00+00:00"}`),
+ expFooter: []byte(`{"kid":"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN"}`),
+ }, {
+ desc: "Decrypt with nonce, with footer (2)",
+ token: "v2.local.pvFdDeNtXxknVPsbBCZF6MGedVhPm40SneExdClOxa9HNR8wFv7" +
+ "cu1cB0B4WxDdT6oUc2toyLR6jA6sc-EUM5ll1EkeY47yYk6q8m1RCpqTIzUr" +
+ "Iu3B6h232h62DnMXKdHn_Smp6L_NfaEnZ-A.eyJraWQiOiJ6VmhNaVBCUDlm" +
+ "UmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9",
+ exp: []byte(`{"data":"this is a secret message","exp":"2019-01-01T00:00:00+00:00"}`),
+ expFooter: []byte(`{"kid":"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN"}`),
+ }}
+
+ for _, c := range cases {
+ got, gotFooter, err := Decrypt(aead, c.token)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ test.Assert(t, c.desc, c.exp, got)
+ test.Assert(t, c.desc, c.expFooter, gotFooter)
+ }
+}
+
+func TestSign(t *testing.T) {
+ hexPrivate := "b4cbfb43" + "df4ce210" + "727d953e" + "4a713307" +
+ "fa19bb7d" + "9f850414" + "38d9e11b" + "942a3774" +
+ "1eb9dbbb" + "bc047c03" + "fd70604e" + "0071f098" +
+ "7e16b28b" + "757225c1" + "1f00415d" + "0e20b1a2"
+
+ sk, err := hex.DecodeString(hexPrivate)
+ if err != nil {
+ t.Fatal()
+ }
+
+ m := []byte(`{"data":"this is a signed message","exp":"2019-01-01T00:00:00+00:00"}`)
+
+ cases := []struct {
+ desc string
+ exp string
+
+ m []byte
+ f []byte
+ }{{
+ desc: "Sign",
+ m: m,
+ exp: "v2.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIi" +
+ "wiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9HQr8URrGnt" +
+ "Tu7Dz9J2IF23d1M7-9lH9xiqdGyJNvzp4angPW5Esc7C5huy_M8I8_Dj" +
+ "JK2ZXC2SUYuOFM-Q_5Cw",
+ }, {
+ desc: "Sign with footer",
+ m: m,
+ f: []byte(`{"kid":"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN"}`),
+ exp: "v2.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIi" +
+ "wiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9flsZsx_gYC" +
+ "R0N_Ec2QxJFFpvQAs7h9HtKwbVK2n1MJ3Rz-hwe8KUqjnd8FAnIJZ601" +
+ "tp7lGkguU63oGbomhoBw.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q" +
+ "3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9",
+ }}
+
+ for _, c := range cases {
+ got, err := Sign(sk, c.m, c.f)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ test.Assert(t, c.desc, c.exp, got)
+ }
+}
+
+func TestVerify(t *testing.T) {
+ hexPublic := "1eb9dbbb" + "bc047c03" + "fd70604e" + "0071f098" +
+ "7e16b28b" + "757225c1" + "1f00415d" + "0e20b1a2"
+
+ public, err := hex.DecodeString(hexPublic)
+ if err != nil {
+ t.Fatal()
+ }
+
+ cases := []struct {
+ desc string
+ token string
+ exp string
+ }{{
+ desc: "Verify",
+ token: "v2.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIi" +
+ "wiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9HQr8URrGnt" +
+ "Tu7Dz9J2IF23d1M7-9lH9xiqdGyJNvzp4angPW5Esc7C5huy_M8I8_Dj" +
+ "JK2ZXC2SUYuOFM-Q_5Cw",
+ exp: `{"data":"this is a signed message","exp":"2019-01-01T00:00:00+00:00"}`,
+ }, {
+ desc: "Verify with footer",
+ token: "v2.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIi" +
+ "wiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9flsZsx_gYC" +
+ "R0N_Ec2QxJFFpvQAs7h9HtKwbVK2n1MJ3Rz-hwe8KUqjnd8FAnIJZ601" +
+ "tp7lGkguU63oGbomhoBw.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q" +
+ "3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9",
+ exp: `{"data":"this is a signed message","exp":"2019-01-01T00:00:00+00:00"}`,
+ }}
+
+ for _, c := range cases {
+ var footer []byte
+
+ pieces := strings.Split(c.token, ".")
+
+ sm, err := base64.RawURLEncoding.DecodeString(pieces[2])
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(pieces) == 4 {
+ footer, err = base64.RawURLEncoding.DecodeString(pieces[3])
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ got, err := Verify(public, sm, footer)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ test.Assert(t, c.desc, c.exp, string(got))
+ }
+}
diff --git a/lib/paseto/public_mode.go b/lib/paseto/v2/public_mode.go
index b43b56d3..98d7d2da 100644
--- a/lib/paseto/public_mode.go
+++ b/lib/paseto/v2/public_mode.go
@@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: BSD-3-Clause
-package paseto
+package pasetov2
import (
"encoding/base64"
diff --git a/lib/paseto/public_mode_test.go b/lib/paseto/v2/public_mode_test.go
index 8b8e7c00..2967b4bd 100644
--- a/lib/paseto/public_mode_test.go
+++ b/lib/paseto/v2/public_mode_test.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020 Shulhan <ms@kilabit.info>
-package paseto
+package pasetov2
import (
"crypto/ed25519"
diff --git a/lib/paseto/public_token.go b/lib/paseto/v2/public_token.go
index 33f28d15..446eb12b 100644
--- a/lib/paseto/public_token.go
+++ b/lib/paseto/v2/public_token.go
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020 Shulhan <ms@kilabit.info>
-package paseto
+package pasetov2
// PublicToken contains the unpacked public token.
type PublicToken struct {