Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JOSE Test Suite #68

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features -- -D warnings
args: --all-features --all-targets -- -D warnings

doc:
runs-on: ubuntu-latest
Expand Down
5 changes: 1 addition & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,4 @@ serde-value = { version = "0.7.0", default-features = false }
mediatype = { version = "0.19.3", features = ["serde"] }

[dev-dependencies]
rand = "0.8.5"

# For X.509 certificates of Json Web Keys
# x509-cert = { git = "https://github.com/RustCrypto/formats.git" }
paste = "1.0.12"
138 changes: 118 additions & 20 deletions src/jwa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ mod hmac;
mod pbes2;
mod rsa;

use alloc::string::String;
use alloc::{borrow::Cow, string::String};

use serde::{Deserialize, Serialize};
use serde::{de::value::CowStrDeserializer, Deserialize, Serialize};

#[doc(inline)]
pub use self::{
Expand All @@ -27,18 +27,114 @@ pub use self::{
rsa::{RsaSigning, RsaesOaep, RsassaPkcs1V1_5, RsassaPss},
};

/// Either a JSON Web Algorithm for signing operations, or an algorithm for
/// encryption operations. Possible values should be registered in the [IANA
/// `JSON Web Signature and Encryption Algorithms` registry][1].
/// Either a JSON Web Algorithm for signing operations, an algorithm for content
/// encryption operations or an algorithm for encryption operations.
/// Possible values should be registered in the [IANA `JSON Web Signature and Encryption Algorithms` registry][1].
///
/// [1]: <https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-algorithms>
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
#[serde(untagged)]
pub enum JsonWebAlgorithm {
/// Signing algorithm.
Signing(JsonWebSigningAlgorithm),
/// Encryption algorithm.
Encryption(JsonWebEncryptionAlgorithm),
/// Unknown algorithm.
Other(String),
}

impl JsonWebAlgorithm {
/// Turn this algorithm into a [`JsonWebKeyAlgorithm`].
pub fn into_jwk_algorithm(self) -> JsonWebKeyAlgorithm {
match self {
Self::Signing(alg) => JsonWebKeyAlgorithm::Signing(alg),
Self::Encryption(alg) => JsonWebKeyAlgorithm::Encryption(alg),
Self::Other(alg) => JsonWebKeyAlgorithm::Other(alg),
}
}
}

impl<'de> Deserialize<'de> for JsonWebAlgorithm {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let val = <Cow<'_, str> as Deserialize>::deserialize(deserializer)?;
let deser = CowStrDeserializer::<'_, D::Error>::new(val.clone());

let signing = <JsonWebSigningAlgorithm as Deserialize>::deserialize(deser.clone())?;
let encryption = <JsonWebEncryptionAlgorithm as Deserialize>::deserialize(deser.clone())?;

if !matches!(signing, JsonWebSigningAlgorithm::Other(_)) {
return Ok(Self::Signing(signing));
}

if !matches!(encryption, JsonWebEncryptionAlgorithm::Other(_)) {
return Ok(Self::Encryption(encryption));
}

Ok(Self::Other(val.into_owned()))
}
}

/// Either a JSON Web Algorithm for signing operations, an algorithm for content
/// encryption operations or an algorithm for encryption operations.
/// Possible values should be registered in the [IANA `JSON Web Signature and Encryption Algorithms` registry][1].
///
/// [1]: <https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-algorithms>
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
#[serde(untagged)]
pub enum JsonWebKeyAlgorithm {
/// Signing algorithm.
Signing(JsonWebSigningAlgorithm),
/// Encryption algorithm.
Encryption(JsonWebEncryptionAlgorithm),
/// Content encryption algorithm.
ContentEncryption(JsonWebContentEncryptionAlgorithm),
/// Unknown algorithm.
Other(String),
}

impl JsonWebKeyAlgorithm {
/// Turn this algorithm into a [`JsonWebAlgorithm`], if possible.
///
/// This will return [`None`] if the algorithm is a content encryption algorithm.
pub fn into_jwa(self) -> Option<JsonWebAlgorithm> {
match self {
Self::Signing(alg) => Some(JsonWebAlgorithm::Signing(alg)),
Self::Encryption(alg) => Some(JsonWebAlgorithm::Encryption(alg)),
Self::ContentEncryption(..) => None,
Self::Other(alg) => Some(JsonWebAlgorithm::Other(alg)),
}
}
}

impl<'de> Deserialize<'de> for JsonWebKeyAlgorithm {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let val = <Cow<'_, str> as Deserialize>::deserialize(deserializer)?;
let deser = CowStrDeserializer::<'_, D::Error>::new(val.clone());

let signing = <JsonWebSigningAlgorithm as Deserialize>::deserialize(deser.clone())?;
let encryption = <JsonWebEncryptionAlgorithm as Deserialize>::deserialize(deser.clone())?;
let content = <JsonWebContentEncryptionAlgorithm as Deserialize>::deserialize(deser)?;

if !matches!(signing, JsonWebSigningAlgorithm::Other(_)) {
return Ok(Self::Signing(signing));
}

if !matches!(encryption, JsonWebEncryptionAlgorithm::Other(_)) {
return Ok(Self::Encryption(encryption));
}

if !matches!(content, JsonWebContentEncryptionAlgorithm::Other(_)) {
return Ok(Self::ContentEncryption(content));
}

Ok(Self::Other(val.into_owned()))
}
}

/// A JSON Web Algorithm (JWA) for singing operations (JWS) as defined in [RFC
Expand Down Expand Up @@ -118,8 +214,6 @@ impl_serde_jwa!(

"none" => Self::None; Self::None,

contrary: <JsonWebEncryptionAlgorithm>::Other,

expected: "a JSON Web Signing Algorithm",
got: "JSON Web Encryption Algorithm",
]
Expand Down Expand Up @@ -194,8 +288,6 @@ impl_serde_jwa!(
"PBES2-HS384+A192KW" => Self::Pbes2(Pbes2::Hs384Aes192); Self::Pbes2(Pbes2::Hs384Aes192),
"PBES2-HS512+A256KW" => Self::Pbes2(Pbes2::Hs512Aes256); Self::Pbes2(Pbes2::Hs512Aes256),

contrary: <JsonWebSigningAlgorithm>::Other,

expected: "a JSON Web Encryption Algorithm",
got: "JSON Web Signing Algorithm",
]
Expand Down Expand Up @@ -239,14 +331,20 @@ impl_serde_jwa!(
]
);

#[test]
fn test_others_not_stealing() {
use alloc::string::ToString;
let jwe = "dir";
let jwa: JsonWebAlgorithm =
serde_json::from_value(serde_json::Value::String(jwe.to_string())).unwrap();
assert!(matches!(
jwa,
JsonWebAlgorithm::Encryption(JsonWebEncryptionAlgorithm::Direct)
));
#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_others_not_stealing() {
use alloc::string::ToString;
let jwe = "dir";
let jwa: JsonWebAlgorithm =
serde_json::from_value(serde_json::Value::String(jwe.to_string())).unwrap();

assert!(matches!(
jwa,
JsonWebAlgorithm::Encryption(JsonWebEncryptionAlgorithm::Direct)
));
}
}
30 changes: 21 additions & 9 deletions src/jwk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ use hashbrown::HashSet;
use serde::{Deserialize, Serialize};

use crate::{
jwa::{EcDSA, JsonWebAlgorithm, JsonWebEncryptionAlgorithm, JsonWebSigningAlgorithm},
jwa::{
EcDSA, JsonWebAlgorithm, JsonWebEncryptionAlgorithm, JsonWebKeyAlgorithm,
JsonWebSigningAlgorithm,
},
jwk::ec::{EcPrivate, EcPublic},
policy::{Checkable, Checked, CryptographicOperation, Policy},
policy::{Checkable, Checked, CryptographicOperation, Policy, PolicyError},
sealed::Sealed,
};

Expand Down Expand Up @@ -199,7 +202,7 @@ pub struct JsonWebKey<A = ()> {
key_operations: Option<HashSet<KeyOperation>>,
/// `alg` parameter section 4.4
#[serde(rename = "alg", skip_serializing_if = "Option::is_none")]
algorithm: Option<JsonWebAlgorithm>,
algorithm: Option<JsonWebKeyAlgorithm>,
/// `kid` parameter section 4.4
// FIXME: Consider an enum if this value is a valid JWK Thumbprint,
// see <https://www.rfc-editor.org/rfc/rfc7638>
Expand Down Expand Up @@ -313,7 +316,7 @@ impl<T> JsonWebKey<T> {
/// See the documentation of [`JsonWebAlgorithm`] for details.
///
/// [Section 4.4 of RFC 7517]: <https://datatracker.ietf.org/doc/html/rfc7517#section-4.4>
pub fn algorithm(&self) -> Option<&JsonWebAlgorithm> {
pub fn algorithm(&self) -> Option<&JsonWebKeyAlgorithm> {
self.algorithm.as_ref()
}

Expand Down Expand Up @@ -401,12 +404,21 @@ where
}

let operations = match alg {
JsonWebAlgorithm::Encryption(..) => [
JsonWebKeyAlgorithm::Encryption(..)
| JsonWebKeyAlgorithm::ContentEncryption(..) => &[
CryptographicOperation::Encrypt,
CryptographicOperation::Decrypt,
],
JsonWebAlgorithm::Signing(..) => {
[CryptographicOperation::Sign, CryptographicOperation::Verify]
JsonWebKeyAlgorithm::Signing(..) => {
&[CryptographicOperation::Sign, CryptographicOperation::Verify]
}
JsonWebKeyAlgorithm::Other(..) => {
return Err((
self,
<P::Error as PolicyError>::custom(
"unknown algorithm that can't be checked",
),
));
}
};
debug_assert!(!operations.is_empty());
Expand Down Expand Up @@ -502,8 +514,8 @@ pub enum JsonWebKeyType {
}

impl JsonWebKeyType {
pub(self) fn compatible_with(&self, alg: &JsonWebAlgorithm) -> bool {
use JsonWebAlgorithm::*;
pub(self) fn compatible_with(&self, alg: &JsonWebKeyAlgorithm) -> bool {
use JsonWebKeyAlgorithm::*;
use JsonWebKeyType::*;

// it is unreadable with the matches! macro and there's no benefit
Expand Down
6 changes: 3 additions & 3 deletions src/jwk/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use hashbrown::HashSet;

use super::{serde_impl::Base64DerCertificate, JsonWebKey, JsonWebKeyType, KeyOperation, KeyUsage};
use crate::{
jwa::JsonWebAlgorithm,
jwa::JsonWebKeyAlgorithm,
policy::{Checkable, Checked, Policy},
};

Expand All @@ -32,7 +32,7 @@ pub struct JsonWebKeyBuilder<A> {
pub(super) key_type: JsonWebKeyType,
pub(super) key_use: Option<KeyUsage>,
pub(super) key_operations: Option<HashSet<KeyOperation>>,
pub(super) algorithm: Option<JsonWebAlgorithm>,
pub(super) algorithm: Option<JsonWebKeyAlgorithm>,
pub(super) kid: Option<String>,
pub(super) x509_url: Option<String>,
pub(super) x509_certificate_chain: Vec<Base64DerCertificate>,
Expand Down Expand Up @@ -146,7 +146,7 @@ impl<A> JsonWebKeyBuilder<A> {
gen_builder_methods! {
key_use: KeyUsage,
key_operations: HashSet<KeyOperation>,
algorithm: JsonWebAlgorithm,
algorithm: JsonWebKeyAlgorithm,
kid: String,
x509_url: String,
x509_certificate_sha1_thumbprint: [u8; 20],
Expand Down
9 changes: 6 additions & 3 deletions src/jwk/key_use.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use alloc::string::{String, ToString};
use alloc::{
borrow::Cow,
string::{String, ToString},
};

use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -39,8 +42,8 @@ impl<'de> Deserialize<'de> for KeyUsage {
where
D: serde::Deserializer<'de>,
{
let val = <&str as Deserialize>::deserialize(deserializer)?;
Ok(match val {
let val = <Cow<'_, str> as Deserialize>::deserialize(deserializer)?;
Ok(match &*val {
"sig" => Self::Signing,
"enc" => Self::Encryption,
_ => Self::Other(val.to_string()),
Expand Down
4 changes: 2 additions & 2 deletions src/jwk/rsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ impl IntoJsonWebKey for RsaPublicKey {
) -> Result<crate::JsonWebKey, Self::Error> {
let alg = alg
.into()
.map(|rsa| jwa::JsonWebAlgorithm::Signing(jwa::JsonWebSigningAlgorithm::Rsa(rsa)));
.map(|rsa| jwa::JsonWebKeyAlgorithm::Signing(jwa::JsonWebSigningAlgorithm::Rsa(rsa)));

let key = super::JsonWebKeyType::Asymmetric(Box::new(super::AsymmetricJsonWebKey::Public(
super::Public::Rsa(self),
Expand Down Expand Up @@ -145,7 +145,7 @@ impl IntoJsonWebKey for RsaPrivateKey {
) -> Result<crate::JsonWebKey, Self::Error> {
let alg = alg
.into()
.map(|rsa| jwa::JsonWebAlgorithm::Signing(jwa::JsonWebSigningAlgorithm::Rsa(rsa)));
.map(|rsa| jwa::JsonWebKeyAlgorithm::Signing(jwa::JsonWebSigningAlgorithm::Rsa(rsa)));

let key = super::JsonWebKeyType::Asymmetric(Box::new(
super::AsymmetricJsonWebKey::Private(super::Private::Rsa(Box::new(self))),
Expand Down
9 changes: 5 additions & 4 deletions src/jwk/signer.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use alloc::{borrow::ToOwned, string::String, vec::Vec};
use alloc::{string::String, vec::Vec};

use digest::Update;

Expand Down Expand Up @@ -212,8 +212,9 @@ where
fn try_from(jwk: Checked<JsonWebKey<T>, P>) -> Result<Self, Self::Error> {
let alg = jwk
.algorithm()
.ok_or(FromJwkError::InvalidAlgorithm)?
.to_owned();
.and_then(|x| x.clone().into_jwa())
.ok_or(FromJwkError::InvalidAlgorithm)?;

let kid = jwk.kid.clone();
let mut signer = JwkSigner::from_key(jwk, alg)?;
signer.key_id = kid;
Expand Down Expand Up @@ -246,8 +247,8 @@ where
}

match alg {
JsonWebAlgorithm::Encryption(..) => Err(InvalidSigningAlgorithmError.into()),
JsonWebAlgorithm::Signing(alg) => Self::new(jwk.into_type().key_type, alg),
_ => Err(InvalidSigningAlgorithmError.into()),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/jwk/symmetric/hmac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ impl<H: HmacVariant> IntoJsonWebKey for HmacKey<H> {
let mut jwk = crate::JsonWebKey::new(key);
jwk.algorithm = alg
.into()
.map(|_| jwa::JsonWebAlgorithm::Signing(H::ALGORITHM));
.map(|_| jwa::JsonWebKeyAlgorithm::Signing(H::ALGORITHM));
Ok(jwk)
}
}
Expand Down
9 changes: 4 additions & 5 deletions src/jwk/verifier.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use alloc::borrow::ToOwned;

use super::{
ec::{
p256::P256Verifier, p384::P384Verifier, secp256k1::Secp256k1Verifier, EcPrivate, EcPublic,
Expand Down Expand Up @@ -110,8 +108,8 @@ where
}

match alg {
JsonWebAlgorithm::Encryption(..) => Err(FromJwkError::InvalidAlgorithm),
JsonWebAlgorithm::Signing(alg) => Self::new(jwk.into_type().key_type, alg),
_ => Err(FromJwkError::InvalidAlgorithm),
}
}
}
Expand All @@ -130,8 +128,9 @@ where
fn try_from(jwk: Checked<JsonWebKey<T>, P>) -> Result<Self, Self::Error> {
let alg = jwk
.algorithm()
.ok_or(FromJwkError::InvalidAlgorithm)?
.to_owned();
.and_then(|x| x.clone().into_jwa())
.ok_or(FromJwkError::InvalidAlgorithm)?;

JwkVerifier::from_key(jwk, alg)
}
}
Expand Down
Loading