Skip to content

Commit

Permalink
Add support for COSE for VCs. (#593)
Browse files Browse the repository at this point in the history
* Update MSRV.
  • Loading branch information
timothee-haudebourg committed Aug 26, 2024
1 parent 0b26eac commit 099293a
Show file tree
Hide file tree
Showing 36 changed files with 2,527 additions and 83 deletions.
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ description = "Core library for Verifiable Credentials and Decentralized Identif
repository = "https://github.com/spruceid/ssi/"
documentation = "https://docs.rs/ssi/"
keywords = ["ssi", "did", "vc", "vp", "jsonld"]
rust-version = "1.77"
rust-version = "1.80"

[workspace]
members = [
Expand Down Expand Up @@ -47,6 +47,7 @@ ssi-claims-core = { path = "./crates/claims/core", version = "0.1", default-feat
ssi-jws = { path = "./crates/claims/crates/jws", version = "0.2", default-features = false }
ssi-jwt = { path = "./crates/claims/crates/jwt", version = "0.2", default-features = false }
ssi-sd-jwt = { path = "./crates/claims/crates/sd-jwt", version = "0.2", default-features = false }
ssi-cose = { path = "./crates/claims/crates/cose", version = "0.1", default-features = false }
ssi-vc = { path = "./crates/claims/crates/vc", version = "0.3", default-features = false }
ssi-vc-jose-cose = { path = "./crates/claims/crates/vc-jose-cose", version = "0.1", default-features = false }
ssi-data-integrity-core = { path = "./crates/claims/crates/data-integrity/core", version = "0.1", default-features = false }
Expand Down Expand Up @@ -95,8 +96,9 @@ multibase = "0.9.1"
serde = "1.0"
serde_json = { version = "1.0", features = ["arbitrary_precision"] }
serde_jcs = "0.1.0"
ciborium = "0.2.2"
bs58 = "0.4"
base64 = "0.12"
base64 = "0.22"
hex = "0.4.3"
derivative = "2.2.0"
educe = "0.4.22"
Expand Down
9 changes: 5 additions & 4 deletions crates/claims/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,20 @@ dif = ["ssi-data-integrity/dif"]
# - `Ed25519Signature2018`
# - `Ed25519Signature2020`
# - `EdDsa2022`
ed25519 = ["ssi-jws/ed25519", "ssi-data-integrity/ed25519", "ssi-verification-methods/ed25519"]
ed25519 = ["ssi-jws/ed25519", "ssi-cose/ed25519", "ssi-data-integrity/ed25519", "ssi-verification-methods/ed25519"]

# Enables signature suites based on secp256k1:
# - `EcdsaSecp256k1Signature2019`
secp256k1 = ["ssi-jws/secp256k1", "ssi-data-integrity/secp256k1", "ssi-verification-methods/secp256k1"]
secp256k1 = ["ssi-jws/secp256k1", "ssi-cose/secp256k1", "ssi-data-integrity/secp256k1", "ssi-verification-methods/secp256k1"]

# Enables signature suites based on secp256r1:
# - `EcdsaSecp256r1Signature2019`
# - `EcdsaRdfc2019`
secp256r1 = ["ssi-jws/secp256r1", "ssi-data-integrity/secp256r1", "ssi-verification-methods/secp256r1"]
secp256r1 = ["ssi-jws/secp256r1", "ssi-cose/secp256r1", "ssi-data-integrity/secp256r1", "ssi-verification-methods/secp256r1"]

# Enables signature suites based on secp384r1:
# - `EcdsaRdfc2019`
secp384r1 = ["ssi-jws/secp384r1", "ssi-data-integrity/secp384r1", "ssi-verification-methods/secp384r1"]
secp384r1 = ["ssi-jws/secp384r1", "ssi-cose/secp384r1", "ssi-data-integrity/secp384r1", "ssi-verification-methods/secp384r1"]

# Enables `RsaSignature2018`
rsa = ["ssi-jws/rsa", "ssi-data-integrity/rsa", "ssi-verification-methods/rsa"]
Expand Down Expand Up @@ -68,6 +68,7 @@ ssi-claims-core.workspace = true
ssi-jws.workspace = true
ssi-jwt.workspace = true
ssi-sd-jwt.workspace = true
ssi-cose.workspace = true
ssi-vc.workspace = true
ssi-vc-jose-cose.workspace = true
ssi-data-integrity.workspace = true
Expand Down
11 changes: 11 additions & 0 deletions crates/claims/core/src/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,17 @@ impl From<std::convert::Infallible> for SignatureError {
}
}

impl From<ssi_crypto::SignatureError> for SignatureError {
fn from(value: ssi_crypto::SignatureError) -> Self {
match value {
ssi_crypto::SignatureError::UnsupportedAlgorithm(a) => {
Self::UnsupportedAlgorithm(a.to_string())
}
e => Self::other(e),
}
}
}

#[derive(Debug, thiserror::Error)]
pub enum MessageSignatureError {
#[error("0")]
Expand Down
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> {}
29 changes: 29 additions & 0 deletions crates/claims/crates/cose/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[package]
name = "ssi-cose"
version = "0.1.0"
edition = "2021"
authors = ["Spruce Systems, Inc."]
license = "Apache-2.0"
description = "CBOR Object Signing and Encryption for the `ssi` library."
repository = "https://github.com/spruceid/ssi/"
documentation = "https://docs.rs/ssi-cose/"

[features]
default = ["secp256r1"]
ed25519 = ["ssi-crypto/ed25519"]
secp256k1 = ["ssi-crypto/secp256k1"]
secp256r1 = ["ssi-crypto/secp256r1"]
secp384r1 = ["ssi-crypto/secp384r1"]

[dependencies]
ssi-crypto.workspace = true
ssi-claims-core.workspace = true
thiserror.workspace = true
ciborium.workspace = true
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"] }
86 changes: 86 additions & 0 deletions crates/claims/crates/cose/src/algorithm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use std::borrow::Cow;

use coset::{
iana::{self, EnumI64},
Algorithm, CoseKey, KeyType,
};
use ssi_crypto::AlgorithmInstance;

use crate::key::{CoseKeyDecode, EC2_CRV};

/// Converts a COSE algorithm into an SSI algorithm instance.
pub fn instantiate_algorithm(algorithm: &Algorithm) -> Option<AlgorithmInstance> {
match algorithm {
Algorithm::Assigned(iana::Algorithm::PS256) => Some(AlgorithmInstance::PS256),
Algorithm::Assigned(iana::Algorithm::PS384) => Some(AlgorithmInstance::PS384),
Algorithm::Assigned(iana::Algorithm::PS512) => Some(AlgorithmInstance::PS512),
Algorithm::Assigned(iana::Algorithm::EdDSA) => Some(AlgorithmInstance::EdDSA),
Algorithm::Assigned(iana::Algorithm::ES256K) => Some(AlgorithmInstance::ES256K),
Algorithm::Assigned(iana::Algorithm::ES256) => Some(AlgorithmInstance::ES256),
Algorithm::Assigned(iana::Algorithm::ES384) => Some(AlgorithmInstance::ES384),
_ => None,
}
}

/// Computes a proper display name for the give COSE algorithm.
pub fn algorithm_name(algorithm: &Algorithm) -> String {
match algorithm {
Algorithm::Assigned(iana::Algorithm::PS256) => "PS256".to_owned(),
Algorithm::Assigned(iana::Algorithm::PS384) => "PS384".to_owned(),
Algorithm::Assigned(iana::Algorithm::PS512) => "PS512".to_owned(),
Algorithm::Assigned(iana::Algorithm::EdDSA) => "EdDSA".to_owned(),
Algorithm::Assigned(iana::Algorithm::ES256K) => "ES256K".to_owned(),
Algorithm::Assigned(iana::Algorithm::ES256) => "ES256".to_owned(),
Algorithm::Assigned(iana::Algorithm::ES384) => "ES384".to_owned(),
Algorithm::Assigned(i) => format!("assigned({})", i.to_i64()),
Algorithm::PrivateUse(i) => format!("private_use({i})"),
Algorithm::Text(text) => text.to_owned(),
}
}

/// Returns the preferred signature algorithm for the give COSE key.
pub fn preferred_algorithm(key: &CoseKey) -> Option<Cow<Algorithm>> {
key.alg
.as_ref()
.map(Cow::Borrowed)
.or_else(|| match key.kty {
KeyType::Assigned(iana::KeyType::RSA) => {
Some(Cow::Owned(Algorithm::Assigned(iana::Algorithm::PS256)))
}
KeyType::Assigned(iana::KeyType::OKP) => {
let crv = key
.parse_required_param(&EC2_CRV, |v| {
v.as_integer().and_then(|i| i64::try_from(i).ok())
})
.ok()?;

match iana::EllipticCurve::from_i64(crv)? {
iana::EllipticCurve::Ed25519 => {
Some(Cow::Owned(Algorithm::Assigned(iana::Algorithm::EdDSA)))
}
_ => None,
}
}
KeyType::Assigned(iana::KeyType::EC2) => {
let crv = key
.parse_required_param(&EC2_CRV, |v| {
v.as_integer().and_then(|i| i64::try_from(i).ok())
})
.ok()?;

match iana::EllipticCurve::from_i64(crv)? {
iana::EllipticCurve::Secp256k1 => {
Some(Cow::Owned(Algorithm::Assigned(iana::Algorithm::ES256K)))
}
iana::EllipticCurve::P_256 => {
Some(Cow::Owned(Algorithm::Assigned(iana::Algorithm::ES256)))
}
iana::EllipticCurve::P_384 => {
Some(Cow::Owned(Algorithm::Assigned(iana::Algorithm::ES384)))
}
_ => None,
}
}
_ => None,
})
}
Loading

0 comments on commit 099293a

Please sign in to comment.