diff options
| author | Shulhan <ms@kilabit.info> | 2026-04-01 06:22:48 +0700 |
|---|---|---|
| committer | Shulhan <ms@kilabit.info> | 2026-04-01 22:12:13 +0700 |
| commit | f18f08e554ed9f93f31641d974448923099ac892 (patch) | |
| tree | c073aeb7553c46ac5c0c73991ffd08589c1d4193 | |
| parent | 5a1b6fd3753653b74577c7992b341a011a0ec416 (diff) | |
| download | pakakeh.go-f18f08e554ed9f93f31641d974448923099ac892.tar.xz | |
paseto/v4: allow empty Footer and empty data in Payload
If the footer is empty then the message will be unpacked with its own
public key, instead of sender public key.
| -rw-r--r-- | lib/paseto/footer.go | 2 | ||||
| -rw-r--r-- | lib/paseto/message_test.go | 1 | ||||
| -rw-r--r-- | lib/paseto/payload.go | 4 | ||||
| -rw-r--r-- | lib/paseto/v4/public_mode.go | 23 | ||||
| -rw-r--r-- | lib/paseto/v4/public_mode_test.go | 66 |
5 files changed, 85 insertions, 11 deletions
diff --git a/lib/paseto/footer.go b/lib/paseto/footer.go index acaa3714..ae4fff09 100644 --- a/lib/paseto/footer.go +++ b/lib/paseto/footer.go @@ -8,5 +8,5 @@ package paseto // MUST NOT include sensitive information. type Footer struct { Data any `json:"data,omitempty"` - PeerID string `json:"peer_id"` + PeerID string `json:"peer_id,omitempty"` } diff --git a/lib/paseto/message_test.go b/lib/paseto/message_test.go index aa4f7a23..a3d21420 100644 --- a/lib/paseto/message_test.go +++ b/lib/paseto/message_test.go @@ -31,7 +31,6 @@ func TestNewMessage(t *testing.T) { "peer_id": "sender" }, "Payload": { - "data": null, "iss": "sender", "sub": "test", "aud": "receiver", diff --git a/lib/paseto/payload.go b/lib/paseto/payload.go index af889d77..346356f7 100644 --- a/lib/paseto/payload.go +++ b/lib/paseto/payload.go @@ -29,9 +29,9 @@ var ( // The claims follow RFC 7519 that includes issuer, subject, audience, // expiration time, not-before time, issued-at, and ID. type Payload struct { - // Data defines actual information to be send in message. + // Data defines additional information to be send in message. // Data must be JSON encodable. - Data any `json:"data"` + Data any `json:"data,omitempty"` // Issuer defines the peer ID that issued the payload. Issuer string `json:"iss,omitempty"` diff --git a/lib/paseto/v4/public_mode.go b/lib/paseto/v4/public_mode.go index e4b281d7..7e54d505 100644 --- a/lib/paseto/v4/public_mode.go +++ b/lib/paseto/v4/public_mode.go @@ -95,6 +95,9 @@ func (pmode *PublicMode) Pack(msg paseto.Message, implicit []byte) ( if err != nil { return ``, fmt.Errorf(`%s: %w`, logp, err) } + if bytes.Equal(msg.RawFooter, []byte(`{}`)) { + msg.RawFooter = nil + } token, err = pmode.Sign(msg.RawPayload, msg.RawFooter, implicit) if err != nil { return ``, fmt.Errorf(`%s: %w`, logp, err) @@ -166,14 +169,20 @@ func (pmode *PublicMode) Unpack(token string, implicit []byte, msg *paseto.Messa return fmt.Errorf(`%s: %w`, logp, err) } - err = json.Unmarshal(msg.RawFooter, &msg.Footer) - if err != nil { - return fmt.Errorf(`%w: %w`, paseto.ErrTokenFooter, err) - } + var sender paseto.Peer + if msg.RawFooter != nil { + err = json.Unmarshal(msg.RawFooter, &msg.Footer) + if err != nil { + return fmt.Errorf(`%w: %w`, paseto.ErrTokenFooter, err) + } - sender, ok := pmode.GetPeer(msg.Footer.PeerID) - if !ok { - return fmt.Errorf(`%s: %w %s`, logp, ErrPeerID, msg.Footer.PeerID) + var ok bool + sender, ok = pmode.GetPeer(msg.Footer.PeerID) + if !ok { + return fmt.Errorf(`%s: %w %s`, logp, ErrPeerID, msg.Footer.PeerID) + } + } else { + sender = pmode.Peer } if !ed25519.Verify(sender.PublicKey, msg.PAE, msg.Sig) { diff --git a/lib/paseto/v4/public_mode_test.go b/lib/paseto/v4/public_mode_test.go index 658facb8..3e060e8a 100644 --- a/lib/paseto/v4/public_mode_test.go +++ b/lib/paseto/v4/public_mode_test.go @@ -9,7 +9,10 @@ import ( "log" "os" "testing" + "testing/cryptotest" + "testing/synctest" + "git.sr.ht/~shulhan/pakakeh.go/lib/paseto" "git.sr.ht/~shulhan/pakakeh.go/lib/test" ) @@ -36,6 +39,69 @@ func (tvp *testVectorPublic) init() { } } +func TestPublicMode_Pack(t *testing.T) { + cryptotest.SetGlobalRandom(t, 1) + + listcase := []struct { + msg paseto.Message + desc string + expToken string + expRawPayload string + expRawFooter string + }{{ + desc: `Empty`, + expToken: `v4.public.e316aAoBcV59pCXGKmxwuKl050jOA9gP9F5qCFWllfEHUzzEWqRtZDK29vC07QoCJZEd-6T_65tybe4uJRuErwwE`, + expRawPayload: `{}`, + }, { + desc: `FooterOnly`, + msg: paseto.Message{ + Footer: paseto.Footer{ + Data: `footer.data`, + }, + }, + expToken: `v4.public.e32fZEGqzzkbqRiMbrlQnsacHTa1RjMBuE5cgciyHTEQiUIwh67S106_rGBROlkJKu71axKQURDKqjLMS1E6liQF.eyJkYXRhIjoiZm9vdGVyLmRhdGEifQ`, + expRawPayload: `{}`, + expRawFooter: `{"data":"footer.data"}`, + }, { + desc: `PayloadAndFooter`, + msg: paseto.Message{ + Payload: paseto.Payload{ + Data: []byte{0x73, 0x68, 0x75, 0x6c}, + }, + Footer: paseto.Footer{ + Data: `footer.data`, + }, + }, + expToken: `v4.public.eyJkYXRhIjoiYzJoMWJBPT0ifXIYsFygQyakjTQ2j500FIxcPKrE3B3G1Sid7NAvvZ9_yqAZgIhhMu4ojqiewIPw81G-ZvX7GO321Ofgvv8_Zgw.eyJkYXRhIjoiZm9vdGVyLmRhdGEifQ`, + expRawPayload: `{"data":"c2h1bA=="}`, + expRawFooter: `{"data":"footer.data"}`, + }} + + seed, err := hex.DecodeString(`b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a3774`) + if err != nil { + t.Fatal(err) + } + pmode := NewPublicMode(``, [32]byte(seed)) + + synctest.Test(t, func(t *testing.T) { + for _, tc := range listcase { + gotToken, err := pmode.Pack(tc.msg, nil) + if err != nil { + t.Fatalf(`%s: %s`, tc.desc, err) + } + test.Assert(t, tc.desc, tc.expToken, gotToken) + + msg := paseto.Message{} + err = UnpackPublicToken(&msg, gotToken, nil) + if err != nil { + t.Fatalf(`%s: %s`, tc.desc, err) + } + test.Assert(t, `RawPayload`, tc.expRawPayload, string(msg.RawPayload)) + test.Assert(t, `RawFooter`, tc.expRawFooter, string(msg.RawFooter)) + } + }) +} + func TestPublicMode_Sign(t *testing.T) { logp := `TestPublicMode_Sign` |
