token

package module
v0.1.5 Latest Latest
Warning

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

Go to latest
Published: Nov 12, 2022 License: MIT Imports: 17 Imported by: 5

README

jdtw.dev/token

Go Go Reference

The token module provides a protobuf-based authorization token and associated tooling. It is designed to be easy to be used securely with no configuration.

Example

// Generate a key for alice and add the public key to a new keyset.
pub, priv, _ := token.GenerateKey("alice")
keyset := token.NewKeyset()
keyset.Add(pub)

Sign a raw token:

// Sign a token...
t, id, _ := priv.Sign(&token.SignOptions{
	Resource: "https://example.com/my/api/endpoint",
	Lifetime: time.Second * 5,
})
log.Printf("Signed token %s\n", id)

// Verify it with the keyset...
subject, id, _ := keyset.Verify(t, &token.VerifyOptions{
	Resource: "https://example.com/my/api/endpoint",
})
log.Printf("Verified token %s, signed by %s\n", id, subject)

Or, if this is for an HTTP request:

// On the client, sign and add to the Authorization header
id, _ := priv.AuthorizeRequest(r, time.Second * 5)
log.Printf("Signed token %s\n", id)

// On the server, verify the token in the Authorization header
nv := nonce.NewMapVerifier(time.Minute) // Prune expired nonces every minute
subject, id, _ := keyset.AuthorizeRequest(r, nv)
log.Printf("Verified token %s, signed by %s\n", id, subject)

Token Format

message Token {
  // Required. The resource that this token is authenticating to.
  string resource = 1;

  // Required. The token lifetime.
  google.protobuf.Timestamp not_before = 2;
  google.protobuf.Timestamp not_after = 3;

  // Required. 16 random bytes that uniquely identify this token.
  bytes nonce = 4;
}

The token is serialized, signed, and placed in a SignedToken message.

message SignedToken {
  // Required. The key identifier. The verifier will attempt to find this key in its keyset
  // and use it to verify the token's signature. This is unauthenticated, but if
  // an attacker substitutes a different key ID, the signature will fail to verify.
  string key_id = 1;

  // Required. Ed25519 signarue: sign(priv, header||token)
  bytes signature = 2;

  // Required. A serialized Token proto.
  bytes token = 3;
}

The SignedToken is serialized and placed in the Authorization header using the custom ProtoEd25519 Scheme.

Token Signing

The Ed25519 signature is over a constant header (jdtw.dev/token/v1) concatenated with the serialized token bytes. Since proto serialization is not canonical, the token is stored in serialized format and deserialized after signature verification.

Token Verification

The server verifies a token by:

  1. Deserializing the SignedToken proto.
  2. Reading the (unauthenticated) key_id from the proto.
  3. Looking up key_id in the VerificationKeyset. If not found, fail.
  4. Verifying the Ed25519 signature with the verification key.
  5. Deserializing the Token proto.
  6. Verifying the token expiry: not_before <= now <= not_after.
  7. Verifying the server resource matches the token resource.
  8. Ensuring that the token's nonce hasn't been seen before. (More on this below.)

Verify returns:

  1. The subject associated with the verification key.
  2. The token's unique ID.

Token Nonces

Sign adds a cryptographically random 16-byte nonce to each token. Verify accepts a nonce.Verifier interface that should check nonce uniqueness. The nonce is returned from both Sign and Verify for use as a unique ID that can be used for logging to match client requests and server requests.

The jdtw.dev/token/nonce package supplies a MapVerifier struct that implements Verifier. It uses an in-memory map of seen nonces (pruning them periodically based on expiry).

Important: MapVerifier is not suitible for use with sharded servers, since nonces will not by synced between them. This will allow nonce reuse across servers. A better strategy would be to use something like Redis to track nonces for sharded servers.

Key Format

message SigningKey {
  // Optional. The ID of this signing key. Unused in the protocol, but good for humans.
  string id = 1;

  // Required. Ed25519 signing key.
  bytes private_key = 3;
}

message VerificationKey {
  // Required. The ID of this verification key.
  string id = 1;

  // Required. The subject of this key. The token library does not care what the
  // format of the subject string is. It can be an email, hostname, SPIFFE ID, etc.
  string subject = 2;

  // Required. Ed25519 public key bytes.
  bytes public_key = 3;
}

message VerificationKeyset {
  // Map of Key ID to verification key.
  map<string, VerificationKey> keys = 1;
}

Key Distribution

This package provides no control plane for key distribution or registration. My personal use cases require tight control over the keys I issue (and there aren't many of them) so I do it manually with the tokenpb tool.

tokenpb tool

The tokenpb tool can be used to generate keys, manage keysets, and sign tokens.

Generate a new key:

tokenpb gen-key --subject "${USER}" --pub pub.pb --priv priv.pb
tokenpb dump-pub pub.pb
echo 'Warning, about to dispaly private key material!'
tokenpb dump-priv priv.pb

Add the key to a new keyset:

tokenpb add-key --pub pub.pb keyset.pb
tokenpb dump-keyset keyset.pb

Sign, verify, and parse a token:

tokenpb sign-token --resource endpoint priv.pb | \
tokenpb verify-token --resource endpoint keyset.pb | \
tokenpb parse-token

Make an HTTP request with a token

token=$(sign-token --resource "GET example.com/api" priv.pb)
curl -v -H "Authorization: ${token}" https://example.com/api

Documentation

Index

Constants

View Source
const Scheme = "ProtoEd25519 "

Scheme is the custom authorization scheme for the Authorization header: Authorization: ProtoEd25519 <base64 encoded signed token>

Variables

View Source
var (
	ErrMissingID      = errors.New("key missing ID")
	ErrInvaidKeyLen   = errors.New("invalid key length")
	ErrMissingSubject = errors.New("key missing subject")
)

Errors returned from unmarshal:

View Source
var (
	ErrUnknownKey = errors.New("unknown key")
	ErrSignature  = errors.New("invalid signature")
	ErrLifetime   = errors.New("invalid lifetime")
	ErrResource   = errors.New("invalid resource")
)

Functions

func GenerateKey

func GenerateKey(subject string) (*VerificationKey, *SigningKey, error)

Generate an Ed25519 keypair for the given subject.

Types

type SignOptions

type SignOptions struct {
	// The resource this token will be used for.
	Resource string
	// The current time. If zero, the current time will be used.
	Now time.Time
	// How long the token should be valid for.
	Lifetime time.Duration
}

type SigningKey

type SigningKey struct {
	// contains filtered or unexported fields
}

func UnmarshalSigningKey

func UnmarshalSigningKey(serialized []byte) (*SigningKey, error)

UnmarshalSigningKey unmarshals a signing key from a binary proto.

func (*SigningKey) AuthorizeRequest

func (s *SigningKey) AuthorizeRequest(r *http.Request, exp time.Duration) (string, error)

AuthorizeRequest signs a token for the given HTTP request and adds it to the Authorization header. Returns the token's unique ID as a hex encoded string.

func (*SigningKey) ID

func (s *SigningKey) ID() string

func (*SigningKey) Marshal

func (k *SigningKey) Marshal() ([]byte, error)

func (*SigningKey) Sign

func (k *SigningKey) Sign(opts *SignOptions) ([]byte, string, error)

Sign a token. Returns the signed token and its unique identifier as a hex encoded string.

func (*SigningKey) String added in v0.1.1

func (s *SigningKey) String() string

type VerificationKey

type VerificationKey struct {
	// contains filtered or unexported fields
}

func UnmarshalVerificationKey

func UnmarshalVerificationKey(serialized []byte) (*VerificationKey, error)

UnmarshalVerificationKey unmarshals a signing key from a binary proto.

func (*VerificationKey) ID

func (v *VerificationKey) ID() string

ID returns the key identifier for this key.

func (*VerificationKey) Marshal

func (k *VerificationKey) Marshal() ([]byte, error)

Marshal the verification key to binary proto.

func (*VerificationKey) String

func (k *VerificationKey) String() string

String returns the JSON-encoded key.

func (*VerificationKey) Subject

func (v *VerificationKey) Subject() string

Subject returns the subject for this key.

type VerificationKeyset

type VerificationKeyset struct {
	// contains filtered or unexported fields
}

VerificationKeyset contains a map of key IDs to verification keys.

func NewKeyset

func NewKeyset() *VerificationKeyset

NewKeyset creates an empty keyset.

func UnmarshalKeyset

func UnmarshalKeyset(serialized []byte) (*VerificationKeyset, error)

UnmarshalKeyset unmarshals a keyset from a binary proto.

func (*VerificationKeyset) Add

Add a verification key to the keyset.

func (*VerificationKeyset) AuthorizeRequest

func (v *VerificationKeyset) AuthorizeRequest(r *http.Request, skew time.Duration, nv nonce.Verifier) (string, string, error)

AuthorizeRequest verifies the token in the Authorization header of the given HTTP request.

func (*VerificationKeyset) Marshal

func (v *VerificationKeyset) Marshal() ([]byte, error)

Marshal the keyset into a binary proto.

func (*VerificationKeyset) Remove

func (v *VerificationKeyset) Remove(id string)

Remove a verification key from the keyset by ID.

func (*VerificationKeyset) String

func (v *VerificationKeyset) String() string

String returns the JSON-encoded keyset.

func (*VerificationKeyset) Verify

func (v *VerificationKeyset) Verify(token []byte, opts *VerifyOptions) (string, string, error)

Verify the given token. Returns the subject that signed the token and the token's unique ID as a hex encoded string.

type VerifyOptions

type VerifyOptions struct {
	// The expected resource.
	Resource string
	// The current time. If zero, the current time will be used.
	Now time.Time
	// The interface with which to verify the token's nonce. If nil,
	// the nonce will not be checked for reuse.
	NonceVerifier nonce.Verifier
	// How much clock skew to allow for.
	Skew time.Duration
}

VerifyOptions contain the options for verifying a signed token.

Directories

Path Synopsis
cmd
proto

Jump to

Keyboard shortcuts

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