Giter Site home page Giter Site logo

o1egl / paseto Goto Github PK

View Code? Open in Web Editor NEW
807.0 23.0 36.0 94 KB

Platform-Agnostic Security Tokens implementation in GO (Golang)

License: MIT License

Go 95.94% Makefile 4.06%
go golang token past auth authentication security microservice soa paseto jwt decoder encoder

paseto's Introduction

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.

paseto's People

Contributors

asauray avatar codermarcel avatar lpar avatar o1egl avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

paseto's Issues

Improve errors

  • Add more error constants
  • Wrap errors with detailed messages using github.com/pkg/errors

JSONToken MarshalJSON issues

Creating a JSONToken, setting the Audience, then marshaling it, then setting the Audience back to empty, will result in a token that still marshals with an Audience.

Assymetric woes

Symmetric/local keys work great, however...

        v2 := paseto.NewV2()
	_, privateKey, err := ed25519.GenerateKey(nil)

	jsonToken := paseto.JSONToken{
		Expiration: time.Now().Add(24 * time.Hour),
		Subject:    "Foo",
		Audience:   "Bar",
		Issuer:     "This guy",
		IssuedAt:   time.Now(),
	}

	// Add custom claim    to the token
	jsonToken.Set("data", "this is a signed message")
	footer := "some footer"

	// Sign data
	token, err := v2.Sign(privateKey, &jsonToken, &footer)

Gives a err of incorrect private key type. What am I doing wrong here?

Can't extend JSONToken

I want to add my own claim to the JSONToken. I tried to use

type MyToken struct {
	paseto.JSONToken
	Groups []string `json:"groups"`
}

but it doesn't work (field does not appear in my token). There is JSONToken.Set, but it only works with strings.

SECURITY: Examples, default usage, `JSONToken` fail to check `Expiration`, `NotBefore` by default.

Given that PASETO is designed for "Resistance to Implementation Error / Misuse", I'm surprised the examples don't cover calling JSONToken.Validate, nor does JSONToken.UnmarshalJSON do this on it's own.

The documentation does indicate that the standard claims are optional, which would mean that calling the default set of validation functions during JSONToken.Unmarshal might break the current usage patterns for some people. That said, the documented usage goes through the trouble of setting an Expiration that is never verified.

This isn't too hard to fix, but I was curious if the maintainers are open to something more intrusive (breaking use of JSONToken without an Expiration, and NotBefore date, and ensuring UnmarshalJSON checks this) to prevent mistakes, or if just updating the documentation would be preferable.

Wanted to open this issue for discussion.

Bind Keys to Version and Purpose

paseto/v2.go

Line 78 in f1000e3

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

paseto/v2.go

Line 138 in f1000e3

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

See https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/03-Algorithm-Lucidity.md

Right now, byte arrays are accepted by this API. There's no mechanism to prevent a user from using a v2 public key as a v2 local key.

How create private/public key ?

How i can create private/public key like from README.md - "Create token using asymetric key (public mode)" and use like files (id_ed25519, id_ed25519.pub)
b, _ := hex.DecodeString("b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")
privateKey := ed25519.PrivateKey(b)

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

Should decryption and verification operations mandate that footer matches an expected value?

In the Paseto documentation, there are two conflicting stances on how one should use the token's footer:

In the former case, the receiver knows and will tolerate only one footer. In the latter case, the receiver only knows the schema of the footer, but can't know its value; rather, the receiver needs to read the value to know how to proceed.

This library doesn't take either approach to heart: neither the decryption nor verification functions accept an expected footer value to match, nor is there a means to extract a footer first to guide the rest of the decryption or verification process.

What is the author's take on the role of the token footer?

naming json tags in struct claims breaks unmarshalling

Hi,

Thanks for the package!

I believe I found a problem with custom claims, specifically when using a struct as custom claim with field named via json tags.

To reproduce:

package main

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"log"

	"github.com/o1egl/paseto/v2"
)

type someClaim struct {
	Foo string `json:"foox"`
}

func main() {
	secret, _ := base64.StdEncoding.DecodeString("vQVOM5M6dUftMNhwkvjTX3ObFupqzRrYMSc/IM9hZ2M=")

	tokenIn := &paseto.JSONToken{}
	tokenIn.Set("someclaim", &someClaim{Foo: "nonempty"})

	tokenInStr, _ := paseto.Encrypt(secret, tokenIn, "")

	tokenOut := &paseto.JSONToken{}
	paseto.Decrypt(tokenInStr, secret, tokenOut, nil)

	var someClaim someClaim
	tokenOut.Get("someclaim", &someClaim)
	out, _ := json.Marshal(someClaim)
	fmt.Printf("claim: %s\n", out)
	
	tokenOutStr, _ := tokenOut.MarshalJSON()
	fmt.Printf("token: %s\n", tokenOutStr)
}

Expected:

claim: {"foox":"nonempty"}
token: {"someclaim":{"foox":"nonempty"}}

Got:

claim: {"foox":""}
token: {"someclaim":{"foox":"nonempty"}}

Removing the json:"foox" tag fixes the issue, but this is of course not ideal.

Cheers

Unable to verify data

I have my token, symmetric key and footer string passed

v2 := paseto.NewV2()
err := v2.Decrypt(token, symmetricKey, &newJSONToken, &newFooter)

But how do I verify data?

I found there is newJSONToken.Validate() function which basically returns an error if there is any.

I have a couple of question for this library:

  1. Is verification done by verifying "key" and "value" set using "Set" method on JSONToken?
  2. Can "token" generated be altered like "JWT" and pass modified or tampered data?
  3. Can "token" generated using "paseto" be decrypted and viewed like "JWT"?

Thanks

how to get version 2.0.0

Hi,
try to install via command go get -u github.com/o1egl/paseto,
but I can only get version 1.0.0, using go get -u github.com/o1egl/[email protected], it said invalid version: unknown revision v2.0.0.
Am I the only one who run into this?
Thanks.

Fail to check expiration

The Decrypt function will not check expiration

for example:

after I call Decrypt

err := paseto.Decrypt(tokenString, key, jsonToken, nil)

I print

fmt.Println("now": time.Now())
fmt.Println("exp": jsonToken.Expiration)

It says

now: 2024-02-06 19:10:29.592541 + 0800 CST m=+1.003330876
exp: 2024-02-06 19:10:28 +0800 CST

but the err is nil

Non-JSON messages

Dear Oleg,

Thanks for your work.

Could I trouble you to explain claims encoding:

According to RFC PASETO encodes claims to be transmitted in a JSON.

I mentioned strings and byte slices are accepted "as is" in your library.

The only place where I can find a rationale for that is
https://github.com/paragonie/paseto/tree/master/docs/02-PHP-Library#using-the-protocol-directly

But also I found:
paragonie/paseto#54 (comment)

Is a JSON encoded payload part of the spec? Is it required that (received||sent) payloads are in this format, or optional?

Originally it was going to be optional so people could use Protobuf, etc. However, I've since decided to just use JSON.

I am somewhat confused in the background of the question / implementation.

It looks like PHP version has builder for assembling JSON and special pure routines for advanced optional usage and that is probably mixed in Go case?

Would you be so kind to help me?

Thank you in advance!

go get retrieves version 1

Using go get retrieves version 1 of the library rather than version 2. Is version 2 available like other versioned packages? I tried adding the version number to the package, but no luck.

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.