README

Golang implementation of PASETO: Platform-Agnostic Security Tokens

License GoDoc Build Status Coverage Status Go Report Card

This is a 100% compatible pure Go (Golang) implementation of PASETO tokens.

PASETO is everything you love about JOSE (JWT, JWE, JWS) without any of the many design deficits that plague the JOSE standards.

Contents

What is PASETO?

PASETO (Platform-Agnostic SEcurity TOkens) is a specification and reference implementation for secure stateless tokens.

Key Differences between PASETO and JWT

Unlike JSON Web Tokens (JWT), which gives developers more than enough rope with which to hang themselves, PASETO only allows secure operations. JWT gives you "algorithm agility", PASETO gives you "versioned protocols". It's incredibly unlikely that you'll be able to use PASETO in an insecure way.

Caution: Neither JWT nor PASETO were designed for stateless session management. PASETO is suitable for tamper-proof cookies, but cannot prevent replay attacks by itself.

PASETO
PASETO Example 1
v2.local.QAxIpVe-ECVNI1z4xQbm_qQYomyT3h8FtV8bxkz8pBJWkT8f7HtlOpbroPDEZUKop_vaglyp76CzYy375cHmKCW8e1CCkV0Lflu4GTDyXMqQdpZMM1E6OaoQW27gaRSvWBrR3IgbFIa0AkuUFw.UGFyYWdvbiBJbml0aWF0aXZlIEVudGVycHJpc2Vz

This decodes to:

  • Version: v2
  • Purpose: local (shared-key authenticated encryption)
  • Payload (hex-encoded):
    400c48a557be10254d235cf8c506e6fea418a26c93de1f05b55f1bc64cfca412
    56913f1fec7b653a96eba0f0c46542a8a7fbda825ca9efa0b3632dfbe5c1e628
    25bc7b5082915d0b7e5bb81930f25cca9076964c33513a39aa105b6ee06914af
    581ad1dc881b1486b4024b9417
    
    • Nonce: 400c48a557be10254d235cf8c506e6fea418a26c93de1f05
    • Authentication tag: 6914af581ad1dc881b1486b4024b9417
  • Decrypted Payload:
    {
      "data": "this is a signed message",
      "exp": "2039-01-01T00:00:00+00:00"
    }
    
    • Key used in this example (hex-encoded):
      707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f  
      
  • Footer:
    Paragon Initiative Enterprises
    
PASETO Example 2
v2.public.eyJleHAiOiIyMDM5LTAxLTAxVDAwOjAwOjAwKzAwOjAwIiwiZGF0YSI6InRoaXMgaXMgYSBzaWduZWQgbWVzc2FnZSJ91gC7-jCWsN3mv4uJaZxZp0btLJgcyVwL-svJD7f4IHyGteKe3HTLjHYTGHI1MtCqJ-ESDLNoE7otkIzamFskCA

This decodes to:

  • Version: v2
  • Purpose: public (public-key digital signature)
  • Payload:
    {
      "data": "this is a signed message",
      "exp": "2039-01-01T00:00:00+00:00"
    }
    
  • Signature (hex-encoded):
    d600bbfa3096b0dde6bf8b89699c59a746ed2c981cc95c0bfacbc90fb7f8207c
    86b5e29edc74cb8c761318723532d0aa27e1120cb36813ba2d908cda985b2408
    
  • Public key (hex-encoded):
    11324397f535562178d53ff538e49d5a162242970556b4edd950c87c7d86648a
    

To learn what each version means, please see this page in the documentation.

JWT

An example JWT (taken from JWT.io) might look like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ 

This decodes to:

Header:

{
  "alg": "HS256",
  "typ": "JWT"
}

Body:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

Signature:

TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

Motivation

As you can see, with JWT, you get to specify an alg header. There are a lot of options to choose from (including none).

There have been ways to exploit JWT libraries by replacing RS256 with HS256 and using the known public key as the HMAC-SHA256 key, thereby allowing arbitrary token forgery.

With PASETO, your options are version and a purpose. There are two possible values for purpose:

  • local -- shared-key encryption (symmetric-key, AEAD)
  • public -- public-key digital signatures (asymmetric-key)

PASETO only allows you to use authenticated modes.

Regardless of the purpose selected, the header (and an optional footer, which is always cleartext but base64url-encoded) is included in the signature or authentication tag.

Installation

To install the library use the following command:

$ go get -u github.com/o1egl/paseto

Usage

This library contains a predefined JsonToken struct for using as payload, but you are free to use any data types and structs you want.

During the encoding process, a payload of type string and []byte is used without transformation. For other data types, the library encodes the payload to JSON.

Create token using symmetric key (local mode):

symmetricKey := []byte("YELLOW SUBMARINE, BLACK WIZARDRY") // Must be 32 bytes
now := time.Now()
exp := now.Add(24 * time.Hour)
nbt := now

jsonToken := paseto.JSONToken{
        Audience:   "test",
        Issuer:     "test_service",
        Jti:        "123",
        Subject:    "test_subject",
        IssuedAt:   now,
        Expiration: exp,
        NotBefore:  nbt,
        }
// Add custom claim    to the token    
jsonToken.Set("data", "this is a signed message")
footer := "some footer"

// Encrypt data
token, err := paseto.Encrypt(symmetricKey, jsonToken, footer)
// token = "v2.local.E42A2iMY9SaZVzt-WkCi45_aebky4vbSUJsfG45OcanamwXwieieMjSjUkgsyZzlbYt82miN1xD-X0zEIhLK_RhWUPLZc9nC0shmkkkHS5Exj2zTpdNWhrC5KJRyUrI0cupc5qrctuREFLAvdCgwZBjh1QSgBX74V631fzl1IErGBgnt2LV1aij5W3hw9cXv4gtm_jSwsfee9HZcCE0sgUgAvklJCDO__8v_fTY7i_Regp5ZPa7h0X0m3yf0n4OXY9PRplunUpD9uEsXJ_MTF5gSFR3qE29eCHbJtRt0FFl81x-GCsQ9H9701TzEjGehCC6Bhw.c29tZSBmb290ZXI"

// Decrypt data
var newJsonToken paseto.JSONToken
var newFooter string
err := paseto.Decrypt(token, symmetricKey, &newJsonToken, &newFooter)

Create token using asymetric key (public mode):

b, _ := hex.DecodeString("b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")
privateKey := ed25519.PrivateKey(b)

b, _ = hex.DecodeString("1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")
publicKey := ed25519.PublicKey(b)

// or create a new keypair 
// publicKey, privateKey, err := ed25519.GenerateKey(nil)

jsonToken := paseto.JSONToken{
        Expiration: time.Now().Add(24 * time.Hour),
        }
        
// Add custom claim    to the token    
jsonToken.Set("data", "this is a signed message")
footer := "some footer"

// Sign data
token, err := paseto.Sign(privateKey, jsonToken, footer)
// token = "v2.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAxOC0wMy0xMlQxOTowODo1NCswMTowMCJ9Ojv0uXlUNXSFhR88KXb568LheLRdeGy2oILR3uyOM_-b7r7i_fX8aljFYUiF-MRr5IRHMBcWPtM0fmn9SOd6Aw.c29tZSBmb290ZXI"

// Verify data
var newJsonToken paseto.JSONToken
var newFooter string
err := paseto.Verify(token, publicKey, &newJsonToken, &newFooter)

Use Parse() function to parse all supported token versions:

IMPORTANT: Version 1 of the protocol is deprecated

b, err := hex.DecodeString("2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0d0a4d494942496a414e42676b71686b6947397730424151454641414f43415138414d49494243674b43415145417878636e47724e4f6136426c41523458707050640d0a746146576946386f7279746c4b534d6a66446831314c687956627a4335416967556b706a457274394d7649482f46384d444a72324f39486b36594b454b574b6f0d0a72333566364b6853303679357a714f722b7a4e34312b39626a52365633322b527345776d5a737a3038375258764e41334e687242633264593647736e57336c5a0d0a34356f5341564a755639553667335a334a574138355972362b6350776134793755632f56726f6d7a674679627355656e33476f724254626a783142384f514a440d0a73652f4b6b6855433655693358384264514f473974523455454775742f6c39703970732b3661474d4c57694357495a54615456784d4f75653133596b777038740d0a3148467635747a6872493055635948687638464a6b315a6435386759464158634e797975737834346e6a6152594b595948646e6b4f6a486e33416b534c4d306b0d0a6c774944415141420d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d")
block, _ := pem.Decode(b)
rsaPubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
v1PublicKey := rsaPubInterface.(*rsa.PublicKey)

b, _ = hex.DecodeString("1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")
v2PublicKey := ed25519.PublicKey(b)


var payload JSONToken
var footer string
version, err := paseto.Parse(token, &payload, &footer, symmetricKey, map[paseto.Version]crypto.PublicKey{paseto.V1: v1PublicKey, paseto.V2: v2PublicKey})

For more information see *_test.go files.

Benchmarks

MacBook Pro (15-inch, 2018) CPU: 2,6 GHz Intel Core i7 RAM: 32 GB 2400 MHz DDR4 OS: macOS 10.14.6 GO: 1.13.7

$ go test -bench . -benchmem

Benchmark_V2_JSONToken_Encrypt-12         137578              8532 ns/op            4186 B/op         59 allocs/op
Benchmark_V2_JSONToken_Decrypt-12         139309              7970 ns/op            2048 B/op         63 allocs/op
Benchmark_V2_JSONToken_Sign-12             21598             55817 ns/op            4426 B/op         60 allocs/op
Benchmark_V2_JSONToken_Verify-12            8772            132142 ns/op            2528 B/op         64 allocs/op
Benchmark_V2_String_Encrypt-12            544958              2051 ns/op            1176 B/op         23 allocs/op
Benchmark_V2_String_Decrypt-12           1000000              1054 ns/op             568 B/op         18 allocs/op
Benchmark_V2_String_Sign-12                25144             47645 ns/op            1144 B/op         23 allocs/op
Benchmark_V2_String_Verify-12               9408            125524 ns/op             744 B/op         18 allocs/op

Supported PASETO Versions

Version 2

Version 2 (the recommended version by the specification) is fully supported.

Version 1

Version 1 (the compatibility version) is fully supported.

Expand ▾ Collapse ▴

Documentation

Overview

go:generate go-enum --file $GOFILE Package paseto provides a Go implementation of PASETO, a secure alternative to the JOSE standards (JWT, JWE, JWS). See https://paseto.io/

Index

Constants

const (

	// XNonceSize is the size of the XChaCha20 nonce in bytes.
	XNonceSize = 24
)

Variables

var (
	// ErrTypeCast type cast error
	ErrTypeCast = errors.New("type cast error")
	// ErrClaimNotFound claim not found error
	ErrClaimNotFound = errors.New("claim not found")
)

var (
	// ErrUnsupportedTokenVersion unsupported parser version
	ErrUnsupportedTokenVersion = errors.New("unsupported parser version")
	// ErrUnsupportedTokenType unsupported token type
	ErrUnsupportedTokenType = errors.New("unsupported token type")
	// ErrIncorrectPrivateKeyType incorrect private key type
	ErrIncorrectPrivateKeyType = errors.New("incorrect private key type")
	// ErrIncorrectPublicKeyType incorrect public key type
	ErrIncorrectPublicKeyType = errors.New("incorrect public key type")
	// ErrPublicKeyNotFound public key for this version not found
	ErrPublicKeyNotFound = errors.New("public key for this version not found")
	// ErrIncorrectTokenFormat incorrect token format
	ErrIncorrectTokenFormat = errors.New("incorrect token format")
	// ErrIncorrectTokenHeader incorrect token header
	ErrIncorrectTokenHeader = errors.New("incorrect token header")
	// ErrInvalidTokenAuth invalid token authentication
	ErrInvalidTokenAuth = errors.New("invalid token authentication")
	// ErrInvalidSignature invalid signature
	ErrInvalidSignature = errors.New("invalid signature")
	// ErrDataUnmarshal can't unmarshal token data to the given type of value
	ErrDataUnmarshal = errors.New("can't unmarshal token data to the given type of value")
	// ErrTokenValidationError invalid token data
	ErrTokenValidationError = errors.New("token validation error")
)

Functions

func Decrypt

func Decrypt(token string, key []byte, payload, footer interface{}) error

Decrypt decrypts a token. Uses V2 protocol as default.

func Encrypt

func Encrypt(key []byte, payload, footer interface{}) (string, error)

Encrypt encrypts a token with a symmetric key. The key length must be 32. Uses V2 protocol as default

func GetTokenInfo

func GetTokenInfo(token string) (Version, Purpose, error)

GetTokenInfo returns the token version (paseto.VersionV1 or paseto.VersionV2) and purpose (paseto.PurposeLocal or paseto.PurposePublic).

func ParseFooter

func ParseFooter(token string, footer interface{}) error

ParseFooter parses the footer from the token and returns it.

func Sign

func Sign(privateKey crypto.PrivateKey, payload, footer interface{}) (string, error)

Sign signs a token with the given private key. The key should be an ed25519.PrivateKey. Uses V2 protocol as default.

func Verify

func Verify(token string, publicKey crypto.PublicKey, value, footer interface{}) error

Verify verifies a token against the given public key. The key should be an ed25519.PublicKey. Uses V2 protocol as default.

Types

type JSONToken

type JSONToken struct {
	// Audience identifies the intended recipients of the token.
	// It should be a string or a URI and is case sensitive.
	Audience string
	// Issuer identifies the entity which issued the token.
	// It should be a string or a URI and is case sensitive.
	Issuer string
	// JTI is a globally unique identifier for the token. It must be created in
	// such a way as to ensure that there is negligible probability that the same
	// value will be used in another token.
	Jti string
	// Subject identifies the principal entity that is the subject of the token.
	// For example, for an authentication token, the subject might be the user ID
	// of a person.
	Subject string
	// Expiration is a time on or after which the token must not be accepted for processing.
	Expiration time.Time
	// IssuedAt is the time at which the token was issued.
	IssuedAt time.Time
	// NotBefore is a time on or before which the token must not be accepted for
	// processing.
	NotBefore time.Time
	// contains filtered or unexported fields
}

JSONToken defines standard token payload claims and allows for additional claims to be added. All of the standard claims are optional.

func (*JSONToken) Get

func (t *JSONToken) Get(key string, v interface{}) error

Get the value of the claim and uses reflection to store it in the value pointed to by v. If the claim doesn't exist an ErrClaimNotFound error is returned

func (JSONToken) MarshalJSON

func (t JSONToken) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler interface nolint:gocritic

func (*JSONToken) Set

func (t *JSONToken) Set(key string, value interface{})

Set sets the value of a custom claim

func (*JSONToken) UnmarshalJSON

func (t *JSONToken) UnmarshalJSON(data []byte) error

UnmarshalJSON implements json.Unmarshaler interface nolint:gocyclo

func (*JSONToken) Validate

func (t *JSONToken) Validate(validators ...Validator) error

Validate validates a token with the given validators. If no validators are specified, then by default it validates the token with ValidAt(time.Now()), which checks IssuedAt, NotBefore and Expiration fields against the current time.

type MapUnmarshaler

type MapUnmarshaler interface {
	// UnmarshalMap receives `v` from DecodeHookFunc.
	// Returned value is used by mapstructure for further processing.
	UnmarshalMap(interface{}) (interface{}, error)
}

MapUnmarshaler is the interface used in mapstructure decoder hook.

type Protocol

type Protocol interface {

	// Encrypt encrypts a token with a symmetric key. The key should be a byte
	// slice of 32 bytes, regardless of whether PASETO v1 or v2 is being used.
	Encrypt(key []byte, payload interface{}, footer interface{}) (string, error)

	// Decrypt decrypts a token which was encrypted with a symmetric key.
	Decrypt(token string, key []byte, payload interface{}, footer interface{}) error

	// Sign signs a token with the given private key. For PASETO v1, the key should
	// be an rsa.PrivateKey. For v2, the key should be an ed25519.PrivateKey.
	Sign(privateKey crypto.PrivateKey, payload interface{}, footer interface{}) (string, error)

	// Verify verifies a token against the given public key. For PASETO v1, the key
	// key should be an rsa.PublicKey. For v2, the key should be an
	// ed25519.PublicKey.
	Verify(token string, publicKey crypto.PublicKey, value interface{}, footer interface{}) error
}

Protocol defines the PASETO token protocol interface.

type Purpose

type Purpose int

ENUM( local public )

Purpose defines the token type by its intended purpose.

const (
	// PurposeLocal is a Purpose of type Local
	PurposeLocal Purpose = iota
	// PurposePublic is a Purpose of type Public
	PurposePublic
)

func ParsePurpose

func ParsePurpose(name string) (Purpose, error)

ParsePurpose attempts to convert a string to a Purpose

func (Purpose) String

func (x Purpose) String() string

String implements the Stringer interface.

type V1

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

V1 is a v1 implementation of PASETO tokens

func NewV1

func NewV1() *V1

NewV1 returns a v1 implementation of PASETO tokens. You should not use PASETO v1 unless you need interoperability with for legacy systems that cannot use modern cryptography.

func (*V1) Decrypt

func (p *V1) Decrypt(token string, key []byte, payload, footer interface{}) error

Decrypt implements Protocol.Decrypt

func (*V1) Encrypt

func (p *V1) Encrypt(key []byte, payload, footer interface{}) (string, error)

Encrypt implements Protocol.Encrypt

func (*V1) Sign

func (p *V1) Sign(privateKey crypto.PrivateKey, payload, footer interface{}) (string, error)

Sign implements Protocol.Sign. privateKey should be of type *rsa.PrivateKey

func (*V1) Verify

func (p *V1) Verify(token string, publicKey crypto.PublicKey, payload, footer interface{}) error

Verify implements Protocol.Verify. publicKey should be of type *rsa.PublicKey

type V2

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

V2 is a v2 implementation of PASETO tokens

func NewV2

func NewV2() *V2

NewV2 returns a v2 implementation of PASETO tokens.

func (*V2) Decrypt

func (*V2) Decrypt(token string, key []byte, payload, footer interface{}) error

Decrypt implements Protocol.Decrypt

func (*V2) Encrypt

func (p *V2) Encrypt(key []byte, payload, footer interface{}) (string, error)

Encrypt implements Protocol.Encrypt

func (*V2) Sign

func (*V2) Sign(privateKey crypto.PrivateKey, payload, footer interface{}) (string, error)

Sign implements Protocol.Sign

func (*V2) Verify

func (*V2) Verify(token string, publicKey crypto.PublicKey, payload, footer interface{}) error

Verify implements Protocol.Verify

type Validator

type Validator func(token *JSONToken) error

Validator defines a JSONToken validator function.

func ForAudience

func ForAudience(audience string) Validator

ForAudience validates that the JSONToken audience has the specified value.

func IdentifiedBy

func IdentifiedBy(jti string) Validator

IdentifiedBy validates that the JSONToken JTI has the specified value.

func IssuedBy

func IssuedBy(issuer string) Validator

IssuedBy validates that the JSONToken issuer has the specified value.

func Subject

func Subject(subject string) Validator

Subject validates that the JSONToken subject has the specified value.

func ValidAt

func ValidAt(t time.Time) Validator

ValidAt validates whether the token is valid at the specified time, based on the values of the IssuedAt, NotBefore and Expiration claims in the token.

type Version

type Version int

Version defines the token version.

ENUM( v1 v2 )

const (
	// VersionV1 is a Version of type V1
	VersionV1 Version = iota
	// VersionV2 is a Version of type V2
	VersionV2
)

func Parse

func Parse(token string, payload, footer interface{},
	symmetricKey []byte, publicKeys map[Version]crypto.PublicKey) (Version, error)

Parse extracts the payload and footer from the token by calling either Decrypt() or Verify(), depending on whether the token is public or private. To parse public tokens you need to provide a map containing V1 and/or V2 public keys, depending on the version of the token. To parse private tokens you need to provide the symmetric key.

func ParseVersion

func ParseVersion(name string) (Version, error)

ParseVersion attempts to convert a string to a Version

func (Version) String

func (x Version) String() string

String implements the Stringer interface.