jwkset

package module
v0.2.3 Latest Latest
Warning

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

Go to latest
Published: Dec 30, 2022 License: Apache-2.0 Imports: 12 Imported by: 24

README

Go Report Card Go Reference

JWK Set

This is a JWK Set (JWKS or jwks) implementation. For a JWK Set client, see github.com/MicahParks/keyfunc. Cryptographic keys can be added, deleted, and read from the JWK Set. A JSON representation of the JWK Set can be created for hosting via HTTPS. This project includes an in-memory storage implementation, but an interface is provided for more advanced use cases. For this implementation, a key ID (kid) is required.

This project only depends on packages from the standard Go library. It has no external dependencies.

The following key types have a JSON representation:

Key type Go private key type Go public key type External link
EC *ecdsa.PrivateKey *ecdsa.PublicKey Elliptic Curve Digital Signature Algorithm (ECDSA)
OKP ed25519.PrivateKey ed25519.PublicKey Edwards-curve Digital Signature Algorithm (EdDSA)
RSA *rsa.PrivateKey *rsa.PublicKey Rivest–Shamir–Adleman (RSA)
oct []byte none

Only the Go types listed in this table have a JSON representation. If you would like support for another key type, please open an issue on GitHub.

Example HTTP server

package main

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"log"
	"net/http"
	"os"

	"github.com/MicahParks/jwkset"
)

const (
	logFmt = "%s\nError: %s"
)

func main() {
	ctx := context.Background()
	logger := log.New(os.Stdout, "", 0)

	jwkSet := jwkset.NewMemory()

	key, err := rsa.GenerateKey(rand.Reader, 4096)
	if err != nil {
		logger.Fatalf(logFmt, "Failed to generate RSA key.", err)
	}

	err = jwkSet.Store.WriteKey(ctx, jwkset.NewKey(key, "my-key-id"))
	if err != nil {
		logger.Fatalf(logFmt, "Failed to store RSA key.", err)
	}

	http.HandleFunc("/jwks.json", func(writer http.ResponseWriter, request *http.Request) {
		// TODO Cache the JWK Set so storage isn't called for every request.
		response, err := jwkSet.JSONPublic(request.Context())
		if err != nil {
			logger.Printf(logFmt, "Failed to get JWK Set JSON.", err)
			writer.WriteHeader(http.StatusInternalServerError)
			return
		}

		writer.Header().Set("Content-Type", "application/json")
		_, _ = writer.Write(response)
	})

	logger.Print("Visit: http://localhost:8080/jwks.json")
	logger.Fatalf("Failed to listen and serve: %s", http.ListenAndServe(":8080", nil))
}

Example for marshalling a single key to a JSON Web Key

package main

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

	"github.com/MicahParks/jwkset"
)

const logFmt = "%s\nError: %s"

func main() {
	logger := log.New(os.Stdout, "", 0)

	// Create an EdDSA key.
	_, private, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		logger.Fatalf(logFmt, "Failed to generate EdDSA key.", err)
	}

	// Wrap the key in the appropriate Go type.
	meta := jwkset.NewKey(private, "my-key-id")

	// Create the approrpiate options to include the private key material in the JSON representation.
	options := jwkset.KeyMarshalOptions{
		AsymmetricPrivate: true,
	}

	// Marshal the key to a different Go type that can be serialized to JSON.
	marshal, err := jwkset.KeyMarshal(meta, options)
	if err != nil {
		logger.Fatalf(logFmt, "Failed to marshal key.", err)
	}

	// Marshal the new type to JSON.
	j, err := json.MarshalIndent(marshal, "", "  ")
	if err != nil {
		logger.Fatalf(logFmt, "Failed to marshal JSON.", err)
	}
	println(string(j))

	// Unmarshal the raw JSON into a Go type that can be deserialized into a key.
	err = json.Unmarshal(j, &marshal)
	if err != nil {
		logger.Fatalf(logFmt, "Failed to unmarshal JSON.", err)
	}

	// Create the appropriate options to include the private key material in the deserialization.
	//
	// If this option is not provided, the resulting key will be of the type ed25519.PublicKey.
	unmarshalOptions := jwkset.KeyUnmarshalOptions{
		AsymmetricPrivate: true,
	}

	// Convert the Go type back into a key with metadata.
	meta, err = jwkset.KeyUnmarshal(marshal, unmarshalOptions)
	if err != nil {
		logger.Fatalf(logFmt, "Failed to unmarshal key.", err)
	}

	// Print the key ID.
	println(meta.KeyID)
}

Test coverage

Test coverage is currently 99%.

$ go test -cover -race
PASS
coverage: 99.0% of statements
ok      github.com/MicahParks/jwkset    0.031s

References

This project was built and tested using various RFCs and services. The services are listed below:

See also:

Documentation

Index

Constants

View Source
const (
	// AlgHS256 is the HMAC using SHA-256 algorithm.
	AlgHS256 ALG = "HS256"
	// AlgHS384 is the HMAC using SHA-384 algorithm.
	AlgHS384 ALG = "HS384"
	// AlgHS512 is the HMAC using SHA-512 algorithm.
	AlgHS512 ALG = "HS512"
	// AlgRS256 is the RSASSA-PKCS1-v1_5 using SHA-256 algorithm.
	AlgRS256 ALG = "RS256"
	// AlgRS384 is the RSASSA-PKCS1-v1_5 using SHA-384 algorithm.
	AlgRS384 ALG = "RS384"
	// AlgRS512 is the RSASSA-PKCS1-v1_5 using SHA-512 algorithm.
	AlgRS512 ALG = "RS512"
	// AlgES256 is the ECDSA using P-256 and SHA-256 algorithm.
	AlgES256 ALG = "ES256"
	// AlgES384 is the ECDSA using P-384 and SHA-384 algorithm.
	AlgES384 ALG = "ES384"
	// AlgES512 is the ECDSA using P-521 and SHA-512 algorithm.
	AlgES512 ALG = "ES512"
	// AlgPS256 is the RSASSA-PSS using SHA-256 and MGF1 with SHA-256 algorithm.
	AlgPS256 ALG = "PS256"
	// AlgPS384 is the RSASSA-PSS using SHA-384 and MGF1 with SHA-384 algorithm.
	AlgPS384 ALG = "PS384"
	// AlgPS512 is the RSASSA-PSS using SHA-512 and MGF1 with SHA-512 algorithm.
	AlgPS512 ALG = "PS512"
	// AlgNone is the No digital signature or MAC performed algorithm.
	AlgNone ALG = "none"
	// ALGEdDSA is the EdDSA algorithm.
	ALGEdDSA ALG = "EdDSA"

	// KeyTypeEC is the key type for ECDSA.
	KeyTypeEC KTY = "EC"
	// KeyTypeOKP is the key type for EdDSA.
	KeyTypeOKP KTY = "OKP"
	// KeyTypeRSA is the key type for RSA.
	KeyTypeRSA KTY = "RSA"
	// KeyTypeOct is the key type for octet sequences, such as HMAC.
	KeyTypeOct KTY = "oct"

	// CurveEd25519 is a curve for EdDSA.
	CurveEd25519 CRV = "Ed25519"
	// CurveP256 is a curve for ECDSA.
	CurveP256 CRV = "P-256"
	// CurveP384 is a curve for ECDSA.
	CurveP384 CRV = "P-384"
	// CurveP521 is a curve for ECDSA.
	CurveP521 CRV = "P-521"
)

Variables

View Source
var (
	// ErrKeyUnmarshalParameter indicates that a JWK's attributes are invalid and cannot be unmarshaled.
	ErrKeyUnmarshalParameter = errors.New("unable to unmarshal JWK due to invalid attributes")
	// ErrUnsupportedKeyType indicates a key type is not supported.
	ErrUnsupportedKeyType = errors.New("unsupported key type")
)
View Source
var ErrKeyNotFound = errors.New("key not found")

ErrKeyNotFound is returned by a Storage implementation when a key is not found.

Functions

This section is empty.

Types

type ALG added in v0.1.0

type ALG string

ALG is a set of "JSON Web Signature and Encryption Algorithms" types from https://www.iana.org/assignments/jose/jose.xhtml(JWA) as defined in https://www.rfc-editor.org/rfc/rfc7518#section-7.1

func (ALG) String added in v0.2.1

func (alg ALG) String() string

type CRV added in v0.1.0

type CRV string

CRV is a set of "JSON Web Key Elliptic Curve" types from https://www.iana.org/assignments/jose/jose.xhtml as mentioned in https://www.rfc-editor.org/rfc/rfc7518.html#section-6.2.1.1.

func (CRV) String added in v0.1.0

func (crv CRV) String() string

type JWKMarshal added in v0.1.0

type JWKMarshal struct {
	// TODO Check that ALG field is utilized fully.
	ALG ALG    `json:"alg,omitempty"` // https://www.rfc-editor.org/rfc/rfc7517#section-4.4 and https://www.rfc-editor.org/rfc/rfc7518#section-4.1
	CRV CRV    `json:"crv,omitempty"` // https://www.rfc-editor.org/rfc/rfc7518#section-6.2.1.1 and https://www.rfc-editor.org/rfc/rfc8037.html#section-2
	D   string `json:"d,omitempty"`   // https://www.rfc-editor.org/rfc/rfc7518#section-6.3.2.1 and https://www.rfc-editor.org/rfc/rfc7518#section-6.2.2.1 and https://www.rfc-editor.org/rfc/rfc8037.html#section-2
	DP  string `json:"dp,omitempty"`  // https://www.rfc-editor.org/rfc/rfc7518#section-6.3.2.4
	DQ  string `json:"dq,omitempty"`  // https://www.rfc-editor.org/rfc/rfc7518#section-6.3.2.5
	E   string `json:"e,omitempty"`   // https://www.rfc-editor.org/rfc/rfc7518#section-6.3.1.2
	K   string `json:"k,omitempty"`   // https://www.rfc-editor.org/rfc/rfc7518#section-6.4.1
	// TODO Use KEYOPS field.
	// KEYOPTS []string `json:"key_ops,omitempty"` // https://www.rfc-editor.org/rfc/rfc7517#section-4.3
	KID string        `json:"kid,omitempty"` // https://www.rfc-editor.org/rfc/rfc7517#section-4.5
	KTY KTY           `json:"kty,omitempty"` // https://www.rfc-editor.org/rfc/rfc7517#section-4.1
	N   string        `json:"n,omitempty"`   // https://www.rfc-editor.org/rfc/rfc7518#section-6.3.1.1
	OTH []OtherPrimes `json:"oth,omitempty"` // https://www.rfc-editor.org/rfc/rfc7518#section-6.3.2.7
	P   string        `json:"p,omitempty"`   // https://www.rfc-editor.org/rfc/rfc7518#section-6.3.2.2
	Q   string        `json:"q,omitempty"`   // https://www.rfc-editor.org/rfc/rfc7518#section-6.3.2.3
	QI  string        `json:"qi,omitempty"`  // https://www.rfc-editor.org/rfc/rfc7518#section-6.3.2.6
	// TODO Use USE field.
	// USE USE        `json:"use,omitempty"` // https://www.rfc-editor.org/rfc/rfc7517#section-4.2
	X string `json:"x,omitempty"` // https://www.rfc-editor.org/rfc/rfc7518#section-6.2.1.2 and https://www.rfc-editor.org/rfc/rfc8037.html#section-2
	// TODO X.509 related fields.
	Y string `json:"y,omitempty"` // https://www.rfc-editor.org/rfc/rfc7518#section-6.2.1.3
}

JWKMarshal is used to marshal or unmarshal a JSON Web Key. https://www.rfc-editor.org/rfc/rfc7517 https://www.rfc-editor.org/rfc/rfc7518 https://www.rfc-editor.org/rfc/rfc8037

func KeyMarshal added in v0.1.0

func KeyMarshal[CustomKeyMeta any](meta KeyWithMeta[CustomKeyMeta], options KeyMarshalOptions) (JWKMarshal, error)

KeyMarshal transforms a KeyWithMeta into a JWKMarshal, which is used to marshal/unmarshal a JSON Web Key.

type JWKSMarshal added in v0.1.0

type JWKSMarshal struct {
	Keys []JWKMarshal `json:"keys"`
}

JWKSMarshal is used to marshal or unmarshal a JSON Web Key Set.

type JWKSet

type JWKSet[CustomKeyMeta any] struct {
	Store Storage[CustomKeyMeta]
}

JWKSet is a set of JSON Web Keys.

func NewMemory

func NewMemory[CustomKeyMeta any]() JWKSet[CustomKeyMeta]

NewMemory creates a new in-memory JWKSet.

func (JWKSet[CustomKeyMeta]) JSONPrivate added in v0.1.0

func (j JWKSet[CustomKeyMeta]) JSONPrivate(ctx context.Context) (json.RawMessage, error)

JSONPrivate creates the JSON representation of the JWKSet public and private key material.

func (JWKSet[CustomKeyMeta]) JSONPublic added in v0.1.0

func (j JWKSet[CustomKeyMeta]) JSONPublic(ctx context.Context) (json.RawMessage, error)

JSONPublic creates the JSON representation of the public keys in JWKSet.

func (JWKSet[CustomKeyMeta]) JSONWithOptions added in v0.1.0

func (j JWKSet[CustomKeyMeta]) JSONWithOptions(ctx context.Context, options KeyMarshalOptions) (json.RawMessage, error)

JSONWithOptions creates the JSON representation of the JWKSet with the given options.

type KTY added in v0.1.0

type KTY string

KTY is a set of "JSON Web Key Types" from https://www.iana.org/assignments/jose/jose.xhtml as mentioned in https://www.rfc-editor.org/rfc/rfc7517#section-4.1

func (KTY) String added in v0.1.0

func (kty KTY) String() string

type KeyMarshalOptions added in v0.1.0

type KeyMarshalOptions struct {
	AsymmetricPrivate bool
	Symmetric         bool
}

KeyMarshalOptions are used to specify options for marshaling a JSON Web Key.

type KeyUnmarshalOptions added in v0.1.0

type KeyUnmarshalOptions struct {
	AsymmetricPrivate bool
	Symmetric         bool
}

KeyUnmarshalOptions are used to specify options for unmarshaling a JSON Web Key.

type KeyWithMeta

type KeyWithMeta[CustomKeyMeta any] struct {
	ALG    ALG
	Custom CustomKeyMeta
	Key    interface{}
	KeyID  string
}

KeyWithMeta is holds a Key and its metadata.

func KeyUnmarshal added in v0.1.0

func KeyUnmarshal[CustomKeyMeta any](jwk JWKMarshal, options KeyUnmarshalOptions) (KeyWithMeta[CustomKeyMeta], error)

KeyUnmarshal transforms a JWKMarshal into a KeyWithMeta, which contains the correct Go type for the cryptographic key.

func NewKey

func NewKey[CustomKeyMeta any](key interface{}, keyID string) KeyWithMeta[CustomKeyMeta]

NewKey creates a new KeyWithMeta.

type OtherPrimes added in v0.1.0

OtherPrimes is for RSA private keys that have more than 2 primes. https://www.rfc-editor.org/rfc/rfc7518#section-6.3.2.7

type Storage

type Storage[CustomKeyMeta any] interface {
	// DeleteKey deletes a key from the storage. It will return ok as true if the key was present for deletion.
	DeleteKey(ctx context.Context, keyID string) (ok bool, err error)

	// ReadKey reads a key from the storage. If the key is not present, it returns ErrKeyNotFound. Any pointers returned
	// should be considered read-only.
	ReadKey(ctx context.Context, keyID string) (KeyWithMeta[CustomKeyMeta], error)

	// SnapshotKeys reads a snapshot of all keys from storage. As with ReadKey, any pointers returned should be
	// considered read-only.
	SnapshotKeys(ctx context.Context) ([]KeyWithMeta[CustomKeyMeta], error)

	// WriteKey writes a key to the storage. If the key already exists, it will be overwritten. After writing a key,
	// any pointers written should be considered owned by the underlying storage.
	WriteKey(ctx context.Context, meta KeyWithMeta[CustomKeyMeta]) error
}

Storage handles storage operations for a JWKSet.

func NewMemoryStorage

func NewMemoryStorage[CustomKeyMeta any]() Storage[CustomKeyMeta]

NewMemoryStorage creates a new in-memory Storage implementation.

Directories

Path Synopsis
cmd
jwksetinfer Module
examples

Jump to

Keyboard shortcuts

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