diff --git a/Cargo.lock b/Cargo.lock index c3c687e3ae..60e9e295c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4331,6 +4331,7 @@ dependencies = [ name = "public_key" version = "0.1.0" dependencies = [ + "askar-crypto", "base64 0.22.1", "bs58", "multibase", diff --git a/Cargo.toml b/Cargo.toml index 15c2d115be..f0fe515e3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,6 +78,8 @@ let_underscore_drop = "allow" indy-vdr = { git = "https://github.com/hyperledger/indy-vdr.git", tag = "v0.4.3", default-features = false, features = [ "log", ] } -indy-vdr-proxy-client = { git = "https://github.com/hyperledger/indy-vdr.git", tag = "v0.4.3" } +indy-vdr-proxy-client = { git = "https://github.com/hyperledger/indy-vdr.git", tag = "v0.4.3" } indy-credx = { git = "https://github.com/hyperledger/indy-shared-rs", tag = "v1.1.0" } -anoncreds = { git = "https://github.com/hyperledger/anoncreds-rs.git", tag = "v0.2.0" } \ No newline at end of file +anoncreds = { git = "https://github.com/hyperledger/anoncreds-rs.git", tag = "v0.2.0" } +aries-askar = { version = "0.3.1" } +askar-crypto = { version = "0.3.1", default-features = false } diff --git a/aries/aries_vcx_wallet/Cargo.toml b/aries/aries_vcx_wallet/Cargo.toml index 53acfeff20..ac239e70b6 100644 --- a/aries/aries_vcx_wallet/Cargo.toml +++ b/aries/aries_vcx_wallet/Cargo.toml @@ -8,12 +8,12 @@ edition.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -vdrtools_wallet = [ "dep:libvdrtools", "dep:indy-api-types"] +vdrtools_wallet = ["dep:libvdrtools", "dep:indy-api-types"] askar_wallet = ["dep:aries-askar"] [dependencies] anyhow = "1.0" -aries-askar = { version = "0.3.1", optional = true } +aries-askar = { workspace = true, optional = true } async-trait = "0.1.68" bs58 = { version = "0.5" } base64 = "0.22.1" @@ -22,7 +22,7 @@ log = "0.4.17" indy-api-types = { path = "../misc/legacy/libvdrtools/indy-api-types", optional = true } serde = { version = "1.0.159", features = ["derive"] } serde_json = "1.0.95" -public_key = { path = "../../did_core/public_key"} +public_key = { path = "../../did_core/public_key" } rand = "0.8.5" thiserror = "1.0.40" tokio = { version = "1.38" } diff --git a/did_core/did_doc/Cargo.toml b/did_core/did_doc/Cargo.toml index 4bad05ee3d..061d4fc36f 100644 --- a/did_core/did_doc/Cargo.toml +++ b/did_core/did_doc/Cargo.toml @@ -3,6 +3,9 @@ name = "did_doc" version = "0.1.0" edition = "2021" +[features] +jwk = ["public_key/jwk"] + [dependencies] base64 = "0.22.1" bs58 = "0.5.0" diff --git a/did_core/did_doc/src/schema/verification_method/mod.rs b/did_core/did_doc/src/schema/verification_method/mod.rs index 0198c2acef..894b28a311 100644 --- a/did_core/did_doc/src/schema/verification_method/mod.rs +++ b/did_core/did_doc/src/schema/verification_method/mod.rs @@ -46,6 +46,8 @@ impl VerificationMethod { PublicKeyField::Multibase { public_key_multibase, } => Key::from_fingerprint(public_key_multibase)?, + #[cfg(feature = "jwk")] + PublicKeyField::Jwk { public_key_jwk } => Key::from_jwk(&public_key_jwk.to_string())?, // TODO - FUTURE - other key types could do with some special handling, i.e. // those where the key_type is encoded within the key field (multibase, jwk, etc) _ => Key::new( @@ -152,3 +154,53 @@ mod tests { assert!(vm.is_err()); } } + +#[cfg(feature = "jwk")] +#[cfg(test)] +mod jwk_tests { + use ::public_key::KeyType; + use serde_json::json; + + use super::*; + + #[test] + fn test_public_key_from_ed25519_jwk_vm() { + let vm: VerificationMethod = serde_json::from_value(json!({ + "id": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH#z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH", + "type": "Ed25519VerificationKey2018", + "controller": "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH", + "publicKeyJwk": { + "kty": "OKP", + "crv": "Ed25519", + "x": "lJZrfAjkBXdfjebMHEUI9usidAPhAlssitLXR3OYxbI" + } + })).unwrap(); + let pk = vm.public_key().unwrap(); + assert!(matches!(pk.key_type(), KeyType::Ed25519)); + assert_eq!( + pk.fingerprint(), + "z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH" + ) + } + + #[test] + fn test_public_key_from_p256_jwk_vm() { + let vm: VerificationMethod = serde_json::from_value(json!({ + "id": "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169#zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169", + "type": "JsonWebKey2020", + "controller": "did:key:zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169", + "publicKeyJwk": { + "kty": "EC", + "crv": "P-256", + "x": "fyNYMN0976ci7xqiSdag3buk-ZCwgXU4kz9XNkBlNUI", + "y": "hW2ojTNfH7Jbi8--CJUo3OCbH3y5n91g-IMA9MLMbTU" + } + })).unwrap(); + let pk = vm.public_key().unwrap(); + assert!(matches!(pk.key_type(), KeyType::P256)); + assert_eq!( + pk.fingerprint(), + "zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169" + ) + } +} diff --git a/did_core/did_doc/src/schema/verification_method/verification_method_type.rs b/did_core/did_doc/src/schema/verification_method/verification_method_type.rs index 9d41289141..f8e715f5f0 100644 --- a/did_core/did_doc/src/schema/verification_method/verification_method_type.rs +++ b/did_core/did_doc/src/schema/verification_method/verification_method_type.rs @@ -66,6 +66,8 @@ impl TryFrom for KeyType { VerificationMethodType::Bls12381G2Key2020 => Ok(KeyType::Bls12381g2), VerificationMethodType::X25519KeyAgreementKey2019 | VerificationMethodType::X25519KeyAgreementKey2020 => Ok(KeyType::X25519), + // The verification method type does not map directly to a key type. + // This may occur when the VM type is a multikey (JsonWebKey, Multikey, etc) _ => Err(DidDocumentBuilderError::UnsupportedVerificationMethodType( value, )), diff --git a/did_core/public_key/Cargo.toml b/did_core/public_key/Cargo.toml index 8928211050..c9baf690e7 100644 --- a/did_core/public_key/Cargo.toml +++ b/did_core/public_key/Cargo.toml @@ -3,6 +3,9 @@ name = "public_key" version = "0.1.0" edition = "2021" +[features] +jwk = ["dep:askar-crypto"] + [dependencies] thiserror = "1.0.40" serde = { version = "1.0.164", features = ["derive"] } @@ -11,3 +14,10 @@ base64 = "0.22.1" bs58 = "0.5.0" multibase = "0.9.1" unsigned-varint = "0.8.0" +# askar-crypto used for jwk conversion. maintain minimal feature set +askar-crypto = { workspace = true, features = [ + "std", + "any_key", + "ec_curves", + "ed25519", +], optional = true } diff --git a/did_core/public_key/src/error.rs b/did_core/public_key/src/error.rs index 07cfbaeb63..13ca4ea541 100644 --- a/did_core/public_key/src/error.rs +++ b/did_core/public_key/src/error.rs @@ -1,3 +1,5 @@ +use std::error::Error; + use thiserror::Error; #[derive(Debug, Error)] @@ -10,8 +12,12 @@ pub enum PublicKeyError { MultibaseDecodingError(#[from] multibase::Error), #[error("Varint decoding error")] VarintDecodingError(#[from] VarintDecodingError), + #[error("JWK decoding error")] + JwkDecodingError(#[from] Box), #[error("Unsupported multicodec descriptor: {0}")] UnsupportedMulticodecDescriptor(u64), + #[error("Unsupported multicodec descriptor: {0}")] + UnsupportedKeyType(String), } #[derive(Debug, Error)] diff --git a/did_core/public_key/src/jwk.rs b/did_core/public_key/src/jwk.rs new file mode 100644 index 0000000000..05184a3a3c --- /dev/null +++ b/did_core/public_key/src/jwk.rs @@ -0,0 +1,100 @@ +use askar_crypto::{ + alg::{AnyKey, BlsCurves, EcCurves}, + jwk::FromJwk, + repr::ToPublicBytes, +}; + +use crate::{Key, KeyType, PublicKeyError}; + +impl Key { + pub fn from_jwk(jwk: &str) -> Result { + let askar_key: Box = + FromJwk::from_jwk(jwk).map_err(|e| PublicKeyError::JwkDecodingError(Box::new(e)))?; + + let askar_alg = askar_key.algorithm(); + let pub_key_bytes = askar_key + .to_public_bytes() + .map_err(|e| PublicKeyError::JwkDecodingError(Box::new(e)))? + .to_vec(); + + let key_type = match askar_alg { + askar_crypto::alg::KeyAlg::Ed25519 => KeyType::Ed25519, + askar_crypto::alg::KeyAlg::Bls12_381(BlsCurves::G1G2) => KeyType::Bls12381g1g2, + askar_crypto::alg::KeyAlg::Bls12_381(BlsCurves::G1) => KeyType::Bls12381g1, + askar_crypto::alg::KeyAlg::Bls12_381(BlsCurves::G2) => KeyType::Bls12381g2, + askar_crypto::alg::KeyAlg::X25519 => KeyType::X25519, + askar_crypto::alg::KeyAlg::EcCurve(EcCurves::Secp256r1) => KeyType::P256, + askar_crypto::alg::KeyAlg::EcCurve(EcCurves::Secp384r1) => KeyType::P384, + _ => return Err(PublicKeyError::UnsupportedKeyType(askar_alg.to_string())), + }; + + Key::new(pub_key_bytes, key_type) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_from_ed25519_jwk() { + // vector from https://w3c-ccg.github.io/did-method-key/#ed25519-x25519 + let jwk = r#"{ + "kty": "OKP", + "crv": "Ed25519", + "x": "O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik" + }"#; + let key = Key::from_jwk(jwk).unwrap(); + assert_eq!( + key.fingerprint(), + "z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp" + ); + } + + #[test] + fn test_from_x25519_jwk() { + // vector from https://w3c-ccg.github.io/did-method-key/#ed25519-x25519 + let jwk = r#"{ + "kty": "OKP", + "crv": "X25519", + "x": "W_Vcc7guviK-gPNDBmevVw-uJVamQV5rMNQGUwCqlH0" + }"#; + let key = Key::from_jwk(jwk).unwrap(); + assert_eq!( + key.fingerprint(), + "z6LShs9GGnqk85isEBzzshkuVWrVKsRp24GnDuHk8QWkARMW" + ); + } + + #[test] + fn test_from_p256_jwk() { + // vector from https://dev.uniresolver.io/ + let jwk = r#"{ + "kty": "EC", + "crv": "P-256", + "x": "fyNYMN0976ci7xqiSdag3buk-ZCwgXU4kz9XNkBlNUI", + "y": "hW2ojTNfH7Jbi8--CJUo3OCbH3y5n91g-IMA9MLMbTU" + }"#; + let key = Key::from_jwk(jwk).unwrap(); + assert_eq!( + key.fingerprint(), + "zDnaerDaTF5BXEavCrfRZEk316dpbLsfPDZ3WJ5hRTPFU2169" + ); + } + + #[test] + fn test_from_p384_jwk() { + // vector from https://dev.uniresolver.io/ + let jwk = r#"{ + "kty": "EC", + "crv": "P-384", + "x": "bKq-gg3sJmfkJGrLl93bsumOTX1NubBySttAV19y5ClWK3DxEmqPy0at5lLqBiiv", + "y": "PJQtdHnInU9SY3e8Nn9aOPoP51OFbs-FWJUsU0TGjRtZ4bnhoZXtS92wdzuAotL9" + }"#; + let key = Key::from_jwk(jwk).unwrap(); + assert_eq!( + key.fingerprint(), + "z82Lkytz3HqpWiBmt2853ZgNgNG8qVoUJnyoMvGw6ZEBktGcwUVdKpUNJHct1wvp9pXjr7Y" + ); + } +} diff --git a/did_core/public_key/src/lib.rs b/did_core/public_key/src/lib.rs index 44c7e06726..a9f9f71955 100644 --- a/did_core/public_key/src/lib.rs +++ b/did_core/public_key/src/lib.rs @@ -1,4 +1,6 @@ mod error; +#[cfg(feature = "jwk")] +mod jwk; mod key; mod key_type; diff --git a/justfile b/justfile index cae10c52d4..cd4d455b9d 100644 --- a/justfile +++ b/justfile @@ -23,7 +23,7 @@ check-aries-vcx-credx: cargo test --manifest-path="aries/aries_vcx/Cargo.toml" -F vdrtools_wallet,credx --tests test-unit test_name="": - RUST_TEST_THREADS=1 cargo test --workspace --lib --exclude aries-vcx-agent --exclude libvdrtools --exclude wallet_migrator --exclude mediator {{test_name}} + RUST_TEST_THREADS=1 cargo test --workspace --lib --exclude aries-vcx-agent --exclude libvdrtools --exclude wallet_migrator --exclude mediator {{test_name}} -F did_doc/jwk -F public_key/jwk test-compatibility-aries-vcx-wallet: cargo test --manifest-path="aries/aries_vcx_wallet/Cargo.toml" -F vdrtools_wallet,askar_wallet wallet_compatibility_ @@ -44,4 +44,4 @@ test-integration-aries-vcx-vdrproxy test_name="": cargo test --manifest-path="aries/aries_vcx/Cargo.toml" -F vdr_proxy_ledger,credx -- --ignored {{test_name}} test-integration-did-crate test_name="": - cargo test --examples -p did_doc -p did_parser_nom -p did_resolver -p did_resolver_registry -p did_resolver_sov -p did_resolver_web -p did_key -p did_peer --test "*" + cargo test --examples -p did_doc -p did_parser_nom -p did_resolver -p did_resolver_registry -p did_resolver_sov -p did_resolver_web -p did_key -p did_peer -F did_doc/jwk --test "*"