322 lines
7.7 KiB
Go
322 lines
7.7 KiB
Go
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
|
|
}
|