Skip to content

Commit

Permalink
Add documentation & more key tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
timothee-haudebourg committed Aug 21, 2024
1 parent ac43fed commit e2dd113
Show file tree
Hide file tree
Showing 7 changed files with 482 additions and 34 deletions.
26 changes: 6 additions & 20 deletions crates/claims/core/src/verification/claims.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,29 +49,15 @@ pub type ClaimsValidity = Result<(), InvalidClaims>;
/// The `validate` function is also provided with the proof, as some claim type
/// require information from the proof to be validated.
pub trait ValidateClaims<E, P = ()> {
fn validate_claims(&self, environment: &E, proof: &P) -> ClaimsValidity;
}

impl<E, P> ValidateClaims<E, P> for () {
fn validate_claims(&self, _env: &E, _proof: &P) -> ClaimsValidity {
fn validate_claims(&self, _environment: &E, _proof: &P) -> ClaimsValidity {
Ok(())
}
}

impl<E, P> ValidateClaims<E, P> for [u8] {
fn validate_claims(&self, _env: &E, _proof: &P) -> ClaimsValidity {
Ok(())
}
}
impl<E, P> ValidateClaims<E, P> for () {}

impl<E, P> ValidateClaims<E, P> for Vec<u8> {
fn validate_claims(&self, _env: &E, _proof: &P) -> ClaimsValidity {
Ok(())
}
}
impl<E, P> ValidateClaims<E, P> for [u8] {}

impl<'a, E, P, T: ?Sized + ToOwned + ValidateClaims<E, P>> ValidateClaims<E, P> for Cow<'a, T> {
fn validate_claims(&self, _env: &E, _proof: &P) -> ClaimsValidity {
Ok(())
}
}
impl<E, P> ValidateClaims<E, P> for Vec<u8> {}

impl<'a, E, P, T: ?Sized + ToOwned + ValidateClaims<E, P>> ValidateClaims<E, P> for Cow<'a, T> {}
8 changes: 7 additions & 1 deletion crates/claims/crates/cose/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,10 @@ ssi-crypto.workspace = true
ssi-claims-core.workspace = true
thiserror.workspace = true
ciborium.workspace = true
coset = { version = "0.3.8", features = ["std"] }
coset = { version = "0.3.8", features = ["std"] }
serde = { workspace = true, features = ["derive"] }

[dev-dependencies]
serde_json.workspace = true
hex.workspace = true
async-std = { workspace = true, features = ["attributes"] }
296 changes: 293 additions & 3 deletions crates/claims/crates/cose/src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,21 @@ impl From<KeyDecodingError> for ssi_claims_core::SignatureError {

/// Decode COSE keys.
pub trait CoseKeyDecode {
/// Reads a key parameter, if it exists.
fn fetch_param(&self, label: &Label) -> Option<&ciborium::Value>;

/// Requires the given key parameter.
///
/// Returns an error if the key parameter is not present in the key.
fn require_param(&self, label: &Label) -> Result<&ciborium::Value, KeyDecodingError> {
self.fetch_param(label)
.ok_or_else(|| KeyDecodingError::MissingParam(label.clone()))
}

/// Requires and parses the given key parameter.
///
/// Returns an error if the key parameter is not present in the key, or
/// if the parsing function `f` returns `None`.
fn parse_required_param<'a, T>(
&'a self,
label: &Label,
Expand Down Expand Up @@ -203,7 +211,7 @@ impl CoseKeyDecode for CoseKey {
})?;

#[allow(unused_variables)]
let d = self.parse_required_param(&OKP_X, ciborium::Value::as_bytes)?;
let d = self.parse_required_param(&OKP_D, ciborium::Value::as_bytes)?;

match iana::EllipticCurve::from_i64(crv) {
#[cfg(feature = "ed25519")]
Expand Down Expand Up @@ -264,14 +272,35 @@ pub enum KeyEncodingError {

/// COSE key encoding
pub trait CoseKeyEncode: Sized {
fn encode_public(key: &PublicKey) -> Result<Self, KeyEncodingError>;
fn encode_public(key: &PublicKey) -> Result<CoseKey, KeyEncodingError>;

fn encode_secret(key: &SecretKey) -> Result<Self, KeyEncodingError>;
fn encode_public_with_id(key: &PublicKey, id: Vec<u8>) -> Result<CoseKey, KeyEncodingError> {
let mut cose_key = Self::encode_public(key)?;
cose_key.key_id = id;
Ok(cose_key)
}

fn encode_secret(key: &SecretKey) -> Result<CoseKey, KeyEncodingError>;

fn encode_secret_with_id(key: &SecretKey, id: Vec<u8>) -> Result<CoseKey, KeyEncodingError> {
let mut cose_key = Self::encode_secret(key)?;
cose_key.key_id = id;
Ok(cose_key)
}
}

impl CoseKeyEncode for CoseKey {
fn encode_public(key: &PublicKey) -> Result<Self, KeyEncodingError> {
match key {
#[cfg(feature = "ed25519")]
PublicKey::Ed25519(key) => Ok(Self {
kty: KeyType::Assigned(iana::KeyType::OKP),
params: vec![
(OKP_CRV, iana::EllipticCurve::Ed25519.to_i64().into()),
(OKP_X, key.as_bytes().to_vec().into()),
],
..Default::default()
}),
#[cfg(feature = "secp256k1")]
PublicKey::Secp256k1(key) => {
use ssi_crypto::k256::elliptic_curve::sec1::ToEncodedPoint;
Expand Down Expand Up @@ -320,6 +349,19 @@ impl CoseKeyEncode for CoseKey {

fn encode_secret(key: &SecretKey) -> Result<Self, KeyEncodingError> {
match key {
#[cfg(feature = "ed25519")]
SecretKey::Ed25519(key) => {
let public_key = key.verifying_key();
Ok(Self {
kty: KeyType::Assigned(iana::KeyType::OKP),
params: vec![
(OKP_CRV, iana::EllipticCurve::Ed25519.to_i64().into()),
(OKP_X, public_key.as_bytes().to_vec().into()),
(OKP_D, key.to_bytes().to_vec().into()),
],
..Default::default()
})
}
#[cfg(feature = "secp256k1")]
SecretKey::Secp256k1(key) => {
use ssi_crypto::k256::elliptic_curve::sec1::ToEncodedPoint;
Expand Down Expand Up @@ -440,3 +482,251 @@ impl CoseKeyGenerate for CoseKey {
Self::encode_secret(&ssi_crypto::SecretKey::generate_p384_from(rng)).unwrap()
}
}

#[cfg(test)]
mod tests {
use super::{CoseKeyDecode, CoseKeyEncode};
use coset::{CborSerializable, CoseKey};
use ssi_crypto::{PublicKey, SecretKey};

/// Public secp256k1 key.
///
/// ```cbor-diagnostic
/// {
/// 1: 1,
/// -1: 6,
/// -2: h'8816d41001dd1a9ddea1232381b2eede803161e88ebb19eaf573d393dec800a7'
/// }
/// ```
#[cfg(feature = "ed25519")]
#[test]
fn public_ed25519_1() {
let input = hex::decode(
"a3010120062158208816d41001dd1a9ddea1232381b2eede803161e88ebb19eaf573d393dec800a7",
)
.unwrap();
let cose_key = CoseKey::from_slice(&input).unwrap();
let key = cose_key.decode_public().unwrap();
assert!(matches!(key, PublicKey::Ed25519(_)));
assert_eq!(
CoseKey::encode_public_with_id(&key, cose_key.key_id.clone()).unwrap(),
cose_key
)
}

/// Secret secp256k1 key.
///
/// ```cbor-diagnostic
/// {
/// 1: 1,
/// -1: 6,
/// -2: h'8816d41001dd1a9ddea1232381b2eede803161e88ebb19eaf573d393dec800a7',
/// -4: h'e25df1249ab766fc5a8c9f98d5e311cd4f7d5fd1c6b6a2032adc973056c87dc3'
/// }
/// ```
#[cfg(feature = "ed25519")]
#[test]
fn secret_ed25519_1() {
let input = hex::decode("a4010120062158208816d41001dd1a9ddea1232381b2eede803161e88ebb19eaf573d393dec800a7235820e25df1249ab766fc5a8c9f98d5e311cd4f7d5fd1c6b6a2032adc973056c87dc3").unwrap();
let cose_key = CoseKey::from_slice(&input).unwrap();
let key = cose_key.decode_secret().unwrap();
assert!(matches!(key, SecretKey::Ed25519(_)));
assert_eq!(
CoseKey::encode_secret_with_id(&key, cose_key.key_id.clone()).unwrap(),
cose_key
)
}

/// Secret secp256k1 key.
///
/// ```cbor-diagnostic
/// {
/// 1: 2,
/// -1: 8,
/// -2: h'394fd5a1e33b8a67d5fa9ddca42d261219dde202e65bbf07bf2f671e157ac41f',
/// -3: h'199d7db667e74905c8371168b815c267db76243fbfd387fa5f2d8a691099a89a'
/// }
/// ```
#[cfg(feature = "secp256k1")]
#[test]
fn public_secp256k1_1() {
let input = hex::decode("a401022008215820394fd5a1e33b8a67d5fa9ddca42d261219dde202e65bbf07bf2f671e157ac41f225820199d7db667e74905c8371168b815c267db76243fbfd387fa5f2d8a691099a89a").unwrap();
let cose_key = CoseKey::from_slice(&input).unwrap();
let key = cose_key.decode_public().unwrap();
assert!(matches!(key, PublicKey::Secp256k1(_)));
assert_eq!(
CoseKey::encode_public_with_id(&key, cose_key.key_id.clone()).unwrap(),
cose_key
)
}

/// Secret secp256k1 key.
///
/// ```cbor-diagnostic
/// {
/// 1: 2,
/// -1: 8,
/// -2: h'394fd5a1e33b8a67d5fa9ddca42d261219dde202e65bbf07bf2f671e157ac41f',
/// -3: h'199d7db667e74905c8371168b815c267db76243fbfd387fa5f2d8a691099a89a',
/// -4: h'3e0fada8be75e5e47ab4c1c91c3f8f9185d1e18a2a16b3400a1eb33c9cdf8b96'
/// }
/// ```
#[cfg(feature = "secp256k1")]
#[test]
fn secret_secp256k1_1() {
let input = hex::decode("a501022008215820394fd5a1e33b8a67d5fa9ddca42d261219dde202e65bbf07bf2f671e157ac41f225820199d7db667e74905c8371168b815c267db76243fbfd387fa5f2d8a691099a89a2358203e0fada8be75e5e47ab4c1c91c3f8f9185d1e18a2a16b3400a1eb33c9cdf8b96").unwrap();
let cose_key = CoseKey::from_slice(&input).unwrap();
let key = cose_key.decode_secret().unwrap();
assert!(matches!(key, SecretKey::Secp256k1(_)));
assert_eq!(
CoseKey::encode_secret_with_id(&key, cose_key.key_id.clone()).unwrap(),
cose_key
)
}

/// A public EC (P-256) key with a `kid` of
/// "meriadoc.brandybuck@buckland.example".
///
/// See: <https://www.rfc-editor.org/rfc/rfc9052.html#appendix-C.7.1>
#[cfg(feature = "secp256r1")]
#[test]
fn public_p256_1() {
let input = hex::decode("a5200121582065eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d2258201e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19c01020258246d65726961646f632e6272616e64796275636b406275636b6c616e642e6578616d706c65").unwrap();
let cose_key = CoseKey::from_slice(&input).unwrap();
let key = cose_key.decode_public().unwrap();
assert!(matches!(key, PublicKey::P256(_)));
assert_eq!(
CoseKey::encode_public_with_id(&key, cose_key.key_id.clone()).unwrap(),
cose_key
)
}

/// A secret EC (P-256) key with a kid of
/// "meriadoc.brandybuck@buckland.example".
///
/// See: <https://www.rfc-editor.org/rfc/rfc9052.html#appendix-C.7.2>
#[cfg(feature = "secp256r1")]
#[test]
fn secret_p256_1() {
let input = hex::decode("a601020258246d65726961646f632e6272616e64796275636b406275636b6c616e642e6578616d706c65200121582065eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d2258201e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19c235820aff907c99f9ad3aae6c4cdf21122bce2bd68b5283e6907154ad911840fa208cf").unwrap();
let cose_key = CoseKey::from_slice(&input).unwrap();
let key = cose_key.decode_secret().unwrap();
assert!(matches!(key, SecretKey::P256(_)));
assert_eq!(
CoseKey::encode_secret_with_id(&key, cose_key.key_id.clone()).unwrap(),
cose_key
)
}

/// A public EC (P-256) key with a kid of "11".
///
/// See: <https://www.rfc-editor.org/rfc/rfc9052.html#appendix-C.7.1>
#[cfg(feature = "secp256r1")]
#[test]
fn public_p256_2() {
let input = hex::decode("a52001215820bac5b11cad8f99f9c72b05cf4b9e26d244dc189f745228255a219a86d6a09eff22582020138bf82dc1b6d562be0fa54ab7804a3a64b6d72ccfed6b6fb6ed28bbfc117e010202423131").unwrap();
let cose_key = CoseKey::from_slice(&input).unwrap();
let key = cose_key.decode_public().unwrap();
assert!(matches!(key, PublicKey::P256(_)));
assert_eq!(
CoseKey::encode_public_with_id(&key, cose_key.key_id.clone()).unwrap(),
cose_key
)
}

/// A secret EC (P-256) key with a kid of "11".
///
/// See: <https://www.rfc-editor.org/rfc/rfc9052.html#appendix-C.7.2>
#[cfg(feature = "secp256r1")]
#[test]
fn secret_p256_2() {
let input = hex::decode("a60102024231312001215820bac5b11cad8f99f9c72b05cf4b9e26d244dc189f745228255a219a86d6a09eff22582020138bf82dc1b6d562be0fa54ab7804a3a64b6d72ccfed6b6fb6ed28bbfc117e23582057c92077664146e876760c9520d054aa93c3afb04e306705db6090308507b4d3").unwrap();
let cose_key = CoseKey::from_slice(&input).unwrap();
let key = cose_key.decode_secret().unwrap();
assert!(matches!(key, SecretKey::P256(_)));
assert_eq!(
CoseKey::encode_secret_with_id(&key, cose_key.key_id.clone()).unwrap(),
cose_key
)
}

/// A public EC (P-256) key with a kid of "peregrin.took@tuckborough.example".
///
/// See: <https://www.rfc-editor.org/rfc/rfc9052.html#appendix-C.7.1>
#[cfg(feature = "secp256r1")]
#[test]
fn public_p256_3() {
let input = hex::decode("a5200121582098f50a4ff6c05861c8860d13a638ea56c3f5ad7590bbfbf054e1c7b4d91d6280225820f01400b089867804b8e9fc96c3932161f1934f4223069170d924b7e03bf822bb0102025821706572656772696e2e746f6f6b407475636b626f726f7567682e6578616d706c65").unwrap();
let cose_key = CoseKey::from_slice(&input).unwrap();
let key = cose_key.decode_public().unwrap();
assert!(matches!(key, PublicKey::P256(_)));
assert_eq!(
CoseKey::encode_public_with_id(&key, cose_key.key_id.clone()).unwrap(),
cose_key
)
}

/// A secret EC (P-256) key with a kid of
/// "peregrin.took@tuckborough.example".
///
/// See: <https://www.rfc-editor.org/rfc/rfc9052.html#appendix-C.7.2>
#[cfg(feature = "secp256r1")]
#[test]
fn secret_256_3() {
let input = hex::decode("a601022001025821706572656772696e2e746f6f6b407475636b626f726f7567682e6578616d706c6521582098f50a4ff6c05861c8860d13a638ea56c3f5ad7590bbfbf054e1c7b4d91d6280225820f01400b089867804b8e9fc96c3932161f1934f4223069170d924b7e03bf822bb23582002d1f7e6f26c43d4868d87ceb2353161740aacf1f7163647984b522a848df1c3").unwrap();
let cose_key = CoseKey::from_slice(&input).unwrap();
let key = cose_key.decode_secret().unwrap();
assert!(matches!(key, SecretKey::P256(_)));
assert_eq!(
CoseKey::encode_secret_with_id(&key, cose_key.key_id.clone()).unwrap(),
cose_key
)
}

/// A public EC (P-384) key.
///
/// ```cbor-diagnostic
/// {
/// 1: 2,
/// -1: 2,
/// -2: h'fa1d31d39853d37fbfd145675635d52795f5feb3eacf11371ad8c6eb30c6f2493b0ec74d8c5b5a20ebf68ce3e0bd2c07',
/// -3: h'7c2b27b366e4fc73b79d28bac0b18ae2f2b0c4e7849656a71aac8987e60af5af57a9af3faf206afc798fa5fb06db15aa'
/// }
/// ```
#[cfg(feature = "secp384r1")]
#[test]
fn public_p384_1() {
let input = hex::decode("a401022002215830fa1d31d39853d37fbfd145675635d52795f5feb3eacf11371ad8c6eb30c6f2493b0ec74d8c5b5a20ebf68ce3e0bd2c072258307c2b27b366e4fc73b79d28bac0b18ae2f2b0c4e7849656a71aac8987e60af5af57a9af3faf206afc798fa5fb06db15aa").unwrap();
let cose_key = CoseKey::from_slice(&input).unwrap();
let key = cose_key.decode_public().unwrap();
assert!(matches!(key, PublicKey::P384(_)));
assert_eq!(
CoseKey::encode_public_with_id(&key, cose_key.key_id.clone()).unwrap(),
cose_key
)
}

/// A secret EC (P-384) key.
///
/// ```cbor-diagnostic
/// {
/// 1: 2,
/// -1: 2,
/// -2: h'fa1d31d39853d37fbfd145675635d52795f5feb3eacf11371ad8c6eb30c6f2493b0ec74d8c5b5a20ebf68ce3e0bd2c07',
/// -3: h'7c2b27b366e4fc73b79d28bac0b18ae2f2b0c4e7849656a71aac8987e60af5af57a9af3faf206afc798fa5fb06db15aa',
/// -4: h'21d8eb2250cdaa19bfb01f03211be11a70ef4739650ed954166531808aa254c1d6d968b36d16184d350600253fa672c0'
/// }
/// ```
#[cfg(feature = "secp384r1")]
#[test]
fn secret_p384_1() {
let input = hex::decode("a501022002215830fa1d31d39853d37fbfd145675635d52795f5feb3eacf11371ad8c6eb30c6f2493b0ec74d8c5b5a20ebf68ce3e0bd2c072258307c2b27b366e4fc73b79d28bac0b18ae2f2b0c4e7849656a71aac8987e60af5af57a9af3faf206afc798fa5fb06db15aa23583021d8eb2250cdaa19bfb01f03211be11a70ef4739650ed954166531808aa254c1d6d968b36d16184d350600253fa672c0").unwrap();
let cose_key = CoseKey::from_slice(&input).unwrap();
let key = cose_key.decode_secret().unwrap();
assert!(matches!(key, SecretKey::P384(_)));
assert_eq!(
CoseKey::encode_secret_with_id(&key, cose_key.key_id.clone()).unwrap(),
cose_key
)
}
}
Loading

0 comments on commit e2dd113

Please sign in to comment.