README

Golang implementation of PASETO: Platform-Agnostic Security Tokens

License GoDoc Build Status Coverage Status Go Report Card

This is 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.

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 tries to encode the payload to json.

Use general parser to parse all supported token versions:

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})

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"

v2 := paseto.NewV2()

// Encrypt data
token, err := v2.Encrypt(symmetricKey, jsonToken, paseto.WithFooter(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 := v2.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"

v2 := paseto.NewV2()

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

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

For more information see *_test.go files.

Benchmarks

MacBook Pro (Retina, 15-inch, Late 2013) CPU: 2,3 GHz Intel Core i7 RAM: 16 GB 1600 MHz DDR3 OS: macOS 10.13.3 GO: 1.10

$ go test -bench . -benchmem

Benchmark_V2_JSONToken_Encrypt-8          100000             11729 ns/op            5808 B/op         63 allocs/op
Benchmark_V2_JSONToken_Decrypt-8          100000             11795 ns/op            3104 B/op         61 allocs/op
Benchmark_V2_JSONToken_Sign-8              20000             71034 ns/op            5136 B/op         60 allocs/op
Benchmark_V2_JSONToken_Verify-8            10000            167387 ns/op            2672 B/op         58 allocs/op
Benchmark_V2_String_Encrypt-8             300000              4295 ns/op            2240 B/op         32 allocs/op
Benchmark_V2_String_Decrypt-8            1000000              1854 ns/op            1512 B/op         22 allocs/op
Benchmark_V2_String_Sign-8                 20000             60374 ns/op            1296 B/op         28 allocs/op
Benchmark_V2_String_Verify-8               10000            156859 ns/op             776 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 compatability version) is fully supported.

Documentation

Overview

    Package paseto provides a Go implementation of PASETO, a secure alternative to the JOSE standards (JWT, JWE, JWS). See https://paseto.io/

    Index

    Constants

    View Source
    const (
    	// Version1 defines protocol version 1
    	Version1 = Version("v1")
    	// Version2 defines protocol version 2
    	Version2 = Version("v2")
    )

    Variables

    View Source
    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 GetTokenInfo

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

      GetTokenInfo returns the token version (paseto.Version1 or paseto.Version2) and purpose (paseto.LOCAL or paseto.PUBLIC).

      func ParseFooter

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

        ParseFooter parses the footer from the token and returns it.

        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) string

            Get returns the value of a custom claim, as a string. If there is no such claim, an empty string is returned.

            func (JSONToken) MarshalJSON

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

              MarshalJSON implements json.Marshaler interface

              func (*JSONToken) Set

              func (t *JSONToken) Set(key string, value string)

                Set sets the value of a custom claim to the string value provided.

                func (*JSONToken) UnmarshalJSON

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

                  UnmarshalJSON implements json.Unmarshaler interface

                  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 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

                        Purpose defines the token type by its intended purpose.

                        const (
                        	// LOCAL defines symmetric encrypted token type
                        	LOCAL Purpose = iota
                        	// PUBLIC defines asymmetric signed token type
                        	PUBLIC
                        )

                        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 interface{}, footer interface{}) error

                              Decrypt implements Protocol.Decrypt

                              func (*V1) Encrypt

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

                                Encrypt implements Protocol.Encrypt

                                func (*V1) Sign

                                func (p *V1) Sign(privateKey crypto.PrivateKey, payload interface{}, 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 interface{}, 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 interface{}, footer interface{}) error

                                          Decrypt implements Protocol.Decrypt

                                          func (*V2) Encrypt

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

                                            Encrypt implements Protocol.Encrypt

                                            func (*V2) Sign

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

                                              Sign implements Protocol.Sign

                                              func (*V2) Verify

                                              func (*V2) Verify(token string, publicKey crypto.PublicKey, payload interface{}, 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 string

                                                              Version defines the token version.

                                                              func Parse

                                                              func Parse(token string, payload interface{}, 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.