badgeverify

package
v0.5.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 3, 2026 License: AGPL-3.0 Imports: 7 Imported by: 0

Documentation

Overview

Package badgeverify defines the canonical wire format for a Pilot "verified address" badge and an offline verifier for it.

A badge is a detached Ed25519-signed credential, bound to a single node_id, that asserts the address was verified through an external identity provider (GitHub / Google / WorkOS). It deliberately carries NO raw external identity (no GitHub login, no email) — only which provider vouched and when. An app certifies a badge is genuine by verifying the issuer signature against a pinned public key, entirely offline: no network round-trip and no trust in the registry that served the badge.

THE BINDING RULE (read this before using Verify):

A badge is public — anyone can fetch any node's badge. A badge is only meaningful when checked against the node_id that the secure/handshake layer has *cryptographically authenticated* for the connection. Always confirm Badge.NodeID equals the authenticated peer's node_id, or use VerifyForNode which does it for you. Verifying the signature alone lets an attacker replay another node's valid badge.

Index

Constants

View Source
const Version = "v1"

Version is the canonical-format version tag carried in every badge. A future incompatible format change bumps this and adds a parser branch.

Variables

View Source
var (
	// ErrNoKey is returned when no pinned key matches the badge's kid (or
	// the keyring is malformed). Fail-closed: with no trust anchor,
	// nothing verifies.
	ErrNoKey = errors.New("badgeverify: no pinned issuer key for badge kid")
	// ErrBadSignature is returned when the signature does not verify.
	ErrBadSignature = errors.New("badgeverify: badge signature verification failed")
	// ErrMalformed is returned when the badge string is not well-formed.
	ErrMalformed = errors.New("badgeverify: malformed badge")
	// ErrExpired is returned when the badge's exp is in the past.
	ErrExpired = errors.New("badgeverify: badge expired")
	// ErrNodeMismatch is returned by VerifyForNode when the badge is for
	// a different node than the authenticated peer.
	ErrNodeMismatch = errors.New("badgeverify: badge node_id does not match peer")
)

Functions

func Canonical

func Canonical(b Badge) (string, error)

Canonical returns the exact byte string that is signed and that travels on the wire as the "badge" field. The issuer signs these bytes; the verifier checks the signature over the bytes it received and only then trusts the parsed fields.

Layout: pilotbadge:v1:<node_id>:<provider>:<verified_at>:<exp>:<kid>:<subject>

func CanonicalEnrollment

func CanonicalEnrollment(e Enrollment) (string, error)

CanonicalEnrollment builds the signed bytes for an enrollment. Layout: pilotenroll:v1:<node_id>:<provider>:<commitment>:<issued_at>:<kid>

func CanonicalRecovery

func CanonicalRecovery(r Recovery) (string, error)

CanonicalRecovery builds the signed bytes for a recovery authorization. Layout: pilotrecover:v1:<node_id>:<new_pubkey>:<commitment>:<exp>:<nonce>:<kid>

Exp must be non-zero: a recovery authorization that never expires would be a permanent, replayable takeover token.

Types

type Badge

type Badge struct {
	NodeID     uint32 // the address this badge is bound to
	Provider   string // identity authority that vouched: "github" | "google" | "workos"
	VerifiedAt int64  // unix seconds, coarsened to day granularity at issue time
	Exp        int64  // unix seconds; 0 means no expiry
	Kid        string // issuer key-id that signed this badge (selects the verify key)
	Subject    string // OPTIONAL public label (Tier 1); empty for Tier 0 (provider-only)
}

Badge is the decoded, structured form of a verified-address credential.

func Parse

func Parse(s string) (Badge, error)

Parse decodes a canonical badge string WITHOUT verifying its signature. Use Verify/VerifyForNode for anything trust-bearing.

func Verify

func Verify(badgeStr, sigB64 string) (Badge, error)

Verify parses badgeStr, then checks that sigB64 is a valid Ed25519 signature over the EXACT received bytes, made with the pinned issuer key named by the badge's kid, and that the badge has not expired.

It does NOT check the binding to a node — see the package doc and VerifyForNode. Callers that already hold the authenticated peer node_id should prefer VerifyForNode.

func VerifyForNode

func VerifyForNode(badgeStr, sigB64 string, peerNodeID uint32) (Badge, error)

VerifyForNode is Verify plus the binding rule: it additionally requires the badge to be bound to peerNodeID (the node_id the secure/handshake layer authenticated for this connection). This is the safe entry point for apps.

type Enrollment

type Enrollment struct {
	NodeID     uint32
	Provider   string
	Commitment string // base64/hex HMAC; never the raw identity
	IssuedAt   int64
	Kid        string
}

Enrollment binds an address to a salted identity commitment. The raw external identity is never present; Commitment is HMAC(verifier_salt, external_id), opaque to everyone but the verifier.

func ParseEnrollment

func ParseEnrollment(s string) (Enrollment, error)

ParseEnrollment decodes an enrollment string WITHOUT verifying its signature. It rejects any non-enrollment prefix (domain separation).

func VerifyEnrollment

func VerifyEnrollment(s, sigB64 string) (Enrollment, error)

VerifyEnrollment checks the signature of an enrollment against the pinned key named by its kid.

type Recovery

type Recovery struct {
	NodeID     uint32
	NewPubKey  string // base64 Ed25519 public key the address rotates to
	Commitment string
	Exp        int64  // unix seconds; MUST be non-zero and is enforced
	Nonce      string // single-use; the registry rejects replays
	Kid        string
}

Recovery authorizes rotating NodeID to NewPubKey because the enrolled identity (Commitment) was re-proven. NewPubKey is bound into the signed bytes, so an intercepted authorization is useless to anyone who does not hold NewPubKey's private key.

func ParseRecovery

func ParseRecovery(s string) (Recovery, error)

ParseRecovery decodes a recovery string WITHOUT verifying its signature. It rejects any non-recovery prefix (domain separation).

func VerifyRecovery

func VerifyRecovery(s, sigB64 string) (Recovery, error)

VerifyRecovery checks a recovery authorization: signature against the pinned (cold recovery-authority) key named by its kid, AND that it has not expired. The caller (registry) MUST additionally enforce single-use of Nonce and that Commitment matches the address's enrolled commitment.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL