Documentation
¶
Overview ¶
Package pip implements the Player Identity Protocol (PIP) from PLAYER_IDENTITY.md.
PIP builds decentralized player authentication on top of ODP. A player's identity travels with them as a Redirection Token (RT) — an ODP packet whose action is "redirect", whose subject is the player's UUID and whose data is a MessagePack-encoded PlayerProfile. Bulky profile fields can be carried out of band and vouched for with an ExtensionSignature.
The package covers four concerns:
- Profiles: PlayerProfile and ExtensionSignature with their MessagePack encoding and extension signing/verification.
- Redirection Tokens: Issue, Parse and RedirectionToken.Verify.
- The Cookie transport channel: Chunk and Assemble split a blob across 5 KiB Minecraft cookies with the CA FE / CA AC framing.
- The redirection receipt handshake: Arrived, Committed and SendReceipt over an RTDP session.
Specification ambiguities and interoperability ¶
The Cookie channel description leaves the exact padding and segment length slightly open; this package resolves it as follows, and interoperating implementations must match:
- Every cookie's data section is exactly SegmentSize (5118) bytes, so a cookie is 2-byte magic + 5118-byte payload = 5120 bytes = 5 KiB. This is the reading consistent with both "read the 5118 bytes after the magic" and "pad segments to 5 KiB alignment". The final (or only) segment is zero-padded up to 5118 bytes; this applies to single-cookie tokens too, not just multi-segment ones. See Chunk.
- Reassembly returns the padding as-is. Callers strip it using a context-supplied length (ExtensionSignature.Size, or the size argument to Assemble); when no length is known the payload is self-delimiting (MessagePack ignores trailing zero padding) and capped at MaxAssembledSize.
Redirection Tokens embed the PlayerProfile as the ODP data field, so the profile's MessagePack encoding is what ODP hashes and signs. See the odp package's "MessagePack canonicalization" note for the encoding rules.
Example ¶
Issue a Redirection Token, carry it through the Cookie channel, then assemble, parse and verify it on the destination server.
package main
import (
"bytes"
"fmt"
"time"
"github.com/icebear67/mfp-go"
"github.com/icebear67/mfp-go/pip"
)
func main() {
gateway, _ := mfp.IdentityFromSeed(bytes.Repeat([]byte{7}, mfp.SeedSize))
destination, _ := mfp.IdentityFromSeed(bytes.Repeat([]byte{8}, mfp.SeedSize))
now := time.Unix(1_700_000_000, 0)
token, _ := pip.Issue(gateway, pip.TokenParams{
Target: destination.Public(),
Subject: []byte("player-uuid"),
Profile: &pip.PlayerProfile{Name: "Steve"},
Time: now.Add(-time.Minute),
Until: now.Add(time.Minute),
})
// Source server: encode the token and split it across client cookies.
encoded, _ := token.Marshal()
cookies, _ := pip.Chunk("pip:redirect", encoded)
// Destination server: reassemble the cookies and verify the token.
assembled, _ := pip.Assemble("pip:redirect", -1, pip.FetcherFromMap(cookies))
parsed, _ := pip.Parse(assembled)
err := parsed.Verify(pip.VerifyParams{
Recipient: destination,
Known: mfp.NewKeySet(gateway.Public()),
Now: func() time.Time { return now },
})
fmt.Println("cookies:", len(cookies))
fmt.Println("player:", parsed.Profile.Name)
fmt.Println("verify:", err)
}
Output: cookies: 1 player: Steve verify: <nil>
Index ¶
- Constants
- Variables
- func Arrived(subject []byte, transactionID int32, tokenSignature []byte) *rtdp.PeerPayload
- func Assemble(id string, size int, fetch Fetcher) ([]byte, error)
- func Chunk(id string, data []byte) (map[string][]byte, error)
- func Committed(subject []byte, transactionID int32) *rtdp.PeerPayload
- func NewTransactionID() (int32, error)
- func SegmentName(id string, i int) string
- func SendReceipt(sess *rtdp.Session, subject []byte, tokenSignature []byte) error
- func ValidateCookieID(id string) error
- type ExtensionSignature
- type Fetcher
- type PlayerProfile
- type RedirectionToken
- type TokenParams
- type VerifyParams
Examples ¶
Constants ¶
const ( // SegmentSize is the data payload carried by each cookie (5118 bytes), so // that magic (2) + payload equals 5120 bytes (5 KiB). SegmentSize = 5118 // MaxAssembledSize is the default cap on assembled data when no context // size is known (0.5 MiB). MaxAssembledSize = 524288 )
Cookie channel constants.
const ( // ActionArrive is the action of a PlayerRedirectionArrived payload. ActionArrive = "arrive" // ActionCommit is the action of a PlayerRedirectionCommitted payload. ActionCommit = "commit" )
Redirection-receipt ODP/RTDP actions.
const ( ExtTexture = "pip:texture" ExtCape = "pip:cape" )
Conventional cookie-channel ids for profile fields lifted into extensions, per PLAYER_IDENTITY.md.
const ActionRedirect = "redirect"
ActionRedirect is the fixed ODP action of a Redirection Token.
Variables ¶
var ( // MagicLast (CA FE) marks the final (or only) segment. MagicLast = [2]byte{0xCA, 0xFE} // MagicMore (CA AC) marks a segment followed by more. MagicMore = [2]byte{0xCA, 0xAC} )
Cookie magic numbers.
var ( // ErrCookieID means an id does not match namespace:value. ErrCookieID = errors.New("pip: invalid cookie id") // ErrCookieMissing means a required segment cookie was not supplied. ErrCookieMissing = errors.New("pip: missing cookie segment") // ErrCookieMagic means a segment had an unrecognized magic number. ErrCookieMagic = errors.New("pip: invalid cookie magic") // ErrCookieShort means a segment was shorter than its magic number. ErrCookieShort = errors.New("pip: cookie segment too short") // ErrCookieSize means the assembled data was smaller than the requested size. ErrCookieSize = errors.New("pip: assembled data shorter than expected") )
Cookie channel errors.
var ( // ErrProfileField means a required profile field is missing or malformed. ErrProfileField = errors.New("pip: invalid player profile") // ErrUnknownExtIssuer means an extension's issuer is not a known key. ErrUnknownExtIssuer = errors.New("pip: unknown extension issuer") // ErrExtSize means the extension data length does not match its declared size. ErrExtSize = errors.New("pip: extension size mismatch") // ErrExtSignature means an extension signature did not verify. ErrExtSignature = errors.New("pip: invalid extension signature") )
Profile / extension errors.
var ( // ErrReceiptRejected means the source server acknowledged the arrival with // a non-empty error message. ErrReceiptRejected = errors.New("pip: source rejected redirection arrival") // ErrReceiptUnexpected means an unexpected packet arrived during the receipt // handshake. ErrReceiptUnexpected = errors.New("pip: unexpected packet during receipt") )
Receipt errors.
var ( // ErrNotRedirect means the ODP packet's action is not "redirect". ErrNotRedirect = errors.New("pip: token action is not redirect") // ErrNoRecipient means Verify was called without a recipient identity, so // the mandatory target check could not be performed. ErrNoRecipient = errors.New("pip: recipient identity required to verify a token") )
Token errors.
Functions ¶
func Arrived ¶
func Arrived(subject []byte, transactionID int32, tokenSignature []byte) *rtdp.PeerPayload
Arrived builds a PlayerRedirectionArrived payload: action "arrive", the player's UUID as subject, the given transaction id, and the token signature (the RT's ODP signature) as data.
func Assemble ¶
Assemble reconstructs data written by Chunk. It walks segments via fetch until a MagicLast segment is seen. When size >= 0 the result is trimmed to exactly size bytes (and it is an error to assemble fewer); when size < 0 the full assembled payload — including any trailing zero padding — is returned, capped at MaxAssembledSize.
func Chunk ¶
Chunk splits data into cookie segments keyed by name. Every segment's payload is padded with zeros to SegmentSize so each cookie is exactly 5 KiB; the final segment carries MagicLast and the rest carry MagicMore.
func Committed ¶
func Committed(subject []byte, transactionID int32) *rtdp.PeerPayload
Committed builds a PlayerRedirectionCommitted payload: action "commit", the player's UUID as subject, the matching transaction id, and no data.
func NewTransactionID ¶
NewTransactionID returns a random non-zero transaction id for use with Arrived. A non-zero id obliges the peer to acknowledge.
func SegmentName ¶
SegmentName returns the cookie name for segment i of id: id for i == 0, and "id-i" otherwise.
func SendReceipt ¶
SendReceipt drives the destination-server side of the receipt handshake over an established session with the source server:
- send PlayerRedirectionArrived (action "arrive");
- await the source server's rtdp.PeerAcknowledge for the same id and fail if it carries an error;
- send PlayerRedirectionCommitted (action "commit").
On success the player may be released. The source server does not acknowledge the commit, per PLAYER_IDENTITY.md.
func ValidateCookieID ¶
ValidateCookieID reports whether id matches the required namespace:value form.
Types ¶
type ExtensionSignature ¶
type ExtensionSignature struct {
Issuer mfp.PublicKey // guarantor's ed25519 public key
Sign []byte // ed25519 signature over SHA3-224(D)
Size uint32 // byte length of D
}
ExtensionSignature vouches for an out-of-band extension datum D. The signature is Sign_issuer(SHA3-224(D)).
func SignExtension ¶
func SignExtension(issuer *mfp.Identity, data []byte) ExtensionSignature
SignExtension produces an ExtensionSignature for data.
type Fetcher ¶
Fetcher retrieves a cookie by name, reporting whether it exists. It models the Minecraft Cookie Request round-trip.
type PlayerProfile ¶
type PlayerProfile struct {
Name string // in-game name (not the UUID)
Texture []byte // optional skin data
Cape []byte // optional cape data
Extensions map[string]ExtensionSignature // optional; keyed by cookie-channel id
}
PlayerProfile is the "data" payload of a Redirection Token. Texture, Cape and Extensions are optional; a nil slice or empty map means the field is omitted from the encoding.
func ParsePlayerProfile ¶
func ParsePlayerProfile(data []byte) (*PlayerProfile, error)
ParsePlayerProfile decodes a MessagePack-encoded profile. Trailing bytes after the map (e.g. cookie-channel zero padding) are ignored.
func (*PlayerProfile) Marshal ¶
func (p *PlayerProfile) Marshal() ([]byte, error)
Marshal encodes the profile as MessagePack. Extension keys are sorted so the output is deterministic.
type RedirectionToken ¶
type RedirectionToken struct {
Packet *odp.Packet
Profile *PlayerProfile
}
RedirectionToken is a decoded RT: its ODP envelope plus the decoded profile.
func Issue ¶
func Issue(issuer *mfp.Identity, params TokenParams) (*RedirectionToken, error)
Issue builds and signs a Redirection Token.
func Parse ¶
func Parse(data []byte) (*RedirectionToken, error)
Parse decodes an ODP envelope as a Redirection Token, decoding the embedded profile. It does not verify signatures; call RedirectionToken.Verify.
func (*RedirectionToken) Marshal ¶
func (t *RedirectionToken) Marshal() ([]byte, error)
Marshal encodes the token as an ODP envelope (MessagePack).
func (*RedirectionToken) Signature ¶
func (t *RedirectionToken) Signature() []byte
Signature returns the token's ODP signature, which doubles as its unique id (used, for example, as the payload of a redirection receipt).
func (*RedirectionToken) Verify ¶
func (t *RedirectionToken) Verify(params VerifyParams) error
Verify authenticates the token: it enforces the ODP checks and additionally requires the action to be "redirect" and the target to be the recipient.
type TokenParams ¶
type TokenParams struct {
// Target is the destination server's public key.
Target mfp.PublicKey
// Subject is the player's UUID.
Subject []byte
// Profile is the player's profile data.
Profile *PlayerProfile
// Time and Until bound the token's validity.
Time time.Time
Until time.Time
}
TokenParams describes a Redirection Token to be issued.
type VerifyParams ¶
type VerifyParams struct {
// Recipient is this server's identity. The token's target must equal its
// public key (PIP mandates this check). Required.
Recipient *mfp.Identity
// Known is the set of authorized issuer keys.
Known *mfp.KeySet
// Replay records used token signatures. See [odp.ReplayGuard].
Replay odp.ReplayGuard
// Now overrides the clock (defaults to time.Now).
Now func() time.Time
}
VerifyParams configures RedirectionToken.Verify.