From adbdf35f81e64f06991af9796fb7a6d871e023e6 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Thu, 18 May 2023 16:32:33 +0900 Subject: [PATCH 1/2] Implement jwe.KeyEncrypter and jwe.KeyDecrypter This allows users to specify a key who can encrypt/decrypt by itself, much like the built-in crypto.Signer interface. --- jwe/decrypt.go | 11 +- jwe/interface.go | 48 ++++++ jwe/internal/keyenc/interface.go | 8 +- jwe/internal/keyenc/keyenc.go | 14 +- jwe/internal/keygen/interface.go | 3 - jwe/internal/keygen/keygen.go | 12 -- jwe/jwe.go | 269 +++++++++++++++++-------------- jwe/jwe_test.go | 44 ++++- 8 files changed, 251 insertions(+), 158 deletions(-) diff --git a/jwe/decrypt.go b/jwe/decrypt.go index 1988f8095..387d4a999 100644 --- a/jwe/decrypt.go +++ b/jwe/decrypt.go @@ -137,8 +137,8 @@ func (d *decrypter) ContentCipher() (content_crypt.Cipher, error) { return d.cipher, nil } -func (d *decrypter) Decrypt(recipientKey, ciphertext []byte) (plaintext []byte, err error) { - cek, keyerr := d.DecryptKey(recipientKey) +func (d *decrypter) Decrypt(recipient Recipient, ciphertext []byte, msg *Message) (plaintext []byte, err error) { + cek, keyerr := d.DecryptKey(recipient, msg) if keyerr != nil { err = fmt.Errorf(`failed to decrypt key: %w`, keyerr) return @@ -226,7 +226,12 @@ func (d *decrypter) decryptSymmetricKey(recipientKey, cek []byte) ([]byte, error } } -func (d *decrypter) DecryptKey(recipientKey []byte) (cek []byte, err error) { +func (d *decrypter) DecryptKey(recipient Recipient, msg *Message) (cek []byte, err error) { + recipientKey := recipient.EncryptedKey() + if kd, ok := d.privkey.(KeyDecrypter); ok { + return kd.DecryptKey(d.keyalg, recipientKey, recipient, msg) + } + if d.keyalg.IsSymmetric() { var ok bool cek, ok = d.privkey.([]byte) diff --git a/jwe/interface.go b/jwe/interface.go index d1044ce1d..02fb2d9e0 100644 --- a/jwe/interface.go +++ b/jwe/interface.go @@ -3,9 +3,57 @@ package jwe import ( "github.com/lestrrat-go/iter/mapiter" "github.com/lestrrat-go/jwx/v2/internal/iter" + "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwe/internal/keygen" ) +// KeyEncrypter is an interface for object that can encrypt a +// content encryption key. +// +// You can use this in place of a regular key (i.e. in jwe.WithKey()) +// to encrypt the content encryption key in a JWE message without +// having to expose the secret key in memory, for example, when you +// want to use hardware security modules (HSMs) to encrypt the key. +type KeyEncrypter interface { + // Algorithm returns the algorithm used to encrypt the key. + Algorithm() jwa.KeyEncryptionAlgorithm + + // EncryptKey encrypts the given content encryption key. + EncryptKey([]byte) ([]byte, error) +} + +// KeyIDer is an interface for things that can return a key ID. +// +// As of this writing, this is solely used to identify KeyEncrypter +// objects that also carry a key ID on its own. +type KeyIDer interface { + KeyID() string +} + +// KeyDecrypter is an interface for objects that can decrypt a content +// encryption key. +// +// You can use this in place of a regular key (i.e. in jwe.WithKey()) +// to decrypt the encrypted key in a JWE message without having to +// expose the secret key in memory, for example, when you want to use +// hardware security modules (HSMs) to decrypt the key. +type KeyDecrypter interface { + // Decrypt decrypts the encrypted key of a JWE message. + // + // Make sure you understand how JWE messages are structured. + // + // For example, while in most circumstances a JWE message will only have one recipient, + // a JWE message may contain multiple recipients, each with their own + // encrypted key. This method will be called for each recipient, instead of + // just once for a message. + // + // Also, header values could be found in either protected/unprotected headers + // of a JWE message, as well as in protected/unprotected headers for each recipient. + // When checking a header value, you can decide to use either one, or both, but you + // must be aware that there are multiple places to look for. + DecryptKey(alg jwa.KeyEncryptionAlgorithm, encryptedKey []byte, recipient Recipient, message *Message) ([]byte, error) +} + // Recipient holds the encrypted key and hints to decrypt the key type Recipient interface { Headers() Headers diff --git a/jwe/internal/keyenc/interface.go b/jwe/internal/keyenc/interface.go index 70fe7301e..4457538eb 100644 --- a/jwe/internal/keyenc/interface.go +++ b/jwe/internal/keyenc/interface.go @@ -11,13 +11,7 @@ import ( // Encrypter is an interface for things that can encrypt keys type Encrypter interface { Algorithm() jwa.KeyEncryptionAlgorithm - Encrypt([]byte) (keygen.ByteSource, error) - // KeyID returns the key id for this Encrypter. This exists so that - // you can pass in a Encrypter to MultiEncrypt, you can rest assured - // that the generated key will have the proper key ID. - KeyID() string - - SetKeyID(string) + EncryptKey([]byte) (keygen.ByteSource, error) } // Decrypter is an interface for things that can decrypt keys diff --git a/jwe/internal/keyenc/keyenc.go b/jwe/internal/keyenc/keyenc.go index 3e19e62b0..ce5e657cd 100644 --- a/jwe/internal/keyenc/keyenc.go +++ b/jwe/internal/keyenc/keyenc.go @@ -46,7 +46,7 @@ func (kw *Noop) KeyID() string { return kw.keyID } -func (kw *Noop) Encrypt(cek []byte) (keygen.ByteSource, error) { +func (kw *Noop) EncryptKey(cek []byte) (keygen.ByteSource, error) { return keygen.ByteKey(kw.sharedkey), nil } @@ -88,7 +88,7 @@ func (kw *AES) Decrypt(enckey []byte) ([]byte, error) { } // KeyEncrypt encrypts the given content encryption key -func (kw *AES) Encrypt(cek []byte) (keygen.ByteSource, error) { +func (kw *AES) EncryptKey(cek []byte) (keygen.ByteSource, error) { block, err := aes.NewCipher(kw.sharedkey) if err != nil { return nil, fmt.Errorf(`failed to create cipher from shared key: %w`, err) @@ -119,7 +119,7 @@ func (kw AESGCMEncrypt) KeyID() string { return kw.keyID } -func (kw AESGCMEncrypt) Encrypt(cek []byte) (keygen.ByteSource, error) { +func (kw AESGCMEncrypt) EncryptKey(cek []byte) (keygen.ByteSource, error) { block, err := aes.NewCipher(kw.sharedkey) if err != nil { return nil, fmt.Errorf(`failed to create cipher from shared key: %w`, err) @@ -181,7 +181,7 @@ func (kw PBES2Encrypt) KeyID() string { return kw.keyID } -func (kw PBES2Encrypt) Encrypt(cek []byte) (keygen.ByteSource, error) { +func (kw PBES2Encrypt) EncryptKey(cek []byte) (keygen.ByteSource, error) { count := 10000 salt := make([]byte, kw.keylen) _, err := io.ReadFull(rand.Reader, salt) @@ -245,7 +245,7 @@ func (kw ECDHESEncrypt) KeyID() string { } // KeyEncrypt encrypts the content encryption key using ECDH-ES -func (kw ECDHESEncrypt) Encrypt(cek []byte) (keygen.ByteSource, error) { +func (kw ECDHESEncrypt) EncryptKey(cek []byte) (keygen.ByteSource, error) { kg, err := kw.generator.Generate() if err != nil { return nil, fmt.Errorf(`failed to create key generator: %w`, err) @@ -443,7 +443,7 @@ func (e RSAOAEPEncrypt) KeyID() string { } // KeyEncrypt encrypts the content encryption key using RSA PKCS1v15 -func (e RSAPKCSEncrypt) Encrypt(cek []byte) (keygen.ByteSource, error) { +func (e RSAPKCSEncrypt) EncryptKey(cek []byte) (keygen.ByteSource, error) { if e.alg != jwa.RSA1_5 { return nil, fmt.Errorf("invalid RSA PKCS encrypt algorithm (%s)", e.alg) } @@ -455,7 +455,7 @@ func (e RSAPKCSEncrypt) Encrypt(cek []byte) (keygen.ByteSource, error) { } // KeyEncrypt encrypts the content encryption key using RSA OAEP -func (e RSAOAEPEncrypt) Encrypt(cek []byte) (keygen.ByteSource, error) { +func (e RSAOAEPEncrypt) EncryptKey(cek []byte) (keygen.ByteSource, error) { var hash hash.Hash switch e.alg { case jwa.RSA_OAEP: diff --git a/jwe/internal/keygen/interface.go b/jwe/internal/keygen/interface.go index 10543c056..ac019ddfa 100644 --- a/jwe/internal/keygen/interface.go +++ b/jwe/internal/keygen/interface.go @@ -12,9 +12,6 @@ type Generator interface { Generate() (ByteSource, error) } -// StaticKeyGenerate uses a static byte buffer to provide keys. -type Static []byte - // RandomKeyGenerate generates random keys type Random struct { keysize int diff --git a/jwe/internal/keygen/keygen.go b/jwe/internal/keygen/keygen.go index 0d9c7ece9..150cbf715 100644 --- a/jwe/internal/keygen/keygen.go +++ b/jwe/internal/keygen/keygen.go @@ -22,18 +22,6 @@ func (k ByteKey) Bytes() []byte { return []byte(k) } -// Size returns the size of the key -func (g Static) Size() int { - return len(g) -} - -// Generate returns the key -func (g Static) Generate() (ByteSource, error) { - buf := make([]byte, g.Size()) - copy(buf, g) - return ByteKey(buf), nil -} - // NewRandom creates a new Generator that returns // random bytes func NewRandom(n int) Random { diff --git a/jwe/jwe.go b/jwe/jwe.go index dfd86132e..67b8e97b3 100644 --- a/jwe/jwe.go +++ b/jwe/jwe.go @@ -37,6 +37,22 @@ var _ = fmtMax var registry = json.NewRegistry() +type keyEncrypterWrapper struct { + encrypter KeyEncrypter +} + +func (w *keyEncrypterWrapper) Algorithm() jwa.KeyEncryptionAlgorithm { + return w.encrypter.Algorithm() +} + +func (w *keyEncrypterWrapper) EncryptKey(cek []byte) (keygen.ByteSource, error) { + encrypted, err := w.encrypter.EncryptKey(cek) + if err != nil { + return nil, err + } + return keygen.ByteKey(encrypted), nil +} + type recipientBuilder struct { alg jwa.KeyEncryptionAlgorithm key interface{} @@ -44,11 +60,18 @@ type recipientBuilder struct { } func (b *recipientBuilder) Build(cek []byte, calg jwa.ContentEncryptionAlgorithm, cc *content_crypt.Generic) (Recipient, []byte, error) { - // we need the raw key + var enc keyenc.Encrypter + + // we need the raw key for later use rawKey := b.key var keyID string - if jwkKey, ok := b.key.(jwk.Key); ok { + if ke, ok := b.key.(KeyEncrypter); ok { + enc = &keyEncrypterWrapper{encrypter: ke} + if kider, ok := enc.(KeyIDer); ok { + keyID = kider.KeyID() + } + } else if jwkKey, ok := b.key.(jwk.Key); ok { // Meanwhile, grab the kid as well keyID = jwkKey.KeyID() @@ -60,114 +83,110 @@ func (b *recipientBuilder) Build(cek []byte, calg jwa.ContentEncryptionAlgorithm rawKey = raw } - // First, create a key encryptor - var enc keyenc.Encrypter - switch b.alg { - case jwa.RSA1_5: - var pubkey rsa.PublicKey - if err := keyconv.RSAPublicKey(&pubkey, rawKey); err != nil { - return nil, nil, fmt.Errorf(`failed to generate public key from key (%T): %w`, rawKey, err) - } - - v, err := keyenc.NewRSAPKCSEncrypt(b.alg, &pubkey) - if err != nil { - return nil, nil, fmt.Errorf(`failed to create RSA PKCS encrypter: %w`, err) - } - enc = v - case jwa.RSA_OAEP, jwa.RSA_OAEP_256: - var pubkey rsa.PublicKey - if err := keyconv.RSAPublicKey(&pubkey, rawKey); err != nil { - return nil, nil, fmt.Errorf(`failed to generate public key from key (%T): %w`, rawKey, err) - } - - v, err := keyenc.NewRSAOAEPEncrypt(b.alg, &pubkey) - if err != nil { - return nil, nil, fmt.Errorf(`failed to create RSA OAEP encrypter: %w`, err) - } - enc = v - case jwa.A128KW, jwa.A192KW, jwa.A256KW, - jwa.A128GCMKW, jwa.A192GCMKW, jwa.A256GCMKW, - jwa.PBES2_HS256_A128KW, jwa.PBES2_HS384_A192KW, jwa.PBES2_HS512_A256KW: - sharedkey, ok := rawKey.([]byte) - if !ok { - return nil, nil, fmt.Errorf(`invalid key: []byte required (%T)`, rawKey) - } - - var err error - switch b.alg { - case jwa.A128KW, jwa.A192KW, jwa.A256KW: - enc, err = keyenc.NewAES(b.alg, sharedkey) - case jwa.PBES2_HS256_A128KW, jwa.PBES2_HS384_A192KW, jwa.PBES2_HS512_A256KW: - enc, err = keyenc.NewPBES2Encrypt(b.alg, sharedkey) - default: - enc, err = keyenc.NewAESGCMEncrypt(b.alg, sharedkey) - } - if err != nil { - return nil, nil, fmt.Errorf(`failed to create key wrap encrypter: %w`, err) - } - // NOTE: there was formerly a restriction, introduced - // in PR #26, which disallowed certain key/content - // algorithm combinations. This seemed bogus, and - // interop with the jose tool demonstrates it. - case jwa.ECDH_ES, jwa.ECDH_ES_A128KW, jwa.ECDH_ES_A192KW, jwa.ECDH_ES_A256KW: - var keysize int + if enc == nil { switch b.alg { - case jwa.ECDH_ES: - // https://tools.ietf.org/html/rfc7518#page-15 - // In Direct Key Agreement mode, the output of the Concat KDF MUST be a - // key of the same length as that used by the "enc" algorithm. - keysize = cc.KeySize() - case jwa.ECDH_ES_A128KW: - keysize = 16 - case jwa.ECDH_ES_A192KW: - keysize = 24 - case jwa.ECDH_ES_A256KW: - keysize = 32 - } - - switch key := rawKey.(type) { - case x25519.PublicKey: - var apu, apv []byte - if hdrs := b.headers; hdrs != nil { - apu = hdrs.AgreementPartyUInfo() - apv = hdrs.AgreementPartyVInfo() + case jwa.RSA1_5: + var pubkey rsa.PublicKey + if err := keyconv.RSAPublicKey(&pubkey, rawKey); err != nil { + return nil, nil, fmt.Errorf(`failed to generate public key from key (%T): %w`, rawKey, err) } - v, err := keyenc.NewECDHESEncrypt(b.alg, calg, keysize, rawKey, apu, apv) + v, err := keyenc.NewRSAPKCSEncrypt(b.alg, &pubkey) if err != nil { - return nil, nil, fmt.Errorf(`failed to create ECDHS key wrap encrypter: %w`, err) + return nil, nil, fmt.Errorf(`failed to create RSA PKCS encrypter: %w`, err) } enc = v - default: - var pubkey ecdsa.PublicKey - if err := keyconv.ECDSAPublicKey(&pubkey, rawKey); err != nil { - return nil, nil, fmt.Errorf(`failed to generate public key from key (%T): %w`, key, err) + case jwa.RSA_OAEP, jwa.RSA_OAEP_256: + var pubkey rsa.PublicKey + if err := keyconv.RSAPublicKey(&pubkey, rawKey); err != nil { + return nil, nil, fmt.Errorf(`failed to generate public key from key (%T): %w`, rawKey, err) } - var apu, apv []byte - if hdrs := b.headers; hdrs != nil { - apu = hdrs.AgreementPartyUInfo() - apv = hdrs.AgreementPartyVInfo() + v, err := keyenc.NewRSAOAEPEncrypt(b.alg, &pubkey) + if err != nil { + return nil, nil, fmt.Errorf(`failed to create RSA OAEP encrypter: %w`, err) + } + enc = v + case jwa.A128KW, jwa.A192KW, jwa.A256KW, + jwa.A128GCMKW, jwa.A192GCMKW, jwa.A256GCMKW, + jwa.PBES2_HS256_A128KW, jwa.PBES2_HS384_A192KW, jwa.PBES2_HS512_A256KW: + sharedkey, ok := rawKey.([]byte) + if !ok { + return nil, nil, fmt.Errorf(`invalid key: []byte required (%T)`, rawKey) } - v, err := keyenc.NewECDHESEncrypt(b.alg, calg, keysize, &pubkey, apu, apv) + var err error + switch b.alg { + case jwa.A128KW, jwa.A192KW, jwa.A256KW: + enc, err = keyenc.NewAES(b.alg, sharedkey) + case jwa.PBES2_HS256_A128KW, jwa.PBES2_HS384_A192KW, jwa.PBES2_HS512_A256KW: + enc, err = keyenc.NewPBES2Encrypt(b.alg, sharedkey) + default: + enc, err = keyenc.NewAESGCMEncrypt(b.alg, sharedkey) + } if err != nil { - return nil, nil, fmt.Errorf(`failed to create ECDHS key wrap encrypter: %w`, err) + return nil, nil, fmt.Errorf(`failed to create key wrap encrypter: %w`, err) + } + // NOTE: there was formerly a restriction, introduced + // in PR #26, which disallowed certain key/content + // algorithm combinations. This seemed bogus, and + // interop with the jose tool demonstrates it. + case jwa.ECDH_ES, jwa.ECDH_ES_A128KW, jwa.ECDH_ES_A192KW, jwa.ECDH_ES_A256KW: + var keysize int + switch b.alg { + case jwa.ECDH_ES: + // https://tools.ietf.org/html/rfc7518#page-15 + // In Direct Key Agreement mode, the output of the Concat KDF MUST be a + // key of the same length as that used by the "enc" algorithm. + keysize = cc.KeySize() + case jwa.ECDH_ES_A128KW: + keysize = 16 + case jwa.ECDH_ES_A192KW: + keysize = 24 + case jwa.ECDH_ES_A256KW: + keysize = 32 } - enc = v - } - case jwa.DIRECT: - sharedkey, ok := rawKey.([]byte) - if !ok { - return nil, nil, fmt.Errorf("invalid key: []byte required") - } - enc, _ = keyenc.NewNoop(b.alg, sharedkey) - default: - return nil, nil, fmt.Errorf(`invalid key encryption algorithm (%s)`, b.alg) - } - if keyID != "" { - enc.SetKeyID(keyID) + switch key := rawKey.(type) { + case x25519.PublicKey: + var apu, apv []byte + if hdrs := b.headers; hdrs != nil { + apu = hdrs.AgreementPartyUInfo() + apv = hdrs.AgreementPartyVInfo() + } + + v, err := keyenc.NewECDHESEncrypt(b.alg, calg, keysize, rawKey, apu, apv) + if err != nil { + return nil, nil, fmt.Errorf(`failed to create ECDHS key wrap encrypter: %w`, err) + } + enc = v + default: + var pubkey ecdsa.PublicKey + if err := keyconv.ECDSAPublicKey(&pubkey, rawKey); err != nil { + return nil, nil, fmt.Errorf(`failed to generate public key from key (%T): %w`, key, err) + } + + var apu, apv []byte + if hdrs := b.headers; hdrs != nil { + apu = hdrs.AgreementPartyUInfo() + apv = hdrs.AgreementPartyVInfo() + } + + v, err := keyenc.NewECDHESEncrypt(b.alg, calg, keysize, &pubkey, apu, apv) + if err != nil { + return nil, nil, fmt.Errorf(`failed to create ECDHS key wrap encrypter: %w`, err) + } + enc = v + } + case jwa.DIRECT: + sharedkey, ok := rawKey.([]byte) + if !ok { + return nil, nil, fmt.Errorf("invalid key: []byte required") + } + enc, _ = keyenc.NewNoop(b.alg, sharedkey) + default: + return nil, nil, fmt.Errorf(`invalid key encryption algorithm (%s)`, b.alg) + } } r := NewRecipient() @@ -178,14 +197,15 @@ func (b *recipientBuilder) Build(cek []byte, calg jwa.ContentEncryptionAlgorithm if err := r.Headers().Set(AlgorithmKey, b.alg); err != nil { return nil, nil, fmt.Errorf(`failed to set header: %w`, err) } - if v := enc.KeyID(); v != "" { - if err := r.Headers().Set(KeyIDKey, v); err != nil { + + if keyID != "" { + if err := r.Headers().Set(KeyIDKey, keyID); err != nil { return nil, nil, fmt.Errorf(`failed to set header: %w`, err) } } var rawCEK []byte - enckey, err := enc.Encrypt(cek) + enckey, err := enc.EncryptKey(cek) if err != nil { return nil, nil, fmt.Errorf(`failed to encrypt key: %w`, err) } @@ -233,6 +253,7 @@ func Encrypt(payload []byte, options ...EncryptOption) ([]byte, error) { // default compression is "none" compression := jwa.NoCompress + // default format is compact serialization format := fmtCompact // builds each "recipient" with encrypted_key and headers @@ -532,7 +553,7 @@ func (dctx *decryptCtx) try(ctx context.Context, recipient Recipient, keyUsed in alg := pair.alg.(jwa.KeyEncryptionAlgorithm) key := pair.key - decrypted, err := dctx.decryptKey(ctx, alg, key, recipient) + decrypted, err := dctx.decryptContent(ctx, alg, key, recipient) if err != nil { lastError = err continue @@ -549,7 +570,7 @@ func (dctx *decryptCtx) try(ctx context.Context, recipient Recipient, keyUsed in return nil, fmt.Errorf(`jwe.Decrypt: tried %d keys, but failed to match any of the keys with recipient (last error = %s)`, tried, lastError) } -func (dctx *decryptCtx) decryptKey(ctx context.Context, alg jwa.KeyEncryptionAlgorithm, key interface{}, recipient Recipient) ([]byte, error) { +func (dctx *decryptCtx) decryptContent(ctx context.Context, alg jwa.KeyEncryptionAlgorithm, key interface{}, recipient Recipient) ([]byte, error) { if jwkKey, ok := key.(jwk.Key); ok { var raw interface{} if err := jwkKey.Raw(&raw); err != nil { @@ -610,31 +631,29 @@ func (dctx *decryptCtx) decryptKey(ctx context.Context, alg jwa.KeyEncryptionAlg } case jwa.A128GCMKW, jwa.A192GCMKW, jwa.A256GCMKW: ivB64, ok := h2.Get(InitializationVectorKey) - if !ok { - return nil, fmt.Errorf(`failed to get 'iv' field`) - } - ivB64Str, ok := ivB64.(string) - if !ok { - return nil, fmt.Errorf("unexpected type for 'iv': %T", ivB64) + if ok { + ivB64Str, ok := ivB64.(string) + if !ok { + return nil, fmt.Errorf("unexpected type for 'iv': %T", ivB64) + } + iv, err := base64.DecodeString(ivB64Str) + if err != nil { + return nil, fmt.Errorf(`failed to b64-decode 'iv': %w`, err) + } + dec.KeyInitializationVector(iv) } tagB64, ok := h2.Get(TagKey) - if !ok { - return nil, fmt.Errorf(`failed to get 'tag' field`) - } - tagB64Str, ok := tagB64.(string) - if !ok { - return nil, fmt.Errorf("unexpected type for 'tag': %T", tagB64) - } - iv, err := base64.DecodeString(ivB64Str) - if err != nil { - return nil, fmt.Errorf(`failed to b64-decode 'iv': %w`, err) - } - tag, err := base64.DecodeString(tagB64Str) - if err != nil { - return nil, fmt.Errorf(`failed to b64-decode 'tag': %w`, err) + if ok { + tagB64Str, ok := tagB64.(string) + if !ok { + return nil, fmt.Errorf("unexpected type for 'tag': %T", tagB64) + } + tag, err := base64.DecodeString(tagB64Str) + if err != nil { + return nil, fmt.Errorf(`failed to b64-decode 'tag': %w`, err) + } + dec.KeyTag(tag) } - dec.KeyInitializationVector(iv) - dec.KeyTag(tag) case jwa.PBES2_HS256_A128KW, jwa.PBES2_HS384_A192KW, jwa.PBES2_HS512_A256KW: saltB64, ok := h2.Get(SaltKey) if !ok { @@ -661,7 +680,7 @@ func (dctx *decryptCtx) decryptKey(ctx context.Context, alg jwa.KeyEncryptionAlg dec.KeyCount(int(countFlt)) } - plaintext, err := dec.Decrypt(recipient.EncryptedKey(), dctx.msg.cipherText) + plaintext, err := dec.Decrypt(recipient, dctx.msg.cipherText, dctx.msg) if err != nil { return nil, fmt.Errorf(`jwe.Decrypt: decryption failed: %w`, err) } diff --git a/jwe/jwe_test.go b/jwe/jwe_test.go index d3732ac82..64800de9d 100644 --- a/jwe/jwe_test.go +++ b/jwe/jwe_test.go @@ -1,6 +1,7 @@ package jwe_test import ( + "bytes" "context" "crypto" "crypto/ecdsa" @@ -674,7 +675,6 @@ func TestGHIssue230(t *testing.T) { func TestReadFile(t *testing.T) { const s = `eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDbSv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaVmqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je81860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi6UklfCpIMfIjf7iGdXKHzg.48V1_ALb6US04U3b.5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A.XFBoMYUZodetZdvTiFvSkQ` - t.Parallel() f, err := os.CreateTemp("", "test-read-file-*.jwe") if !assert.NoError(t, err, `os.CreateTemp should succeed`) { @@ -841,3 +841,45 @@ func TestGH840(t *testing.T) { _, err = jwe.Encrypt([]byte(payload), jwe.WithKey(jwa.ECDH_ES_A128KW, pubkey)) require.Error(t, err, `jwe.Encrypt should fail (instead of panic)`) } + +type dummyKeyEncrypterDecrypter struct { + key []byte +} + +func (kd *dummyKeyEncrypterDecrypter) DecryptKey(alg jwa.KeyEncryptionAlgorithm, cek []byte, _ jwe.Recipient, _ *jwe.Message) ([]byte, error) { + return bytes.TrimSuffix(cek, kd.key), nil +} + +func (kd *dummyKeyEncrypterDecrypter) Algorithm() jwa.KeyEncryptionAlgorithm { + return jwa.A128GCMKW +} + +func (kd *dummyKeyEncrypterDecrypter) EncryptKey(key []byte) ([]byte, error) { + return append(key, kd.key...), nil +} + +var _ jwe.KeyEncrypter = (*dummyKeyEncrypterDecrypter)(nil) + +func TestGH924(t *testing.T) { + sharedKey := []byte("abra-kadabra") + + ked := &dummyKeyEncrypterDecrypter{key: sharedKey} + + payload := []byte("Lorem Ipsum") + encrypted, err := jwe.Encrypt( + payload, + jwe.WithJSON(), + jwe.WithKey(jwa.A128GCMKW, ked), + jwe.WithContentEncryption(jwa.A128GCM), + ) + require.NoError(t, err, `jwe.Encrypt should succeed`) + + var msg jwe.Message + decrypted, err := jwe.Decrypt( + encrypted, + jwe.WithKey(jwa.A128GCMKW, ked), + jwe.WithMessage(&msg), + ) + require.NoError(t, err, `jwe.Decrypt should succeed`) + require.Equal(t, payload, decrypted, `decrypt messages match`) +} From e78337d9ff875ad4a6196b931293864776d91ac2 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Sat, 10 Jun 2023 23:54:33 +0900 Subject: [PATCH 2/2] Add experimental label to this feature --- Changes | 11 +++++++++++ jwe/interface.go | 6 ++++++ 2 files changed, 17 insertions(+) diff --git a/Changes b/Changes index d5e2d340f..38fa19cbd 100644 --- a/Changes +++ b/Changes @@ -5,6 +5,17 @@ v2 has many incompatibilities with v1. To see the full list of differences betwe v1 and v2, please read the Changes-v2.md file (https://github.com/lestrrat-go/jwx/blob/develop/v2/Changes-v2.md) v2.0.10 - UNRELEASED +[New Features] + * [jwe] (EXPERIMENTAL) Added `jwe.KeyEncrypter` and `jwe.KeyDecrypter` interfaces + that works in similar ways as how `crypto.Signer` works for signature + generation and verification. It can act as the interface for your encryption/decryption + keys that are for example stored in an hardware device. + + This feature is labeled experimental because the API for the above interfaces have not + been battle tested, and may need to changed yet. Please be aware that until the API + is deemed stable, you may have to adapat our code to these possible changes, + _even_ during minor version upgrades of this library. + [Bug fixes] * Registering JWS signers/verifiers did not work since v2.0.0, because the way we handle algorithm names changed in 2aa98ce6884187180a7145b73da78c859dd46c84. diff --git a/jwe/interface.go b/jwe/interface.go index 02fb2d9e0..828412f67 100644 --- a/jwe/interface.go +++ b/jwe/interface.go @@ -14,6 +14,9 @@ import ( // to encrypt the content encryption key in a JWE message without // having to expose the secret key in memory, for example, when you // want to use hardware security modules (HSMs) to encrypt the key. +// +// This API is experimental and may change without notice, even +// in minor releases. type KeyEncrypter interface { // Algorithm returns the algorithm used to encrypt the key. Algorithm() jwa.KeyEncryptionAlgorithm @@ -37,6 +40,9 @@ type KeyIDer interface { // to decrypt the encrypted key in a JWE message without having to // expose the secret key in memory, for example, when you want to use // hardware security modules (HSMs) to decrypt the key. +// +// This API is experimental and may change without notice, even +// in minor releases. type KeyDecrypter interface { // Decrypt decrypts the encrypted key of a JWE message. //