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

Add support for COSE for VCs. #593

Merged
merged 13 commits into from
Aug 26, 2024
Merged
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"
sbihel marked this conversation as resolved.
Show resolved Hide resolved
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