Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+87
View File
@@ -0,0 +1,87 @@
load("@rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "jwk",
srcs = [
"cache.go",
"convert.go",
"ecdsa.go",
"ecdsa_gen.go",
"errors.go",
"fetch.go",
"filter.go",
"interface.go",
"interface_gen.go",
"io.go",
"jwk.go",
"key_ops.go",
"okp.go",
"okp_gen.go",
"options.go",
"options_gen.go",
"parser.go",
"rsa.go",
"rsa_gen.go",
"set.go",
"symmetric.go",
"symmetric_gen.go",
"usage.go",
"whitelist.go",
"x509.go",
],
importpath = "github.com/lestrrat-go/jwx/v3/jwk",
visibility = ["//visibility:public"],
deps = [
"//cert",
"//internal/base64",
"//internal/ecutil",
"//transform",
"//internal/json",
"//internal/pool",
"//internal/tokens",
"//jwa",
"//jwk/ecdsa",
"//jwk/jwkbb",
"@com_github_lestrrat_go_blackmagic//:blackmagic",
"@com_github_lestrrat_go_httprc_v3//:httprc",
"@com_github_lestrrat_go_option_v2//:option",
],
)
go_test(
name = "jwk_test",
srcs = [
"filter_test.go",
"headers_test.go",
"jwk_internal_test.go",
"jwk_test.go",
"options_gen_test.go",
"refresh_test.go",
"set_test.go",
"x5c_test.go",
],
data = glob(["testdata/**"]),
embed = [":jwk"],
deps = [
"//cert",
"//internal/base64",
"//internal/jose",
"//internal/json",
"//internal/jwxtest",
"//internal/tokens",
"//jwa",
"//jwk/ecdsa",
"//jws",
"@com_github_lestrrat_go_blackmagic//:blackmagic",
"@com_github_lestrrat_go_httprc_v3//:httprc",
"@com_github_lestrrat_go_httprc_v3//tracesink",
"@com_github_stretchr_testify//assert",
"@com_github_stretchr_testify//require",
],
)
alias(
name = "go_default_library",
actual = ":jwk",
visibility = ["//visibility:public"],
)
+215
View File
@@ -0,0 +1,215 @@
# JWK [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/jwx/v3/jwk.svg)](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk)
Package jwk implements JWK as described in [RFC7517](https://tools.ietf.org/html/rfc7517).
If you are looking to use JWT wit JWKs, look no further than [github.com/lestrrat-go/jwx](../jwt).
* Parse and work with RSA/EC/Symmetric/OKP JWK types
* Convert to and from JSON
* Convert to and from raw key types (e.g. *rsa.PrivateKey)
* Ability to keep a JWKS fresh using *jwk.AutoRefresh
## Supported key types:
| kty | Curve | Go Key Type |
|:----|:------------------------|:----------------------------------------------|
| RSA | N/A | rsa.PrivateKey / rsa.PublicKey (2) |
| EC | P-256<br>P-384<br>P-521<br>secp256k1 (1) | ecdsa.PrivateKey / ecdsa.PublicKey (2) |
| oct | N/A | []byte |
| OKP | Ed25519 (1) | ed25519.PrivateKey / ed25519.PublicKey (2) |
| | X25519 (1) | (jwx/)x25519.PrivateKey / x25519.PublicKey (2)|
* Note 1: Experimental
* Note 2: Either value or pointers accepted (e.g. rsa.PrivateKey or *rsa.PrivateKey)
# Documentation
Please read the [API reference](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk), or
the how-to style documentation on how to use JWK can be found in the [docs directory](../docs/04-jwk.md).
# Auto-Refresh a key during a long-running process
<!-- INCLUDE(examples/jwk_cache_example_test.go) -->
```go
package examples_test
import (
"context"
"fmt"
"time"
"github.com/lestrrat-go/httprc/v3"
"github.com/lestrrat-go/jwx/v3/jwk"
)
func Example_jwk_cache() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
const googleCerts = `https://www.googleapis.com/oauth2/v3/certs`
// First, set up the `jwk.Cache` object. You need to pass it a
// `context.Context` object to control the lifecycle of the background fetching goroutine.
c, err := jwk.NewCache(ctx, httprc.NewClient())
if err != nil {
fmt.Printf("failed to create cache: %s\n", err)
return
}
// Tell *jwk.Cache that we only want to refresh this JWKS periodically.
if err := c.Register(ctx, googleCerts); err != nil {
fmt.Printf("failed to register google JWKS: %s\n", err)
return
}
// Pretend that this is your program's main loop
MAIN:
for {
select {
case <-ctx.Done():
break MAIN
default:
}
keyset, err := c.Lookup(ctx, googleCerts)
if err != nil {
fmt.Printf("failed to fetch google JWKS: %s\n", err)
return
}
_ = keyset
// The returned `keyset` will always be "reasonably" new.
//
// By "reasonably" we mean that we cannot guarantee that the keys will be refreshed
// immediately after it has been rotated in the remote source. But it should be close\
// enough, and should you need to forcefully refresh the token using the `(jwk.Cache).Refresh()` method.
//
// If refetching the keyset fails, a cached version will be returned from the previous
// successful sync
// Do interesting stuff with the keyset... but here, we just
// sleep for a bit
time.Sleep(time.Second)
// Because we're a dummy program, we just cancel the loop now.
// If this were a real program, you presumably loop forever
cancel()
}
// OUTPUT:
}
```
source: [examples/jwk_cache_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwk_cache_example_test.go)
<!-- END INCLUDE -->
Parse and use a JWK key:
<!-- INCLUDE(examples/jwk_example_test.go) -->
```go
package examples_test
import (
"context"
"fmt"
"log"
"github.com/lestrrat-go/jwx/v3/internal/json"
"github.com/lestrrat-go/jwx/v3/jwk"
)
func Example_jwk_usage() {
// Use jwk.Cache if you intend to keep reuse the JWKS over and over
set, err := jwk.Fetch(context.Background(), "https://www.googleapis.com/oauth2/v3/certs")
if err != nil {
log.Printf("failed to parse JWK: %s", err)
return
}
// Key sets can be serialized back to JSON
{
jsonbuf, err := json.Marshal(set)
if err != nil {
log.Printf("failed to marshal key set into JSON: %s", err)
return
}
log.Printf("%s", jsonbuf)
}
for i := 0; i < set.Len(); i++ {
var rawkey any // This is where we would like to store the raw key, like *rsa.PrivateKey or *ecdsa.PrivateKey
key, ok := set.Key(i) // This retrieves the corresponding jwk.Key
if !ok {
log.Printf("failed to get key at index %d", i)
return
}
// jws and jwe operations can be performed using jwk.Key, but you could also
// covert it to their "raw" forms, such as *rsa.PrivateKey or *ecdsa.PrivateKey
if err := jwk.Export(key, &rawkey); err != nil {
log.Printf("failed to create public key: %s", err)
return
}
_ = rawkey
// You can create jwk.Key from a raw key, too
fromRawKey, err := jwk.Import(rawkey)
if err != nil {
log.Printf("failed to acquire raw key from jwk.Key: %s", err)
return
}
// Keys can be serialized back to JSON
jsonbuf, err := json.Marshal(key)
if err != nil {
log.Printf("failed to marshal key into JSON: %s", err)
return
}
fromJSONKey, err := jwk.Parse(jsonbuf)
if err != nil {
log.Printf("failed to parse json: %s", err)
return
}
_ = fromJSONKey
_ = fromRawKey
}
// OUTPUT:
}
//nolint:govet
func Example_jwk_marshal_json() {
// JWKs that inherently involve randomness such as RSA and EC keys are
// not used in this example, because they may produce different results
// depending on the environment.
//
// (In fact, even if you use a static source of randomness, tests may fail
// because of internal changes in the Go runtime).
raw := []byte("01234567890123456789012345678901234567890123456789ABCDEF")
// This would create a symmetric key
key, err := jwk.Import(raw)
if err != nil {
fmt.Printf("failed to create symmetric key: %s\n", err)
return
}
if _, ok := key.(jwk.SymmetricKey); !ok {
fmt.Printf("expected jwk.SymmetricKey, got %T\n", key)
return
}
key.Set(jwk.KeyIDKey, "mykey")
buf, err := json.MarshalIndent(key, "", " ")
if err != nil {
fmt.Printf("failed to marshal key into JSON: %s\n", err)
return
}
fmt.Printf("%s\n", buf)
// OUTPUT:
// {
// "k": "MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODlBQkNERUY",
// "kid": "mykey",
// "kty": "oct"
// }
}
```
source: [examples/jwk_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwk_example_test.go)
<!-- END INCLUDE -->
+362
View File
@@ -0,0 +1,362 @@
package jwk
import (
"context"
"fmt"
"io"
"net/http"
"github.com/lestrrat-go/httprc/v3"
)
type HTTPClient = httprc.HTTPClient
type ErrorSink = httprc.ErrorSink
type TraceSink = httprc.TraceSink
// Cache is a container built on top of github.com/lestrrat-go/httprc/v3
// that keeps track of Set object by their source URLs.
// The Set objects are stored in memory, and are refreshed automatically
// behind the scenes.
//
// Before retrieving the Set objects, the user must pre-register the
// URLs they intend to use by calling `Register()`
//
// c := jwk.NewCache(ctx, httprc.NewClient())
// c.Register(ctx, url, options...)
//
// Once registered, you can call `Get()` to retrieve the Set object.
//
// All JWKS objects that are retrieved via this mechanism should be
// treated read-only, as they are shared among all consumers, as well
// as the `jwk.Cache` object.
//
// There are cases where `jwk.Cache` and `jwk.CachedSet` should and
// should not be used.
//
// First and foremost, do NOT use a cache for those JWKS objects that
// need constant checking. For example, unreliable or user-provided JWKS (i.e. those
// JWKS that are not from a well-known provider) should not be fetched
// through a `jwk.Cache` or `jwk.CachedSet`.
//
// For example, if you have a flaky JWKS server for development
// that can go down often, you should consider alternatives such as
// providing `http.Client` with a caching `http.RoundTripper` configured
// (see `jwk.WithHTTPClient`), setting up a reverse proxy, etc.
// These techniques allow you to set up a more robust way to both cache
// and report precise causes of the problems than using `jwk.Cache` or
// `jwk.CachedSet`. If you handle the caching at the HTTP level like this,
// you will be able to use a simple `jwk.Fetch` call and not worry about the cache.
//
// User-provided JWKS objects may also be problematic, as it may go down
// unexpectedly (and frequently!), and it will be hard to detect when
// the URLs or its contents are swapped.
//
// A good use-case for `jwk.Cache` and `jwk.CachedSet` are for "stable"
// JWKS objects.
//
// When we say "stable", we are thinking of JWKS that should mostly be
// ALWAYS available. A good example are those JWKS objects provided by
// major cloud providers such as Google Cloud, AWS, or Azure.
// Stable JWKS may still experience intermittent network connectivity problems,
// but you can expect that they will eventually recover in relatively
// short period of time. They rarely change URLs, and the contents are
// expected to be valid or otherwise it would cause havoc to those providers
//
// We also know that these stable JWKS objects are rotated periodically,
// which is a perfect use for `jwk.Cache` and `jwk.CachedSet`. The caches
// can be configured to periodically refresh the JWKS thereby keeping them
// fresh without extra intervention from the developer.
//
// Notice that for these recommended use-cases the requirement to check
// the validity or the availability of the JWKS objects are non-existent,
// as it is expected that they will be available and will be valid. The
// caching mechanism can hide intermittent connectivity problems as well
// as keep the objects mostly fresh.
type Cache struct {
ctrl httprc.Controller
}
// Transformer is a specialized version of `httprc.Transformer` that implements
// conversion from a `http.Response` object to a `jwk.Set` object. Use this in
// conjection with `httprc.NewResource` to create a `httprc.Resource` object
// to auto-update `jwk.Set` objects.
type Transformer struct {
parseOptions []ParseOption
}
func (t Transformer) Transform(_ context.Context, res *http.Response) (Set, error) {
buf, err := io.ReadAll(res.Body)
if err != nil {
return nil, fmt.Errorf(`failed to read response body status: %w`, err)
}
set, err := Parse(buf, t.parseOptions...)
if err != nil {
return nil, fmt.Errorf(`failed to parse JWK set at %q: %w`, res.Request.URL.String(), err)
}
return set, nil
}
// NewCache creates a new `jwk.Cache` object.
//
// Under the hood, `jwk.Cache` uses `httprc.Client` manage the
// fetching and caching of JWKS objects, and thus spawns multiple goroutines
// per `jwk.Cache` object.
//
// The provided `httprc.Client` object must NOT be started prior to
// passing it to `jwk.NewCache`. The `jwk.Cache` object will start
// the `httprc.Client` object on its own.
func NewCache(ctx context.Context, client *httprc.Client) (*Cache, error) {
ctrl, err := client.Start(ctx)
if err != nil {
return nil, fmt.Errorf(`failed to start httprc.Client: %w`, err)
}
return &Cache{
ctrl: ctrl,
}, nil
}
// Register registers a URL to be managed by the cache. URLs must
// be registered before issuing `Get`
//
// The `Register` method is a thin wrapper around `(httprc.Controller).Add`
func (c *Cache) Register(ctx context.Context, u string, options ...RegisterOption) error {
var parseOptions []ParseOption
var resourceOptions []httprc.NewResourceOption
waitReady := true
for _, option := range options {
switch option := option.(type) {
case ParseOption:
parseOptions = append(parseOptions, option)
case ResourceOption:
var v httprc.NewResourceOption
if err := option.Value(&v); err != nil {
return fmt.Errorf(`failed to retrieve NewResourceOption option value: %w`, err)
}
resourceOptions = append(resourceOptions, v)
default:
switch option.Ident() {
case identHTTPClient{}:
var cli HTTPClient
if err := option.Value(&cli); err != nil {
return fmt.Errorf(`failed to retrieve HTTPClient option value: %w`, err)
}
resourceOptions = append(resourceOptions, httprc.WithHTTPClient(cli))
case identWaitReady{}:
if err := option.Value(&waitReady); err != nil {
return fmt.Errorf(`failed to retrieve WaitReady option value: %w`, err)
}
}
}
}
r, err := httprc.NewResource[Set](u, &Transformer{
parseOptions: parseOptions,
}, resourceOptions...)
if err != nil {
return fmt.Errorf(`failed to create httprc.Resource: %w`, err)
}
if err := c.ctrl.Add(ctx, r, httprc.WithWaitReady(waitReady)); err != nil {
return fmt.Errorf(`failed to add resource to httprc.Client: %w`, err)
}
return nil
}
// LookupResource returns the `httprc.Resource` object associated with the
// given URL `u`. If the URL has not been registered, an error is returned.
func (c *Cache) LookupResource(ctx context.Context, u string) (*httprc.ResourceBase[Set], error) {
r, err := c.ctrl.Lookup(ctx, u)
if err != nil {
return nil, fmt.Errorf(`failed to lookup resource %q: %w`, u, err)
}
//nolint:forcetypeassert
return r.(*httprc.ResourceBase[Set]), nil
}
func (c *Cache) Lookup(ctx context.Context, u string) (Set, error) {
r, err := c.LookupResource(ctx, u)
if err != nil {
return nil, fmt.Errorf(`failed to lookup resource %q: %w`, u, err)
}
set := r.Resource()
if set == nil {
return nil, fmt.Errorf(`resource %q is not ready`, u)
}
return set, nil
}
func (c *Cache) Ready(ctx context.Context, u string) bool {
r, err := c.LookupResource(ctx, u)
if err != nil {
return false
}
if err := r.Ready(ctx); err != nil {
return false
}
return true
}
// Refresh is identical to Get(), except it always fetches the
// specified resource anew, and updates the cached content
//
// Please refer to the documentation for `(httprc.Cache).Refresh` for
// more details
func (c *Cache) Refresh(ctx context.Context, u string) (Set, error) {
if err := c.ctrl.Refresh(ctx, u); err != nil {
return nil, fmt.Errorf(`failed to refresh resource %q: %w`, u, err)
}
return c.Lookup(ctx, u)
}
// IsRegistered returns true if the given URL `u` has already been registered
// in the cache.
func (c *Cache) IsRegistered(ctx context.Context, u string) bool {
_, err := c.LookupResource(ctx, u)
return err == nil
}
// Unregister removes the given URL `u` from the cache.
func (c *Cache) Unregister(ctx context.Context, u string) error {
return c.ctrl.Remove(ctx, u)
}
func (c *Cache) Shutdown(ctx context.Context) error {
return c.ctrl.ShutdownContext(ctx)
}
// CachedSet is a thin shim over jwk.Cache that allows the user to cloak
// jwk.Cache as if it's a `jwk.Set`. Behind the scenes, the `jwk.Set` is
// retrieved from the `jwk.Cache` for every operation.
//
// Since `jwk.CachedSet` always deals with a cached version of the `jwk.Set`,
// all operations that mutate the object (such as AddKey(), RemoveKey(), et. al)
// are no-ops and return an error.
//
// Note that since this is a utility shim over `jwk.Cache`, you _will_ lose
// the ability to control the finer details (such as controlling how long to
// wait for in case of a fetch failure using `context.Context`)
//
// Make sure that you read the documentation for `jwk.Cache` as well.
type CachedSet interface {
Set
cached() (Set, error) // used as a marker
}
type cachedSet struct {
r *httprc.ResourceBase[Set]
}
func (c *Cache) CachedSet(u string) (CachedSet, error) {
r, err := c.LookupResource(context.Background(), u)
if err != nil {
return nil, fmt.Errorf(`failed to lookup resource %q: %w`, u, err)
}
return NewCachedSet(r), nil
}
func NewCachedSet(r *httprc.ResourceBase[Set]) CachedSet {
return &cachedSet{
r: r,
}
}
func (cs *cachedSet) cached() (Set, error) {
if err := cs.r.Ready(context.Background()); err != nil {
return nil, fmt.Errorf(`failed to fetch resource: %w`, err)
}
return cs.r.Resource(), nil
}
// AddKey is a no-op for `jwk.CachedSet`, as the `jwk.Set` should be treated read-only
func (*cachedSet) AddKey(_ Key) error {
return fmt.Errorf(`(jwk.Cachedset).AddKey: jwk.CachedSet is immutable`)
}
// Clear is a no-op for `jwk.CachedSet`, as the `jwk.Set` should be treated read-only
func (*cachedSet) Clear() error {
return fmt.Errorf(`(jwk.cachedSet).Clear: jwk.CachedSet is immutable`)
}
// Set is a no-op for `jwk.CachedSet`, as the `jwk.Set` should be treated read-only
func (*cachedSet) Set(_ string, _ any) error {
return fmt.Errorf(`(jwk.cachedSet).Set: jwk.CachedSet is immutable`)
}
// Remove is a no-op for `jwk.CachedSet`, as the `jwk.Set` should be treated read-only
func (*cachedSet) Remove(_ string) error {
// TODO: Remove() should be renamed to Remove(string) error
return fmt.Errorf(`(jwk.cachedSet).Remove: jwk.CachedSet is immutable`)
}
// RemoveKey is a no-op for `jwk.CachedSet`, as the `jwk.Set` should be treated read-only
func (*cachedSet) RemoveKey(_ Key) error {
return fmt.Errorf(`(jwk.cachedSet).RemoveKey: jwk.CachedSet is immutable`)
}
func (cs *cachedSet) Clone() (Set, error) {
set, err := cs.cached()
if err != nil {
return nil, fmt.Errorf(`failed to get cached jwk.Set: %w`, err)
}
return set.Clone()
}
// Get returns the value of non-Key field stored in the jwk.Set
func (cs *cachedSet) Get(name string, dst any) error {
set, err := cs.cached()
if err != nil {
return err
}
return set.Get(name, dst)
}
// Key returns the Key at the specified index
func (cs *cachedSet) Key(idx int) (Key, bool) {
set, err := cs.cached()
if err != nil {
return nil, false
}
return set.Key(idx)
}
func (cs *cachedSet) Index(key Key) int {
set, err := cs.cached()
if err != nil {
return -1
}
return set.Index(key)
}
func (cs *cachedSet) Keys() []string {
set, err := cs.cached()
if err != nil {
return nil
}
return set.Keys()
}
func (cs *cachedSet) Len() int {
set, err := cs.cached()
if err != nil {
return -1
}
return set.Len()
}
func (cs *cachedSet) LookupKeyID(kid string) (Key, bool) {
set, err := cs.cached()
if err != nil {
return nil, false
}
return set.LookupKeyID(kid)
}
+399
View File
@@ -0,0 +1,399 @@
package jwk
import (
"crypto/ecdh"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rsa"
"errors"
"fmt"
"math/big"
"reflect"
"sync"
"github.com/lestrrat-go/blackmagic"
"github.com/lestrrat-go/jwx/v3/internal/ecutil"
"github.com/lestrrat-go/jwx/v3/jwa"
)
// # Converting between Raw Keys and `jwk.Key`s
//
// A converter that converts from a raw key to a `jwk.Key` is called a KeyImporter.
// A converter that converts from a `jwk.Key` to a raw key is called a KeyExporter.
var keyImporters = make(map[reflect.Type]KeyImporter)
var keyExporters = make(map[jwa.KeyType][]KeyExporter)
var muKeyImporters sync.RWMutex
var muKeyExporters sync.RWMutex
// RegisterKeyImporter registers a KeyImporter for the given raw key. When `jwk.Import()` is called,
// the library will look up the appropriate KeyImporter for the given raw key type (via `reflect`)
// and execute the KeyImporters in succession until either one of them succeeds, or all of them fail.
func RegisterKeyImporter(from any, conv KeyImporter) {
muKeyImporters.Lock()
defer muKeyImporters.Unlock()
keyImporters[reflect.TypeOf(from)] = conv
}
// RegisterKeyExporter registers a KeyExporter for the given key type. When `key.Raw()` is called,
// the library will look up the appropriate KeyExporter for the given key type and execute the
// KeyExporters in succession until either one of them succeeds, or all of them fail.
func RegisterKeyExporter(kty jwa.KeyType, conv KeyExporter) {
muKeyExporters.Lock()
defer muKeyExporters.Unlock()
convs, ok := keyExporters[kty]
if !ok {
convs = []KeyExporter{conv}
} else {
convs = append([]KeyExporter{conv}, convs...)
}
keyExporters[kty] = convs
}
// KeyImporter is used to convert from a raw key to a `jwk.Key`. mneumonic: from the PoV of the `jwk.Key`,
// we're _importing_ a raw key.
type KeyImporter interface {
// Import takes the raw key to be converted, and returns a `jwk.Key` or an error if the conversion fails.
Import(any) (Key, error)
}
// KeyImportFunc is a convenience type to implement KeyImporter as a function.
type KeyImportFunc func(any) (Key, error)
func (f KeyImportFunc) Import(raw any) (Key, error) {
return f(raw)
}
// KeyExporter is used to convert from a `jwk.Key` to a raw key. mneumonic: from the PoV of the `jwk.Key`,
// we're _exporting_ it to a raw key.
type KeyExporter interface {
// Export takes the `jwk.Key` to be converted, and a hint (the raw key to be converted to).
// The hint is the object that the user requested the result to be assigned to.
// The method should return the converted raw key, or an error if the conversion fails.
//
// Third party modules MUST NOT modifiy the hint object.
//
// When the user calls `key.Export(dst)`, the `dst` object is a _pointer_ to the
// object that the user wants the result to be assigned to, but the converter
// receives the _value_ that this pointer points to, to make it easier to
// detect the type of the result.
//
// Note that the second argument may be an `any` (which means that the
// user has delegated the type detection to the converter).
//
// Export must NOT modify the hint object, and should return jwk.ContinueError
// if the hint object is not compatible with the converter.
Export(Key, any) (any, error)
}
// KeyExportFunc is a convenience type to implement KeyExporter as a function.
type KeyExportFunc func(Key, any) (any, error)
func (f KeyExportFunc) Export(key Key, hint any) (any, error) {
return f(key, hint)
}
func init() {
{
f := KeyImportFunc(rsaPrivateKeyToJWK)
k := rsa.PrivateKey{}
RegisterKeyImporter(k, f)
RegisterKeyImporter(&k, f)
}
{
f := KeyImportFunc(rsaPublicKeyToJWK)
k := rsa.PublicKey{}
RegisterKeyImporter(k, f)
RegisterKeyImporter(&k, f)
}
{
f := KeyImportFunc(ecdsaPrivateKeyToJWK)
k := ecdsa.PrivateKey{}
RegisterKeyImporter(k, f)
RegisterKeyImporter(&k, f)
}
{
f := KeyImportFunc(ecdsaPublicKeyToJWK)
k := ecdsa.PublicKey{}
RegisterKeyImporter(k, f)
RegisterKeyImporter(&k, f)
}
{
f := KeyImportFunc(okpPrivateKeyToJWK)
for _, k := range []any{ed25519.PrivateKey(nil)} {
RegisterKeyImporter(k, f)
}
}
{
f := KeyImportFunc(ecdhPrivateKeyToJWK)
for _, k := range []any{ecdh.PrivateKey{}, &ecdh.PrivateKey{}} {
RegisterKeyImporter(k, f)
}
}
{
f := KeyImportFunc(okpPublicKeyToJWK)
for _, k := range []any{ed25519.PublicKey(nil)} {
RegisterKeyImporter(k, f)
}
}
{
f := KeyImportFunc(ecdhPublicKeyToJWK)
for _, k := range []any{ecdh.PublicKey{}, &ecdh.PublicKey{}} {
RegisterKeyImporter(k, f)
}
}
RegisterKeyImporter([]byte(nil), KeyImportFunc(bytesToKey))
}
func ecdhPrivateKeyToJWK(src any) (Key, error) {
var raw *ecdh.PrivateKey
switch src := src.(type) {
case *ecdh.PrivateKey:
raw = src
case ecdh.PrivateKey:
raw = &src
default:
return nil, fmt.Errorf(`cannot convert key type '%T' to ECDH jwk.Key`, src)
}
switch raw.Curve() {
case ecdh.X25519():
return okpPrivateKeyToJWK(raw)
case ecdh.P256():
return ecdhPrivateKeyToECJWK(raw, elliptic.P256())
case ecdh.P384():
return ecdhPrivateKeyToECJWK(raw, elliptic.P384())
case ecdh.P521():
return ecdhPrivateKeyToECJWK(raw, elliptic.P521())
default:
return nil, fmt.Errorf(`unsupported curve %s`, raw.Curve())
}
}
func ecdhPrivateKeyToECJWK(raw *ecdh.PrivateKey, crv elliptic.Curve) (Key, error) {
pub := raw.PublicKey()
rawpub := pub.Bytes()
size := ecutil.CalculateKeySize(crv)
var x, y, d big.Int
x.SetBytes(rawpub[1 : 1+size])
y.SetBytes(rawpub[1+size:])
d.SetBytes(raw.Bytes())
var ecdsaPriv ecdsa.PrivateKey
ecdsaPriv.Curve = crv
ecdsaPriv.D = &d
ecdsaPriv.X = &x
ecdsaPriv.Y = &y
return ecdsaPrivateKeyToJWK(&ecdsaPriv)
}
func ecdhPublicKeyToJWK(src any) (Key, error) {
var raw *ecdh.PublicKey
switch src := src.(type) {
case *ecdh.PublicKey:
raw = src
case ecdh.PublicKey:
raw = &src
default:
return nil, fmt.Errorf(`cannot convert key type '%T' to ECDH jwk.Key`, src)
}
switch raw.Curve() {
case ecdh.X25519():
return okpPublicKeyToJWK(raw)
case ecdh.P256():
return ecdhPublicKeyToECJWK(raw, elliptic.P256())
case ecdh.P384():
return ecdhPublicKeyToECJWK(raw, elliptic.P384())
case ecdh.P521():
return ecdhPublicKeyToECJWK(raw, elliptic.P521())
default:
return nil, fmt.Errorf(`unsupported curve %s`, raw.Curve())
}
}
func ecdhPublicKeyToECJWK(raw *ecdh.PublicKey, crv elliptic.Curve) (Key, error) {
rawbytes := raw.Bytes()
size := ecutil.CalculateKeySize(crv)
var x, y big.Int
x.SetBytes(rawbytes[1 : 1+size])
y.SetBytes(rawbytes[1+size:])
var ecdsaPriv ecdsa.PublicKey
ecdsaPriv.Curve = crv
ecdsaPriv.X = &x
ecdsaPriv.Y = &y
return ecdsaPublicKeyToJWK(&ecdsaPriv)
}
// These may seem a bit repetitive and redandunt, but the problem is that
// each key type has its own Import method -- for example, Import(*ecdsa.PrivateKey)
// vs Import(*rsa.PrivateKey), and therefore they can't just be bundled into
// a single function.
func rsaPrivateKeyToJWK(src any) (Key, error) {
var raw *rsa.PrivateKey
switch src := src.(type) {
case *rsa.PrivateKey:
raw = src
case rsa.PrivateKey:
raw = &src
default:
return nil, fmt.Errorf(`cannot convert key type '%T' to RSA jwk.Key`, src)
}
k := newRSAPrivateKey()
if err := k.Import(raw); err != nil {
return nil, fmt.Errorf(`failed to initialize %T from %T: %w`, k, raw, err)
}
return k, nil
}
func rsaPublicKeyToJWK(src any) (Key, error) {
var raw *rsa.PublicKey
switch src := src.(type) {
case *rsa.PublicKey:
raw = src
case rsa.PublicKey:
raw = &src
default:
return nil, fmt.Errorf(`cannot convert key type '%T' to RSA jwk.Key`, src)
}
k := newRSAPublicKey()
if err := k.Import(raw); err != nil {
return nil, fmt.Errorf(`failed to initialize %T from %T: %w`, k, raw, err)
}
return k, nil
}
func ecdsaPrivateKeyToJWK(src any) (Key, error) {
var raw *ecdsa.PrivateKey
switch src := src.(type) {
case *ecdsa.PrivateKey:
raw = src
case ecdsa.PrivateKey:
raw = &src
default:
return nil, fmt.Errorf(`cannot convert key type '%T' to ECDSA jwk.Key`, src)
}
k := newECDSAPrivateKey()
if err := k.Import(raw); err != nil {
return nil, fmt.Errorf(`failed to initialize %T from %T: %w`, k, raw, err)
}
return k, nil
}
func ecdsaPublicKeyToJWK(src any) (Key, error) {
var raw *ecdsa.PublicKey
switch src := src.(type) {
case *ecdsa.PublicKey:
raw = src
case ecdsa.PublicKey:
raw = &src
default:
return nil, fmt.Errorf(`cannot convert key type '%T' to ECDSA jwk.Key`, src)
}
k := newECDSAPublicKey()
if err := k.Import(raw); err != nil {
return nil, fmt.Errorf(`failed to initialize %T from %T: %w`, k, raw, err)
}
return k, nil
}
func okpPrivateKeyToJWK(src any) (Key, error) {
var raw any
switch src.(type) {
case ed25519.PrivateKey, *ecdh.PrivateKey:
raw = src
case ecdh.PrivateKey:
raw = &src
default:
return nil, fmt.Errorf(`cannot convert key type '%T' to OKP jwk.Key`, src)
}
k := newOKPPrivateKey()
if err := k.Import(raw); err != nil {
return nil, fmt.Errorf(`failed to initialize %T from %T: %w`, k, raw, err)
}
return k, nil
}
func okpPublicKeyToJWK(src any) (Key, error) {
var raw any
switch src.(type) {
case ed25519.PublicKey, *ecdh.PublicKey:
raw = src
case ecdh.PublicKey:
raw = &src
default:
return nil, fmt.Errorf(`jwk: convert raw to OKP jwk.Key: cannot convert key type '%T' to OKP jwk.Key`, src)
}
k := newOKPPublicKey()
if err := k.Import(raw); err != nil {
return nil, fmt.Errorf(`failed to initialize %T from %T: %w`, k, raw, err)
}
return k, nil
}
func bytesToKey(src any) (Key, error) {
var raw []byte
switch src := src.(type) {
case []byte:
raw = src
default:
return nil, fmt.Errorf(`cannot convert key type '%T' to symmetric jwk.Key`, src)
}
k := newSymmetricKey()
if err := k.Import(raw); err != nil {
return nil, fmt.Errorf(`failed to initialize %T from %T: %w`, k, raw, err)
}
return k, nil
}
// Export converts a `jwk.Key` to a Export key. The dst argument must be a pointer to the
// object that the user wants the result to be assigned to.
//
// Normally you would pass a pointer to the zero value of the raw key type
// such as &(*rsa.PrivateKey) or &(*ecdsa.PublicKey), which gets assigned
// the converted key.
//
// If you do not know the exact type of a jwk.Key before attempting
// to obtain the raw key, you can simply pass a pointer to an
// empty interface as the second argument
//
// If you already know the exact type, it is recommended that you
// pass a pointer to the zero value of the actual key type for efficiency.
//
// Be careful when/if you are using a third party key type that implements
// the `jwk.Key` interface, as the first argument. This function tries hard
// to Do The Right Thing, but it is not guaranteed to work in all cases,
// especially when the object implements the `jwk.Key` interface via
// embedding.
func Export(key Key, dst any) error {
// dst better be a pointer
rv := reflect.ValueOf(dst)
if rv.Kind() != reflect.Ptr {
return fmt.Errorf(`jwk.Export: destination object must be a pointer`)
}
muKeyExporters.RLock()
exporters, ok := keyExporters[key.KeyType()]
muKeyExporters.RUnlock()
if !ok {
return fmt.Errorf(`jwk.Export: no exporters registered for key type '%T'`, key)
}
for _, conv := range exporters {
v, err := conv.Export(key, dst)
if err != nil {
if errors.Is(err, ContinueError()) {
continue
}
return fmt.Errorf(`jwk.Export: failed to export jwk.Key to raw format: %w`, err)
}
if err := blackmagic.AssignIfCompatible(dst, v); err != nil {
return fmt.Errorf(`jwk.Export: failed to assign key: %w`, err)
}
return nil
}
return fmt.Errorf(`jwk.Export: no suitable exporter found for key type '%T'`, key)
}
+294
View File
@@ -0,0 +1,294 @@
// Package jwk implements JWK as described in https://tools.ietf.org/html/rfc7517
//
// This package implements jwk.Key to represent a single JWK, and jwk.Set to represent
// a set of JWKs.
//
// The `jwk.Key` type is an interface, which hides the underlying implementation for
// each key type. Each key type can further be converted to interfaces for known
// types, such as `jwk.ECDSAPrivateKey`, `jwk.RSAPublicKey`, etc. This may not necessarily
// work for third party key types (see section on "Registering a key type" below).
//
// Users can create a JWK in two ways. One is to unmarshal a JSON representation of a
// key. The second one is to use `jwk.Import()` to import a raw key and convert it to
// a jwk.Key.
//
// # Simple Usage
//
// You can parse a JWK from a JSON payload:
//
// jwk.ParseKey([]byte(`{"kty":"EC",...}`))
//
// You can go back and forth between raw key types and JWKs:
//
// jwkKey, _ := jwk.Import(rsaPrivateKey)
// var rawKey *rsa.PRrivateKey
// jwkKey.Raw(&rawKey)
//
// You can use them to sign/verify/encrypt/decrypt:
//
// jws.Sign([]byte(`...`), jws.WithKey(jwa.RS256, jwkKey))
// jwe.Encrypt([]byte(`...`), jwe.WithKey(jwa.RSA_OAEP, jwkKey))
//
// See examples/jwk_parse_example_test.go and other files in the exmaples/ directory for more.
//
// # Advanced Usage: Registering a custom key type and conversion routines
//
// Caveat Emptor: Functionality around registering keys
// (KeyProbe/KeyParser/KeyImporter/KeyExporter) should be considered experimental.
// While we expect that the functionality itself will remain, the API may
// change in backward incompatible ways, even during minor version
// releases.
//
// ## tl;dr
//
// * KeyProbe: Used for parsing JWKs in JSON format. Probes hint fields to be used for later parsing by KeyParser
// * KeyParser: Used for parsing JWKs in JSON format. Parses the JSON payload into a jwk.Key using the KeyProbe as hint
// * KeyImporter: Used for converting raw key into jwk.Key.
// * KeyExporter: Used for converting jwk.Key into raw key.
//
// ## Overview
//
// You can add the ability to use a JWK type that this library does not
// implement out of the box. You can do this by registering your own
// KeyParser, KeyImporter, and KeyExporter instances.
//
// func init() {
// jwk.RegiserProbeField(reflect.StructField{Name: "SomeHint", Type: reflect.TypeOf(""), Tag: `json:"some_hint"`})
// jwk.RegisterKeyParser(&MyKeyParser{})
// jwk.RegisterKeyImporter(&MyKeyImporter{})
// jwk.RegisterKeyExporter(&MyKeyExporter{})
// }
//
// The KeyParser is used to parse JSON payloads and conver them into a jwk.Key.
// The KeyImporter is used to convert a raw key (e.g. *rsa.PrivateKey, *ecdsa.PrivateKey, etc) into a jwk.Key.
// The KeyExporter is used to convert a jwk.Key into a raw key.
//
// Although we believe the mechanism has been streamline quite a lot, it is also true
// that the entire process of parsing and converting keys are much more convoluted than you might
// think. Please know before hand that if you intend to add support for a new key type,
// it _WILL_ require you to learn this module pretty much in-and-out.
//
// Read on for more explanation.
//
// ## Registering a KeyParser
//
// In order to understand how parsing works, we need to explain how the `jwk.ParseKey()` works.
//
// The first thing that occurs when parsing a key is a partial
// unmarshaling of the payload into a hint / probe object.
//
// Because the `json.Unmarshal` works by calling the `UnmarshalJSON`
// method on a concrete object, we need to create a concrete object first.
// In order/ to create the appropriate Go object, we need to know which concrete
// object to create from the JSON payload, meaning we need to peek into the
// payload and figure out what type of key it is.
//
// In order to do this, we effectively need to parse the JSON payload twice.
// First, we "probe" the payload to figure out what kind of key it is, then
// we parse it again to create the actual key object.
//
// For probing, we create a new "probe" object (KeyProbe, which is not
// directly available to end users) to populate the object with hints from the payload.
// For example, a JWK representing an RSA key would look like:
//
// { "kty": "RSA", "n": ..., "e": ..., ... }
//
// The default KeyProbe is constructed to unmarshal "kty" and "d" fields,
// because that is enough information to determine what kind of key to
// construct.
//
// For example, if the payload contains "kty" field with the value "RSA",
// we know that it's an RSA key. If it contains "EC", we know that it's
// an EC key. Furthermore, if the payload contains some value in the "d" field, we can
// also tell that this is a private key, as only private keys need
// this field.
//
// For most cases, the default KeyProbe implementation should be sufficient.
// However, there may be cases in the future where there are new key types
// that require further information. Perhaps you are embedding another hint
// in your JWK to further specify what kind of key it is. In that case, you
// would need to probe more.
//
// Normally you can only change how an object is unmarshaled by specifying
// JSON tags when defining a struct, but we use `reflect` package capabilities
// to create an object dynamically, which is shared among all parsing operations.
//
// To add a new field to be probed, you need to register a new `reflect.StructField`
// object that has all of the information. For example, the code below would
// register a field named "MyHint" that is of type string, and has a JSON tag
// of "my_hint".
//
// jwk.RegisterProbeField(reflect.StructField{Name: "MyHint", Type: reflect.TypeOf(""), Tag: `json:"my_hint"`})
//
// The value of this field can be retrieved by calling `Get()` method on the
// KeyProbe object (from the `KeyParser`'s `ParseKey()` method discussed later)
//
// var myhint string
// _ = probe.Get("MyHint", &myhint)
//
// var kty string
// _ = probe.Get("Kty", &kty)
//
// This mechanism allows you to be flexible when trying to determine the key type
// to instantiate.
//
// ## Parse via the KeyParser
//
// When `jwk.Parse` / `jwk.ParseKey` is called, the library will first probe
// the payload as discussed above.
//
// Once the probe is done, the library will iterate over the registered parsers
// and attempt to parse the key by calling their `ParseKey()` methods.
//
// The parsers will be called in reverse order that they were registered.
// This means that it will try all parsers that were registered by third
// parties, and once those are exhausted, the default parser will be used.
//
// Each parser's `ParseKey()“ method will receive three arguments: the probe object, a
// KeyUnmarshaler, and the raw payload. The probe object can be used
// as a hint to determine what kind of key to instantiate. An example
// pseudocode may look like this:
//
// var kty string
// _ = probe.Get("Kty", &kty)
// switch kty {
// case "RSA":
// // create an RSA key
// case "EC":
// // create an EC key
// ...
// }
//
// The `KeyUnmarshaler` is a thin wrapper around `json.Unmarshal`. It works almost
// identical to `json.Unmarshal`, but it allows us to add extra magic that is
// specific to this library (which users do not need to be aware of) before calling
// the actual `json.Unmarshal`. Please use the `KeyUnmarshaler` to unmarshal JWKs instead of `json.Unmarshal`.
//
// Putting it all together, the boiler plate for registering a new parser may look like this:
//
// func init() {
// jwk.RegisterFieldProbe(reflect.StructField{Name: "MyHint", Type: reflect.TypeOf(""), Tag: `json:"my_hint"`})
// jwk.RegisterParser(&MyKeyParser{})
// }
//
// type MyKeyParser struct { ... }
// func(*MyKeyParser) ParseKey(rawProbe *KeyProbe, unmarshaler KeyUnmarshaler, data []byte) (jwk.Key, error) {
// // Create concrete type
// var hint string
// if err := probe.Get("MyHint", &hint); err != nil {
// // if it doesn't have the `my_hint` field, it probably means
// // it's not for us, so we return ContinueParseError so that
// // the next parser can pick it up
// return nil, jwk.ContinueParseError()
// }
//
// // Use hint to determine concrete key type
// var key jwk.Key
// switch hint {
// case ...:
// key = = myNewAwesomeJWK()
// ...
// }
//
// return unmarshaler.Unmarshal(data, key)
// }
//
// ## Registering KeyImporter/KeyExporter
//
// If you are going to do anything with the key that was parsed by your KeyParser,
// you will need to tell the library how to convert back and forth between
// raw keys and JWKs. Conversion from raw keys to jwk.Keys are done by KeyImporters,
// and conversion from jwk.Keys to raw keys are done by KeyExporters.
//
// ## Using jwk.Import() using KeyImporter
//
// Each KeyImporter is hooked to run against a specific raw key type.
//
// When `jwk.Import()` is called, the library will iterate over all registered
// KeyImporters for the specified raw key type, and attempt to convert the raw
// key to a JWK by calling the `Import()` method on each KeyImporter.
//
// The KeyImporter's `Import()` method will receive the raw key to be converted,
// and should return a JWK or an error if the conversion fails, or the return
// `jwk.ContinueError()` if the specified raw key cannot be handled by ths/ KeyImporter.
//
// Once a KeyImporter is available, you will be able to pass the raw key to `jwk.Import()`.
// The following example shows how you might register a KeyImporter for a hypotheical
// mypkg.SuperSecretKey:
//
// jwk.RegisterKeyImporter(&mypkg.SuperSecretKey{}, jwk.KeyImportFunc(imnportSuperSecretKey))
//
// func importSuperSecretKey(key any) (jwk.Key, error) {
// mykey, ok := key.(*mypkg.SuperSecretKey)
// if !ok {
// // You must return jwk.ContinueError here, or otherwise
// // processing will stop with an error
// return nil, fmt.Errorf("invalid key type %T for importer: %w", key, jwk.ContinueError())
// }
//
// return mypkg.SuperSecretJWK{ .... }, nil // You could reuse existing JWK types if you can
// }
//
// ## Registering a KeyExporter
//
// KeyExporters are the opposite of KeyImporters: they convert a JWK to a raw key when `key.Raw(...)` is
// called. If you intend to use `key.Raw(...)` for a JWK created using one of your KeyImporters,
// you will also
//
// KeyExporters are registered by key type. For example, if you want to register a KeyExporter for
// RSA keys, you would do:
//
// jwk.RegisterKeyExporter(jwa.RSA, jwk.KeyExportFunc(exportRSAKey))
//
// For a given JWK, it will be passed a "destination" object to store the exported raw key. For example,
// an RSA-based private JWK can be exported to a `*rsa.PrivateKey` or to a `*any`, but not
// to a `*ecdsa.PrivateKey`:
//
// var dst *rsa.PrivateKey
// key.Raw(&dst) // OK
//
// var dst any
// key.Raw(&dst) // OK
//
// var dst *ecdsa.PrivateKey
// key.Raw(&dst) // Error, if key is an RSA key
//
// You will need to handle this distinction yourself in your KeyImporter. For example, certain
// elliptic curve keys can be expressed in JWK in the same format, minus the "kty". In that case
// you will need to check for the type of the destination object and return an error if it is
// not compatible with your key.
//
// var raw mypkg.PrivateKey // assume a hypothetical private key type using a different curve than standard ones lie P-256
// key, _ := jwk.Import(raw)
// // key could be jwk.ECDSAPrivateKey, with different curve than P-256
//
// var dst *ecdsa.PrivateKey
// key.Raw(&dst) // your KeyImporter will be called with *ecdsa.PrivateKey, which is not compatible with your key
//
// To implement this your code should look like the following:
//
// jwk.RegisterKeyExporter(jwk.EC, jwk.KeyExportFunc(exportMyKey))
//
// func exportMyKey(key jwk.Key, hint any) (any, error) {
// // check if the type of object in hint is compatible with your key
// switch hint.(type) {
// case *mypkg.PrivateKey, *any:
// // OK, we can proceed
// default:
// // Not compatible, return jwk.ContinueError
// return nil, jwk.ContinueError()
// }
//
// // key is a jwk.ECDSAPrivateKey or jwk.ECDSAPublicKey
// switch key := key.(type) {
// case jwk.ECDSAPrivateKey:
// // convert key to mypkg.PrivateKey
// case jwk.ECDSAPublicKey:
// // convert key to mypkg.PublicKey
// default:
// // Not compatible, return jwk.ContinueError
// return nil, jwk.ContinueError()
// }
// return ..., nil
// }
package jwk
+402
View File
@@ -0,0 +1,402 @@
package jwk
import (
"crypto"
"crypto/ecdh"
"crypto/ecdsa"
"crypto/elliptic"
"fmt"
"math/big"
"reflect"
"github.com/lestrrat-go/jwx/v3/internal/base64"
"github.com/lestrrat-go/jwx/v3/internal/ecutil"
"github.com/lestrrat-go/jwx/v3/jwa"
ourecdsa "github.com/lestrrat-go/jwx/v3/jwk/ecdsa"
)
func init() {
ourecdsa.RegisterCurve(jwa.P256(), elliptic.P256())
ourecdsa.RegisterCurve(jwa.P384(), elliptic.P384())
ourecdsa.RegisterCurve(jwa.P521(), elliptic.P521())
RegisterKeyExporter(jwa.EC(), KeyExportFunc(ecdsaJWKToRaw))
}
func (k *ecdsaPublicKey) Import(rawKey *ecdsa.PublicKey) error {
k.mu.Lock()
defer k.mu.Unlock()
if rawKey.X == nil {
return fmt.Errorf(`invalid ecdsa.PublicKey`)
}
if rawKey.Y == nil {
return fmt.Errorf(`invalid ecdsa.PublicKey`)
}
xbuf := ecutil.AllocECPointBuffer(rawKey.X, rawKey.Curve)
ybuf := ecutil.AllocECPointBuffer(rawKey.Y, rawKey.Curve)
defer ecutil.ReleaseECPointBuffer(xbuf)
defer ecutil.ReleaseECPointBuffer(ybuf)
k.x = make([]byte, len(xbuf))
copy(k.x, xbuf)
k.y = make([]byte, len(ybuf))
copy(k.y, ybuf)
alg, err := ourecdsa.AlgorithmFromCurve(rawKey.Curve)
if err != nil {
return fmt.Errorf(`jwk: failed to get algorithm for converting ECDSA public key to JWK: %w`, err)
}
k.crv = &alg
return nil
}
func (k *ecdsaPrivateKey) Import(rawKey *ecdsa.PrivateKey) error {
k.mu.Lock()
defer k.mu.Unlock()
if rawKey.PublicKey.X == nil {
return fmt.Errorf(`invalid ecdsa.PrivateKey`)
}
if rawKey.PublicKey.Y == nil {
return fmt.Errorf(`invalid ecdsa.PrivateKey`)
}
if rawKey.D == nil {
return fmt.Errorf(`invalid ecdsa.PrivateKey`)
}
xbuf := ecutil.AllocECPointBuffer(rawKey.PublicKey.X, rawKey.Curve)
ybuf := ecutil.AllocECPointBuffer(rawKey.PublicKey.Y, rawKey.Curve)
dbuf := ecutil.AllocECPointBuffer(rawKey.D, rawKey.Curve)
defer ecutil.ReleaseECPointBuffer(xbuf)
defer ecutil.ReleaseECPointBuffer(ybuf)
defer ecutil.ReleaseECPointBuffer(dbuf)
k.x = make([]byte, len(xbuf))
copy(k.x, xbuf)
k.y = make([]byte, len(ybuf))
copy(k.y, ybuf)
k.d = make([]byte, len(dbuf))
copy(k.d, dbuf)
alg, err := ourecdsa.AlgorithmFromCurve(rawKey.Curve)
if err != nil {
return fmt.Errorf(`jwk: failed to get algorithm for converting ECDSA private key to JWK: %w`, err)
}
k.crv = &alg
return nil
}
func buildECDSAPublicKey(alg jwa.EllipticCurveAlgorithm, xbuf, ybuf []byte) (*ecdsa.PublicKey, error) {
crv, err := ourecdsa.CurveFromAlgorithm(alg)
if err != nil {
return nil, fmt.Errorf(`jwk: failed to get algorithm for ECDSA public key: %w`, err)
}
var x, y big.Int
x.SetBytes(xbuf)
y.SetBytes(ybuf)
return &ecdsa.PublicKey{Curve: crv, X: &x, Y: &y}, nil
}
func buildECDHPublicKey(alg jwa.EllipticCurveAlgorithm, xbuf, ybuf []byte) (*ecdh.PublicKey, error) {
var ecdhcrv ecdh.Curve
switch alg {
case jwa.X25519():
ecdhcrv = ecdh.X25519()
case jwa.P256():
ecdhcrv = ecdh.P256()
case jwa.P384():
ecdhcrv = ecdh.P384()
case jwa.P521():
ecdhcrv = ecdh.P521()
default:
return nil, fmt.Errorf(`jwk: unsupported ECDH curve %s`, alg)
}
return ecdhcrv.NewPublicKey(append([]byte{0x04}, append(xbuf, ybuf...)...))
}
func buildECDHPrivateKey(alg jwa.EllipticCurveAlgorithm, dbuf []byte) (*ecdh.PrivateKey, error) {
var ecdhcrv ecdh.Curve
switch alg {
case jwa.X25519():
ecdhcrv = ecdh.X25519()
case jwa.P256():
ecdhcrv = ecdh.P256()
case jwa.P384():
ecdhcrv = ecdh.P384()
case jwa.P521():
ecdhcrv = ecdh.P521()
default:
return nil, fmt.Errorf(`jwk: unsupported ECDH curve %s`, alg)
}
return ecdhcrv.NewPrivateKey(dbuf)
}
var ecdsaConvertibleTypes = []reflect.Type{
reflect.TypeFor[ECDSAPrivateKey](),
reflect.TypeFor[ECDSAPublicKey](),
}
func ecdsaJWKToRaw(keyif Key, hint any) (any, error) {
var isECDH bool
extracted, err := extractEmbeddedKey(keyif, ecdsaConvertibleTypes)
if err != nil {
return nil, fmt.Errorf(`jwk: failed to extract embedded key: %w`, err)
}
switch k := extracted.(type) {
case ECDSAPrivateKey:
switch hint.(type) {
case ecdsa.PrivateKey, *ecdsa.PrivateKey:
case ecdh.PrivateKey, *ecdh.PrivateKey:
isECDH = true
default:
rv := reflect.ValueOf(hint)
//nolint:revive
if rv.Kind() == reflect.Ptr && rv.Elem().Kind() == reflect.Interface {
// pointer to an interface value, presumably they want us to dynamically
// create an object of the right type
} else {
return nil, fmt.Errorf(`invalid destination object type %T: %w`, hint, ContinueError())
}
}
locker, ok := k.(rlocker)
if ok {
locker.rlock()
defer locker.runlock()
}
crv, ok := k.Crv()
if !ok {
return nil, fmt.Errorf(`missing "crv" field`)
}
if isECDH {
d, ok := k.D()
if !ok {
return nil, fmt.Errorf(`missing "d" field`)
}
return buildECDHPrivateKey(crv, d)
}
x, ok := k.X()
if !ok {
return nil, fmt.Errorf(`missing "x" field`)
}
y, ok := k.Y()
if !ok {
return nil, fmt.Errorf(`missing "y" field`)
}
pubk, err := buildECDSAPublicKey(crv, x, y)
if err != nil {
return nil, fmt.Errorf(`failed to build public key: %w`, err)
}
var key ecdsa.PrivateKey
var d big.Int
origD, ok := k.D()
if !ok {
return nil, fmt.Errorf(`missing "d" field`)
}
d.SetBytes(origD)
key.D = &d
key.PublicKey = *pubk
return &key, nil
case ECDSAPublicKey:
switch hint.(type) {
case ecdsa.PublicKey, *ecdsa.PublicKey:
case ecdh.PublicKey, *ecdh.PublicKey:
isECDH = true
default:
rv := reflect.ValueOf(hint)
//nolint:revive
if rv.Kind() == reflect.Ptr && rv.Elem().Kind() == reflect.Interface {
// pointer to an interface value, presumably they want us to dynamically
// create an object of the right type
} else {
return nil, fmt.Errorf(`invalid destination object type %T: %w`, hint, ContinueError())
}
}
locker, ok := k.(rlocker)
if ok {
locker.rlock()
defer locker.runlock()
}
crv, ok := k.Crv()
if !ok {
return nil, fmt.Errorf(`missing "crv" field`)
}
x, ok := k.X()
if !ok {
return nil, fmt.Errorf(`missing "x" field`)
}
y, ok := k.Y()
if !ok {
return nil, fmt.Errorf(`missing "y" field`)
}
if isECDH {
return buildECDHPublicKey(crv, x, y)
}
return buildECDSAPublicKey(crv, x, y)
default:
return nil, ContinueError()
}
}
func makeECDSAPublicKey(src Key) (Key, error) {
newKey := newECDSAPublicKey()
// Iterate and copy everything except for the bits that should not be in the public key
for _, k := range src.Keys() {
switch k {
case ECDSADKey:
continue
default:
var v any
if err := src.Get(k, &v); err != nil {
return nil, fmt.Errorf(`ecdsa: makeECDSAPublicKey: failed to get field %q: %w`, k, err)
}
if err := newKey.Set(k, v); err != nil {
return nil, fmt.Errorf(`ecdsa: makeECDSAPublicKey: failed to set field %q: %w`, k, err)
}
}
}
return newKey, nil
}
func (k *ecdsaPrivateKey) PublicKey() (Key, error) {
return makeECDSAPublicKey(k)
}
func (k *ecdsaPublicKey) PublicKey() (Key, error) {
return makeECDSAPublicKey(k)
}
func ecdsaThumbprint(hash crypto.Hash, crv, x, y string) []byte {
h := hash.New()
fmt.Fprint(h, `{"crv":"`)
fmt.Fprint(h, crv)
fmt.Fprint(h, `","kty":"EC","x":"`)
fmt.Fprint(h, x)
fmt.Fprint(h, `","y":"`)
fmt.Fprint(h, y)
fmt.Fprint(h, `"}`)
return h.Sum(nil)
}
// Thumbprint returns the JWK thumbprint using the indicated
// hashing algorithm, according to RFC 7638
func (k ecdsaPublicKey) Thumbprint(hash crypto.Hash) ([]byte, error) {
k.mu.RLock()
defer k.mu.RUnlock()
var key ecdsa.PublicKey
if err := Export(&k, &key); err != nil {
return nil, fmt.Errorf(`failed to export ecdsa.PublicKey for thumbprint generation: %w`, err)
}
xbuf := ecutil.AllocECPointBuffer(key.X, key.Curve)
ybuf := ecutil.AllocECPointBuffer(key.Y, key.Curve)
defer ecutil.ReleaseECPointBuffer(xbuf)
defer ecutil.ReleaseECPointBuffer(ybuf)
return ecdsaThumbprint(
hash,
key.Curve.Params().Name,
base64.EncodeToString(xbuf),
base64.EncodeToString(ybuf),
), nil
}
// Thumbprint returns the JWK thumbprint using the indicated
// hashing algorithm, according to RFC 7638
func (k ecdsaPrivateKey) Thumbprint(hash crypto.Hash) ([]byte, error) {
k.mu.RLock()
defer k.mu.RUnlock()
var key ecdsa.PrivateKey
if err := Export(&k, &key); err != nil {
return nil, fmt.Errorf(`failed to export ecdsa.PrivateKey for thumbprint generation: %w`, err)
}
xbuf := ecutil.AllocECPointBuffer(key.X, key.Curve)
ybuf := ecutil.AllocECPointBuffer(key.Y, key.Curve)
defer ecutil.ReleaseECPointBuffer(xbuf)
defer ecutil.ReleaseECPointBuffer(ybuf)
return ecdsaThumbprint(
hash,
key.Curve.Params().Name,
base64.EncodeToString(xbuf),
base64.EncodeToString(ybuf),
), nil
}
func ecdsaValidateKey(k interface {
Crv() (jwa.EllipticCurveAlgorithm, bool)
X() ([]byte, bool)
Y() ([]byte, bool)
}, checkPrivate bool) error {
crvtyp, ok := k.Crv()
if !ok {
return fmt.Errorf(`missing "crv" field`)
}
crv, err := ourecdsa.CurveFromAlgorithm(crvtyp)
if err != nil {
return fmt.Errorf(`invalid curve algorithm %q: %w`, crvtyp, err)
}
keySize := ecutil.CalculateKeySize(crv)
if x, ok := k.X(); !ok || len(x) != keySize {
return fmt.Errorf(`invalid "x" length (%d) for curve %q`, len(x), crv.Params().Name)
}
if y, ok := k.Y(); !ok || len(y) != keySize {
return fmt.Errorf(`invalid "y" length (%d) for curve %q`, len(y), crv.Params().Name)
}
if checkPrivate {
if priv, ok := k.(keyWithD); ok {
if d, ok := priv.D(); !ok || len(d) != keySize {
return fmt.Errorf(`invalid "d" length (%d) for curve %q`, len(d), crv.Params().Name)
}
} else {
return fmt.Errorf(`missing "d" value`)
}
}
return nil
}
func (k *ecdsaPrivateKey) Validate() error {
if err := ecdsaValidateKey(k, true); err != nil {
return NewKeyValidationError(fmt.Errorf(`jwk.ECDSAPrivateKey: %w`, err))
}
return nil
}
func (k *ecdsaPublicKey) Validate() error {
if err := ecdsaValidateKey(k, false); err != nil {
return NewKeyValidationError(fmt.Errorf(`jwk.ECDSAPublicKey: %w`, err))
}
return nil
}
+15
View File
@@ -0,0 +1,15 @@
load("@rules_go//go:def.bzl", "go_library")
go_library(
name = "ecdsa",
srcs = ["ecdsa.go"],
importpath = "github.com/lestrrat-go/jwx/v3/jwk/ecdsa",
visibility = ["//visibility:public"],
deps = ["//jwa"],
)
alias(
name = "go_default_library",
actual = ":ecdsa",
visibility = ["//visibility:public"],
)
+76
View File
@@ -0,0 +1,76 @@
package ecdsa
import (
"crypto/elliptic"
"fmt"
"sync"
"github.com/lestrrat-go/jwx/v3/jwa"
)
var muCurves sync.RWMutex
var algToCurveMap map[jwa.EllipticCurveAlgorithm]elliptic.Curve
var curveToAlgMap map[elliptic.Curve]jwa.EllipticCurveAlgorithm
var algList []jwa.EllipticCurveAlgorithm
func init() {
muCurves.Lock()
algToCurveMap = make(map[jwa.EllipticCurveAlgorithm]elliptic.Curve)
curveToAlgMap = make(map[elliptic.Curve]jwa.EllipticCurveAlgorithm)
muCurves.Unlock()
}
// RegisterCurve registers a jwa.EllipticCurveAlgorithm constant and its
// corresponding elliptic.Curve object. Users do not need to call this unless
// they are registering a new ECDSA key type
func RegisterCurve(alg jwa.EllipticCurveAlgorithm, crv elliptic.Curve) {
muCurves.Lock()
defer muCurves.Unlock()
algToCurveMap[alg] = crv
curveToAlgMap[crv] = alg
rebuildCurves()
}
func rebuildCurves() {
l := len(algToCurveMap)
if cap(algList) < l {
algList = make([]jwa.EllipticCurveAlgorithm, 0, l)
} else {
algList = algList[:0]
}
for alg := range algToCurveMap {
algList = append(algList, alg)
}
}
// Algorithms returns the list of registered jwa.EllipticCurveAlgorithms
// that ca be used for ECDSA keys.
func Algorithms() []jwa.EllipticCurveAlgorithm {
muCurves.RLock()
defer muCurves.RUnlock()
return algList
}
func AlgorithmFromCurve(crv elliptic.Curve) (jwa.EllipticCurveAlgorithm, error) {
alg, ok := curveToAlgMap[crv]
if !ok {
return jwa.InvalidEllipticCurve(), fmt.Errorf(`unknown elliptic curve: %q`, crv)
}
return alg, nil
}
func CurveFromAlgorithm(alg jwa.EllipticCurveAlgorithm) (elliptic.Curve, error) {
crv, ok := algToCurveMap[alg]
if !ok {
return nil, fmt.Errorf(`unknown elliptic curve algorithm: %q`, alg)
}
return crv, nil
}
func IsCurveAvailable(alg jwa.EllipticCurveAlgorithm) bool {
_, ok := algToCurveMap[alg]
return ok
}
File diff suppressed because it is too large Load Diff
+79
View File
@@ -0,0 +1,79 @@
package jwk
import (
"errors"
"fmt"
)
var cpe = &continueError{}
// ContinueError returns an opaque error that can be returned
// when a `KeyParser`, `KeyImporter`, or `KeyExporter` cannot handle the given payload,
// but would like the process to continue with the next handler.
func ContinueError() error {
return cpe
}
type continueError struct{}
func (e *continueError) Error() string {
return "continue parsing"
}
type importError struct {
error
}
func (e importError) Unwrap() error {
return e.error
}
func (importError) Is(err error) bool {
_, ok := err.(importError)
return ok
}
func importerr(f string, args ...any) error {
return importError{fmt.Errorf(`jwk.Import: `+f, args...)}
}
var errDefaultImportError = importError{errors.New(`import error`)}
func ImportError() error {
return errDefaultImportError
}
type parseError struct {
error
}
func (e parseError) Unwrap() error {
return e.error
}
func (parseError) Is(err error) bool {
_, ok := err.(parseError)
return ok
}
func bparseerr(prefix string, f string, args ...any) error {
return parseError{fmt.Errorf(prefix+`: `+f, args...)}
}
func parseerr(f string, args ...any) error {
return bparseerr(`jwk.Parse`, f, args...)
}
func rparseerr(f string, args ...any) error {
return bparseerr(`jwk.ParseReader`, f, args...)
}
func sparseerr(f string, args ...any) error {
return bparseerr(`jwk.ParseString`, f, args...)
}
var errDefaultParseError = parseError{errors.New(`parse error`)}
func ParseError() error {
return errDefaultParseError
}
+13
View File
@@ -0,0 +1,13 @@
//go:build jwx_es256k
package jwk
import (
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/lestrrat-go/jwx/v3/jwa"
ourecdsa "github.com/lestrrat-go/jwx/v3/jwk/ecdsa"
)
func init() {
ourecdsa.RegisterCurve(jwa.Secp256k1(), secp256k1.S256())
}
+117
View File
@@ -0,0 +1,117 @@
package jwk
import (
"context"
"fmt"
"io"
"net/http"
)
// Fetcher is an interface that represents an object that fetches a JWKS.
// Currently this is only used in the `jws.WithVerifyAuto` option.
//
// Particularly, do not confuse this as the backend to `jwk.Fetch()` function.
// If you need to control how `jwk.Fetch()` implements HTTP requests look into
// providing a custom `http.Client` object via `jwk.WithHTTPClient` option
type Fetcher interface {
Fetch(context.Context, string, ...FetchOption) (Set, error)
}
// FetchFunc describes a type of Fetcher that is represented as a function.
//
// You can use this to wrap functions (e.g. `jwk.Fetch“) as a Fetcher object.
type FetchFunc func(context.Context, string, ...FetchOption) (Set, error)
func (ff FetchFunc) Fetch(ctx context.Context, u string, options ...FetchOption) (Set, error) {
return ff(ctx, u, options...)
}
// CachedFetcher wraps `jwk.Cache` so that it can be used as a `jwk.Fetcher`.
//
// One notable diffence from a general use fetcher is that `jwk.CachedFetcher`
// can only be used with JWKS URLs that have been registered with the cache.
// Please read the documentation fo `(jwk.CachedFetcher).Fetch` for more details.
//
// This object is intended to be used with `jws.WithVerifyAuto` option, specifically
// for a scenario where there is a very small number of JWKS URLs that are trusted
// and used to verify JWS messages. It is NOT meant to be used as a general purpose
// caching fetcher object.
type CachedFetcher struct {
cache *Cache
}
// NewCachedFetcher creates a new `jwk.CachedFetcher` object.
func NewCachedFetcher(cache *Cache) *CachedFetcher {
return &CachedFetcher{cache}
}
// Fetch fetches a JWKS from the cache. If the JWKS URL has not been registered with
// the cache, an error is returned.
func (f *CachedFetcher) Fetch(ctx context.Context, u string, _ ...FetchOption) (Set, error) {
if !f.cache.IsRegistered(ctx, u) {
return nil, fmt.Errorf(`jwk.CachedFetcher: url %q has not been registered`, u)
}
return f.cache.Lookup(ctx, u)
}
// Fetch fetches a JWK resource specified by a URL. The url must be
// pointing to a resource that is supported by `net/http`.
//
// This function is just a wrapper around `net/http` and `jwk.Parse`.
// There is nothing special here, so you are safe to use your own
// mechanism to fetch the JWKS.
//
// If you are using the same `jwk.Set` for long periods of time during
// the lifecycle of your program, and would like to periodically refresh the
// contents of the object with the data at the remote resource,
// consider using `jwk.Cache`, which automatically refreshes
// jwk.Set objects asynchronously.
func Fetch(ctx context.Context, u string, options ...FetchOption) (Set, error) {
var parseOptions []ParseOption
//nolint:revive // I want to keep the type of `wl` as `Whitelist` instead of `InsecureWhitelist`
var wl Whitelist = InsecureWhitelist{}
var client HTTPClient = http.DefaultClient
for _, option := range options {
if parseOpt, ok := option.(ParseOption); ok {
parseOptions = append(parseOptions, parseOpt)
continue
}
switch option.Ident() {
case identHTTPClient{}:
if err := option.Value(&client); err != nil {
return nil, fmt.Errorf(`failed to retrieve HTTPClient option value: %w`, err)
}
case identFetchWhitelist{}:
if err := option.Value(&wl); err != nil {
return nil, fmt.Errorf(`failed to retrieve fetch whitelist option value: %w`, err)
}
}
}
if !wl.IsAllowed(u) {
return nil, fmt.Errorf(`jwk.Fetch: url %q has been rejected by whitelist`, u)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
if err != nil {
return nil, fmt.Errorf(`jwk.Fetch: failed to create new request: %w`, err)
}
res, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf(`jwk.Fetch: request failed: %w`, err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, fmt.Errorf(`jwk.Fetch: request returned status %d, expected 200`, res.StatusCode)
}
buf, err := io.ReadAll(res.Body)
if err != nil {
return nil, fmt.Errorf(`jwk.Fetch: failed to read response body for %q: %w`, u, err)
}
return Parse(buf, parseOptions...)
}
+28
View File
@@ -0,0 +1,28 @@
package jwk
import (
"github.com/lestrrat-go/jwx/v3/transform"
)
// KeyFilter is an interface that allows users to filter JWK key fields.
// It provides two methods: Filter and Reject; Filter returns a new key with only
// the fields that match the filter criteria, while Reject returns a new key with
// only the fields that DO NOT match the filter.
//
// EXPERIMENTAL: This API is experimental and its interface and behavior is
// subject to change in future releases. This API is not subject to semver
// compatibility guarantees.
type KeyFilter interface {
Filter(key Key) (Key, error)
Reject(key Key) (Key, error)
}
// NewFieldNameFilter creates a new FieldNameFilter with the specified field names.
//
// Note that because some JWK fields are associated with the type instead of
// stored as data, this filter will not be able to remove them. An example would
// be the `kty` field: it's associated with the underlying JWK key type, and will
// always be present even if you attempt to remove it.
func NewFieldNameFilter(names ...string) KeyFilter {
return transform.NewNameBasedFilter[Key](names...)
}
+148
View File
@@ -0,0 +1,148 @@
package jwk
import (
"sync"
"github.com/lestrrat-go/jwx/v3/internal/json"
)
// AsymmetricKey describes a Key that represents a key in an asymmetric key pair,
// which in turn can be either a private or a public key. This interface
// allows those keys to be queried if they are one or the other.
type AsymmetricKey interface {
IsPrivate() bool
}
// KeyUsageType is used to denote what this key should be used for
type KeyUsageType string
const (
// ForSignature is the value used in the headers to indicate that
// this key should be used for signatures
ForSignature KeyUsageType = "sig"
// ForEncryption is the value used in the headers to indicate that
// this key should be used for encrypting
ForEncryption KeyUsageType = "enc"
)
type KeyOperation string
type KeyOperationList []KeyOperation
const (
KeyOpSign KeyOperation = "sign" // (compute digital signature or MAC)
KeyOpVerify KeyOperation = "verify" // (verify digital signature or MAC)
KeyOpEncrypt KeyOperation = "encrypt" // (encrypt content)
KeyOpDecrypt KeyOperation = "decrypt" // (decrypt content and validate decryption, if applicable)
KeyOpWrapKey KeyOperation = "wrapKey" // (encrypt key)
KeyOpUnwrapKey KeyOperation = "unwrapKey" // (decrypt key and validate decryption, if applicable)
KeyOpDeriveKey KeyOperation = "deriveKey" // (derive key)
KeyOpDeriveBits KeyOperation = "deriveBits" // (derive bits not to be used as a key)
)
// Set represents JWKS object, a collection of jwk.Key objects.
//
// Sets can be safely converted to and from JSON using the standard
// `"encoding/json".Marshal` and `"encoding/json".Unmarshal`. However,
// if you do not know if the payload contains a single JWK or a JWK set,
// consider using `jwk.Parse()` to always get a `jwk.Set` out of it.
//
// Since v1.2.12, JWK sets with private parameters can be parsed as well.
// Such private parameters can be accessed via the `Field()` method.
// If a resource contains a single JWK instead of a JWK set, private parameters
// are stored in _both_ the resulting `jwk.Set` object and the `jwk.Key` object .
//
//nolint:interfacebloat
type Set interface {
// AddKey adds the specified key. If the key already exists in the set,
// an error is returned.
AddKey(Key) error
// Clear resets the list of keys associated with this set, emptying the
// internal list of `jwk.Key`s, as well as clearing any other non-key
// fields
Clear() error
// Get returns the key at index `idx`. If the index is out of range,
// then the second return value is false.
Key(int) (Key, bool)
// Get returns the value of a private field in the key set.
//
// For the purposes of a key set, any field other than the "keys" field is
// considered to be a private field. In other words, you cannot use this
// method to directly access the list of keys in the set
Get(string, any) error
// Set sets the value of a single field.
//
// This method, which takes an `any`, exists because
// these objects can contain extra _arbitrary_ fields that users can
// specify, and there is no way of knowing what type they could be.
Set(string, any) error
// Remove removes the specified non-key field from the set.
// Keys may not be removed using this method. See RemoveKey for
// removing keys.
Remove(string) error
// Index returns the index where the given key exists, -1 otherwise
Index(Key) int
// Len returns the number of keys in the set
Len() int
// LookupKeyID returns the first key matching the given key id.
//
// The second return value is false if there are no keys matching the key id.
// The set *may* contain multiple keys with the same key id. If you
// need all of them, Len() and Key(int)
//
// This method is meant to be used to lookup a key with a unique ID.
// Bacauseof this, you cannot use this method to lookup keys with an empty key ID
// (i.e. `kid` is not specified, or is an empty string).
LookupKeyID(string) (Key, bool)
// RemoveKey removes the key from the set.
// RemoveKey returns an error when the specified key does not exist
// in set.
RemoveKey(Key) error
// Keys returns the list of keys present in the Set, except for `keys`.
// e.g. if you had `{"keys": ["a", "b"], "c": .., "d": ...}`, this method would
// return `["c", "d"]`. Note that the order of the keys is not guaranteed.
//
// TODO: name is confusing between this and Key()
Keys() []string
// Clone create a new set with identical keys. Keys themselves are not cloned.
Clone() (Set, error)
}
type set struct {
keys []Key
mu sync.RWMutex
dc DecodeCtx
privateParams map[string]any
}
type PublicKeyer interface {
// PublicKey creates the corresponding PublicKey type for this object.
// All fields are copied onto the new public key, except for those that are not allowed.
// Returned value must not be the receiver itself.
PublicKey() (Key, error)
}
type DecodeCtx interface {
json.DecodeCtx
IgnoreParseError() bool
}
type KeyWithDecodeCtx interface {
SetDecodeCtx(DecodeCtx)
DecodeCtx() DecodeCtx
}
// Used internally: It's used to lock a key
type rlocker interface {
rlock()
runlock()
}
+109
View File
@@ -0,0 +1,109 @@
// Code generated by tools/cmd/genjwk/main.go. DO NOT EDIT.
package jwk
import (
"crypto"
"github.com/lestrrat-go/jwx/v3/cert"
"github.com/lestrrat-go/jwx/v3/jwa"
)
const (
KeyTypeKey = "kty"
KeyUsageKey = "use"
KeyOpsKey = "key_ops"
AlgorithmKey = "alg"
KeyIDKey = "kid"
X509URLKey = "x5u"
X509CertChainKey = "x5c"
X509CertThumbprintKey = "x5t"
X509CertThumbprintS256Key = "x5t#S256"
)
// Key defines the minimal interface for each of the
// key types. Their use and implementation differ significantly
// between each key type, so you should use type assertions
// to perform more specific tasks with each key
type Key interface {
// Has returns true if the specified field has a value, even if
// the value is empty-ish (e.g. 0, false, "") as long as it has been
// explicitly set.
Has(string) bool
// Get is used to extract the value of any field, including non-standard fields, out of the key.
//
// The first argument is the name of the field. The second argument is a pointer
// to a variable that will receive the value of the field. The method returns
// an error if the field does not exist, or if the value cannot be assigned to
// the destination variable. Note that a field is considered to "exist" even if
// the value is empty-ish (e.g. 0, false, ""), as long as it is explicitly set.
Get(string, any) error
// Set sets the value of a single field. Note that certain fields,
// notably "kty", cannot be altered, but will not return an error
//
// This method, which takes an `any`, exists because
// these objects can contain extra _arbitrary_ fields that users can
// specify, and there is no way of knowing what type they could be
Set(string, any) error
// Remove removes the field associated with the specified key.
// There is no way to remove the `kty` (key type). You will ALWAYS be left with one field in a jwk.Key.
Remove(string) error
// Validate performs _minimal_ checks if the data stored in the key are valid.
// By minimal, we mean that it does not check if the key is valid for use in
// cryptographic operations. For example, it does not check if an RSA key's
// `e` field is a valid exponent, or if the `n` field is a valid modulus.
// Instead, it checks for things such as the _presence_ of some required fields,
// or if certain keys' values are of particular length.
//
// Note that depending on th underlying key type, use of this method requires
// that multiple fields in the key are properly populated. For example, an EC
// key's "x", "y" fields cannot be validated unless the "crv" field is populated first.
//
// Validate is never called by `UnmarshalJSON()` or `Set`. It must explicitly be
// called by the user
Validate() error
// Thumbprint returns the JWK thumbprint using the indicated
// hashing algorithm, according to RFC 7638
Thumbprint(crypto.Hash) ([]byte, error)
// Keys returns a list of the keys contained in this jwk.Key.
Keys() []string
// Clone creates a new instance of the same type
Clone() (Key, error)
// PublicKey creates the corresponding PublicKey type for this object.
// All fields are copied onto the new public key, except for those that are not allowed.
//
// If the key is already a public key, it returns a new copy minus the disallowed fields as above.
PublicKey() (Key, error)
// KeyType returns the `kty` of a JWK
KeyType() jwa.KeyType
// KeyUsage returns `use` of a JWK
KeyUsage() (string, bool)
// KeyOps returns `key_ops` of a JWK
KeyOps() (KeyOperationList, bool)
// Algorithm returns `alg` of a JWK
// Algorithm returns the value of the `alg` field.
//
// This field may contain either `jwk.SignatureAlgorithm`, `jwk.KeyEncryptionAlgorithm`, or `jwk.ContentEncryptionAlgorithm`.
// This is why there exists a `jwa.KeyAlgorithm` type that encompasses both types.
Algorithm() (jwa.KeyAlgorithm, bool)
// KeyID returns `kid` of a JWK
KeyID() (string, bool)
// X509URL returns `x5u` of a JWK
X509URL() (string, bool)
// X509CertChain returns `x5c` of a JWK
X509CertChain() (*cert.Chain, bool)
// X509CertThumbprint returns `x5t` of a JWK
X509CertThumbprint() (string, bool)
// X509CertThumbprintS256 returns `x5t#S256` of a JWK
X509CertThumbprintS256() (string, bool)
}
+42
View File
@@ -0,0 +1,42 @@
// Code generated by tools/cmd/genreadfile/main.go. DO NOT EDIT.
package jwk
import (
"fmt"
"io/fs"
"os"
)
type sysFS struct{}
func (sysFS) Open(path string) (fs.File, error) {
return os.Open(path)
}
func ReadFile(path string, options ...ReadFileOption) (Set, error) {
var parseOptions []ParseOption
for _, option := range options {
if po, ok := option.(ParseOption); ok {
parseOptions = append(parseOptions, po)
}
}
var srcFS fs.FS = sysFS{}
for _, option := range options {
switch option.Ident() {
case identFS{}:
if err := option.Value(&srcFS); err != nil {
return nil, fmt.Errorf("failed to set fs.FS: %w", err)
}
}
}
f, err := srcFS.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return ParseReader(f, parseOptions...)
}
+710
View File
@@ -0,0 +1,710 @@
//go:generate ../tools/cmd/genjwk.sh
package jwk
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"io"
"math/big"
"reflect"
"slices"
"github.com/lestrrat-go/jwx/v3/internal/base64"
"github.com/lestrrat-go/jwx/v3/internal/json"
)
var registry = json.NewRegistry()
func bigIntToBytes(n *big.Int) ([]byte, error) {
if n == nil {
return nil, fmt.Errorf(`invalid *big.Int value`)
}
return n.Bytes(), nil
}
func init() {
if err := RegisterProbeField(reflect.StructField{
Name: "Kty",
Type: reflect.TypeFor[string](),
Tag: `json:"kty"`,
}); err != nil {
panic(fmt.Errorf("failed to register mandatory probe for 'kty' field: %w", err))
}
if err := RegisterProbeField(reflect.StructField{
Name: "D",
Type: reflect.TypeFor[json.RawMessage](),
Tag: `json:"d,omitempty"`,
}); err != nil {
panic(fmt.Errorf("failed to register mandatory probe for 'kty' field: %w", err))
}
}
// Import creates a jwk.Key from the given key (RSA/ECDSA/symmetric keys).
//
// The constructor auto-detects the type of key to be instantiated
// based on the input type:
//
// - "crypto/rsa".PrivateKey and "crypto/rsa".PublicKey creates an RSA based key
// - "crypto/ecdsa".PrivateKey and "crypto/ecdsa".PublicKey creates an EC based key
// - "crypto/ed25519".PrivateKey and "crypto/ed25519".PublicKey creates an OKP based key
// - "crypto/ecdh".PrivateKey and "crypto/ecdh".PublicKey creates an OKP based key
// - []byte creates a symmetric key
func Import(raw any) (Key, error) {
if raw == nil {
return nil, importerr(`a non-nil key is required`)
}
muKeyImporters.RLock()
conv, ok := keyImporters[reflect.TypeOf(raw)]
muKeyImporters.RUnlock()
if !ok {
return nil, importerr(`failed to convert %T to jwk.Key: no converters were able to convert`, raw)
}
return conv.Import(raw)
}
// PublicSetOf returns a new jwk.Set consisting of
// public keys of the keys contained in the set.
//
// This is useful when you are generating a set of private keys, and
// you want to generate the corresponding public versions for the
// users to verify with.
//
// Be aware that all fields will be copied onto the new public key. It is the caller's
// responsibility to remove any fields, if necessary.
func PublicSetOf(v Set) (Set, error) {
newSet := NewSet()
n := v.Len()
for i := range n {
k, ok := v.Key(i)
if !ok {
return nil, fmt.Errorf(`key not found`)
}
pubKey, err := PublicKeyOf(k)
if err != nil {
return nil, fmt.Errorf(`failed to get public key of %T: %w`, k, err)
}
if err := newSet.AddKey(pubKey); err != nil {
return nil, fmt.Errorf(`failed to add key to public key set: %w`, err)
}
}
return newSet, nil
}
// PublicKeyOf returns the corresponding public version of the jwk.Key.
// If `v` is a SymmetricKey, then the same value is returned.
// If `v` is already a public key, the key itself is returned.
//
// If `v` is a private key type that has a `PublicKey()` method, be aware
// that all fields will be copied onto the new public key. It is the caller's
// responsibility to remove any fields, if necessary
//
// If `v` is a raw key, the key is first converted to a `jwk.Key`
func PublicKeyOf(v any) (Key, error) {
// This should catch all jwk.Key instances
if pk, ok := v.(PublicKeyer); ok {
return pk.PublicKey()
}
jk, err := Import(v)
if err != nil {
return nil, fmt.Errorf(`jwk.PublicKeyOf: failed to convert key into JWK: %w`, err)
}
return jk.PublicKey()
}
// PublicRawKeyOf returns the corresponding public key of the given
// value `v` (e.g. given *rsa.PrivateKey, *rsa.PublicKey is returned)
// If `v` is already a public key, the key itself is returned.
//
// The returned value will always be a pointer to the public key,
// except when a []byte (e.g. symmetric key, ed25519 key) is passed to `v`.
// In this case, the same []byte value is returned.
//
// This function must go through converting the object once to a jwk.Key,
// then back to a raw key, so it's not exactly efficient.
func PublicRawKeyOf(v any) (any, error) {
pk, ok := v.(PublicKeyer)
if !ok {
k, err := Import(v)
if err != nil {
return nil, fmt.Errorf(`jwk.PublicRawKeyOf: failed to convert key to jwk.Key: %w`, err)
}
pk, ok = k.(PublicKeyer)
if !ok {
return nil, fmt.Errorf(`jwk.PublicRawKeyOf: failed to convert key to jwk.PublicKeyer: %w`, err)
}
}
pubk, err := pk.PublicKey()
if err != nil {
return nil, fmt.Errorf(`jwk.PublicRawKeyOf: failed to obtain public key from %T: %w`, v, err)
}
var raw any
if err := Export(pubk, &raw); err != nil {
return nil, fmt.Errorf(`jwk.PublicRawKeyOf: failed to obtain raw key from %T: %w`, pubk, err)
}
return raw, nil
}
// ParseRawKey is a combination of ParseKey and Raw. It parses a single JWK key,
// and assigns the "raw" key to the given parameter. The key must either be
// a pointer to an empty interface, or a pointer to the actual raw key type
// such as *rsa.PrivateKey, *ecdsa.PublicKey, *[]byte, etc.
func ParseRawKey(data []byte, rawkey any) error {
key, err := ParseKey(data)
if err != nil {
return fmt.Errorf(`failed to parse key: %w`, err)
}
if err := Export(key, rawkey); err != nil {
return fmt.Errorf(`failed to assign to raw key variable: %w`, err)
}
return nil
}
type setDecodeCtx struct {
json.DecodeCtx
ignoreParseError bool
}
func (ctx *setDecodeCtx) IgnoreParseError() bool {
return ctx.ignoreParseError
}
// ParseKey parses a single key JWK. Unlike `jwk.Parse` this method will
// report failure if you attempt to pass a JWK set. Only use this function
// when you know that the data is a single JWK.
//
// Given a WithPEM(true) option, this function assumes that the given input
// is PEM encoded ASN.1 DER format key.
//
// Note that a successful parsing of any type of key does NOT necessarily
// guarantee a valid key. For example, no checks against expiration dates
// are performed for certificate expiration, no checks against missing
// parameters are performed, etc.
func ParseKey(data []byte, options ...ParseOption) (Key, error) {
var parsePEM bool
var localReg *json.Registry
var pemDecoder PEMDecoder
for _, option := range options {
switch option.Ident() {
case identPEM{}:
if err := option.Value(&parsePEM); err != nil {
return nil, fmt.Errorf(`failed to retrieve PEM option value: %w`, err)
}
case identPEMDecoder{}:
if err := option.Value(&pemDecoder); err != nil {
return nil, fmt.Errorf(`failed to retrieve PEMDecoder option value: %w`, err)
}
case identLocalRegistry{}:
if err := option.Value(&localReg); err != nil {
return nil, fmt.Errorf(`failed to retrieve local registry option value: %w`, err)
}
case identTypedField{}:
var pair typedFieldPair // temporary var needed for typed field
if err := option.Value(&pair); err != nil {
return nil, fmt.Errorf(`failed to retrieve typed field option value: %w`, err)
}
if localReg == nil {
localReg = json.NewRegistry()
}
localReg.Register(pair.Name, pair.Value)
case identIgnoreParseError{}:
return nil, fmt.Errorf(`jwk.WithIgnoreParseError() cannot be used for ParseKey()`)
}
}
if parsePEM {
var raw any
// PEMDecoder should probably be deprecated, because of being a misnomer.
if pemDecoder != nil {
if err := decodeX509WithPEMDEcoder(&raw, data, pemDecoder); err != nil {
return nil, fmt.Errorf(`failed to decode PEM encoded key: %w`, err)
}
} else {
// This version takes into account the various X509 decoders that are
// pre-registered.
if err := decodeX509(&raw, data); err != nil {
return nil, fmt.Errorf(`failed to decode X.509 encoded key: %w`, err)
}
}
return Import(raw)
}
probe, err := keyProbe.Probe(data)
if err != nil {
return nil, fmt.Errorf(`jwk.Parse: failed to probe data: %w`, err)
}
unmarshaler := keyUnmarshaler{localReg: localReg}
muKeyParser.RLock()
parsers := make([]KeyParser, len(keyParsers))
copy(parsers, keyParsers)
muKeyParser.RUnlock()
for i := len(parsers) - 1; i >= 0; i-- {
parser := parsers[i]
key, err := parser.ParseKey(probe, &unmarshaler, data)
if err == nil {
return key, nil
}
if errors.Is(err, ContinueError()) {
continue
}
return nil, err
}
return nil, fmt.Errorf(`jwk.Parse: no parser was able to parse the key`)
}
// Parse parses JWK from the incoming []byte.
//
// For JWK sets, this is a convenience function. You could just as well
// call `json.Unmarshal` against an empty set created by `jwk.NewSet()`
// to parse a JSON buffer into a `jwk.Set`.
//
// This function exists because many times the user does not know before hand
// if a JWK(s) resource at a remote location contains a single JWK key or
// a JWK set, and `jwk.Parse()` can handle either case, returning a JWK Set
// even if the data only contains a single JWK key
//
// If you are looking for more information on how JWKs are parsed, or if
// you know for sure that you have a single key, please see the documentation
// for `jwk.ParseKey()`.
func Parse(src []byte, options ...ParseOption) (Set, error) {
var parsePEM bool
var parseX509 bool
var localReg *json.Registry
var ignoreParseError bool
var pemDecoder PEMDecoder
for _, option := range options {
switch option.Ident() {
case identPEM{}:
if err := option.Value(&parsePEM); err != nil {
return nil, parseerr(`failed to retrieve PEM option value: %w`, err)
}
case identX509{}:
if err := option.Value(&parseX509); err != nil {
return nil, parseerr(`failed to retrieve X509 option value: %w`, err)
}
case identPEMDecoder{}:
if err := option.Value(&pemDecoder); err != nil {
return nil, parseerr(`failed to retrieve PEMDecoder option value: %w`, err)
}
case identIgnoreParseError{}:
if err := option.Value(&ignoreParseError); err != nil {
return nil, parseerr(`failed to retrieve IgnoreParseError option value: %w`, err)
}
case identTypedField{}:
var pair typedFieldPair // temporary var needed for typed field
if err := option.Value(&pair); err != nil {
return nil, parseerr(`failed to retrieve typed field option value: %w`, err)
}
if localReg == nil {
localReg = json.NewRegistry()
}
localReg.Register(pair.Name, pair.Value)
}
}
s := NewSet()
if parsePEM || parseX509 {
if pemDecoder == nil {
pemDecoder = NewPEMDecoder()
}
src = bytes.TrimSpace(src)
for len(src) > 0 {
raw, rest, err := pemDecoder.Decode(src)
if err != nil {
return nil, parseerr(`failed to parse PEM encoded key: %w`, err)
}
key, err := Import(raw)
if err != nil {
return nil, parseerr(`failed to create jwk.Key from %T: %w`, raw, err)
}
if err := s.AddKey(key); err != nil {
return nil, parseerr(`failed to add jwk.Key to set: %w`, err)
}
src = bytes.TrimSpace(rest)
}
return s, nil
}
if localReg != nil || ignoreParseError {
dcKs, ok := s.(KeyWithDecodeCtx)
if !ok {
return nil, parseerr(`typed field was requested, but the key set (%T) does not support DecodeCtx`, s)
}
dc := &setDecodeCtx{
DecodeCtx: json.NewDecodeCtx(localReg),
ignoreParseError: ignoreParseError,
}
dcKs.SetDecodeCtx(dc)
defer func() { dcKs.SetDecodeCtx(nil) }()
}
if err := json.Unmarshal(src, s); err != nil {
return nil, parseerr(`failed to unmarshal JWK set: %w`, err)
}
return s, nil
}
// ParseReader parses a JWK set from the incoming byte buffer.
func ParseReader(src io.Reader, options ...ParseOption) (Set, error) {
// meh, there's no way to tell if a stream has "ended" a single
// JWKs except when we encounter an EOF, so just... ReadAll
buf, err := io.ReadAll(src)
if err != nil {
return nil, rparseerr(`failed to read from io.Reader: %w`, err)
}
set, err := Parse(buf, options...)
if err != nil {
return nil, rparseerr(`failed to parse reader: %w`, err)
}
return set, nil
}
// ParseString parses a JWK set from the incoming string.
func ParseString(s string, options ...ParseOption) (Set, error) {
set, err := Parse([]byte(s), options...)
if err != nil {
return nil, sparseerr(`failed to parse string: %w`, err)
}
return set, nil
}
// AssignKeyID is a convenience function to automatically assign the "kid"
// section of the key, if it already doesn't have one. It uses Key.Thumbprint
// method with crypto.SHA256 as the default hashing algorithm
func AssignKeyID(key Key, options ...AssignKeyIDOption) error {
if key.Has(KeyIDKey) {
return nil
}
hash := crypto.SHA256
for _, option := range options {
switch option.Ident() {
case identThumbprintHash{}:
if err := option.Value(&hash); err != nil {
return fmt.Errorf(`failed to retrieve thumbprint hash option value: %w`, err)
}
}
}
h, err := key.Thumbprint(hash)
if err != nil {
return fmt.Errorf(`failed to generate thumbprint: %w`, err)
}
if err := key.Set(KeyIDKey, base64.EncodeToString(h)); err != nil {
return fmt.Errorf(`failed to set "kid": %w`, err)
}
return nil
}
// NOTE: may need to remove this to allow pluggale key types
func cloneKey(src Key) (Key, error) {
var dst Key
switch src.(type) {
case RSAPrivateKey:
dst = newRSAPrivateKey()
case RSAPublicKey:
dst = newRSAPublicKey()
case ECDSAPrivateKey:
dst = newECDSAPrivateKey()
case ECDSAPublicKey:
dst = newECDSAPublicKey()
case OKPPrivateKey:
dst = newOKPPrivateKey()
case OKPPublicKey:
dst = newOKPPublicKey()
case SymmetricKey:
dst = newSymmetricKey()
default:
return nil, fmt.Errorf(`jwk.cloneKey: unknown key type %T`, src)
}
for _, k := range src.Keys() {
// It's absolutely
var v any
if err := src.Get(k, &v); err != nil {
return nil, fmt.Errorf(`jwk.cloneKey: failed to get %q: %w`, k, err)
}
if err := dst.Set(k, v); err != nil {
return nil, fmt.Errorf(`jwk.cloneKey: failed to set %q: %w`, k, err)
}
}
return dst, nil
}
// Pem serializes the given jwk.Key in PEM encoded ASN.1 DER format,
// using either PKCS8 for private keys and PKIX for public keys.
// If you need to encode using PKCS1 or SEC1, you must do it yourself.
//
// # Argument must be of type jwk.Key or jwk.Set
//
// Currently only EC (including Ed25519) and RSA keys (and jwk.Set
// comprised of these key types) are supported.
func Pem(v any) ([]byte, error) {
var set Set
switch v := v.(type) {
case Key:
set = NewSet()
if err := set.AddKey(v); err != nil {
return nil, fmt.Errorf(`failed to add key to set: %w`, err)
}
case Set:
set = v
default:
return nil, fmt.Errorf(`argument to Pem must be either jwk.Key or jwk.Set: %T`, v)
}
var ret []byte
for i := range set.Len() {
key, _ := set.Key(i)
typ, buf, err := asnEncode(key)
if err != nil {
return nil, fmt.Errorf(`failed to encode content for key #%d: %w`, i, err)
}
var block pem.Block
block.Type = typ
block.Bytes = buf
ret = append(ret, pem.EncodeToMemory(&block)...)
}
return ret, nil
}
func asnEncode(key Key) (string, []byte, error) {
switch key := key.(type) {
case ECDSAPrivateKey:
var rawkey ecdsa.PrivateKey
if err := Export(key, &rawkey); err != nil {
return "", nil, fmt.Errorf(`failed to get raw key from jwk.Key: %w`, err)
}
buf, err := x509.MarshalECPrivateKey(&rawkey)
if err != nil {
return "", nil, fmt.Errorf(`failed to marshal PKCS8: %w`, err)
}
return pmECPrivateKey, buf, nil
case RSAPrivateKey, OKPPrivateKey:
var rawkey any
if err := Export(key, &rawkey); err != nil {
return "", nil, fmt.Errorf(`failed to get raw key from jwk.Key: %w`, err)
}
buf, err := x509.MarshalPKCS8PrivateKey(rawkey)
if err != nil {
return "", nil, fmt.Errorf(`failed to marshal PKCS8: %w`, err)
}
return pmPrivateKey, buf, nil
case RSAPublicKey, ECDSAPublicKey, OKPPublicKey:
var rawkey any
if err := Export(key, &rawkey); err != nil {
return "", nil, fmt.Errorf(`failed to get raw key from jwk.Key: %w`, err)
}
buf, err := x509.MarshalPKIXPublicKey(rawkey)
if err != nil {
return "", nil, fmt.Errorf(`failed to marshal PKIX: %w`, err)
}
return pmPublicKey, buf, nil
default:
return "", nil, fmt.Errorf(`unsupported key type %T`, key)
}
}
type CustomDecoder = json.CustomDecoder
type CustomDecodeFunc = json.CustomDecodeFunc
// RegisterCustomField allows users to specify that a private field
// be decoded as an instance of the specified type. This option has
// a global effect.
//
// For example, suppose you have a custom field `x-birthday`, which
// you want to represent as a string formatted in RFC3339 in JSON,
// but want it back as `time.Time`.
//
// In such case you would register a custom field as follows
//
// jwk.RegisterCustomField(`x-birthday`, time.Time{})
//
// Then you can use a `time.Time` variable to extract the value
// of `x-birthday` field, instead of having to use `any`
// and later convert it to `time.Time`
//
// var bday time.Time
// _ = key.Get(`x-birthday`, &bday)
//
// If you need a more fine-tuned control over the decoding process,
// you can register a `CustomDecoder`. For example, below shows
// how to register a decoder that can parse RFC1123 format string:
//
// jwk.RegisterCustomField(`x-birthday`, jwk.CustomDecodeFunc(func(data []byte) (any, error) {
// return time.Parse(time.RFC1123, string(data))
// }))
//
// Please note that use of custom fields can be problematic if you
// are using a library that does not implement MarshalJSON/UnmarshalJSON
// and you try to roundtrip from an object to JSON, and then back to an object.
// For example, in the above example, you can _parse_ time values formatted
// in the format specified in RFC822, but when you convert an object into
// JSON, it will be formatted in RFC3339, because that's what `time.Time`
// likes to do. To avoid this, it's always better to use a custom type
// that wraps your desired type (in this case `time.Time`) and implement
// MarshalJSON and UnmashalJSON.
func RegisterCustomField(name string, object any) {
registry.Register(name, object)
}
// Equal compares two keys and returns true if they are equal. The comparison
// is solely done against the thumbprints of k1 and k2. It is possible for keys
// that have, for example, different key IDs, key usage, etc, to be considered equal.
func Equal(k1, k2 Key) bool {
h := crypto.SHA256
tp1, err := k1.Thumbprint(h)
if err != nil {
return false // can't report error
}
tp2, err := k2.Thumbprint(h)
if err != nil {
return false // can't report error
}
return bytes.Equal(tp1, tp2)
}
// IsPrivateKey returns true if the supplied key is a private key of an
// asymmetric key pair. The argument `k` must implement the `AsymmetricKey`
// interface.
//
// An error is returned if the supplied key is not an `AsymmetricKey`.
func IsPrivateKey(k Key) (bool, error) {
asymmetric, ok := k.(AsymmetricKey)
if ok {
return asymmetric.IsPrivate(), nil
}
return false, fmt.Errorf("jwk.IsPrivateKey: %T is not an asymmetric key", k)
}
type keyValidationError struct {
err error
}
func (e *keyValidationError) Error() string {
return fmt.Sprintf(`key validation failed: %s`, e.err)
}
func (e *keyValidationError) Unwrap() error {
return e.err
}
func (e *keyValidationError) Is(target error) bool {
_, ok := target.(*keyValidationError)
return ok
}
// NewKeyValidationError wraps the given error with an error that denotes
// `key.Validate()` has failed. This error type should ONLY be used as
// return value from the `Validate()` method.
func NewKeyValidationError(err error) error {
return &keyValidationError{err: err}
}
func IsKeyValidationError(err error) bool {
var kve keyValidationError
return errors.Is(err, &kve)
}
// Configure is used to configure global behavior of the jwk package.
func Configure(options ...GlobalOption) {
var strictKeyUsagePtr *bool
for _, option := range options {
switch option.Ident() {
case identStrictKeyUsage{}:
var v bool
if err := option.Value(&v); err != nil {
continue
}
strictKeyUsagePtr = &v
}
}
if strictKeyUsagePtr != nil {
strictKeyUsage.Store(*strictKeyUsagePtr)
}
}
// These are used when validating keys.
type keyWithD interface {
D() ([]byte, bool)
}
var _ keyWithD = &okpPrivateKey{}
func extractEmbeddedKey(keyif Key, concretTypes []reflect.Type) (Key, error) {
rv := reflect.ValueOf(keyif)
// If the value can be converted to one of the concrete types, then we're done
if slices.ContainsFunc(concretTypes, func(t reflect.Type) bool {
return rv.Type().ConvertibleTo(t)
}) {
return keyif, nil
}
// When a struct implements the Key interface via embedding, you unfortunately
// cannot use a type switch to determine the concrete type, because
if rv.Kind() == reflect.Ptr {
if rv.IsNil() {
return nil, fmt.Errorf(`invalid key value (0): %w`, ContinueError())
}
rv = rv.Elem()
}
if rv.Kind() != reflect.Struct {
return nil, fmt.Errorf(`invalid key value type %T (1): %w`, keyif, ContinueError())
}
if rv.NumField() == 0 {
return nil, fmt.Errorf(`invalid key value type %T (2): %w`, keyif, ContinueError())
}
// Iterate through the fields of the struct to find the first field that
// implements the Key interface
rt := rv.Type()
for i := range rv.NumField() {
field := rv.Field(i)
ft := rt.Field(i)
if !ft.Anonymous {
// We can only salvage this object if the object implements jwk.Key
// via embedding, so we skip fields that are not anonymous
continue
}
if field.CanInterface() {
if k, ok := field.Interface().(Key); ok {
return extractEmbeddedKey(k, concretTypes)
}
}
}
return nil, fmt.Errorf(`invalid key value type %T (3): %w`, keyif, ContinueError())
}
+17
View File
@@ -0,0 +1,17 @@
load("@rules_go//go:def.bzl", "go_library")
go_library(
name = "jwkbb",
srcs = ["x509.go"],
importpath = "github.com/lestrrat-go/jwx/v3/jwk/jwkbb",
visibility = ["//visibility:public"],
deps = [
"@com_github_lestrrat_go_blackmagic//:blackmagic",
],
)
alias(
name = "go_default_library",
actual = ":jwkbb",
visibility = ["//visibility:public"],
)
+111
View File
@@ -0,0 +1,111 @@
package jwkbb
import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/lestrrat-go/blackmagic"
)
const (
PrivateKeyBlockType = `PRIVATE KEY`
PublicKeyBlockType = `PUBLIC KEY`
ECPrivateKeyBlockType = `EC PRIVATE KEY`
RSAPublicKeyBlockType = `RSA PUBLIC KEY`
RSAPrivateKeyBlockType = `RSA PRIVATE KEY`
CertificateBlockType = `CERTIFICATE`
)
// EncodeX509 encodes the given value into ASN.1 DER format, and returns
// the encoded bytes. The value must be one of the following types:
// *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey,
// *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey.
//
// Users can pass a pre-allocated byte slice (but make sure its length is
// changed so that the encoded buffer is appended to the correct location)
// as `dst` to avoid allocations.
func EncodeX509(dst []byte, v any) ([]byte, error) {
var block pem.Block
// Try to convert it into a certificate
switch v := v.(type) {
case *rsa.PrivateKey:
block.Type = RSAPrivateKeyBlockType
block.Bytes = x509.MarshalPKCS1PrivateKey(v)
case *ecdsa.PrivateKey:
marshaled, err := x509.MarshalECPrivateKey(v)
if err != nil {
return nil, err
}
block.Type = ECPrivateKeyBlockType
block.Bytes = marshaled
case ed25519.PrivateKey:
marshaled, err := x509.MarshalPKCS8PrivateKey(v)
if err != nil {
return nil, err
}
block.Type = PrivateKeyBlockType
block.Bytes = marshaled
case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey:
marshaled, err := x509.MarshalPKIXPublicKey(v)
if err != nil {
return nil, err
}
block.Type = PublicKeyBlockType
block.Bytes = marshaled
default:
return nil, fmt.Errorf(`unsupported type %T for ASN.1 DER encoding`, v)
}
encoded := pem.EncodeToMemory(&block)
dst = append(dst, encoded...)
return dst, nil
}
func DecodeX509(dst any, block *pem.Block) error {
switch block.Type {
// Handle the semi-obvious cases
case RSAPrivateKeyBlockType:
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return fmt.Errorf(`failed to parse PKCS1 private key: %w`, err)
}
return blackmagic.AssignIfCompatible(dst, key)
case RSAPublicKeyBlockType:
key, err := x509.ParsePKCS1PublicKey(block.Bytes)
if err != nil {
return fmt.Errorf(`failed to parse PKCS1 public key: %w`, err)
}
return blackmagic.AssignIfCompatible(dst, key)
case ECPrivateKeyBlockType:
key, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
return fmt.Errorf(`failed to parse EC private key: %w`, err)
}
return blackmagic.AssignIfCompatible(dst, key)
case PublicKeyBlockType:
// XXX *could* return dsa.PublicKey
key, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return fmt.Errorf(`failed to parse PKIX public key: %w`, err)
}
return blackmagic.AssignIfCompatible(dst, key)
case PrivateKeyBlockType:
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return fmt.Errorf(`failed to parse PKCS8 private key: %w`, err)
}
return blackmagic.AssignIfCompatible(dst, key)
case CertificateBlockType:
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return fmt.Errorf(`failed to parse certificate: %w`, err)
}
return blackmagic.AssignIfCompatible(dst, cert.PublicKey)
default:
return fmt.Errorf(`invalid PEM block type %s`, block.Type)
}
}
+58
View File
@@ -0,0 +1,58 @@
package jwk
import "fmt"
func (ops *KeyOperationList) Get() KeyOperationList {
if ops == nil {
return nil
}
return *ops
}
func (ops *KeyOperationList) Accept(v any) error {
switch x := v.(type) {
case string:
return ops.Accept([]string{x})
case []any:
l := make([]string, len(x))
for i, e := range x {
if es, ok := e.(string); ok {
l[i] = es
} else {
return fmt.Errorf(`invalid list element type: expected string, got %T`, v)
}
}
return ops.Accept(l)
case []string:
list := make(KeyOperationList, len(x))
for i, e := range x {
switch e := KeyOperation(e); e {
case KeyOpSign, KeyOpVerify, KeyOpEncrypt, KeyOpDecrypt, KeyOpWrapKey, KeyOpUnwrapKey, KeyOpDeriveKey, KeyOpDeriveBits:
list[i] = e
default:
return fmt.Errorf(`invalid keyoperation %v`, e)
}
}
*ops = list
return nil
case []KeyOperation:
list := make(KeyOperationList, len(x))
for i, e := range x {
switch e {
case KeyOpSign, KeyOpVerify, KeyOpEncrypt, KeyOpDecrypt, KeyOpWrapKey, KeyOpUnwrapKey, KeyOpDeriveKey, KeyOpDeriveBits:
list[i] = e
default:
return fmt.Errorf(`invalid keyoperation %v`, e)
}
}
*ops = list
return nil
case KeyOperationList:
*ops = x
return nil
default:
return fmt.Errorf(`invalid value %T`, v)
}
}
+321
View File
@@ -0,0 +1,321 @@
package jwk
import (
"bytes"
"crypto"
"crypto/ecdh"
"crypto/ed25519"
"fmt"
"reflect"
"github.com/lestrrat-go/blackmagic"
"github.com/lestrrat-go/jwx/v3/internal/base64"
"github.com/lestrrat-go/jwx/v3/jwa"
)
func init() {
RegisterKeyExporter(jwa.OKP(), KeyExportFunc(okpJWKToRaw))
}
// Mental note:
//
// Curve25519 refers to a particular curve, and is represented in its Montgomery form.
//
// Ed25519 refers to the biratinally equivalent curve of Curve25519, except it's in Edwards form.
// Ed25519 is the name of the curve and the also the signature scheme using that curve.
// The full name of the scheme is Edwards Curve Digital Signature Algorithm, and thus it is
// also referred to as EdDSA.
//
// X25519 refers to the Diffie-Hellman key exchange protocol that uses Cruve25519.
// Because this is an elliptic curve based Diffie Hellman protocol, it is also referred to
// as ECDH.
//
// OKP keys are used to represent private/public pairs of thse elliptic curve
// keys. But note that the name just means Octet Key Pair.
func (k *okpPublicKey) Import(rawKeyIf any) error {
k.mu.Lock()
defer k.mu.Unlock()
var crv jwa.EllipticCurveAlgorithm
switch rawKey := rawKeyIf.(type) {
case ed25519.PublicKey:
k.x = rawKey
crv = jwa.Ed25519()
k.crv = &crv
case *ecdh.PublicKey:
k.x = rawKey.Bytes()
crv = jwa.X25519()
k.crv = &crv
default:
return fmt.Errorf(`unknown key type %T`, rawKeyIf)
}
return nil
}
func (k *okpPrivateKey) Import(rawKeyIf any) error {
k.mu.Lock()
defer k.mu.Unlock()
var crv jwa.EllipticCurveAlgorithm
switch rawKey := rawKeyIf.(type) {
case ed25519.PrivateKey:
k.d = rawKey.Seed()
k.x = rawKey.Public().(ed25519.PublicKey) //nolint:forcetypeassert
crv = jwa.Ed25519()
k.crv = &crv
case *ecdh.PrivateKey:
// k.d = rawKey.Seed()
k.d = rawKey.Bytes()
k.x = rawKey.PublicKey().Bytes()
crv = jwa.X25519()
k.crv = &crv
default:
return fmt.Errorf(`unknown key type %T`, rawKeyIf)
}
return nil
}
func buildOKPPublicKey(alg jwa.EllipticCurveAlgorithm, xbuf []byte) (any, error) {
switch alg {
case jwa.Ed25519():
return ed25519.PublicKey(xbuf), nil
case jwa.X25519():
ret, err := ecdh.X25519().NewPublicKey(xbuf)
if err != nil {
return nil, fmt.Errorf(`failed to parse x25519 public key %x (size %d): %w`, xbuf, len(xbuf), err)
}
return ret, nil
default:
return nil, fmt.Errorf(`invalid curve algorithm %s`, alg)
}
}
// Raw returns the EC-DSA public key represented by this JWK
func (k *okpPublicKey) Raw(v any) error {
k.mu.RLock()
defer k.mu.RUnlock()
crv, ok := k.Crv()
if !ok {
return fmt.Errorf(`missing "crv" field`)
}
pubk, err := buildOKPPublicKey(crv, k.x)
if err != nil {
return fmt.Errorf(`jwk.OKPPublicKey: failed to build public key: %w`, err)
}
if err := blackmagic.AssignIfCompatible(v, pubk); err != nil {
return fmt.Errorf(`jwk.OKPPublicKey: failed to assign to destination variable: %w`, err)
}
return nil
}
func buildOKPPrivateKey(alg jwa.EllipticCurveAlgorithm, xbuf []byte, dbuf []byte) (any, error) {
if len(dbuf) == 0 {
return nil, fmt.Errorf(`cannot use empty seed`)
}
switch alg {
case jwa.Ed25519():
if len(dbuf) != ed25519.SeedSize {
return nil, fmt.Errorf(`ed25519: wrong private key size`)
}
ret := ed25519.NewKeyFromSeed(dbuf)
//nolint:forcetypeassert
if !bytes.Equal(xbuf, ret.Public().(ed25519.PublicKey)) {
return nil, fmt.Errorf(`ed25519: invalid x value given d value`)
}
return ret, nil
case jwa.X25519():
ret, err := ecdh.X25519().NewPrivateKey(dbuf)
if err != nil {
return nil, fmt.Errorf(`x25519: unable to construct x25519 private key from seed: %w`, err)
}
return ret, nil
default:
return nil, fmt.Errorf(`invalid curve algorithm %s`, alg)
}
}
var okpConvertibleKeys = []reflect.Type{
reflect.TypeFor[OKPPrivateKey](),
reflect.TypeFor[OKPPublicKey](),
}
// This is half baked. I think it will blow up if we used ecdh.* keys and/or x25519 keys
func okpJWKToRaw(key Key, _ any /* this is unused because this is half baked */) (any, error) {
extracted, err := extractEmbeddedKey(key, okpConvertibleKeys)
if err != nil {
return nil, fmt.Errorf(`jwk.OKP: failed to extract embedded key: %w`, err)
}
switch key := extracted.(type) {
case OKPPrivateKey:
locker, ok := key.(rlocker)
if ok {
locker.rlock()
defer locker.runlock()
}
crv, ok := key.Crv()
if !ok {
return nil, fmt.Errorf(`missing "crv" field`)
}
x, ok := key.X()
if !ok {
return nil, fmt.Errorf(`missing "x" field`)
}
d, ok := key.D()
if !ok {
return nil, fmt.Errorf(`missing "d" field`)
}
privk, err := buildOKPPrivateKey(crv, x, d)
if err != nil {
return nil, fmt.Errorf(`jwk.OKPPrivateKey: failed to build private key: %w`, err)
}
return privk, nil
case OKPPublicKey:
locker, ok := key.(rlocker)
if ok {
locker.rlock()
defer locker.runlock()
}
crv, ok := key.Crv()
if !ok {
return nil, fmt.Errorf(`missing "crv" field`)
}
x, ok := key.X()
if !ok {
return nil, fmt.Errorf(`missing "x" field`)
}
pubk, err := buildOKPPublicKey(crv, x)
if err != nil {
return nil, fmt.Errorf(`jwk.OKPPublicKey: failed to build public key: %w`, err)
}
return pubk, nil
default:
return nil, ContinueError()
}
}
func makeOKPPublicKey(src Key) (Key, error) {
newKey := newOKPPublicKey()
// Iterate and copy everything except for the bits that should not be in the public key
for _, k := range src.Keys() {
switch k {
case OKPDKey:
continue
default:
var v any
if err := src.Get(k, &v); err != nil {
return nil, fmt.Errorf(`failed to get field %q: %w`, k, err)
}
if err := newKey.Set(k, v); err != nil {
return nil, fmt.Errorf(`failed to set field %q: %w`, k, err)
}
}
}
return newKey, nil
}
func (k *okpPrivateKey) PublicKey() (Key, error) {
return makeOKPPublicKey(k)
}
func (k *okpPublicKey) PublicKey() (Key, error) {
return makeOKPPublicKey(k)
}
func okpThumbprint(hash crypto.Hash, crv, x string) []byte {
h := hash.New()
fmt.Fprint(h, `{"crv":"`)
fmt.Fprint(h, crv)
fmt.Fprint(h, `","kty":"OKP","x":"`)
fmt.Fprint(h, x)
fmt.Fprint(h, `"}`)
return h.Sum(nil)
}
// Thumbprint returns the JWK thumbprint using the indicated
// hashing algorithm, according to RFC 7638 / 8037
func (k okpPublicKey) Thumbprint(hash crypto.Hash) ([]byte, error) {
k.mu.RLock()
defer k.mu.RUnlock()
crv, ok := k.Crv()
if !ok {
return nil, fmt.Errorf(`missing "crv" field`)
}
return okpThumbprint(
hash,
crv.String(),
base64.EncodeToString(k.x),
), nil
}
// Thumbprint returns the JWK thumbprint using the indicated
// hashing algorithm, according to RFC 7638 / 8037
func (k okpPrivateKey) Thumbprint(hash crypto.Hash) ([]byte, error) {
k.mu.RLock()
defer k.mu.RUnlock()
crv, ok := k.Crv()
if !ok {
return nil, fmt.Errorf(`missing "crv" field`)
}
return okpThumbprint(
hash,
crv.String(),
base64.EncodeToString(k.x),
), nil
}
func validateOKPKey(key interface {
Crv() (jwa.EllipticCurveAlgorithm, bool)
X() ([]byte, bool)
}) error {
if v, ok := key.Crv(); !ok || v == jwa.InvalidEllipticCurve() {
return fmt.Errorf(`invalid curve algorithm`)
}
if v, ok := key.X(); !ok || len(v) == 0 {
return fmt.Errorf(`missing "x" field`)
}
if priv, ok := key.(keyWithD); ok {
if d, ok := priv.D(); !ok || len(d) == 0 {
return fmt.Errorf(`missing "d" field`)
}
}
return nil
}
func (k *okpPublicKey) Validate() error {
k.mu.RLock()
defer k.mu.RUnlock()
if err := validateOKPKey(k); err != nil {
return NewKeyValidationError(fmt.Errorf(`jwk.OKPPublicKey: %w`, err))
}
return nil
}
func (k *okpPrivateKey) Validate() error {
k.mu.RLock()
defer k.mu.RUnlock()
if err := validateOKPKey(k); err != nil {
return NewKeyValidationError(fmt.Errorf(`jwk.OKPPrivateKey: %w`, err))
}
return nil
}
File diff suppressed because it is too large Load Diff
+76
View File
@@ -0,0 +1,76 @@
package jwk
import (
"time"
"github.com/lestrrat-go/httprc/v3"
"github.com/lestrrat-go/option/v2"
)
type identTypedField struct{}
type typedFieldPair struct {
Name string
Value any
}
// WithTypedField allows a private field to be parsed into the object type of
// your choice. It works much like the RegisterCustomField, but the effect
// is only applicable to the jwt.Parse function call which receives this option.
//
// While this can be extremely useful, this option should be used with caution:
// There are many caveats that your entire team/user-base needs to be aware of,
// and therefore in general its use is discouraged. Only use it when you know
// what you are doing, and you document its use clearly for others.
//
// First and foremost, this is a "per-object" option. Meaning that given the same
// serialized format, it is possible to generate two objects whose internal
// representations may differ. That is, if you parse one _WITH_ the option,
// and the other _WITHOUT_, their internal representation may completely differ.
// This could potentially lead to problems.
//
// Second, specifying this option will slightly slow down the decoding process
// as it needs to consult multiple definitions sources (global and local), so
// be careful if you are decoding a large number of tokens, as the effects will stack up.
func WithTypedField(name string, object any) ParseOption {
return &parseOption{
option.New(identTypedField{},
typedFieldPair{Name: name, Value: object},
),
}
}
type registerResourceOption struct {
option.Interface
}
func (registerResourceOption) registerOption() {}
func (registerResourceOption) resourceOption() {}
type identNewResourceOption struct{}
// WithHttprcResourceOption can be used to pass arbitrary `httprc.NewResourceOption`
// to `(httprc.Client).Add` by way of `(jwk.Cache).Register`.
func WithHttprcResourceOption(o httprc.NewResourceOption) RegisterOption {
return &registerResourceOption{
option.New(identNewResourceOption{}, o),
}
}
// WithConstantInterval can be used to pass `httprc.WithConstantInterval` option to
// `(httprc.Client).Add` by way of `(jwk.Cache).Register`.
func WithConstantInterval(d time.Duration) RegisterOption {
return WithHttprcResourceOption(httprc.WithConstantInterval(d))
}
// WithMinInterval can be used to pass `httprc.WithMinInterval` option to
// `(httprc.Client).Add` by way of `(jwk.Cache).Register`.
func WithMinInterval(d time.Duration) RegisterOption {
return WithHttprcResourceOption(httprc.WithMinInterval(d))
}
// WithMaxInterval can be used to pass `httprc.WithMaxInterval` option to
// `(httprc.Client).Add` by way of `(jwk.Cache).Register`.
func WithMaxInterval(d time.Duration) RegisterOption {
return WithHttprcResourceOption(httprc.WithMaxInterval(d))
}
+143
View File
@@ -0,0 +1,143 @@
package_name: jwk
output: jwk/options_gen.go
interfaces:
- name: CacheOption
comment: |
CacheOption is a type of Option that can be passed to the
the `jwk.NewCache()` function.
- name: ResourceOption
comment: |
ResourceOption is a type of Option that can be passed to the `httprc.NewResource` function
by way of RegisterOption.
- name: AssignKeyIDOption
- name: FetchOption
methods:
- fetchOption
- parseOption
- registerOption
comment: |
FetchOption is a type of Option that can be passed to `jwk.Fetch()`
FetchOption also implements the `RegisterOption`, and thus can
safely be passed to `(*jwk.Cache).Register()`
- name: ParseOption
methods:
- fetchOption
- registerOption
- readFileOption
comment: |
ParseOption is a type of Option that can be passed to `jwk.Parse()`
ParseOption also implements the `ReadFileOption` and `NewCacheOption`,
and thus safely be passed to `jwk.ReadFile` and `(*jwk.Cache).Configure()`
- name: ReadFileOption
comment: |
ReadFileOption is a type of `Option` that can be passed to `jwk.ReadFile`
- name: RegisterOption
comment: |
RegisterOption describes options that can be passed to `(jwk.Cache).Register()`
- name: RegisterFetchOption
methods:
- fetchOption
- registerOption
- parseOption
comment: |
RegisterFetchOption describes options that can be passed to `(jwk.Cache).Register()` and `jwk.Fetch()`
- name: GlobalOption
comment: |
GlobalOption is a type of Option that can be passed to the `jwk.Configure()` to
change the global configuration of the jwk package.
options:
- ident: HTTPClient
interface: RegisterFetchOption
argument_type: HTTPClient
comment: |
WithHTTPClient allows users to specify the "net/http".Client object that
is used when fetching jwk.Set objects.
- ident: ThumbprintHash
interface: AssignKeyIDOption
argument_type: crypto.Hash
- ident: LocalRegistry
option_name: withLocalRegistry
interface: ParseOption
argument_type: '*json.Registry'
comment: This option is only available for internal code. Users don't get to play with it
- ident: PEM
interface: ParseOption
argument_type: bool
comment: |
WithPEM specifies that the input to `Parse()` is a PEM encoded key.
This option is planned to be deprecated in the future. The plan is to
replace it with `jwk.WithX509(true)`
- ident: X509
interface: ParseOption
argument_type: bool
comment: |
WithX509 specifies that the input to `Parse()` is an X.509 encoded key
- ident: PEMDecoder
interface: ParseOption
argument_type: PEMDecoder
comment: |
WithPEMDecoder specifies the PEMDecoder object to use when decoding
PEM encoded keys. This option can be passed to `jwk.Parse()`
This option is planned to be deprecated in the future. The plan is to
use `jwk.RegisterX509Decoder()` to register a custom X.509 decoder globally.
- ident: FetchWhitelist
interface: FetchOption
argument_type: Whitelist
comment: |
WithFetchWhitelist specifies the Whitelist object to use when
fetching JWKs from a remote source. This option can be passed
to both `jwk.Fetch()`
- ident: IgnoreParseError
interface: ParseOption
argument_type: bool
comment: |
WithIgnoreParseError is only applicable when used with `jwk.Parse()`
(i.e. to parse JWK sets). If passed to `jwk.ParseKey()`, the function
will return an error no matter what the input is.
DO NOT USE WITHOUT EXHAUSTING ALL OTHER ROUTES FIRST.
The option specifies that errors found during parsing of individual
keys are ignored. For example, if you had keys A, B, C where B is
invalid (e.g. it does not contain the required fields), then the
resulting JWKS will contain keys A and C only.
This options exists as an escape hatch for those times when a
key in a JWKS that is irrelevant for your use case is causing
your JWKS parsing to fail, and you want to get to the rest of the
keys in the JWKS.
Again, DO NOT USE unless you have exhausted all other routes.
When you use this option, you will not be able to tell if you are
using a faulty JWKS, except for when there are JSON syntax errors.
- ident: FS
interface: ReadFileOption
argument_type: fs.FS
comment: |
WithFS specifies the source `fs.FS` object to read the file from.
- ident: WaitReady
interface: RegisterOption
argument_type: bool
comment: |
WithWaitReady specifies that the `jwk.Cache` should wait until the
first fetch is done before returning from the `Register()` call.
This option is by default true. Specify a false value if you would
like to return immediately from the `Register()` call.
This options is exactly the same as `httprc.WithWaitReady()`
- ident: StrictKeyUsage
interface: GlobalOption
argument_type: bool
comment: |
WithStrictKeyUsage specifies if during JWK parsing, the "use" field
should be confined to the values that have been registered via
`jwk.RegisterKeyType()`. By default this option is true, and the
initial allowed values are "use" and "enc" only.
If this option is set to false, then the "use" field can be any
value. If this options is set to true, then the "use" field must
be one of the registered values, and otherwise an error will be
reported during parsing / assignment to `jwk.KeyUsageType`
+297
View File
@@ -0,0 +1,297 @@
// Code generated by tools/cmd/genoptions/main.go. DO NOT EDIT.
package jwk
import (
"crypto"
"io/fs"
"github.com/lestrrat-go/jwx/v3/internal/json"
"github.com/lestrrat-go/option/v2"
)
type Option = option.Interface
type AssignKeyIDOption interface {
Option
assignKeyIDOption()
}
type assignKeyIDOption struct {
Option
}
func (*assignKeyIDOption) assignKeyIDOption() {}
// CacheOption is a type of Option that can be passed to the
// the `jwk.NewCache()` function.
type CacheOption interface {
Option
cacheOption()
}
type cacheOption struct {
Option
}
func (*cacheOption) cacheOption() {}
// FetchOption is a type of Option that can be passed to `jwk.Fetch()`
// FetchOption also implements the `RegisterOption`, and thus can
// safely be passed to `(*jwk.Cache).Register()`
type FetchOption interface {
Option
fetchOption()
parseOption()
registerOption()
}
type fetchOption struct {
Option
}
func (*fetchOption) fetchOption() {}
func (*fetchOption) parseOption() {}
func (*fetchOption) registerOption() {}
// GlobalOption is a type of Option that can be passed to the `jwk.Configure()` to
// change the global configuration of the jwk package.
type GlobalOption interface {
Option
globalOption()
}
type globalOption struct {
Option
}
func (*globalOption) globalOption() {}
// ParseOption is a type of Option that can be passed to `jwk.Parse()`
// ParseOption also implements the `ReadFileOption` and `NewCacheOption`,
// and thus safely be passed to `jwk.ReadFile` and `(*jwk.Cache).Configure()`
type ParseOption interface {
Option
fetchOption()
registerOption()
readFileOption()
}
type parseOption struct {
Option
}
func (*parseOption) fetchOption() {}
func (*parseOption) registerOption() {}
func (*parseOption) readFileOption() {}
// ReadFileOption is a type of `Option` that can be passed to `jwk.ReadFile`
type ReadFileOption interface {
Option
readFileOption()
}
type readFileOption struct {
Option
}
func (*readFileOption) readFileOption() {}
// RegisterFetchOption describes options that can be passed to `(jwk.Cache).Register()` and `jwk.Fetch()`
type RegisterFetchOption interface {
Option
fetchOption()
registerOption()
parseOption()
}
type registerFetchOption struct {
Option
}
func (*registerFetchOption) fetchOption() {}
func (*registerFetchOption) registerOption() {}
func (*registerFetchOption) parseOption() {}
// RegisterOption describes options that can be passed to `(jwk.Cache).Register()`
type RegisterOption interface {
Option
registerOption()
}
type registerOption struct {
Option
}
func (*registerOption) registerOption() {}
// ResourceOption is a type of Option that can be passed to the `httprc.NewResource` function
// by way of RegisterOption.
type ResourceOption interface {
Option
resourceOption()
}
type resourceOption struct {
Option
}
func (*resourceOption) resourceOption() {}
type identFS struct{}
type identFetchWhitelist struct{}
type identHTTPClient struct{}
type identIgnoreParseError struct{}
type identLocalRegistry struct{}
type identPEM struct{}
type identPEMDecoder struct{}
type identStrictKeyUsage struct{}
type identThumbprintHash struct{}
type identWaitReady struct{}
type identX509 struct{}
func (identFS) String() string {
return "WithFS"
}
func (identFetchWhitelist) String() string {
return "WithFetchWhitelist"
}
func (identHTTPClient) String() string {
return "WithHTTPClient"
}
func (identIgnoreParseError) String() string {
return "WithIgnoreParseError"
}
func (identLocalRegistry) String() string {
return "withLocalRegistry"
}
func (identPEM) String() string {
return "WithPEM"
}
func (identPEMDecoder) String() string {
return "WithPEMDecoder"
}
func (identStrictKeyUsage) String() string {
return "WithStrictKeyUsage"
}
func (identThumbprintHash) String() string {
return "WithThumbprintHash"
}
func (identWaitReady) String() string {
return "WithWaitReady"
}
func (identX509) String() string {
return "WithX509"
}
// WithFS specifies the source `fs.FS` object to read the file from.
func WithFS(v fs.FS) ReadFileOption {
return &readFileOption{option.New(identFS{}, v)}
}
// WithFetchWhitelist specifies the Whitelist object to use when
// fetching JWKs from a remote source. This option can be passed
// to both `jwk.Fetch()`
func WithFetchWhitelist(v Whitelist) FetchOption {
return &fetchOption{option.New(identFetchWhitelist{}, v)}
}
// WithHTTPClient allows users to specify the "net/http".Client object that
// is used when fetching jwk.Set objects.
func WithHTTPClient(v HTTPClient) RegisterFetchOption {
return &registerFetchOption{option.New(identHTTPClient{}, v)}
}
// WithIgnoreParseError is only applicable when used with `jwk.Parse()`
// (i.e. to parse JWK sets). If passed to `jwk.ParseKey()`, the function
// will return an error no matter what the input is.
//
// DO NOT USE WITHOUT EXHAUSTING ALL OTHER ROUTES FIRST.
//
// The option specifies that errors found during parsing of individual
// keys are ignored. For example, if you had keys A, B, C where B is
// invalid (e.g. it does not contain the required fields), then the
// resulting JWKS will contain keys A and C only.
//
// This options exists as an escape hatch for those times when a
// key in a JWKS that is irrelevant for your use case is causing
// your JWKS parsing to fail, and you want to get to the rest of the
// keys in the JWKS.
//
// Again, DO NOT USE unless you have exhausted all other routes.
// When you use this option, you will not be able to tell if you are
// using a faulty JWKS, except for when there are JSON syntax errors.
func WithIgnoreParseError(v bool) ParseOption {
return &parseOption{option.New(identIgnoreParseError{}, v)}
}
// This option is only available for internal code. Users don't get to play with it
func withLocalRegistry(v *json.Registry) ParseOption {
return &parseOption{option.New(identLocalRegistry{}, v)}
}
// WithPEM specifies that the input to `Parse()` is a PEM encoded key.
//
// This option is planned to be deprecated in the future. The plan is to
// replace it with `jwk.WithX509(true)`
func WithPEM(v bool) ParseOption {
return &parseOption{option.New(identPEM{}, v)}
}
// WithPEMDecoder specifies the PEMDecoder object to use when decoding
// PEM encoded keys. This option can be passed to `jwk.Parse()`
//
// This option is planned to be deprecated in the future. The plan is to
// use `jwk.RegisterX509Decoder()` to register a custom X.509 decoder globally.
func WithPEMDecoder(v PEMDecoder) ParseOption {
return &parseOption{option.New(identPEMDecoder{}, v)}
}
// WithStrictKeyUsage specifies if during JWK parsing, the "use" field
// should be confined to the values that have been registered via
// `jwk.RegisterKeyType()`. By default this option is true, and the
// initial allowed values are "use" and "enc" only.
//
// If this option is set to false, then the "use" field can be any
// value. If this options is set to true, then the "use" field must
// be one of the registered values, and otherwise an error will be
// reported during parsing / assignment to `jwk.KeyUsageType`
func WithStrictKeyUsage(v bool) GlobalOption {
return &globalOption{option.New(identStrictKeyUsage{}, v)}
}
func WithThumbprintHash(v crypto.Hash) AssignKeyIDOption {
return &assignKeyIDOption{option.New(identThumbprintHash{}, v)}
}
// WithWaitReady specifies that the `jwk.Cache` should wait until the
// first fetch is done before returning from the `Register()` call.
//
// This option is by default true. Specify a false value if you would
// like to return immediately from the `Register()` call.
//
// This options is exactly the same as `httprc.WithWaitReady()`
func WithWaitReady(v bool) RegisterOption {
return &registerOption{option.New(identWaitReady{}, v)}
}
// WithX509 specifies that the input to `Parse()` is an X.509 encoded key
func WithX509(v bool) ParseOption {
return &parseOption{option.New(identX509{}, v)}
}
+244
View File
@@ -0,0 +1,244 @@
package jwk
import (
"fmt"
"reflect"
"sync"
"github.com/lestrrat-go/blackmagic"
"github.com/lestrrat-go/jwx/v3/internal/json"
"github.com/lestrrat-go/jwx/v3/jwa"
)
// KeyParser represents a type that can parse a JSON representation of a JWK into
// a jwk.Key.
// See KeyConvertor for a type that can convert a raw key into a jwk.Key
type KeyParser interface {
// ParseKey parses a JSON payload to a `jwk.Key` object. The first
// argument is an object that contains some hints as to what kind of
// key the JSON payload contains.
//
// If your KeyParser decides that the payload is not something
// you can parse, and you would like to continue parsing with
// the remaining KeyParser instances that are registered,
// return a `jwk.ContinueParseError`. Any other errors will immediately
// halt the parsing process.
//
// When unmarshaling JSON, use the unmarshaler object supplied as
// the second argument. This will ensure that the JSON is unmarshaled
// in a way that is compatible with the rest of the library.
ParseKey(probe *KeyProbe, unmarshaler KeyUnmarshaler, payload []byte) (Key, error)
}
// KeyParseFunc is a type of KeyParser that is based on a function/closure
type KeyParseFunc func(probe *KeyProbe, unmarshaler KeyUnmarshaler, payload []byte) (Key, error)
func (f KeyParseFunc) ParseKey(probe *KeyProbe, unmarshaler KeyUnmarshaler, payload []byte) (Key, error) {
return f(probe, unmarshaler, payload)
}
// protects keyParsers
var muKeyParser sync.RWMutex
// list of parsers
var keyParsers = []KeyParser{KeyParseFunc(defaultParseKey)}
// RegisterKeyParser adds a new KeyParser. Parsers are called in FILO order.
// That is, the last parser to be registered is called first. There is no
// check for duplicate entries.
func RegisterKeyParser(kp KeyParser) {
muKeyParser.Lock()
defer muKeyParser.Unlock()
keyParsers = append(keyParsers, kp)
}
func defaultParseKey(probe *KeyProbe, unmarshaler KeyUnmarshaler, data []byte) (Key, error) {
var key Key
var kty string
var d json.RawMessage
if err := probe.Get("Kty", &kty); err != nil {
return nil, fmt.Errorf(`jwk.Parse: failed to get "kty" hint: %w`, err)
}
// We ignore errors from this field, as it's optional
_ = probe.Get("D", &d)
switch v, _ := jwa.LookupKeyType(kty); v {
case jwa.RSA():
if d != nil {
key = newRSAPrivateKey()
} else {
key = newRSAPublicKey()
}
case jwa.EC():
if d != nil {
key = newECDSAPrivateKey()
} else {
key = newECDSAPublicKey()
}
case jwa.OctetSeq():
key = newSymmetricKey()
case jwa.OKP():
if d != nil {
key = newOKPPrivateKey()
} else {
key = newOKPPublicKey()
}
default:
return nil, fmt.Errorf(`invalid key type from JSON (%s)`, kty)
}
if err := unmarshaler.UnmarshalKey(data, key); err != nil {
return nil, fmt.Errorf(`failed to unmarshal JSON into key (%T): %w`, key, err)
}
return key, nil
}
type keyUnmarshaler struct {
localReg *json.Registry
}
func (ku *keyUnmarshaler) UnmarshalKey(data []byte, key any) error {
if ku.localReg != nil {
dcKey, ok := key.(json.DecodeCtxContainer)
if !ok {
return fmt.Errorf(`typed field was requested, but the key (%T) does not support DecodeCtx`, key)
}
dc := json.NewDecodeCtx(ku.localReg)
dcKey.SetDecodeCtx(dc)
defer func() { dcKey.SetDecodeCtx(nil) }()
}
if err := json.Unmarshal(data, key); err != nil {
return fmt.Errorf(`failed to unmarshal JSON into key (%T): %w`, key, err)
}
return nil
}
// keyProber is the object that starts the probing. When Probe() is called,
// it creates (possibly from a cached value) an object that is used to
// hold hint values.
type keyProber struct {
mu sync.RWMutex
pool *sync.Pool
fields map[string]reflect.StructField
typ reflect.Type
}
func (kp *keyProber) AddField(field reflect.StructField) error {
kp.mu.Lock()
defer kp.mu.Unlock()
if _, ok := kp.fields[field.Name]; ok {
return fmt.Errorf(`field name %s is already registered`, field.Name)
}
kp.fields[field.Name] = field
kp.makeStructType()
// Update pool (note: the logic is the same, but we need to recreate it
// so that we don't accidentally use old stored values)
kp.pool = &sync.Pool{
New: kp.makeStruct,
}
return nil
}
func (kp *keyProber) makeStructType() {
// DOES NOT LOCK
fields := make([]reflect.StructField, 0, len(kp.fields))
for _, f := range kp.fields {
fields = append(fields, f)
}
kp.typ = reflect.StructOf(fields)
}
func (kp *keyProber) makeStruct() any {
return reflect.New(kp.typ)
}
func (kp *keyProber) Probe(data []byte) (*KeyProbe, error) {
kp.mu.RLock()
defer kp.mu.RUnlock()
// if the field list unchanged, so is the pool object, so effectively
// we should be using the cached version
v := kp.pool.Get()
if v == nil {
return nil, fmt.Errorf(`probe: failed to get object from pool`)
}
rv, ok := v.(reflect.Value)
if !ok {
return nil, fmt.Errorf(`probe: value returned from pool as of type %T, expected reflect.Value`, v)
}
if err := json.Unmarshal(data, rv.Interface()); err != nil {
return nil, fmt.Errorf(`probe: failed to unmarshal data: %w`, err)
}
return &KeyProbe{data: rv}, nil
}
// KeyProbe is the object that carries the hints when parsing a key.
// The exact list of fields can vary depending on the types of key
// that are registered.
//
// Use `Get()` to access the value of a field.
//
// The underlying data stored in a KeyProbe is recycled each
// time a value is parsed, therefore you are not allowed to hold
// onto this object after ParseKey() is done.
type KeyProbe struct {
data reflect.Value
}
// Get returns the value of the field with the given `name“.
// `dst` must be a pointer to a value that can hold the type of
// the value of the field, which is determined by the
// field type registered through `jwk.RegisterProbeField()`
func (kp *KeyProbe) Get(name string, dst any) error {
f := kp.data.Elem().FieldByName(name)
if !f.IsValid() {
return fmt.Errorf(`field %s not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, f.Addr().Interface()); err != nil {
return fmt.Errorf(`failed to assign value of field %q to %T: %w`, name, dst, err)
}
return nil
}
// We don't really need the object, we need to know its type
var keyProbe = &keyProber{
fields: make(map[string]reflect.StructField),
}
// RegisterProbeField adds a new field to be probed during the initial
// phase of parsing. This is done by partially parsing the JSON payload,
// and we do this by calling `json.Unmarshal` using a dynamic type that
// can possibly be modified during runtime. This function is used to
// add a new field to this dynamic type.
//
// Note that the `Name` field for the given `reflect.StructField` must start
// with an upper case alphabet, such that it is treated as an exported field.
// So for example, if you want to probe the "my_hint" field, you should specify
// the field name as "MyHint" or similar.
//
// Also the field name must be unique. If you believe that your field name may
// collide with other packages that may want to add their own probes,
// it is the responsibility of the caller
// to ensure that the field name is unique (possibly by prefixing the field
// name with a unique string). It is important to note that the field name
// need not be the same as the JSON field name. For example, your field name
// could be "MyPkg_MyHint", while the actual JSON field name could be "my_hint".
//
// If the field name is not unique, an error is returned.
func RegisterProbeField(p reflect.StructField) error {
// locking is done inside keyProbe
return keyProbe.AddField(p)
}
// KeyUnmarshaler is a thin wrapper around json.Unmarshal. It behaves almost
// exactly like json.Unmarshal, but it allows us to add extra magic that
// is specific to this library before calling the actual json.Unmarshal.
type KeyUnmarshaler interface {
UnmarshalKey(data []byte, key any) error
}
+360
View File
@@ -0,0 +1,360 @@
package jwk
import (
"crypto"
"crypto/rsa"
"encoding/binary"
"fmt"
"math/big"
"reflect"
"github.com/lestrrat-go/jwx/v3/internal/base64"
"github.com/lestrrat-go/jwx/v3/internal/pool"
"github.com/lestrrat-go/jwx/v3/jwa"
)
func init() {
RegisterKeyExporter(jwa.RSA(), KeyExportFunc(rsaJWKToRaw))
}
func (k *rsaPrivateKey) Import(rawKey *rsa.PrivateKey) error {
k.mu.Lock()
defer k.mu.Unlock()
d, err := bigIntToBytes(rawKey.D)
if err != nil {
return fmt.Errorf(`invalid rsa.PrivateKey: %w`, err)
}
k.d = d
l := len(rawKey.Primes)
if l < 0 /* I know, I'm being paranoid */ || l > 2 {
return fmt.Errorf(`invalid number of primes in rsa.PrivateKey: need 0 to 2, but got %d`, len(rawKey.Primes))
}
if l > 0 {
p, err := bigIntToBytes(rawKey.Primes[0])
if err != nil {
return fmt.Errorf(`invalid rsa.PrivateKey: %w`, err)
}
k.p = p
}
if l > 1 {
q, err := bigIntToBytes(rawKey.Primes[1])
if err != nil {
return fmt.Errorf(`invalid rsa.PrivateKey: %w`, err)
}
k.q = q
}
// dp, dq, qi are optional values
if v, err := bigIntToBytes(rawKey.Precomputed.Dp); err == nil {
k.dp = v
}
if v, err := bigIntToBytes(rawKey.Precomputed.Dq); err == nil {
k.dq = v
}
if v, err := bigIntToBytes(rawKey.Precomputed.Qinv); err == nil {
k.qi = v
}
// public key part
n, e, err := importRsaPublicKeyByteValues(&rawKey.PublicKey)
if err != nil {
return fmt.Errorf(`invalid rsa.PrivateKey: %w`, err)
}
k.n = n
k.e = e
return nil
}
func importRsaPublicKeyByteValues(rawKey *rsa.PublicKey) ([]byte, []byte, error) {
n, err := bigIntToBytes(rawKey.N)
if err != nil {
return nil, nil, fmt.Errorf(`invalid rsa.PublicKey: %w`, err)
}
data := make([]byte, 8)
binary.BigEndian.PutUint64(data, uint64(rawKey.E))
i := 0
for ; i < len(data); i++ {
if data[i] != 0x0 {
break
}
}
return n, data[i:], nil
}
func (k *rsaPublicKey) Import(rawKey *rsa.PublicKey) error {
k.mu.Lock()
defer k.mu.Unlock()
n, e, err := importRsaPublicKeyByteValues(rawKey)
if err != nil {
return fmt.Errorf(`invalid rsa.PrivateKey: %w`, err)
}
k.n = n
k.e = e
return nil
}
func buildRSAPublicKey(key *rsa.PublicKey, n, e []byte) {
bin := pool.BigInt().Get()
bie := pool.BigInt().Get()
defer pool.BigInt().Put(bie)
bin.SetBytes(n)
bie.SetBytes(e)
key.N = bin
key.E = int(bie.Int64())
}
var rsaConvertibleKeys = []reflect.Type{
reflect.TypeFor[RSAPrivateKey](),
reflect.TypeFor[RSAPublicKey](),
}
func rsaJWKToRaw(key Key, hint any) (any, error) {
extracted, err := extractEmbeddedKey(key, rsaConvertibleKeys)
if err != nil {
return nil, fmt.Errorf(`failed to extract embedded key: %w`, err)
}
switch key := extracted.(type) {
case RSAPrivateKey:
switch hint.(type) {
case *rsa.PrivateKey, *any:
default:
return nil, fmt.Errorf(`invalid destination object type %T for private RSA JWK: %w`, hint, ContinueError())
}
locker, ok := key.(rlocker)
if !ok {
locker.rlock()
defer locker.runlock()
}
od, ok := key.D()
if !ok {
return nil, fmt.Errorf(`missing "d" value`)
}
oq, ok := key.Q()
if !ok {
return nil, fmt.Errorf(`missing "q" value`)
}
op, ok := key.P()
if !ok {
return nil, fmt.Errorf(`missing "p" value`)
}
var d, q, p big.Int // note: do not use from sync.Pool
d.SetBytes(od)
q.SetBytes(oq)
p.SetBytes(op)
// optional fields
var dp, dq, qi *big.Int
if odp, ok := key.DP(); ok {
dp = &big.Int{} // note: do not use from sync.Pool
dp.SetBytes(odp)
}
if odq, ok := key.DQ(); ok {
dq = &big.Int{} // note: do not use from sync.Pool
dq.SetBytes(odq)
}
if oqi, ok := key.QI(); ok {
qi = &big.Int{} // note: do not use from sync.Pool
qi.SetBytes(oqi)
}
n, ok := key.N()
if !ok {
return nil, fmt.Errorf(`missing "n" value`)
}
e, ok := key.E()
if !ok {
return nil, fmt.Errorf(`missing "e" value`)
}
var privkey rsa.PrivateKey
buildRSAPublicKey(&privkey.PublicKey, n, e)
privkey.D = &d
privkey.Primes = []*big.Int{&p, &q}
if dp != nil {
privkey.Precomputed.Dp = dp
}
if dq != nil {
privkey.Precomputed.Dq = dq
}
if qi != nil {
privkey.Precomputed.Qinv = qi
}
// This may look like a no-op, but it's required if we want to
// compare it against a key generated by rsa.GenerateKey
privkey.Precomputed.CRTValues = []rsa.CRTValue{}
return &privkey, nil
case RSAPublicKey:
switch hint.(type) {
case *rsa.PublicKey, *any:
default:
return nil, fmt.Errorf(`invalid destination object type %T for public RSA JWK: %w`, hint, ContinueError())
}
locker, ok := key.(rlocker)
if !ok {
locker.rlock()
defer locker.runlock()
}
n, ok := key.N()
if !ok {
return nil, fmt.Errorf(`missing "n" value`)
}
e, ok := key.E()
if !ok {
return nil, fmt.Errorf(`missing "e" value`)
}
var pubkey rsa.PublicKey
buildRSAPublicKey(&pubkey, n, e)
return &pubkey, nil
default:
return nil, ContinueError()
}
}
func makeRSAPublicKey(src Key) (Key, error) {
newKey := newRSAPublicKey()
// Iterate and copy everything except for the bits that should not be in the public key
for _, k := range src.Keys() {
switch k {
case RSADKey, RSADPKey, RSADQKey, RSAPKey, RSAQKey, RSAQIKey:
continue
default:
var v any
if err := src.Get(k, &v); err != nil {
return nil, fmt.Errorf(`rsa: makeRSAPublicKey: failed to get field %q: %w`, k, err)
}
if err := newKey.Set(k, v); err != nil {
return nil, fmt.Errorf(`rsa: makeRSAPublicKey: failed to set field %q: %w`, k, err)
}
}
}
return newKey, nil
}
func (k *rsaPrivateKey) PublicKey() (Key, error) {
return makeRSAPublicKey(k)
}
func (k *rsaPublicKey) PublicKey() (Key, error) {
return makeRSAPublicKey(k)
}
// Thumbprint returns the JWK thumbprint using the indicated
// hashing algorithm, according to RFC 7638
func (k rsaPrivateKey) Thumbprint(hash crypto.Hash) ([]byte, error) {
k.mu.RLock()
defer k.mu.RUnlock()
var key rsa.PrivateKey
if err := Export(&k, &key); err != nil {
return nil, fmt.Errorf(`failed to export RSA private key: %w`, err)
}
return rsaThumbprint(hash, &key.PublicKey)
}
func (k rsaPublicKey) Thumbprint(hash crypto.Hash) ([]byte, error) {
k.mu.RLock()
defer k.mu.RUnlock()
var key rsa.PublicKey
if err := Export(&k, &key); err != nil {
return nil, fmt.Errorf(`failed to export RSA public key: %w`, err)
}
return rsaThumbprint(hash, &key)
}
func rsaThumbprint(hash crypto.Hash, key *rsa.PublicKey) ([]byte, error) {
buf := pool.BytesBuffer().Get()
defer pool.BytesBuffer().Put(buf)
buf.WriteString(`{"e":"`)
buf.WriteString(base64.EncodeUint64ToString(uint64(key.E)))
buf.WriteString(`","kty":"RSA","n":"`)
buf.WriteString(base64.EncodeToString(key.N.Bytes()))
buf.WriteString(`"}`)
h := hash.New()
if _, err := buf.WriteTo(h); err != nil {
return nil, fmt.Errorf(`failed to write rsaThumbprint: %w`, err)
}
return h.Sum(nil), nil
}
func validateRSAKey(key interface {
N() ([]byte, bool)
E() ([]byte, bool)
}, checkPrivate bool) error {
n, ok := key.N()
if !ok {
return fmt.Errorf(`missing "n" value`)
}
e, ok := key.E()
if !ok {
return fmt.Errorf(`missing "e" value`)
}
if len(n) == 0 {
// Ideally we would like to check for the actual length, but unlike
// EC keys, we have nothing in the key itself that will tell us
// how many bits this key should have.
return fmt.Errorf(`missing "n" value`)
}
if len(e) == 0 {
return fmt.Errorf(`missing "e" value`)
}
if checkPrivate {
if priv, ok := key.(keyWithD); ok {
if d, ok := priv.D(); !ok || len(d) == 0 {
return fmt.Errorf(`missing "d" value`)
}
} else {
return fmt.Errorf(`missing "d" value`)
}
}
return nil
}
func (k *rsaPrivateKey) Validate() error {
if err := validateRSAKey(k, true); err != nil {
return NewKeyValidationError(fmt.Errorf(`jwk.RSAPrivateKey: %w`, err))
}
return nil
}
func (k *rsaPublicKey) Validate() error {
if err := validateRSAKey(k, false); err != nil {
return NewKeyValidationError(fmt.Errorf(`jwk.RSAPublicKey: %w`, err))
}
return nil
}
File diff suppressed because it is too large Load Diff
+319
View File
@@ -0,0 +1,319 @@
package jwk
import (
"bytes"
"fmt"
"maps"
"reflect"
"sort"
"github.com/lestrrat-go/blackmagic"
"github.com/lestrrat-go/jwx/v3/internal/json"
"github.com/lestrrat-go/jwx/v3/internal/pool"
"github.com/lestrrat-go/jwx/v3/internal/tokens"
)
const keysKey = `keys` // appease linter
func newSet() *set {
return &set{
privateParams: make(map[string]any),
}
}
// NewSet creates and empty `jwk.Set` object
func NewSet() Set {
return newSet()
}
func (s *set) Set(n string, v any) error {
s.mu.RLock()
defer s.mu.RUnlock()
if n == keysKey {
vl, ok := v.([]Key)
if !ok {
return fmt.Errorf(`value for field "keys" must be []jwk.Key`)
}
s.keys = vl
return nil
}
s.privateParams[n] = v
return nil
}
func (s *set) Get(name string, dst any) error {
s.mu.RLock()
defer s.mu.RUnlock()
v, ok := s.privateParams[name]
if !ok {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, v); err != nil {
return fmt.Errorf(`failed to assign value to dst: %w`, err)
}
return nil
}
func (s *set) Key(idx int) (Key, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
if idx >= 0 && idx < len(s.keys) {
return s.keys[idx], true
}
return nil, false
}
func (s *set) Len() int {
s.mu.RLock()
defer s.mu.RUnlock()
return len(s.keys)
}
// indexNL is Index(), but without the locking
func (s *set) indexNL(key Key) int {
for i, k := range s.keys {
if k == key {
return i
}
}
return -1
}
func (s *set) Index(key Key) int {
s.mu.RLock()
defer s.mu.RUnlock()
return s.indexNL(key)
}
func (s *set) AddKey(key Key) error {
s.mu.Lock()
defer s.mu.Unlock()
if reflect.ValueOf(key).IsNil() {
panic("nil key")
}
if i := s.indexNL(key); i > -1 {
return fmt.Errorf(`(jwk.Set).AddKey: key already exists`)
}
s.keys = append(s.keys, key)
return nil
}
func (s *set) Remove(name string) error {
s.mu.Lock()
defer s.mu.Unlock()
delete(s.privateParams, name)
return nil
}
func (s *set) RemoveKey(key Key) error {
s.mu.Lock()
defer s.mu.Unlock()
for i, k := range s.keys {
if k == key {
switch i {
case 0:
s.keys = s.keys[1:]
case len(s.keys) - 1:
s.keys = s.keys[:i]
default:
s.keys = append(s.keys[:i], s.keys[i+1:]...)
}
return nil
}
}
return fmt.Errorf(`(jwk.Set).RemoveKey: specified key does not exist in set`)
}
func (s *set) Clear() error {
s.mu.Lock()
defer s.mu.Unlock()
s.keys = nil
s.privateParams = make(map[string]any)
return nil
}
func (s *set) Keys() []string {
ret := make([]string, len(s.privateParams))
var i int
for k := range s.privateParams {
ret[i] = k
i++
}
return ret
}
func (s *set) MarshalJSON() ([]byte, error) {
s.mu.RLock()
defer s.mu.RUnlock()
buf := pool.BytesBuffer().Get()
defer pool.BytesBuffer().Put(buf)
enc := json.NewEncoder(buf)
fields := []string{keysKey}
for k := range s.privateParams {
fields = append(fields, k)
}
sort.Strings(fields)
buf.WriteByte(tokens.OpenCurlyBracket)
for i, field := range fields {
if i > 0 {
buf.WriteByte(tokens.Comma)
}
fmt.Fprintf(buf, `%q:`, field)
if field != keysKey {
if err := enc.Encode(s.privateParams[field]); err != nil {
return nil, fmt.Errorf(`failed to marshal field %q: %w`, field, err)
}
} else {
buf.WriteByte(tokens.OpenSquareBracket)
for j, k := range s.keys {
if j > 0 {
buf.WriteByte(tokens.Comma)
}
if err := enc.Encode(k); err != nil {
return nil, fmt.Errorf(`failed to marshal key #%d: %w`, i, err)
}
}
buf.WriteByte(tokens.CloseSquareBracket)
}
}
buf.WriteByte(tokens.CloseCurlyBracket)
ret := make([]byte, buf.Len())
copy(ret, buf.Bytes())
return ret, nil
}
func (s *set) UnmarshalJSON(data []byte) error {
s.mu.Lock()
defer s.mu.Unlock()
s.privateParams = make(map[string]any)
s.keys = nil
var options []ParseOption
var ignoreParseError bool
if dc := s.dc; dc != nil {
if localReg := dc.Registry(); localReg != nil {
options = append(options, withLocalRegistry(localReg))
}
ignoreParseError = dc.IgnoreParseError()
}
var sawKeysField bool
dec := json.NewDecoder(bytes.NewReader(data))
LOOP:
for {
tok, err := dec.Token()
if err != nil {
return fmt.Errorf(`error reading token: %w`, err)
}
switch tok := tok.(type) {
case json.Delim:
// Assuming we're doing everything correctly, we should ONLY
// get either tokens.OpenCurlyBracket or tokens.CloseCurlyBracket here.
if tok == tokens.CloseCurlyBracket { // End of object
break LOOP
} else if tok != tokens.OpenCurlyBracket {
return fmt.Errorf(`expected '%c' but got '%c'`, tokens.OpenCurlyBracket, tok)
}
case string:
switch tok {
case "keys":
sawKeysField = true
var list []json.RawMessage
if err := dec.Decode(&list); err != nil {
return fmt.Errorf(`failed to decode "keys": %w`, err)
}
for i, keysrc := range list {
key, err := ParseKey(keysrc, options...)
if err != nil {
if !ignoreParseError {
return fmt.Errorf(`failed to decode key #%d in "keys": %w`, i, err)
}
continue
}
s.keys = append(s.keys, key)
}
default:
var v any
if err := dec.Decode(&v); err != nil {
return fmt.Errorf(`failed to decode value for key %q: %w`, tok, err)
}
s.privateParams[tok] = v
}
}
}
// This is really silly, but we can only detect the
// lack of the "keys" field after going through the
// entire object once
// Not checking for len(s.keys) == 0, because it could be
// an empty key set
if !sawKeysField {
key, err := ParseKey(data, options...)
if err != nil {
return fmt.Errorf(`failed to parse sole key in key set`)
}
s.keys = append(s.keys, key)
}
return nil
}
func (s *set) LookupKeyID(kid string) (Key, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
for i := range s.Len() {
key, ok := s.Key(i)
if !ok {
return nil, false
}
gotkid, ok := key.KeyID()
if ok && gotkid == kid {
return key, true
}
}
return nil, false
}
func (s *set) DecodeCtx() DecodeCtx {
s.mu.RLock()
defer s.mu.RUnlock()
return s.dc
}
func (s *set) SetDecodeCtx(dc DecodeCtx) {
s.mu.Lock()
defer s.mu.Unlock()
s.dc = dc
}
func (s *set) Clone() (Set, error) {
s2 := newSet()
s.mu.RLock()
defer s.mu.RUnlock()
s2.keys = make([]Key, len(s.keys))
copy(s2.keys, s.keys)
maps.Copy(s2.privateParams, s.privateParams)
return s2, nil
}
+105
View File
@@ -0,0 +1,105 @@
package jwk
import (
"crypto"
"fmt"
"reflect"
"github.com/lestrrat-go/jwx/v3/internal/base64"
"github.com/lestrrat-go/jwx/v3/jwa"
)
func init() {
RegisterKeyExporter(jwa.OctetSeq(), KeyExportFunc(octetSeqToRaw))
}
func (k *symmetricKey) Import(rawKey []byte) error {
k.mu.Lock()
defer k.mu.Unlock()
if len(rawKey) == 0 {
return fmt.Errorf(`non-empty []byte key required`)
}
k.octets = rawKey
return nil
}
var symmetricConvertibleKeys = []reflect.Type{
reflect.TypeFor[SymmetricKey](),
}
func octetSeqToRaw(key Key, hint any) (any, error) {
extracted, err := extractEmbeddedKey(key, symmetricConvertibleKeys)
if err != nil {
return nil, fmt.Errorf(`failed to extract embedded key: %w`, err)
}
switch key := extracted.(type) {
case SymmetricKey:
switch hint.(type) {
case *[]byte, *any:
default:
return nil, fmt.Errorf(`invalid destination object type %T for symmetric key: %w`, hint, ContinueError())
}
locker, ok := key.(rlocker)
if ok {
locker.rlock()
defer locker.runlock()
}
ooctets, ok := key.Octets()
if !ok {
return nil, fmt.Errorf(`jwk.SymmetricKey: missing "k" field`)
}
octets := make([]byte, len(ooctets))
copy(octets, ooctets)
return octets, nil
default:
return nil, ContinueError()
}
}
// Thumbprint returns the JWK thumbprint using the indicated
// hashing algorithm, according to RFC 7638
func (k *symmetricKey) Thumbprint(hash crypto.Hash) ([]byte, error) {
k.mu.RLock()
defer k.mu.RUnlock()
var octets []byte
if err := Export(k, &octets); err != nil {
return nil, fmt.Errorf(`failed to export symmetric key: %w`, err)
}
h := hash.New()
fmt.Fprint(h, `{"k":"`)
fmt.Fprint(h, base64.EncodeToString(octets))
fmt.Fprint(h, `","kty":"oct"}`)
return h.Sum(nil), nil
}
func (k *symmetricKey) PublicKey() (Key, error) {
newKey := newSymmetricKey()
for _, key := range k.Keys() {
var v any
if err := k.Get(key, &v); err != nil {
return nil, fmt.Errorf(`failed to get field %q: %w`, key, err)
}
if err := newKey.Set(key, v); err != nil {
return nil, fmt.Errorf(`failed to set field %q: %w`, key, err)
}
}
return newKey, nil
}
func (k *symmetricKey) Validate() error {
octets, ok := k.Octets()
if !ok || len(octets) == 0 {
return NewKeyValidationError(fmt.Errorf(`jwk.SymmetricKey: missing "k" field`))
}
return nil
}
+620
View File
@@ -0,0 +1,620 @@
// Code generated by tools/cmd/genjwk/main.go. DO NOT EDIT.
package jwk
import (
"bytes"
"fmt"
"sort"
"sync"
"github.com/lestrrat-go/blackmagic"
"github.com/lestrrat-go/jwx/v3/cert"
"github.com/lestrrat-go/jwx/v3/internal/base64"
"github.com/lestrrat-go/jwx/v3/internal/json"
"github.com/lestrrat-go/jwx/v3/internal/pool"
"github.com/lestrrat-go/jwx/v3/internal/tokens"
"github.com/lestrrat-go/jwx/v3/jwa"
)
const (
SymmetricOctetsKey = "k"
)
type SymmetricKey interface {
Key
Octets() ([]byte, bool)
}
type symmetricKey struct {
algorithm *jwa.KeyAlgorithm // https://tools.ietf.org/html/rfc7517#section-4.4
keyID *string // https://tools.ietf.org/html/rfc7515#section-4.1.4
keyOps *KeyOperationList // https://tools.ietf.org/html/rfc7517#section-4.3
keyUsage *string // https://tools.ietf.org/html/rfc7517#section-4.2
octets []byte
x509CertChain *cert.Chain // https://tools.ietf.org/html/rfc7515#section-4.1.6
x509CertThumbprint *string // https://tools.ietf.org/html/rfc7515#section-4.1.7
x509CertThumbprintS256 *string // https://tools.ietf.org/html/rfc7515#section-4.1.8
x509URL *string // https://tools.ietf.org/html/rfc7515#section-4.1.5
privateParams map[string]any
mu *sync.RWMutex
dc json.DecodeCtx
}
var _ SymmetricKey = &symmetricKey{}
var _ Key = &symmetricKey{}
func newSymmetricKey() *symmetricKey {
return &symmetricKey{
mu: &sync.RWMutex{},
privateParams: make(map[string]any),
}
}
func (h symmetricKey) KeyType() jwa.KeyType {
return jwa.OctetSeq()
}
func (h symmetricKey) rlock() {
h.mu.RLock()
}
func (h symmetricKey) runlock() {
h.mu.RUnlock()
}
func (h *symmetricKey) Algorithm() (jwa.KeyAlgorithm, bool) {
if h.algorithm != nil {
return *(h.algorithm), true
}
return nil, false
}
func (h *symmetricKey) KeyID() (string, bool) {
if h.keyID != nil {
return *(h.keyID), true
}
return "", false
}
func (h *symmetricKey) KeyOps() (KeyOperationList, bool) {
if h.keyOps != nil {
return *(h.keyOps), true
}
return nil, false
}
func (h *symmetricKey) KeyUsage() (string, bool) {
if h.keyUsage != nil {
return *(h.keyUsage), true
}
return "", false
}
func (h *symmetricKey) Octets() ([]byte, bool) {
if h.octets != nil {
return h.octets, true
}
return nil, false
}
func (h *symmetricKey) X509CertChain() (*cert.Chain, bool) {
return h.x509CertChain, true
}
func (h *symmetricKey) X509CertThumbprint() (string, bool) {
if h.x509CertThumbprint != nil {
return *(h.x509CertThumbprint), true
}
return "", false
}
func (h *symmetricKey) X509CertThumbprintS256() (string, bool) {
if h.x509CertThumbprintS256 != nil {
return *(h.x509CertThumbprintS256), true
}
return "", false
}
func (h *symmetricKey) X509URL() (string, bool) {
if h.x509URL != nil {
return *(h.x509URL), true
}
return "", false
}
func (h *symmetricKey) Has(name string) bool {
h.mu.RLock()
defer h.mu.RUnlock()
switch name {
case KeyTypeKey:
return true
case AlgorithmKey:
return h.algorithm != nil
case KeyIDKey:
return h.keyID != nil
case KeyOpsKey:
return h.keyOps != nil
case KeyUsageKey:
return h.keyUsage != nil
case SymmetricOctetsKey:
return h.octets != nil
case X509CertChainKey:
return h.x509CertChain != nil
case X509CertThumbprintKey:
return h.x509CertThumbprint != nil
case X509CertThumbprintS256Key:
return h.x509CertThumbprintS256 != nil
case X509URLKey:
return h.x509URL != nil
default:
_, ok := h.privateParams[name]
return ok
}
}
func (h *symmetricKey) Get(name string, dst any) error {
h.mu.RLock()
defer h.mu.RUnlock()
switch name {
case KeyTypeKey:
if err := blackmagic.AssignIfCompatible(dst, h.KeyType()); err != nil {
return fmt.Errorf(`symmetricKey.Get: failed to assign value for field %q to destination object: %w`, name, err)
}
case AlgorithmKey:
if h.algorithm == nil {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, *(h.algorithm)); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
return nil
case KeyIDKey:
if h.keyID == nil {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, *(h.keyID)); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
return nil
case KeyOpsKey:
if h.keyOps == nil {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, *(h.keyOps)); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
return nil
case KeyUsageKey:
if h.keyUsage == nil {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, *(h.keyUsage)); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
return nil
case SymmetricOctetsKey:
if h.octets == nil {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, h.octets); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
return nil
case X509CertChainKey:
if h.x509CertChain == nil {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, h.x509CertChain); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
return nil
case X509CertThumbprintKey:
if h.x509CertThumbprint == nil {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, *(h.x509CertThumbprint)); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
return nil
case X509CertThumbprintS256Key:
if h.x509CertThumbprintS256 == nil {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, *(h.x509CertThumbprintS256)); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
return nil
case X509URLKey:
if h.x509URL == nil {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, *(h.x509URL)); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
return nil
default:
v, ok := h.privateParams[name]
if !ok {
return fmt.Errorf(`field %q not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, v); err != nil {
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
}
}
return nil
}
func (h *symmetricKey) Set(name string, value any) error {
h.mu.Lock()
defer h.mu.Unlock()
return h.setNoLock(name, value)
}
func (h *symmetricKey) setNoLock(name string, value any) error {
switch name {
case "kty":
return nil
case AlgorithmKey:
switch v := value.(type) {
case string, jwa.SignatureAlgorithm, jwa.KeyEncryptionAlgorithm, jwa.ContentEncryptionAlgorithm:
tmp, err := jwa.KeyAlgorithmFrom(v)
if err != nil {
return fmt.Errorf(`invalid algorithm for %q key: %w`, AlgorithmKey, err)
}
h.algorithm = &tmp
default:
return fmt.Errorf(`invalid type for %q key: %T`, AlgorithmKey, value)
}
return nil
case KeyIDKey:
if v, ok := value.(string); ok {
h.keyID = &v
return nil
}
return fmt.Errorf(`invalid value for %s key: %T`, KeyIDKey, value)
case KeyOpsKey:
var acceptor KeyOperationList
if err := acceptor.Accept(value); err != nil {
return fmt.Errorf(`invalid value for %s key: %w`, KeyOpsKey, err)
}
h.keyOps = &acceptor
return nil
case KeyUsageKey:
switch v := value.(type) {
case KeyUsageType:
switch v {
case ForSignature, ForEncryption:
tmp := v.String()
h.keyUsage = &tmp
default:
return fmt.Errorf(`invalid key usage type %s`, v)
}
case string:
h.keyUsage = &v
default:
return fmt.Errorf(`invalid key usage type %s`, v)
}
case SymmetricOctetsKey:
if v, ok := value.([]byte); ok {
h.octets = v
return nil
}
return fmt.Errorf(`invalid value for %s key: %T`, SymmetricOctetsKey, value)
case X509CertChainKey:
if v, ok := value.(*cert.Chain); ok {
h.x509CertChain = v
return nil
}
return fmt.Errorf(`invalid value for %s key: %T`, X509CertChainKey, value)
case X509CertThumbprintKey:
if v, ok := value.(string); ok {
h.x509CertThumbprint = &v
return nil
}
return fmt.Errorf(`invalid value for %s key: %T`, X509CertThumbprintKey, value)
case X509CertThumbprintS256Key:
if v, ok := value.(string); ok {
h.x509CertThumbprintS256 = &v
return nil
}
return fmt.Errorf(`invalid value for %s key: %T`, X509CertThumbprintS256Key, value)
case X509URLKey:
if v, ok := value.(string); ok {
h.x509URL = &v
return nil
}
return fmt.Errorf(`invalid value for %s key: %T`, X509URLKey, value)
default:
if h.privateParams == nil {
h.privateParams = map[string]any{}
}
h.privateParams[name] = value
}
return nil
}
func (k *symmetricKey) Remove(key string) error {
k.mu.Lock()
defer k.mu.Unlock()
switch key {
case AlgorithmKey:
k.algorithm = nil
case KeyIDKey:
k.keyID = nil
case KeyOpsKey:
k.keyOps = nil
case KeyUsageKey:
k.keyUsage = nil
case SymmetricOctetsKey:
k.octets = nil
case X509CertChainKey:
k.x509CertChain = nil
case X509CertThumbprintKey:
k.x509CertThumbprint = nil
case X509CertThumbprintS256Key:
k.x509CertThumbprintS256 = nil
case X509URLKey:
k.x509URL = nil
default:
delete(k.privateParams, key)
}
return nil
}
func (k *symmetricKey) Clone() (Key, error) {
key, err := cloneKey(k)
if err != nil {
return nil, fmt.Errorf(`symmetricKey.Clone: %w`, err)
}
return key, nil
}
func (k *symmetricKey) DecodeCtx() json.DecodeCtx {
k.mu.RLock()
defer k.mu.RUnlock()
return k.dc
}
func (k *symmetricKey) SetDecodeCtx(dc json.DecodeCtx) {
k.mu.Lock()
defer k.mu.Unlock()
k.dc = dc
}
func (h *symmetricKey) UnmarshalJSON(buf []byte) error {
h.mu.Lock()
defer h.mu.Unlock()
h.algorithm = nil
h.keyID = nil
h.keyOps = nil
h.keyUsage = nil
h.octets = nil
h.x509CertChain = nil
h.x509CertThumbprint = nil
h.x509CertThumbprintS256 = nil
h.x509URL = nil
dec := json.NewDecoder(bytes.NewReader(buf))
LOOP:
for {
tok, err := dec.Token()
if err != nil {
return fmt.Errorf(`error reading token: %w`, err)
}
switch tok := tok.(type) {
case json.Delim:
// Assuming we're doing everything correctly, we should ONLY
// get either tokens.OpenCurlyBracket or tokens.CloseCurlyBracket here.
if tok == tokens.CloseCurlyBracket { // End of object
break LOOP
} else if tok != tokens.OpenCurlyBracket {
return fmt.Errorf(`expected '%c' but got '%c'`, tokens.OpenCurlyBracket, tok)
}
case string: // Objects can only have string keys
switch tok {
case KeyTypeKey:
val, err := json.ReadNextStringToken(dec)
if err != nil {
return fmt.Errorf(`error reading token: %w`, err)
}
if val != jwa.OctetSeq().String() {
return fmt.Errorf(`invalid kty value for RSAPublicKey (%s)`, val)
}
case AlgorithmKey:
var s string
if err := dec.Decode(&s); err != nil {
return fmt.Errorf(`failed to decode value for key %s: %w`, AlgorithmKey, err)
}
alg, err := jwa.KeyAlgorithmFrom(s)
if err != nil {
return fmt.Errorf(`failed to decode value for key %s: %w`, AlgorithmKey, err)
}
h.algorithm = &alg
case KeyIDKey:
if err := json.AssignNextStringToken(&h.keyID, dec); err != nil {
return fmt.Errorf(`failed to decode value for key %s: %w`, KeyIDKey, err)
}
case KeyOpsKey:
var decoded KeyOperationList
if err := dec.Decode(&decoded); err != nil {
return fmt.Errorf(`failed to decode value for key %s: %w`, KeyOpsKey, err)
}
h.keyOps = &decoded
case KeyUsageKey:
if err := json.AssignNextStringToken(&h.keyUsage, dec); err != nil {
return fmt.Errorf(`failed to decode value for key %s: %w`, KeyUsageKey, err)
}
case SymmetricOctetsKey:
if err := json.AssignNextBytesToken(&h.octets, dec); err != nil {
return fmt.Errorf(`failed to decode value for key %s: %w`, SymmetricOctetsKey, err)
}
case X509CertChainKey:
var decoded cert.Chain
if err := dec.Decode(&decoded); err != nil {
return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertChainKey, err)
}
h.x509CertChain = &decoded
case X509CertThumbprintKey:
if err := json.AssignNextStringToken(&h.x509CertThumbprint, dec); err != nil {
return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintKey, err)
}
case X509CertThumbprintS256Key:
if err := json.AssignNextStringToken(&h.x509CertThumbprintS256, dec); err != nil {
return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintS256Key, err)
}
case X509URLKey:
if err := json.AssignNextStringToken(&h.x509URL, dec); err != nil {
return fmt.Errorf(`failed to decode value for key %s: %w`, X509URLKey, err)
}
default:
if dc := h.dc; dc != nil {
if localReg := dc.Registry(); localReg != nil {
decoded, err := localReg.Decode(dec, tok)
if err == nil {
h.setNoLock(tok, decoded)
continue
}
}
}
decoded, err := registry.Decode(dec, tok)
if err == nil {
h.setNoLock(tok, decoded)
continue
}
return fmt.Errorf(`could not decode field %s: %w`, tok, err)
}
default:
return fmt.Errorf(`invalid token %T`, tok)
}
}
if h.octets == nil {
return fmt.Errorf(`required field k is missing`)
}
return nil
}
func (h symmetricKey) MarshalJSON() ([]byte, error) {
data := make(map[string]any)
fields := make([]string, 0, 9)
data[KeyTypeKey] = jwa.OctetSeq()
fields = append(fields, KeyTypeKey)
if h.algorithm != nil {
data[AlgorithmKey] = *(h.algorithm)
fields = append(fields, AlgorithmKey)
}
if h.keyID != nil {
data[KeyIDKey] = *(h.keyID)
fields = append(fields, KeyIDKey)
}
if h.keyOps != nil {
data[KeyOpsKey] = *(h.keyOps)
fields = append(fields, KeyOpsKey)
}
if h.keyUsage != nil {
data[KeyUsageKey] = *(h.keyUsage)
fields = append(fields, KeyUsageKey)
}
if h.octets != nil {
data[SymmetricOctetsKey] = h.octets
fields = append(fields, SymmetricOctetsKey)
}
if h.x509CertChain != nil {
data[X509CertChainKey] = h.x509CertChain
fields = append(fields, X509CertChainKey)
}
if h.x509CertThumbprint != nil {
data[X509CertThumbprintKey] = *(h.x509CertThumbprint)
fields = append(fields, X509CertThumbprintKey)
}
if h.x509CertThumbprintS256 != nil {
data[X509CertThumbprintS256Key] = *(h.x509CertThumbprintS256)
fields = append(fields, X509CertThumbprintS256Key)
}
if h.x509URL != nil {
data[X509URLKey] = *(h.x509URL)
fields = append(fields, X509URLKey)
}
for k, v := range h.privateParams {
data[k] = v
fields = append(fields, k)
}
sort.Strings(fields)
buf := pool.BytesBuffer().Get()
defer pool.BytesBuffer().Put(buf)
buf.WriteByte(tokens.OpenCurlyBracket)
enc := json.NewEncoder(buf)
for i, f := range fields {
if i > 0 {
buf.WriteRune(tokens.Comma)
}
buf.WriteRune(tokens.DoubleQuote)
buf.WriteString(f)
buf.WriteString(`":`)
v := data[f]
switch v := v.(type) {
case []byte:
buf.WriteRune(tokens.DoubleQuote)
buf.WriteString(base64.EncodeToString(v))
buf.WriteRune(tokens.DoubleQuote)
default:
if err := enc.Encode(v); err != nil {
return nil, fmt.Errorf(`failed to encode value for field %s: %w`, f, err)
}
buf.Truncate(buf.Len() - 1)
}
}
buf.WriteByte(tokens.CloseCurlyBracket)
ret := make([]byte, buf.Len())
copy(ret, buf.Bytes())
return ret, nil
}
func (h *symmetricKey) Keys() []string {
h.mu.RLock()
defer h.mu.RUnlock()
keys := make([]string, 0, 9+len(h.privateParams))
keys = append(keys, KeyTypeKey)
if h.algorithm != nil {
keys = append(keys, AlgorithmKey)
}
if h.keyID != nil {
keys = append(keys, KeyIDKey)
}
if h.keyOps != nil {
keys = append(keys, KeyOpsKey)
}
if h.keyUsage != nil {
keys = append(keys, KeyUsageKey)
}
if h.octets != nil {
keys = append(keys, SymmetricOctetsKey)
}
if h.x509CertChain != nil {
keys = append(keys, X509CertChainKey)
}
if h.x509CertThumbprint != nil {
keys = append(keys, X509CertThumbprintKey)
}
if h.x509CertThumbprintS256 != nil {
keys = append(keys, X509CertThumbprintS256Key)
}
if h.x509URL != nil {
keys = append(keys, X509URLKey)
}
for k := range h.privateParams {
keys = append(keys, k)
}
return keys
}
var symmetricStandardFields KeyFilter
func init() {
symmetricStandardFields = NewFieldNameFilter(KeyTypeKey, KeyUsageKey, KeyOpsKey, AlgorithmKey, KeyIDKey, X509URLKey, X509CertChainKey, X509CertThumbprintKey, X509CertThumbprintS256Key, SymmetricOctetsKey)
}
// SymmetricStandardFieldsFilter returns a KeyFilter that filters out standard Symmetric fields.
func SymmetricStandardFieldsFilter() KeyFilter {
return symmetricStandardFields
}
+74
View File
@@ -0,0 +1,74 @@
package jwk
import (
"fmt"
"sync"
"sync/atomic"
)
var strictKeyUsage = atomic.Bool{}
var keyUsageNames = map[string]struct{}{}
var muKeyUsageName sync.RWMutex
// RegisterKeyUsage registers a possible value that can be used for KeyUsageType.
// Normally, key usage (or the "use" field in a JWK) is either "sig" or "enc",
// but other values may be used.
//
// While this module only works with "sig" and "enc", it is possible that
// systems choose to use other values. This function allows users to register
// new values to be accepted as valid key usage types. Values are case sensitive.
//
// Furthermore, the check against registered values can be completely turned off
// by setting the global option `jwk.WithStrictKeyUsage(false)`.
func RegisterKeyUsage(v string) {
muKeyUsageName.Lock()
defer muKeyUsageName.Unlock()
keyUsageNames[v] = struct{}{}
}
func UnregisterKeyUsage(v string) {
muKeyUsageName.Lock()
defer muKeyUsageName.Unlock()
delete(keyUsageNames, v)
}
func init() {
strictKeyUsage.Store(true)
RegisterKeyUsage("sig")
RegisterKeyUsage("enc")
}
func isValidUsage(v string) bool {
// This function can return true if strictKeyUsage is false
if !strictKeyUsage.Load() {
return true
}
muKeyUsageName.RLock()
defer muKeyUsageName.RUnlock()
_, ok := keyUsageNames[v]
return ok
}
func (k KeyUsageType) String() string {
return string(k)
}
func (k *KeyUsageType) Accept(v any) error {
switch v := v.(type) {
case KeyUsageType:
if !isValidUsage(v.String()) {
return fmt.Errorf("invalid key usage type: %q", v)
}
*k = v
return nil
case string:
if !isValidUsage(v) {
return fmt.Errorf("invalid key usage type: %q", v)
}
*k = KeyUsageType(v)
return nil
}
return fmt.Errorf("invalid Go type for key usage type: %T", v)
}
+38
View File
@@ -0,0 +1,38 @@
package jwk
import "github.com/lestrrat-go/httprc/v3"
type Whitelist = httprc.Whitelist
type WhitelistFunc = httprc.WhitelistFunc
// InsecureWhitelist is an alias to httprc.InsecureWhitelist. Use
// functions in the `httprc` package to interact with this type.
type InsecureWhitelist = httprc.InsecureWhitelist
func NewInsecureWhitelist() InsecureWhitelist {
return httprc.NewInsecureWhitelist()
}
// BlockAllWhitelist is an alias to httprc.BlockAllWhitelist. Use
// functions in the `httprc` package to interact with this type.
type BlockAllWhitelist = httprc.BlockAllWhitelist
func NewBlockAllWhitelist() BlockAllWhitelist {
return httprc.NewBlockAllWhitelist()
}
// RegexpWhitelist is an alias to httprc.RegexpWhitelist. Use
// functions in the `httprc` package to interact with this type.
type RegexpWhitelist = httprc.RegexpWhitelist
func NewRegexpWhitelist() *RegexpWhitelist {
return httprc.NewRegexpWhitelist()
}
// MapWhitelist is an alias to httprc.MapWhitelist. Use
// functions in the `httprc` package to interact with this type.
type MapWhitelist = httprc.MapWhitelist
func NewMapWhitelist() MapWhitelist {
return httprc.NewMapWhitelist()
}
+249
View File
@@ -0,0 +1,249 @@
package jwk
import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"sync"
"github.com/lestrrat-go/blackmagic"
"github.com/lestrrat-go/jwx/v3/jwk/jwkbb"
)
// PEMDecoder is an interface to describe an object that can decode
// a key from PEM encoded ASN.1 DER format.
//
// A PEMDecoder can be specified as an option to `jwk.Parse()` or `jwk.ParseKey()`
// along with the `jwk.WithPEM()` option.
type PEMDecoder interface {
Decode([]byte) (any, []byte, error)
}
// PEMEncoder is an interface to describe an object that can encode
// a key into PEM encoded ASN.1 DER format.
//
// `jwk.Key` instances do not implement a way to encode themselves into
// PEM format. Normally you can just use `jwk.EncodePEM()` to do this, but
// this interface allows you to generalize the encoding process by
// abstracting the `jwk.EncodePEM()` function using `jwk.PEMEncodeFunc`
// along with alternate implementations, should you need them.
type PEMEncoder interface {
Encode(any) (string, []byte, error)
}
type PEMEncodeFunc func(any) (string, []byte, error)
func (f PEMEncodeFunc) Encode(v any) (string, []byte, error) {
return f(v)
}
func encodeX509(v any) (string, []byte, error) {
// we can't import jwk, so just use the interface
if key, ok := v.(Key); ok {
var raw any
if err := Export(key, &raw); err != nil {
return "", nil, fmt.Errorf(`failed to get raw key out of %T: %w`, key, err)
}
v = raw
}
// Try to convert it into a certificate
switch v := v.(type) {
case *rsa.PrivateKey:
return pmRSAPrivateKey, x509.MarshalPKCS1PrivateKey(v), nil
case *ecdsa.PrivateKey:
marshaled, err := x509.MarshalECPrivateKey(v)
if err != nil {
return "", nil, err
}
return pmECPrivateKey, marshaled, nil
case ed25519.PrivateKey:
marshaled, err := x509.MarshalPKCS8PrivateKey(v)
if err != nil {
return "", nil, err
}
return pmPrivateKey, marshaled, nil
case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey:
marshaled, err := x509.MarshalPKIXPublicKey(v)
if err != nil {
return "", nil, err
}
return pmPublicKey, marshaled, nil
default:
return "", nil, fmt.Errorf(`unsupported type %T for ASN.1 DER encoding`, v)
}
}
// EncodePEM encodes the key into a PEM encoded ASN.1 DER format.
// The key can be a jwk.Key or a raw key instance, but it must be one of
// the types supported by `x509` package.
//
// Internally, it uses the same routine as `jwk.EncodeX509()`, and therefore
// the same caveats apply
func EncodePEM(v any) ([]byte, error) {
typ, marshaled, err := encodeX509(v)
if err != nil {
return nil, fmt.Errorf(`failed to encode key in x509: %w`, err)
}
block := &pem.Block{
Type: typ,
Bytes: marshaled,
}
return pem.EncodeToMemory(block), nil
}
const (
pmPrivateKey = `PRIVATE KEY`
pmPublicKey = `PUBLIC KEY`
pmECPrivateKey = `EC PRIVATE KEY`
pmRSAPublicKey = `RSA PUBLIC KEY`
pmRSAPrivateKey = `RSA PRIVATE KEY`
)
// NewPEMDecoder returns a PEMDecoder that decodes keys in PEM encoded ASN.1 DER format.
// You can use it as argument to `jwk.WithPEMDecoder()` option.
//
// The use of this function is planned to be deprecated. The plan is to replace the
// `jwk.WithPEMDecoder()` option with globally available custom X509 decoders which
// can be registered via `jwk.RegisterX509Decoder()` function.
func NewPEMDecoder() PEMDecoder {
return pemDecoder{}
}
type pemDecoder struct{}
// Decode decodes a key in PEM encoded ASN.1 DER format.
// and returns a raw key.
func (pemDecoder) Decode(src []byte) (any, []byte, error) {
block, rest := pem.Decode(src)
if block == nil {
return nil, rest, fmt.Errorf(`failed to decode PEM data`)
}
var ret any
if err := jwkbb.DecodeX509(&ret, block); err != nil {
return nil, rest, err
}
return ret, rest, nil
}
// X509Decoder is an interface that describes an object that can decode
// a PEM encoded ASN.1 DER format into a specific type of key.
//
// This interface is experimental, and may change in the future.
type X509Decoder interface {
// DecodeX509 decodes the given PEM block into the destination object.
// The destination object must be a pointer to a type that can hold the
// decoded key, such as *rsa.PrivateKey, *ecdsa.PrivateKey, etc.
DecodeX509(dst any, block *pem.Block) error
}
// X509DecodeFunc is a function type that implements the X509Decoder interface.
// It allows you to create a custom X509Decoder by providing a function
// that takes a destination and a PEM block, and returns an error if the decoding fails.
//
// This interface is experimental, and may change in the future.
type X509DecodeFunc func(dst any, block *pem.Block) error
func (f X509DecodeFunc) DecodeX509(dst any, block *pem.Block) error {
return f(dst, block)
}
var muX509Decoders sync.Mutex
var x509Decoders = map[any]int{}
var x509DecoderList = []X509Decoder{}
type identDefaultX509Decoder struct{}
func init() {
RegisterX509Decoder(identDefaultX509Decoder{}, X509DecodeFunc(jwkbb.DecodeX509))
}
// RegisterX509Decoder registers a new X509Decoder that can decode PEM encoded ASN.1 DER format.
// Because the decoder could be non-comparable, you must provide an identifier that can be used
// as a map key to identify the decoder.
//
// This function is experimental, and may change in the future.
func RegisterX509Decoder(ident any, decoder X509Decoder) {
if decoder == nil {
panic(`jwk.RegisterX509Decoder: decoder cannot be nil`)
}
muX509Decoders.Lock()
defer muX509Decoders.Unlock()
if _, ok := x509Decoders[ident]; ok {
return // already registered
}
x509Decoders[ident] = len(x509DecoderList)
x509DecoderList = append(x509DecoderList, decoder)
}
// UnregisterX509Decoder unregisters the X509Decoder identified by the given identifier.
// If the identifier is not registered, it does nothing.
//
// This function is experimental, and may change in the future.
func UnregisterX509Decoder(ident any) {
muX509Decoders.Lock()
defer muX509Decoders.Unlock()
idx, ok := x509Decoders[ident]
if !ok {
return // not registered
}
delete(x509Decoders, ident)
l := len(x509DecoderList)
switch idx {
case l - 1:
// if the last element, just truncate the slice
x509DecoderList = x509DecoderList[:l-1]
case 0:
// if the first element, just shift the slice
x509DecoderList = x509DecoderList[1:]
default:
// if the element is in the middle, remove it by slicing
// and appending the two slices together
x509DecoderList = append(x509DecoderList[:idx], x509DecoderList[idx+1:]...)
}
}
// decodeX509 decodes a PEM encoded ASN.1 DER format into the given destination.
// It tries all registered X509 decoders until one of them succeeds.
// If no decoder can handle the PEM block, it returns an error.
func decodeX509(dst any, src []byte) error {
block, _ := pem.Decode(src)
if block == nil {
return fmt.Errorf(`failed to decode PEM data`)
}
var errs []error
for _, d := range x509DecoderList {
if err := d.DecodeX509(dst, block); err != nil {
errs = append(errs, err)
continue
}
// successfully decoded
return nil
}
return fmt.Errorf(`failed to decode X509 data using any of the decoders: %w`, errors.Join(errs...))
}
func decodeX509WithPEMDEcoder(dst any, src []byte, decoder PEMDecoder) error {
ret, _, err := decoder.Decode(src)
if err != nil {
return fmt.Errorf(`failed to decode PEM data: %w`, err)
}
if err := blackmagic.AssignIfCompatible(dst, ret); err != nil {
return fmt.Errorf(`failed to assign decoded key to destination: %w`, err)
}
return nil
}