jwx

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: May 4, 2020 License: MIT Imports: 0 Imported by: 4

README

jwx

Implementation of various JWx technologies

Build Status GoDoc codecov.io

Status

Done

PR/issues welcome.

Package name Notes
jwt RFC 7519
jwk RFC 7517 + RFC 7638
jwa RFC 7518
jws RFC 7515
jwe RFC 7516
In progress:
  • jwe - more algorithms

Why?

My goal was to write a server that heavily uses JWK and JWT. At first glance the libraries that already exist seemed sufficient, but soon I realized that

  1. To completely implement the protocols, I needed the entire JWT, JWK, JWS, JWE (and JWA, by necessity).
  2. Most of the libraries that existed only deal with a subset of the various JWx specifications that were necessary to implement their specific needs

For example, a certain library looks like it had most of JWS, JWE, JWK covered, but then it lacked the ability to include private claims in its JWT responses. Another library had support of all the private claims, but completely lacked in its flexibility to generate various different response formats.

Because I was writing the server side (and the client side for testing), I needed the entire JOSE toolset to properly implement my server, and they needed to be flexible enough to fulfill the entire spec that I was writing.

So here's go-jwx. This library is extensible, customizable, and hopefully well organized to the point that it is easy for you to slice and dice it.

As of this writing (Nov 2015), it's still lacking a few of the algorithms for JWE that are described in JWA (which I believe to be less frequently used), but in general you should be able to do pretty much everything allowed in the specifications.

Notes for users of pre-1.0.0 release

The API has been reworked quite substantially between pre- and post 1.0.0 releases. Please check out the Changes file (or the diff, if you are into that sort of thing)

Synopsis

JWT

See the examples here as well: https://github.com/lestrrat-go/jwx/jwt

func ExampleJWT() {
  const aLongLongTimeAgo = 233431200

  t := jwt.New()
  t.Set(jwt.SubjectKey, `https://github.com/lestrrat-go/jwx/jwt`)
  t.Set(jwt.AudienceKey, `Golang Users`)
  t.Set(jwt.IssuedAtKey, time.Unix(aLongLongTimeAgo, 0))
  t.Set(`privateClaimKey`, `Hello, World!`)

  buf, err := json.MarshalIndent(t, "", "  ")
  if err != nil {
    fmt.Printf("failed to generate JSON: %s\n", err)
    return
  }

  fmt.Printf("%s\n", buf)
  fmt.Printf("aud -> '%s'\n", t.Audience())
  fmt.Printf("iat -> '%s'\n", t.IssuedAt().Format(time.RFC3339))
  if v, ok := t.Get(`privateClaimKey`); ok {
    fmt.Printf("privateClaimKey -> '%s'\n", v)
  }
  fmt.Printf("sub -> '%s'\n", t.Subject())
}
JWT (with OpenID claims)

jwt package can work with token types other than the default one. For OpenID claims, use the token created by openid.New(), or use the jwt.WithOpenIDClaims(). If you need to use other specialized claims, use jwt.WithToken() to specify the exact token type

func Example_openid() {
  const aLongLongTimeAgo = 233431200

  t := openid.New()
  t.Set(jwt.SubjectKey, `https://github.com/lestrrat-go/jwx/jwt`)
  t.Set(jwt.AudienceKey, `Golang Users`)
  t.Set(jwt.IssuedAtKey, time.Unix(aLongLongTimeAgo, 0))
  t.Set(`privateClaimKey`, `Hello, World!`)

  addr := openid.NewAddress()
  addr.Set(openid.AddressPostalCodeKey, `105-0011`)
  addr.Set(openid.AddressCountryKey, `日本`)
  addr.Set(openid.AddressRegionKey, `東京都`)
  addr.Set(openid.AddressLocalityKey, `港区`)
  addr.Set(openid.AddressStreetAddressKey, `芝公園 4-2-8`)
  t.Set(openid.AddressKey, addr)

  buf, err := json.MarshalIndent(t, "", "  ")
  if err != nil {
    fmt.Printf("failed to generate JSON: %s\n", err)
    return
  }
  fmt.Printf("%s\n", buf)

  t2, err := jwt.ParseBytes(buf, jwt.WithOpenIDClaims())
  if err != nil {
    fmt.Printf("failed to parse JSON: %s\n", err)
    return
  }
  if _, ok := t2.(openid.Token); !ok {
    fmt.Printf("using jwt.WithOpenIDClaims() creates an openid.Token instance")
    return
  }
}
JWK

See the examples here as well: https://godoc.org/github.com/lestrrat-go/jwx/jwk#pkg-examples

Create a JWK file from RSA public key:

import(
  "crypto/rand"
  "crypto/rsa"
  "encoding/json"
  "log"
  "os"

  "github.com/lestrrat-go/jwx/jwk"
)

func main() {
  privkey, err := rsa.GenerateKey(rand.Reader, 2048)
  if err != nil {
    log.Printf("failed to generate private key: %s", err)
    return
  }

  key, err := jwk.New(&privkey.PublicKey)
  if err != nil {
    log.Printf("failed to create JWK: %s", err)
    return
  }

  jsonbuf, err := json.MarshalIndent(key, "", "  ")
  if err != nil {
    log.Printf("failed to generate JSON: %s", err)
    return
  }

  os.Stdout.Write(jsonbuf)
}

Parse and use a JWK key:

import(
  "log"

  "github.com/lestrrat-go/jwx/jwk"
)

func main() {
  set, err := jwk.Fetch("https://foobar.domain/jwk.json")
  if err != nil {
    log.Printf("failed to parse JWK: %s", err)
    return
  }

  // If you KNOW you have exactly one key, you can just
  // use set.Keys[0]
  keys := set.LookupKeyID("mykey")
  if len(keys) == 0 {
    log.Printf("failed to lookup key: %s", err)
    return
  }

  var key interface{}
  if err := keys[0].Raw(&key); err != nil {
    log.Printf("failed to create public key: %s", err)
    return
  }

  // Use key for jws.Verify() or whatever
}
JWS

See also VerifyWithJWK and VerifyWithJKU

import(
  "crypto/rand"
  "crypto/rsa"
  "log"

  "github.com/lestrrat-go/jwx/jwa"
  "github.com/lestrrat-go/jwx/jws"
)

func main() {
  privkey, err := rsa.GenerateKey(rand.Reader, 2048)
  if err != nil {
    log.Printf("failed to generate private key: %s", err)
    return
  }

  buf, err := jws.Sign([]byte("Lorem ipsum"), jwa.RS256, privkey)
  if err != nil {
    log.Printf("failed to created JWS message: %s", err)
    return
  }

  // When you received a JWS message, you can verify the signature
  // and grab the payload sent in the message in one go:
  verified, err := jws.Verify(buf, jwa.RS256, &privkey.PublicKey)
  if err != nil {
    log.Printf("failed to verify message: %s", err)
    return
  }

  log.Printf("signed message verified! -> %s", verified)
}

Supported signature algorithms:

Algorithm Supported? Constant in go-jwx
HMAC using SHA-256 YES jwa.HS256
HMAC using SHA-384 YES jwa.HS384
HMAC using SHA-512 YES jwa.HS512
RSASSA-PKCS-v1.5 using SHA-256 YES jwa.RS256
RSASSA-PKCS-v1.5 using SHA-384 YES jwa.RS384
RSASSA-PKCS-v1.5 using SHA-512 YES jwa.RS512
ECDSA using P-256 and SHA-256 YES jwa.ES256
ECDSA using P-384 and SHA-384 YES jwa.ES384
ECDSA using P-521 and SHA-512 YES jwa.ES512
RSASSA-PSS using SHA256 and MGF1-SHA256 YES jwa.PS256
RSASSA-PSS using SHA384 and MGF1-SHA384 YES jwa.PS384
RSASSA-PSS using SHA512 and MGF1-SHA512 YES jwa.PS512
JWE

See the examples here as well: https://godoc.org/github.com/lestrrat-go/jwx/jwe#pkg-examples

import(
  "crypto/rand"
  "crypto/rsa"
  "log"

  "github.com/lestrrat-go/jwx/jwa"
  "github.com/lestrrat-go/jwx/jwe"
)

func main() {
  privkey, err := rsa.GenerateKey(rand.Reader, 2048)
  if err != nil {
    log.Printf("failed to generate private key: %s", err)
    return
  }

  payload := []byte("Lorem Ipsum")

  encrypted, err := jwe.Encrypt(payload, jwa.RSA1_5, &privkey.PublicKey, jwa.A128CBC_HS256, jwa.NoCompress)
  if err != nil {
    log.Printf("failed to encrypt payload: %s", err)
    return
  }

  decrypted, err := jwe.Decrypt(encrypted, jwa.RSA1_5, privkey)
  if err != nil {
    log.Printf("failed to decrypt: %s", err)
    return
  }

  if string(decrypted) != "Lorem Ipsum" {
    log.Printf("WHAT?!")
    return
  }
}

Supported key encryption algorithm:

Algorithm Supported? Constant in go-jwx
RSA-PKCS1v1.5 YES jwa.RSA1_5
RSA-OAEP-SHA1 YES jwa.RSA_OAEP
RSA-OAEP-SHA256 YES jwa.RSA_OAEP_256
AES key wrap (128) YES jwa.A128KW
AES key wrap (192) YES jwa.A192KW
AES key wrap (256) YES jwa.A256KW
Direct encryption NO jwa.DIRECT
ECDH-ES YES jwa.ECDH_ES
ECDH-ES + AES key wrap (128) YES jwa.ECDH_ES_A128KW
ECDH-ES + AES key wrap (192) YES jwa.ECDH_ES_A192KW
ECDH-ES + AES key wrap (256) YES jwa.ECDH_ES_A256KW
AES-GCM key wrap (128) NO jwa.A128GCMKW
AES-GCM key wrap (192) NO jwa.A192GCMKW
AES-GCM key wrap (256) NO jwa.A256GCMKW
PBES2 + HMAC-SHA256 + AES key wrap (128) NO jwa.PBES2_HS256_A128KW
PBES2 + HMAC-SHA384 + AES key wrap (192) NO jwa.PBES2_HS384_A192KW
PBES2 + HMAC-SHA512 + AES key wrap (256) NO jwa.PBES2_HS512_A256KW

Supported content encryption algorithm:

Algorithm Supported? Constant in go-jwx
AES-CBC + HMAC-SHA256 (128) YES jwa.A128CBC_HS256
AES-CBC + HMAC-SHA384 (192) YES jwa.A192CBC_HS384
AES-CBC + HMAC-SHA512 (256) YES jwa.A256CBC_HS512
AES-GCM (128) YES jwa.A128GCM
AES-GCM (192) YES jwa.A192GCM
AES-GCM (256) YES jwa.A256GCM

PRs welcome to support missing algorithms!

Contributions

PRs welcome!

Credits

Documentation

Overview

Package jwx contains tools that deal with the various JWx (JOSE) technologies such as JWT, JWS, JWE, etc in Go.

JWS (https://tools.ietf.org/html/rfc7515)
JWE (https://tools.ietf.org/html/rfc7516)
JWK (https://tools.ietf.org/html/rfc7517)
JWA (https://tools.ietf.org/html/rfc7518)
JWT (https://tools.ietf.org/html/rfc7519)

The primary focus of this library tool set is to implement the extremely flexible OAuth2 / OpenID Connect protocols. There are many other libraries out there that deal with all or parts of these JWx technologies:

https://github.com/dgrijalva/jwt-go
https://github.com/square/go-jose
https://github.com/coreos/oidc
https://golang.org/x/oauth2

This library exists because there was a need for a toolset that encompasses the whole set of JWx technologies in a highly customizable manner, in one package.

You can find more high level documentation at Github (https://github.com/lestrrat-go/jwx)

Example (Jwe)
package main

import (
	"crypto/rand"
	"crypto/rsa"
	"log"

	"github.com/lestrrat-go/jwx/jwa"
	"github.com/lestrrat-go/jwx/jwe"
)

func main() {
	privkey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		log.Printf("failed to generate private key: %s", err)
		return
	}

	payload := []byte("Lorem Ipsum")

	encrypted, err := jwe.Encrypt(payload, jwa.RSA1_5, &privkey.PublicKey, jwa.A128CBC_HS256, jwa.NoCompress)
	if err != nil {
		log.Printf("failed to encrypt payload: %s", err)
		return
	}

	decrypted, err := jwe.Decrypt(encrypted, jwa.RSA1_5, privkey)
	if err != nil {
		log.Printf("failed to decrypt: %s", err)
		return
	}

	if string(decrypted) != "Lorem Ipsum" {
		log.Printf("WHAT?!")
		return
	}
}
Output:

Example (Jwk)
package main

import (
	"log"

	"github.com/lestrrat-go/jwx/jwk"
)

func main() {
	set, err := jwk.FetchHTTP("https://foobar.domain/jwk.json")
	if err != nil {
		log.Printf("failed to parse JWK: %s", err)
		return
	}

	// If you KNOW you have exactly one key, you can just
	// use set.Keys[0]
	keys := set.LookupKeyID("mykey")
	if len(keys) == 0 {
		log.Printf("failed to lookup key: %s", err)
		return
	}

	var key interface{} // This is the raw key, like *rsa.PrivateKey or *ecdsa.PrivateKey
	if err := keys[0].Raw(&key); err != nil {
		log.Printf("failed to create public key: %s", err)
		return
	}

	// Use key for jws.Verify() or whatever
	_ = key
}
Output:

Example (Jws)
package main

import (
	"crypto/rand"
	"crypto/rsa"
	"log"

	"github.com/lestrrat-go/jwx/jwa"
	"github.com/lestrrat-go/jwx/jws"
)

func main() {
	privkey, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		log.Printf("failed to generate private key: %s", err)
		return
	}

	buf, err := jws.Sign([]byte("Lorem ipsum"), jwa.RS256, privkey)
	if err != nil {
		log.Printf("failed to created JWS message: %s", err)
		return
	}

	// When you received a JWS message, you can verify the signature
	// and grab the payload sent in the message in one go:
	verified, err := jws.Verify(buf, jwa.RS256, &privkey.PublicKey)
	if err != nil {
		log.Printf("failed to verify message: %s", err)
		return
	}

	log.Printf("signed message verified! -> %s", verified)
}
Output:

Example (Jwt)
package main

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/lestrrat-go/jwx/jwt"
)

func main() {
	const aLongLongTimeAgo = 233431200

	t := jwt.New()
	t.Set(jwt.SubjectKey, `https://github.com/lestrrat-go/jwx/jwt`)
	t.Set(jwt.AudienceKey, `Golang Users`)
	t.Set(jwt.IssuedAtKey, time.Unix(aLongLongTimeAgo, 0))
	t.Set(`privateClaimKey`, `Hello, World!`)

	buf, err := json.MarshalIndent(t, "", "  ")
	if err != nil {
		fmt.Printf("failed to generate JSON: %s\n", err)
		return
	}

	fmt.Printf("%s\n", buf)
	fmt.Printf("aud -> '%s'\n", t.Audience())
	fmt.Printf("iat -> '%s'\n", t.IssuedAt().Format(time.RFC3339))
	if v, ok := t.Get(`privateClaimKey`); ok {
		fmt.Printf("privateClaimKey -> '%s'\n", v)
	}
	fmt.Printf("sub -> '%s'\n", t.Subject())
}
Output:

Example (Openid)
package main

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/lestrrat-go/jwx/jwt"
	"github.com/lestrrat-go/jwx/jwt/openid"
)

func main() {
	const aLongLongTimeAgo = 233431200

	t := openid.New()
	t.Set(jwt.SubjectKey, `https://github.com/lestrrat-go/jwx/jwt`)
	t.Set(jwt.AudienceKey, `Golang Users`)
	t.Set(jwt.IssuedAtKey, time.Unix(aLongLongTimeAgo, 0))
	t.Set(`privateClaimKey`, `Hello, World!`)

	addr := openid.NewAddress()
	addr.Set(openid.AddressPostalCodeKey, `105-0011`)
	addr.Set(openid.AddressCountryKey, `日本`)
	addr.Set(openid.AddressRegionKey, `東京都`)
	addr.Set(openid.AddressLocalityKey, `港区`)
	addr.Set(openid.AddressStreetAddressKey, `芝公園 4-2-8`)
	t.Set(openid.AddressKey, addr)

	buf, err := json.MarshalIndent(t, "", "  ")
	if err != nil {
		fmt.Printf("failed to generate JSON: %s\n", err)
		return
	}
	fmt.Printf("%s\n", buf)

	t2, err := jwt.ParseBytes(buf, jwt.WithOpenIDClaims())
	if err != nil {
		fmt.Printf("failed to parse JSON: %s\n", err)
		return
	}
	if _, ok := t2.(openid.Token); !ok {
		fmt.Printf("using jwt.WithOpenIDClaims() creates an openid.Token instance")
		return
	}
}
Output:

Directories

Path Synopsis
Package buffer provides a very thin wrapper around []byte buffer called `Buffer`, to provide functionalities that are often used within the jwx related packages
Package buffer provides a very thin wrapper around []byte buffer called `Buffer`, to provide functionalities that are often used within the jwx related packages
cmd
jwx
internal
padbuf
Package padbuf implements a simple buffer that knows how to pad/unpad itself so that the buffer size aligns with an arbitrary block size.
Package padbuf implements a simple buffer that knows how to pad/unpad itself so that the buffer size aligns with an arbitrary block size.
jwa
Package jwa defines the various algorithm described in https://tools.ietf.org/html/rfc7518
Package jwa defines the various algorithm described in https://tools.ietf.org/html/rfc7518
jwe
This file is auto-generated by internal/cmd/genheaders/main.go.
This file is auto-generated by internal/cmd/genheaders/main.go.
jwk
Package jwk implements JWK as described in https://tools.ietf.org/html/rfc7517
Package jwk implements JWK as described in https://tools.ietf.org/html/rfc7517
jws
This file is auto-generated.
This file is auto-generated.
jwt
Package jwt implements JSON Web Tokens as described in https://tools.ietf.org/html/rfc7519 This file is auto-generated by jwt/internal/cmd/gentoken/main.go.
Package jwt implements JSON Web Tokens as described in https://tools.ietf.org/html/rfc7519 This file is auto-generated by jwt/internal/cmd/gentoken/main.go.
openid
Package openid provides a specialized token that provides utilities to work with OpenID JWT tokens.
Package openid provides a specialized token that provides utilities to work with OpenID JWT tokens.

Jump to

Keyboard shortcuts

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