711 lines
21 KiB
Go
711 lines
21 KiB
Go
//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())
|
|
}
|