Skip to content

Commit 42f9d38

Browse files
committed
feat: add key type validation for JWE paths
Add validate_jwe_key_type to enforce key type checks at construction time, restoring structural parity with the JWS validation path.
1 parent 42a561f commit 42f9d38

3 files changed

Lines changed: 114 additions & 2 deletions

File tree

src/gose/internal/key_helpers.gleam

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,34 @@ pub fn validate_jws_key_type(
131131
}
132132
}
133133

134+
pub fn validate_jwe_key_type(
135+
alg: jwa.JweAlg,
136+
key: jwk.Jwk,
137+
) -> Result(Nil, gose.GoseError) {
138+
let key_type = jwk.key_type(key)
139+
case alg, key_type {
140+
jwa.JweDirect, jwk.OctKeyType
141+
| jwa.JweAesKeyWrap(_, _), jwk.OctKeyType
142+
| jwa.JweChaCha20KeyWrap(_), jwk.OctKeyType
143+
-> Ok(Nil)
144+
145+
jwa.JweRsa(_), jwk.RsaKeyType -> Ok(Nil)
146+
147+
jwa.JweEcdhEs(_), jwk.EcKeyType -> Ok(Nil)
148+
jwa.JweEcdhEs(_), jwk.OkpKeyType -> validate_xdh_key(key)
149+
150+
jwa.JwePbes2(_), _ ->
151+
Error(gose.InvalidState("use password_decryptor for PBES2 algorithms"))
152+
153+
_, _ ->
154+
Error(gose.InvalidState(
155+
"algorithm "
156+
<> jwa.jwe_alg_to_string(alg)
157+
<> " incompatible with key type",
158+
))
159+
}
160+
}
161+
134162
fn validate_ec_curve(
135163
key: jwk.Jwk,
136164
expected: ec.Curve,
@@ -152,6 +180,16 @@ fn validate_eddsa_key(key: jwk.Jwk) -> Result(Nil, gose.GoseError) {
152180
}
153181
}
154182

183+
fn validate_xdh_key(key: jwk.Jwk) -> Result(Nil, gose.GoseError) {
184+
case jwk.xdh_curve(key) {
185+
Ok(_) -> Ok(Nil)
186+
Error(_) ->
187+
Error(gose.InvalidState(
188+
"ECDH-ES algorithm requires an EC or XDH key (X25519/X448), not EdDSA",
189+
))
190+
}
191+
}
192+
155193
/// Validate that a key's `alg` field matches the expected JWE algorithm.
156194
///
157195
/// If the key has no `alg` field set, validation passes (any algorithm allowed).
@@ -222,6 +260,7 @@ pub fn validate_key_for_jwe_decryption(
222260
alg: jwa.JweAlg,
223261
key: jwk.Jwk,
224262
) -> Result(Nil, gose.GoseError) {
263+
use _ <- result.try(validate_jwe_key_type(alg, key))
225264
use _ <- result.try(validate_key_use(key, ForDecryption))
226265
use _ <- result.try(validate_key_ops(key, ForDecryption))
227266
validate_key_algorithm_jwe(key, alg)
@@ -234,6 +273,7 @@ pub fn validate_key_for_jwe_encryption(
234273
alg: jwa.JweAlg,
235274
key: jwk.Jwk,
236275
) -> Result(Nil, gose.GoseError) {
276+
use _ <- result.try(validate_jwe_key_type(alg, key))
237277
use _ <- result.try(validate_key_use(key, ForEncryption))
238278
use _ <- result.try(validate_key_ops(key, ForEncryption))
239279
validate_key_algorithm_jwe(key, alg)

test/gose/internal/key_helpers_test.gleam

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,3 +511,75 @@ pub fn validate_key_for_jwe_encryption_wrong_alg_test() {
511511
assert msg
512512
== "key algorithm mismatch: key has RSA-OAEP, expected RSA-OAEP-256"
513513
}
514+
515+
pub fn validate_jwe_key_type_oct_alg_ok_test() {
516+
let key = jwk.generate_enc_key(jwa.AesGcm(jwa.Aes128))
517+
let result = key_helpers.validate_jwe_key_type(jwa.JweDirect, key)
518+
assert result == Ok(Nil)
519+
}
520+
521+
pub fn validate_jwe_key_type_rsa_ok_test() {
522+
let key = fixtures.rsa_private_key()
523+
let result =
524+
key_helpers.validate_jwe_key_type(jwa.JweRsa(jwa.RsaOaepSha256), key)
525+
assert result == Ok(Nil)
526+
}
527+
528+
pub fn validate_jwe_key_type_ecdh_es_ec_ok_test() {
529+
let key = fixtures.ec_p256_key()
530+
let result =
531+
key_helpers.validate_jwe_key_type(jwa.JweEcdhEs(jwa.EcdhEsDirect), key)
532+
assert result == Ok(Nil)
533+
}
534+
535+
pub fn validate_jwe_key_type_ecdh_es_xdh_ok_test() {
536+
let key = fixtures.x25519_key()
537+
let result =
538+
key_helpers.validate_jwe_key_type(jwa.JweEcdhEs(jwa.EcdhEsDirect), key)
539+
assert result == Ok(Nil)
540+
}
541+
542+
pub fn validate_jwe_key_type_ecdh_es_rejects_eddsa_test() {
543+
let key = fixtures.ed25519_key()
544+
let assert Error(gose.InvalidState(msg)) =
545+
key_helpers.validate_jwe_key_type(jwa.JweEcdhEs(jwa.EcdhEsDirect), key)
546+
assert msg
547+
== "ECDH-ES algorithm requires an EC or XDH key (X25519/X448), not EdDSA"
548+
}
549+
550+
pub fn validate_jwe_key_type_pbes2_rejects_any_key_test() {
551+
let key = jwk.generate_hmac_key(jwa.HmacSha256)
552+
let assert Error(gose.InvalidState(msg)) =
553+
key_helpers.validate_jwe_key_type(
554+
jwa.JwePbes2(jwa.Pbes2Sha256Aes128Kw),
555+
key,
556+
)
557+
assert msg == "use password_decryptor for PBES2 algorithms"
558+
}
559+
560+
pub fn validate_jwe_key_type_wrong_key_type_test() {
561+
let key = jwk.generate_hmac_key(jwa.HmacSha256)
562+
let assert Error(gose.InvalidState(msg)) =
563+
key_helpers.validate_jwe_key_type(jwa.JweRsa(jwa.RsaOaepSha256), key)
564+
assert msg == "algorithm RSA-OAEP-256 incompatible with key type"
565+
}
566+
567+
pub fn validate_key_for_jwe_decryption_wrong_key_type_test() {
568+
let key = jwk.generate_hmac_key(jwa.HmacSha256)
569+
let assert Error(gose.InvalidState(msg)) =
570+
key_helpers.validate_key_for_jwe_decryption(
571+
jwa.JweRsa(jwa.RsaOaepSha256),
572+
key,
573+
)
574+
assert msg == "algorithm RSA-OAEP-256 incompatible with key type"
575+
}
576+
577+
pub fn validate_key_for_jwe_encryption_wrong_key_type_test() {
578+
let key = jwk.generate_hmac_key(jwa.HmacSha256)
579+
let assert Error(gose.InvalidState(msg)) =
580+
key_helpers.validate_key_for_jwe_encryption(
581+
jwa.JweRsa(jwa.RsaOaepSha256),
582+
key,
583+
)
584+
assert msg == "algorithm RSA-OAEP-256 incompatible with key type"
585+
}

test/gose/jwe_test.gleam

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -377,14 +377,14 @@ pub fn wrong_key_type_test() {
377377
|> jwe.encrypt(rsa_key, <<"test":utf8>>)
378378

379379
assert result1
380-
== Error(gose.InvalidState("direct encryption requires an octet key"))
380+
== Error(gose.InvalidState("algorithm dir incompatible with key type"))
381381

382382
let result2 =
383383
jwe.new_rsa(jwa.RsaOaepSha1, jwa.AesGcm(jwa.Aes256))
384384
|> jwe.encrypt(octet_key, <<"test":utf8>>)
385385

386386
assert result2
387-
== Error(gose.InvalidState("RSA encryption requires an RSA key"))
387+
== Error(gose.InvalidState("algorithm RSA-OAEP incompatible with key type"))
388388
}
389389

390390
pub fn wrong_key_size_test() {

0 commit comments

Comments
 (0)