diff --git a/Cargo.lock b/Cargo.lock index cad5220..bb75185 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,9 +14,9 @@ dependencies = [ [[package]] name = "actix-rt" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28f32d40287d3f402ae0028a9d54bef51af15c8769492826a69d28f81893151d" +checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" dependencies = [ "actix-macros", "futures-core", @@ -97,29 +97,12 @@ dependencies = [ "libc", ] -[[package]] -name = "anyhow" -version = "1.0.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" - [[package]] name = "arrayref" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" -[[package]] -name = "async-trait" -version = "0.1.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.53", -] - [[package]] name = "atty" version = "0.2.14" @@ -311,6 +294,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-targets 0.52.4", ] @@ -488,9 +472,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "der" @@ -513,8 +497,8 @@ dependencies = [ [[package]] name = "didcomm-rs" -version = "0.8.0" -source = "git+https://github.com/nodecross/didcomm-rs.git?tag=v0.8.0#b206c57b85165dec6e4e5a8f734b27ad5ef7befe" +version = "0.8.1" +source = "git+https://github.com/nodecross/didcomm-rs.git?tag=v0.8.1#45729172897436d177d6965113997efa0d1d094b" dependencies = [ "aes-gcm", "arrayref", @@ -817,19 +801,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "hdwallet" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a03ba7d4c9ea41552cd4351965ff96883e629693ae85005c501bb4b9e1c48a7" -dependencies = [ - "lazy_static", - "rand_core 0.6.4", - "ring", - "secp256k1", - "thiserror", -] - [[package]] name = "hermit-abi" version = "0.1.19" @@ -927,18 +898,6 @@ dependencies = [ "cc", ] -[[package]] -name = "ibig" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1fcc7f316b2c079dde77564a1360639c1a956a23fa96122732e416cb10717bb" -dependencies = [ - "cfg-if", - "num-traits", - "rand 0.8.5", - "static_assertions", -] - [[package]] name = "itoa" version = "1.0.10" @@ -990,12 +949,6 @@ dependencies = [ "cpufeatures 0.2.12", ] -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - [[package]] name = "libaes" version = "0.6.5" @@ -1008,16 +961,6 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" -[[package]] -name = "libloading" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" -dependencies = [ - "cfg-if", - "windows-targets 0.52.4", -] - [[package]] name = "lock_api" version = "0.4.11" @@ -1068,32 +1011,26 @@ dependencies = [ [[package]] name = "nodex-didcomm" -version = "0.1.0" +version = "0.2.0" dependencies = [ "actix-rt", - "anyhow", - "arrayref", - "async-trait", "chrono", "cuid", "data-encoding", "didcomm-rs", - "getrandom 0.2.12", - "hdwallet", "hex", - "hmac 0.12.1", "http", - "ibig", "k256 0.13.3", - "libloading", - "log", "rand 0.8.5", + "rand_core 0.6.4", "serde", "serde_jcs", "serde_json", "sha2 0.10.8", "thiserror", + "trait-variant", "x25519-dalek 2.0.1", + "zeroize", ] [[package]] @@ -1436,21 +1373,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi", -] - [[package]] name = "rustc-demangle" version = "0.1.23" @@ -1499,24 +1421,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "secp256k1" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4124a35fe33ae14259c490fd70fa199a32b9ce9502f2ee6bc4f81ec06fa65894" -dependencies = [ - "secp256k1-sys", -] - -[[package]] -name = "secp256k1-sys" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" -dependencies = [ - "cc", -] - [[package]] name = "semver" version = "1.0.22" @@ -1556,11 +1460,12 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -1666,12 +1571,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "spki" version = "0.4.1" @@ -1691,12 +1590,6 @@ dependencies = [ "der 0.7.8", ] -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "subtle" version = "2.4.1" @@ -1748,18 +1641,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", @@ -1782,6 +1675,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "trait-variant" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.53", +] + [[package]] name = "typenum" version = "1.17.0" @@ -1810,12 +1714,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "uuid" version = "1.8.0" @@ -1897,16 +1795,6 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" -[[package]] -name = "web-sys" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "winapi" version = "0.3.9" @@ -2104,9 +1992,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ "zeroize_derive", ] diff --git a/Cargo.toml b/Cargo.toml index d59b31c..644dbff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,42 +1,36 @@ [package] categories = ["cryptography", "embedded"] -edition = "2018" +edition = "2021" keywords = ["did", "embedded", "iot", "root-of-trust"] name = "nodex-didcomm" readme = "README.md" -version = "0.1.0" +version = "0.2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow = "1.0.82" -arrayref = { version = "0.3.7" } -async-trait = "0.1.79" -chrono = { version = "0.4" } -cuid = { version = "1.3.2" } -data-encoding = { version = "2.5.0" } -didcomm-rs = { git = "https://github.com/nodecross/didcomm-rs.git", tag = "v0.8.0", default-features = false, features = ["raw-crypto"] } -getrandom = { version = "0.2" } -hdwallet = { version = "0.4.1" } -hex = { version = "0.4.3" } -hmac = { version = "0.12.1" } -http = { version = "1.1.0" } -ibig = { version = "0.3.6" } +chrono = { version = "0.4", features = ["serde"] } +cuid = "1.3.2" +data-encoding = "2.6.0" +didcomm-rs = { git = "https://github.com/nodecross/didcomm-rs.git", tag = "v0.8.1", default-features = false, features = ["raw-crypto"] } +hex = "0.4.3" +http = "1.1.0" k256 = { version = "0.13.3", features = [ "ecdh", "ecdsa", "serde", "sha256", ] } -libloading = { version = "0.8.3" } -log = { version = "0.4.21" } +rand_core = "0.6.4" serde = { version = "1.0.204", features = ["derive"] } -serde_jcs = { version = "0.1.0" } -serde_json = { version = "1.0.116" } -sha2 = { version = "0.10.8" } -thiserror = "1.0.59" +serde_jcs = "0.1.0" +serde_json = "1.0.120" +sha2 = "0.10.8" +thiserror = "1.0.63" +trait-variant = "0.1.2" x25519-dalek = { version = "2.0.1", features = ["static_secrets"] } +zeroize = "1.8.1" [dev-dependencies] -actix-rt = { version = "2.9.0" } -rand = { version = "0.8.5" } +actix-rt = "2.10.0" +rand = "0.8.5" diff --git a/src/common/cipher/jws.rs b/src/common/cipher/jws.rs deleted file mode 100644 index 310fb9f..0000000 --- a/src/common/cipher/jws.rs +++ /dev/null @@ -1,170 +0,0 @@ -use serde::{Deserialize, Serialize}; -use serde_json::{json, Value}; -use thiserror::Error; - -use super::signer::{Signer, SignerError}; -use crate::{ - common::runtime::{self, base64_url::PaddingType}, - keyring::secp256k1::Secp256k1, -}; - -#[derive(Debug, Serialize, Deserialize)] -struct JWSHeader { - alg: String, - b64: bool, - crit: Vec, -} - -pub struct Jws {} - -#[derive(Debug, Error)] -pub enum JwsEncodeError { - #[error(transparent)] - SignerError(#[from] SignerError), -} - -#[derive(Debug, Error)] -pub enum JwsDecodeError { - #[error(transparent)] - SignerError(#[from] SignerError), - #[error("InvalidJws : {0}")] - InvalidJws(String), - #[error(transparent)] - Base64UrlError(#[from] runtime::base64_url::Base64UrlError), - #[error(transparent)] - JsonParseError(#[from] serde_json::Error), - #[error("InvalidAlgorithm: {0}")] - InvalidAlgorithm(String), - #[error("b64 option is not supported")] - B64NotSupported, - #[error("b64 option is not supported, but contained")] - B64NotSupportedButContained, - #[error("EmptyPayload")] - EmptyPayload, -} - -impl Jws { - pub fn encode(object: &Value, context: &Secp256k1) -> Result { - // NOTE: header - let header = - JWSHeader { alg: "ES256K".to_string(), b64: false, crit: vec!["b64".to_string()] }; - let header = runtime::base64_url::Base64Url::encode( - json!(&header).to_string().as_bytes(), - &PaddingType::NoPadding, - ); - - // NOTE: payload - let payload = runtime::base64_url::Base64Url::encode( - object.to_string().as_bytes(), - &PaddingType::NoPadding, - ); - - // NOTE: message - let message = [header.clone(), payload].join("."); - - // NOTE: signature - let signature = Signer::sign(&message, context)?; - let signature = runtime::base64_url::Base64Url::encode(&signature, &PaddingType::NoPadding); - - Ok([header, "".to_string(), signature].join(".")) - } - - pub fn verify(object: &Value, jws: &str, context: &Secp256k1) -> Result { - let split: Vec = jws.split('.').map(|v| v.to_string()).collect(); - - if split.len() != 3 { - return Err(JwsDecodeError::InvalidJws(jws.to_string())); - } - - let _header = split[0].clone(); - let __payload = split[1].clone(); - let _signature = split[2].clone(); - - // NOTE: header - let decoded = - runtime::base64_url::Base64Url::decode_as_string(&_header, &PaddingType::NoPadding)?; - let header = serde_json::from_str::(&decoded)?; - - if header.alg != *"ES256K" { - return Err(JwsDecodeError::InvalidAlgorithm(header.alg)); - } - if header.b64 { - return Err(JwsDecodeError::B64NotSupported); - } - if header.crit.iter().all(|v| v != "b64") { - return Err(JwsDecodeError::B64NotSupportedButContained); - }; - - // NOTE: payload - if __payload != *"".to_string() { - return Err(JwsDecodeError::EmptyPayload); - } - let _payload = runtime::base64_url::Base64Url::encode( - object.to_string().as_bytes(), - &PaddingType::NoPadding, - ); - - // NOTE: message - let message = [_header, _payload].join("."); - - // NOTE: signature - let signature = - runtime::base64_url::Base64Url::decode_as_bytes(&_signature, &PaddingType::NoPadding)?; - - // NOTE: verify - Ok(Signer::verify(&message, &signature, context)?) - } -} - -#[cfg(test)] -pub mod tests { - - use super::*; - use crate::keyring::{self}; - - const SECRET_KEY: [u8; 32] = [ - 0xc7, 0x39, 0x80, 0x5a, 0xb0, 0x3d, 0xa6, 0x2d, 0xdb, 0xe0, 0x33, 0x90, 0xac, 0xdf, 0x76, - 0x15, 0x64, 0x0a, 0xa6, 0xed, 0x31, 0xb8, 0xf1, 0x82, 0x43, 0xf0, 0x4a, 0x57, 0x2c, 0x52, - 0x8e, 0xdb, - ]; - - const PUBLIC_KEY: [u8; 33] = [ - 0x02, 0x70, 0x96, 0x45, 0x32, 0xf0, 0x83, 0xf4, 0x5f, 0xe8, 0xe8, 0xcc, 0xea, 0x96, 0xa2, - 0x2f, 0x60, 0x18, 0xd4, 0x6a, 0x40, 0x6f, 0x58, 0x3a, 0xb2, 0x26, 0xb1, 0x92, 0x83, 0xaa, - 0x60, 0x5c, 0x44, - ]; - - fn message() -> String { - String::from(r#"{"k":"0123456789abcdef"}"#) - } - - fn signature() -> String { - String::from( - "eyJhbGciOiJFUzI1NksiLCJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdfQ..vuhCrs1zs9Mlhof0TXgN9XQEY9ZJ2g2kZsH4Ef99wn5MR0pQOhkAHvgYZfHBvXOR795WnWKF_rUiE85abp5CAA", - ) - } - - #[test] - pub fn test_encode() { - let context = - keyring::secp256k1::Secp256k1::new(PUBLIC_KEY.to_vec(), SECRET_KEY.to_vec()).unwrap(); - - let json: Value = serde_json::from_str(&message()).unwrap(); - - let result = Jws::encode(&json, &context).unwrap(); - - assert_eq!(result, signature()) - } - - #[test] - pub fn test_verify() { - let context = - keyring::secp256k1::Secp256k1::new(PUBLIC_KEY.to_vec(), SECRET_KEY.to_vec()).unwrap(); - - let json: Value = serde_json::from_str(&message()).unwrap(); - - let result = Jws::verify(&json, &signature(), &context).unwrap(); - - assert!(result) - } -} diff --git a/src/common/cipher/mod.rs b/src/common/cipher/mod.rs deleted file mode 100644 index 36add51..0000000 --- a/src/common/cipher/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub(crate) mod jws; -pub(crate) mod signer; diff --git a/src/common/cipher/signer.rs b/src/common/cipher/signer.rs deleted file mode 100644 index bc10769..0000000 --- a/src/common/cipher/signer.rs +++ /dev/null @@ -1,84 +0,0 @@ -use thiserror::Error; - -use crate::{common::runtime, keyring::secp256k1::Secp256k1}; - -pub struct Signer {} - -#[derive(Debug, Error)] -pub enum SignerError { - #[error(transparent)] - Secp256k1Error(#[from] runtime::secp256k1::Secp256k1Error), -} - -impl Signer { - pub fn sign(message: &str, context: &Secp256k1) -> Result, SignerError> { - Ok(runtime::secp256k1::Secp256k1::ecdsa_sign( - message.as_bytes(), - &context.get_secret_key(), - )?) - } - - pub fn verify( - message: &str, - signature: &[u8], - context: &Secp256k1, - ) -> Result { - Ok(runtime::secp256k1::Secp256k1::ecdsa_verify( - signature, - message.as_bytes(), - &context.get_public_key(), - )?) - } -} - -#[cfg(test)] -pub mod tests { - - use super::*; - use crate::keyring::{self}; - - const SECRET_KEY: [u8; 32] = [ - 0xc7, 0x39, 0x80, 0x5a, 0xb0, 0x3d, 0xa6, 0x2d, 0xdb, 0xe0, 0x33, 0x90, 0xac, 0xdf, 0x76, - 0x15, 0x64, 0x0a, 0xa6, 0xed, 0x31, 0xb8, 0xf1, 0x82, 0x43, 0xf0, 0x4a, 0x57, 0x2c, 0x52, - 0x8e, 0xdb, - ]; - - const PUBLIC_KEY: [u8; 33] = [ - 0x02, 0x70, 0x96, 0x45, 0x32, 0xf0, 0x83, 0xf4, 0x5f, 0xe8, 0xe8, 0xcc, 0xea, 0x96, 0xa2, - 0x2f, 0x60, 0x18, 0xd4, 0x6a, 0x40, 0x6f, 0x58, 0x3a, 0xb2, 0x26, 0xb1, 0x92, 0x83, 0xaa, - 0x60, 0x5c, 0x44, - ]; - - fn message() -> String { - String::from(r#"{"k":"0123456789abcdef"}"#) - } - - fn digest() -> Vec { - vec![ - 0xf7, 0x85, 0xd1, 0x25, 0xdd, 0x45, 0x64, 0x1b, 0xad, 0x3c, 0x54, 0x67, 0x4b, 0xf6, - 0xc1, 0xdf, 0xef, 0xf9, 0xe0, 0x05, 0xc8, 0xe0, 0xcf, 0x23, 0x5e, 0x29, 0x79, 0x28, - 0xb2, 0xa4, 0x54, 0x6e, 0x37, 0x4f, 0xcf, 0x9f, 0x09, 0xb9, 0x2d, 0x9c, 0x71, 0x6f, - 0xf5, 0x58, 0xd4, 0x30, 0x2b, 0xa6, 0x5c, 0x5c, 0xf5, 0xfb, 0x8a, 0xac, 0xdd, 0x26, - 0xb0, 0xc2, 0x10, 0xfa, 0xe1, 0x4c, 0xd9, 0x10, - ] - } - - #[test] - pub fn test_sign() { - let context = - keyring::secp256k1::Secp256k1::new(PUBLIC_KEY.to_vec(), SECRET_KEY.to_vec()).unwrap(); - - let result = Signer::sign(&message(), &context).unwrap(); - - assert_eq!(result, digest()) - } - - #[test] - pub fn test_verify() { - let context = - keyring::secp256k1::Secp256k1::new(PUBLIC_KEY.to_vec(), SECRET_KEY.to_vec()).unwrap(); - - let result = Signer::verify(&message(), &digest(), &context).unwrap(); - assert!(result) - } -} diff --git a/src/common/mod.rs b/src/common/mod.rs deleted file mode 100644 index fc34ee3..0000000 --- a/src/common/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod cipher; -pub mod runtime; -pub mod utils; diff --git a/src/common/runtime/base64_url.rs b/src/common/runtime/base64_url.rs deleted file mode 100644 index fde74ec..0000000 --- a/src/common/runtime/base64_url.rs +++ /dev/null @@ -1,117 +0,0 @@ -use data_encoding::{BASE64URL, BASE64URL_NOPAD}; -use thiserror::Error; - -pub struct Base64Url {} - -pub enum PaddingType { - #[allow(dead_code)] - Padding, - NoPadding, -} - -#[derive(Error, Debug)] -pub enum Base64UrlError { - #[error("decode failed")] - DecodeFailed(#[from] data_encoding::DecodeError), - #[error("convert from utf8 failed")] - ConvertFromUtf8Error(#[from] std::string::FromUtf8Error), -} - -impl Base64Url { - pub fn encode(content: &[u8], padding: &PaddingType) -> String { - match padding { - PaddingType::Padding => BASE64URL.encode(content), - PaddingType::NoPadding => BASE64URL_NOPAD.encode(content), - } - } - - pub fn decode_as_bytes( - message: &str, - padding: &PaddingType, - ) -> Result, Base64UrlError> { - (match padding { - PaddingType::Padding => BASE64URL.decode(message.as_bytes()), - PaddingType::NoPadding => BASE64URL_NOPAD.decode(message.as_bytes()), - }) - .map_err(|e| e.into()) - } - - pub fn decode_as_string( - message: &str, - padding: &PaddingType, - ) -> Result { - let bytes = (match padding { - PaddingType::Padding => BASE64URL.decode(message.as_bytes()), - PaddingType::NoPadding => BASE64URL_NOPAD.decode(message.as_bytes()), - })?; - - String::from_utf8(bytes).map_err(|e| e.into()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn message() -> String { - String::from("0123456789abcdef") - } - - #[test] - fn test_base64url_encode() { - let result = Base64Url::encode(message().as_bytes(), &PaddingType::Padding); - - assert_eq!(result, String::from("MDEyMzQ1Njc4OWFiY2RlZg==")); - } - - #[test] - fn test_base64url_encode_nopad() { - let result = Base64Url::encode(message().as_bytes(), &PaddingType::NoPadding); - - assert_eq!(result, String::from("MDEyMzQ1Njc4OWFiY2RlZg")); - } - - #[test] - fn test_base64url_decode_byte() { - let encoded = Base64Url::encode(message().as_bytes(), &PaddingType::Padding); - let result = Base64Url::decode_as_bytes(&encoded, &PaddingType::Padding).unwrap(); - - assert_eq!( - result, - vec![ - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, - 0x65, 0x66, - ] - ); - } - - #[test] - fn test_base64url_decode_byte_nopad() { - let encoded = Base64Url::encode(message().as_bytes(), &PaddingType::NoPadding); - let result = Base64Url::decode_as_bytes(&encoded, &PaddingType::NoPadding).unwrap(); - - assert_eq!( - result, - vec![ - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, - 0x65, 0x66, - ] - ); - } - - #[test] - fn test_base64url_decode_string() { - let encoded = Base64Url::encode(message().as_bytes(), &PaddingType::Padding); - let result = Base64Url::decode_as_string(&encoded, &PaddingType::Padding).unwrap(); - - assert_eq!(result, message()); - } - - #[test] - fn test_base64url_decode_string_nopad() { - let encoded = Base64Url::encode(message().as_bytes(), &PaddingType::NoPadding); - let result = Base64Url::decode_as_string(&encoded, &PaddingType::NoPadding).unwrap(); - - assert_eq!(result, message()); - } -} diff --git a/src/common/runtime/bip32.rs b/src/common/runtime/bip32.rs deleted file mode 100644 index 62c76e0..0000000 --- a/src/common/runtime/bip32.rs +++ /dev/null @@ -1,77 +0,0 @@ -use hdwallet::{ChainPath, DefaultKeyChain, ExtendedPrivKey, ExtendedPubKey, KeyChain}; -use thiserror::Error; - -use crate::keyring::secp256k1::{COMPRESSED_PUBLIC_KEY_SIZE, PRIVATE_KEY_SIZE}; - -#[derive(Debug)] -pub struct BIP32Container { - pub public_key: [u8; COMPRESSED_PUBLIC_KEY_SIZE], - pub private_key: [u8; PRIVATE_KEY_SIZE], -} - -pub struct BIP32 {} - -#[derive(Error, Debug)] -pub enum BIP32Error { - #[error("error in hdwallet")] - Hdwallet(hdwallet::error::Error), -} - -impl BIP32 { - pub fn get_node(seed: &[u8], derivation_path: &str) -> Result { - let master = ExtendedPrivKey::with_seed(seed).map_err(BIP32Error::Hdwallet)?; - - let chain = DefaultKeyChain::new(master); - let path = ChainPath::new(derivation_path); - - let (private_key, _) = chain.derive_private_key(path).map_err(BIP32Error::Hdwallet)?; - - let public_key = ExtendedPubKey::from_private_key(&private_key); - - Ok(BIP32Container { - private_key: private_key.private_key.secret_bytes(), - public_key: public_key.public_key.serialize(), - }) - } -} - -#[cfg(test)] -pub mod tests { - - use super::*; - - fn seed() -> Vec { - vec![ - 0x2d, 0xc5, 0x00, 0xab, 0xea, 0x17, 0xfe, 0x36, 0x19, 0x46, 0xd6, 0x11, 0x3a, 0xf6, - 0xbc, 0x26, 0xf4, 0x8e, 0xed, 0x90, 0x4d, 0x95, 0x27, 0xb5, 0x69, 0x18, 0xbf, 0xb5, - 0xce, 0x24, 0x42, 0x51, - ] - } - - fn derivation_path() -> String { - // NOTE: Ethereum - String::from("m/44'/60'/0'/0/0") - } - - #[test] - fn test_get_node() { - let result = BIP32::get_node(&seed(), &derivation_path()).unwrap(); - - assert_eq!( - result.private_key, - [ - 0xc8, 0x05, 0x4c, 0xe7, 0x85, 0x6e, 0x13, 0x9a, 0xab, 0xec, 0x36, 0x5f, 0x6b, 0xe4, - 0xf1, 0x1b, 0x51, 0x50, 0xb2, 0xd7, 0x6c, 0x6b, 0xb4, 0xf0, 0x05, 0xfd, 0x0b, 0x1e, - 0x0b, 0x5c, 0x92, 0x87, - ] - ); - assert_eq!( - result.public_key, - [ - 0x02, 0x67, 0xfe, 0x57, 0xf7, 0x1e, 0xdb, 0x76, 0x60, 0x96, 0x99, 0x35, 0x0f, 0x5b, - 0x29, 0x75, 0xba, 0x6e, 0xf9, 0x00, 0x6e, 0x65, 0x27, 0xf2, 0xe4, 0xfb, 0xad, 0x41, - 0xd3, 0x74, 0xf0, 0x4f, 0x3a, - ] - ); - } -} diff --git a/src/common/runtime/jcs.rs b/src/common/runtime/jcs.rs deleted file mode 100644 index d5462c8..0000000 --- a/src/common/runtime/jcs.rs +++ /dev/null @@ -1,38 +0,0 @@ -use serde_jcs; -use serde_json::{self, Value}; -use thiserror::Error; - -pub struct Jcs {} - -#[derive(Debug, Error)] -pub enum JcsError { - #[error("Decode failed")] - DecodeFailed(serde_json::Error), - #[error("Serialize failed")] - SerializeFailed(serde_json::Error), -} - -impl Jcs { - pub fn canonicalize(input: &str) -> Result { - let json = serde_json::from_str::(input).map_err(JcsError::DecodeFailed)?; - - serde_jcs::to_string(&json).map_err(JcsError::SerializeFailed) - } -} - -#[cfg(test)] -pub mod tests { - - use super::*; - - fn json() -> String { - String::from(r#"{"c":2,"a":1,"b":[]}"#) - } - - #[test] - fn test_canonicalize() { - let result = Jcs::canonicalize(&json()).unwrap(); - - assert_eq!(result, r#"{"a":1,"b":[],"c":2}"#); - } -} diff --git a/src/common/runtime/mod.rs b/src/common/runtime/mod.rs deleted file mode 100644 index 43b1d7a..0000000 --- a/src/common/runtime/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod base64_url; -pub mod bip32; -pub mod jcs; -pub mod multihash; -pub mod random; -pub mod secp256k1; diff --git a/src/common/runtime/multihash.rs b/src/common/runtime/multihash.rs deleted file mode 100644 index 2c696fe..0000000 --- a/src/common/runtime/multihash.rs +++ /dev/null @@ -1,162 +0,0 @@ -use sha2::{Digest, Sha256}; -use thiserror::Error; - -use super::jcs::JcsError; -use crate::common::runtime::base64_url::{Base64Url, PaddingType}; - -const MULTIHASH_SHA256_CODE: u8 = 0x12; // 0x12 = 18 -const MULTIHASH_SHA256_SIZE: u8 = 0x20; // 0x20 = 32 - -pub struct Multihash {} - -#[derive(Eq, PartialEq, Debug)] -pub struct DecodedContainer { - hash: Vec, - algorithm: u64, -} - -#[derive(Debug, Error)] -pub enum MultihashError { - #[error(transparent)] - FromUtf8Error(#[from] std::string::FromUtf8Error), - #[error(transparent)] - JsonCanonicalizationError(#[from] JcsError), - #[error("InvalidLength: {0}")] - InvalidLength(usize), - #[error("expected length is {0}, but actual length is {1}")] - SizeValidationFailed(usize, usize), -} - -impl Multihash { - pub fn hash_as_non_multihash_buffer(message: &[u8]) -> Vec { - let mut hasher = Sha256::new(); - - hasher.update(message); - - hasher.finalize().to_vec() - } - - // [NOTE]: SHA2-256 ONLY - pub fn hash(message: &[u8]) -> Vec { - let mut prefix: Vec = Vec::from([MULTIHASH_SHA256_CODE, MULTIHASH_SHA256_SIZE]); - - let mut hashed: Vec = Multihash::hash_as_non_multihash_buffer(message); - let mut joined: Vec = Vec::from([]); - - joined.append(&mut prefix); - joined.append(&mut hashed); - - joined - } - - pub fn hash_then_encode(message: &[u8]) -> String { - let hashed = Multihash::hash(message); - - Base64Url::encode(&hashed, &PaddingType::NoPadding) - } - - pub fn canonicalize_then_double_hash_then_encode( - message: &[u8], - ) -> Result { - let plain = String::from_utf8(message.to_vec())?; - - let canonicalized = super::jcs::Jcs::canonicalize(&plain)?; - - let hashed = Multihash::hash_as_non_multihash_buffer(canonicalized.as_bytes()); - - Ok(Multihash::hash_then_encode(&hashed)) - } - - #[allow(dead_code)] - pub fn decode(encoded: &[u8]) -> Result { - // check for: [ code, size, digest... ] - if encoded.len() < 2 { - return Err(MultihashError::InvalidLength(encoded.len())); - } - - let code = encoded[0]; - let length = encoded[1]; - let digest = encoded[2..].to_vec(); - - if digest.len() != usize::from(length) { - return Err(MultihashError::SizeValidationFailed(usize::from(length), digest.len())); - } - - Ok(DecodedContainer { hash: digest, algorithm: u64::from(code) }) - } -} - -#[cfg(test)] -mod tests { - - use super::*; - - fn message() -> String { - String::from(r#"{"k":"0123456789abcdef"}"#) - } - - #[test] - fn test_hash() { - let result = Multihash::hash(message().as_bytes()); - - assert_eq!( - result, - vec![ - 0x12, 0x20, 0x5f, 0x46, 0x25, 0xd4, 0xf6, 0x1e, 0xdb, 0x52, 0x78, 0x07, 0x45, 0x5f, - 0x48, 0xf8, 0xbe, 0x27, 0x8e, 0x71, 0xe8, 0x4a, 0xa9, 0x4d, 0x23, 0x11, 0x1f, 0xfa, - 0xb3, 0xb6, 0x30, 0x93, 0xa7, 0x13, - ] - ); - } - - #[test] - fn test_hash_as_non_multihash_buffer() { - let result = Multihash::hash_as_non_multihash_buffer(message().as_bytes()); - - assert_eq!( - result, - vec![ - 0x5f, 0x46, 0x25, 0xd4, 0xf6, 0x1e, 0xdb, 0x52, 0x78, 0x07, 0x45, 0x5f, 0x48, 0xf8, - 0xbe, 0x27, 0x8e, 0x71, 0xe8, 0x4a, 0xa9, 0x4d, 0x23, 0x11, 0x1f, 0xfa, 0xb3, 0xb6, - 0x30, 0x93, 0xa7, 0x13, - ] - ); - } - - #[test] - fn test_canonicalize_then_double_hash_then_encode() { - let result = - match Multihash::canonicalize_then_double_hash_then_encode(message().as_bytes()) { - Ok(v) => v, - Err(_) => panic!(), - }; - - assert_eq!(result, String::from("EiAEX1W46vVid7IjJyFY5ibjmyrgepTjW0rYrw-wo4xLCw")); - } - - #[test] - fn test_hash_then_encode() { - let result = Multihash::hash_then_encode(message().as_bytes()); - - assert_eq!(result, String::from("EiBfRiXU9h7bUngHRV9I-L4njnHoSqlNIxEf-rO2MJOnEw")); - } - - #[test] - fn test_decode() { - let encoded = Multihash::hash(message().as_bytes()); - let result = Multihash::decode(&encoded); - - assert!(result.is_ok()); - assert_eq!( - result.unwrap(), - DecodedContainer { - hash: vec![ - 0x5f, 0x46, 0x25, 0xd4, 0xf6, 0x1e, 0xdb, 0x52, 0x78, 0x07, 0x45, 0x5f, 0x48, - 0xf8, 0xbe, 0x27, 0x8e, 0x71, 0xe8, 0x4a, 0xa9, 0x4d, 0x23, 0x11, 0x1f, 0xfa, - 0xb3, 0xb6, 0x30, 0x93, 0xa7, 0x13, - ], - algorithm: 18, - } - ); - } -} diff --git a/src/common/runtime/random.rs b/src/common/runtime/random.rs deleted file mode 100644 index 2cb5739..0000000 --- a/src/common/runtime/random.rs +++ /dev/null @@ -1,37 +0,0 @@ -use thiserror::Error; - -pub struct Random {} - -#[derive(Debug, Error)] -pub enum RandomError { - #[error(transparent)] - GetRandomError(#[from] getrandom::Error), -} - -impl Random { - pub fn bytes(size: &usize) -> Result, RandomError> { - let mut bytes = vec![0u8; *size]; - - getrandom::getrandom(&mut bytes)?; - Ok(bytes) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_should_success_random_bytes_32() { - let result = Random::bytes(&32).unwrap(); - - assert_eq!(result.len(), 32); - } - - #[test] - fn it_should_success_random_bytes_128() { - let result = Random::bytes(&128).unwrap(); - - assert_eq!(result.len(), 128); - } -} diff --git a/src/common/runtime/secp256k1.rs b/src/common/runtime/secp256k1.rs deleted file mode 100644 index c5c381b..0000000 --- a/src/common/runtime/secp256k1.rs +++ /dev/null @@ -1,172 +0,0 @@ -use hmac::digest::generic_array::GenericArray; -use k256::{ - ecdsa::{ - signature::{Signer, Verifier}, - Signature, SigningKey, VerifyingKey, - }, - elliptic_curve::{ecdh::diffie_hellman, sec1::ToEncodedPoint}, - PublicKey, SecretKey, -}; -use thiserror::Error; - -pub struct Secp256k1 {} - -#[derive(Debug, Error)] -pub enum Secp256k1Error { - #[error("SecretKeyConvertError")] - KeyConvertError(#[from] k256::elliptic_curve::Error), - #[error("PublicKeyConvertError : {0:?}")] - SignatureError(#[from] k256::ecdsa::Error), - #[error("invalid signature length: {0}")] - InvalidSignatureLength(usize), -} - -impl Secp256k1 { - pub fn ecdh(private_key: &[u8], public_key: &[u8]) -> Result<[u8; 32], Secp256k1Error> { - let sk = SecretKey::from_slice(private_key)?; - let pk = PublicKey::from_sec1_bytes(public_key)?; - - let shared = diffie_hellman(sk.to_nonzero_scalar(), pk.as_affine()); - let converted: [u8; 32] = (*shared.raw_secret_bytes()).into(); - - Ok(converted) - } - - #[allow(dead_code)] - pub fn generate_public_key(private_key: &[u8]) -> Result, Secp256k1Error> { - let signing_key = SigningKey::from_slice(private_key)?; - Ok(signing_key.verifying_key().to_sec1_bytes().to_vec()) - } - - pub fn convert_public_key( - public_key: &[u8], - compress: bool, - ) -> Result, Secp256k1Error> { - let public_key = PublicKey::from_sec1_bytes(public_key)?; - Ok(public_key.to_encoded_point(compress).as_bytes().to_vec()) - } - - pub fn ecdsa_sign(message: &[u8], private_key: &[u8]) -> Result, Secp256k1Error> { - let signing_key = SigningKey::from_slice(private_key)?; - - let signature: Signature = signing_key.try_sign(message)?; - - Ok(signature.to_vec()) - } - - pub fn ecdsa_verify( - signature: &[u8], - message: &[u8], - public_key: &[u8], - ) -> Result { - let verify_key = VerifyingKey::from_sec1_bytes(public_key)?; - - if signature.len() != 64 { - return Err(Secp256k1Error::InvalidSignatureLength(signature.len())); - } - - let r = GenericArray::from_slice(&signature[0..32]); - let s = GenericArray::from_slice(&signature[32..]); - - let wrapped_signature = Signature::from_scalars(*r, *s)?; - - match verify_key.verify(message, &wrapped_signature) { - Ok(()) => Ok(true), - Err(e) => { - log::error!("signature error occurred. {:?}", e); - Ok(false) - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn message() -> String { - String::from("0123456789abcdef") - } - - const PRIVATE_KEY: [u8; 32] = [ - 0x03, 0xb4, 0xad, 0xcd, 0x59, 0x36, 0x3f, 0x4e, 0xb9, 0xd0, 0x9f, 0x2a, 0x34, 0xcd, 0x3d, - 0x26, 0xa8, 0x12, 0x33, 0x0c, 0x2f, 0x88, 0x7c, 0xe5, 0xf8, 0x53, 0x89, 0x48, 0xff, 0xac, - 0x74, 0xc0, - ]; - - const PUBLIC_KEY: [u8; 33] = [ - 0x02, 0x51, 0x84, 0x22, 0x3f, 0xe8, 0x5d, 0x53, 0x20, 0x3c, 0xf9, 0xd3, 0x7f, 0x68, 0x22, - 0xe6, 0x33, 0xe8, 0xd7, 0x9f, 0x48, 0xb1, 0x32, 0xdf, 0x0b, 0x8c, 0x8a, 0x64, 0x11, 0x41, - 0xf3, 0x19, 0xb6, - ]; - - const UNCOMPRESSED_PUBLIC_KEY: [u8; 65] = [ - 0x04, 0x51, 0x84, 0x22, 0x3f, 0xe8, 0x5d, 0x53, 0x20, 0x3c, 0xf9, 0xd3, 0x7f, 0x68, 0x22, - 0xe6, 0x33, 0xe8, 0xd7, 0x9f, 0x48, 0xb1, 0x32, 0xdf, 0x0b, 0x8c, 0x8a, 0x64, 0x11, 0x41, - 0xf3, 0x19, 0xb6, 0xa3, 0x70, 0x7c, 0xa5, 0x18, 0x61, 0xe1, 0xe2, 0xde, 0xa4, 0x3c, 0x23, - 0x84, 0xf1, 0x79, 0xed, 0x44, 0xe9, 0x8c, 0x4a, 0xd0, 0x38, 0x89, 0x21, 0xd9, 0x6a, 0x1e, - 0x05, 0x93, 0x15, 0xe7, 0x54, - ]; - - #[test] - fn test() { - let shared_1 = Secp256k1::ecdh(&PRIVATE_KEY, &PUBLIC_KEY).unwrap(); - - let shared_2 = Secp256k1::ecdh(&PRIVATE_KEY, &UNCOMPRESSED_PUBLIC_KEY).unwrap(); - - assert_eq!(shared_1.len(), 32); - assert_eq!(shared_1, shared_2); - } - - #[test] - fn test_generate_public_key() { - let result = Secp256k1::generate_public_key(&PRIVATE_KEY).unwrap(); - - assert_eq!(result, &PUBLIC_KEY); - } - - #[test] - fn test_convert_public_key() { - let result_1 = Secp256k1::convert_public_key(&PUBLIC_KEY, true).unwrap(); - - assert_eq!(result_1, &PUBLIC_KEY); - - let result_2 = Secp256k1::convert_public_key(&PUBLIC_KEY, false).unwrap(); - - assert_eq!(result_2, &UNCOMPRESSED_PUBLIC_KEY); - } - - #[test] - fn test_ecdsa_sign() { - let message = message().as_bytes().to_vec(); - - let result = Secp256k1::ecdsa_sign(&message, &PRIVATE_KEY).unwrap(); - - assert_eq!( - result, - vec![ - 0x5c, 0xc6, 0x48, 0x2a, 0x15, 0xd8, 0x0d, 0xc0, 0xbe, 0x6d, 0xb8, 0x31, 0xad, 0x9c, - 0x4c, 0xac, 0xd9, 0x3a, 0xb3, 0x6c, 0x08, 0x8a, 0x8b, 0x3c, 0x49, 0xc5, 0xbc, 0x79, - 0x9b, 0xf1, 0xa2, 0x69, 0x3a, 0xc2, 0xa7, 0xdc, 0xd9, 0xa5, 0x16, 0x52, 0x66, 0xa2, - 0x6d, 0xe1, 0x23, 0x41, 0x98, 0xa2, 0x4d, 0xba, 0x08, 0x74, 0x87, 0x5d, 0xba, 0xc4, - 0x00, 0x70, 0x9c, 0x99, 0x3d, 0xd0, 0xf0, 0x2c - ] - ); - } - - #[test] - fn test_ecdsa_verify() { - let message = message().as_bytes().to_vec(); - - let signature = Secp256k1::ecdsa_sign(&message, &PRIVATE_KEY).unwrap(); - - let result_1 = Secp256k1::ecdsa_verify(&signature, &message, &PUBLIC_KEY).unwrap(); - - assert!(result_1); - - let result_2 = - Secp256k1::ecdsa_verify(&signature, &message, &UNCOMPRESSED_PUBLIC_KEY).unwrap(); - - assert!(result_2); - } -} diff --git a/src/common/utils.rs b/src/common/utils.rs deleted file mode 100644 index bd71b53..0000000 --- a/src/common/utils.rs +++ /dev/null @@ -1,15 +0,0 @@ -pub mod json { - use serde_json::Value; - - pub fn merge(a: &mut Value, b: Value) { - match (a, b) { - (a @ &mut Value::Object(_), Value::Object(b)) => { - let a = a.as_object_mut().unwrap(); - for (k, v) in b { - merge(a.entry(k).or_insert(Value::Null), v); - } - } - (a, b) => *a = b, - } - } -} diff --git a/src/did/did_repository.rs b/src/did/did_repository.rs index 6d54e1e..9e48f21 100644 --- a/src/did/did_repository.rs +++ b/src/did/did_repository.rs @@ -1,123 +1,161 @@ -use anyhow::Context; +use std::convert::TryInto; + use http::StatusCode; use super::sidetree::{ - client::{HttpError, SidetreeHttpClient}, - payload::{CommitmentKeys, DIDCreateRequest, OperationPayloadBuilder}, + client::SidetreeHttpClient, + payload::{ + did_create_payload, DidDocument, DidPatchDocument, DidResolutionResponse, ToPublicKey, + }, +}; +use crate::keyring::{ + jwk::Jwk, + keypair::{KeyPair, KeyPairing}, }; -use crate::{did::sidetree::payload::DIDResolutionResponse, keyring::keypair::KeyPairing}; #[derive(Debug, thiserror::Error)] -pub enum CreateIdentifierError { - #[error("Failed to convert public key: {0}")] - PublicKeyConvertFailed(crate::keyring::secp256k1::Secp256k1Error), +pub enum CreateIdentifierError { #[error("Failed to convert to JWK: {0}")] - JwkConvertFailed(#[from] crate::keyring::secp256k1::Secp256k1Error), + Jwk(#[from] crate::keyring::jwk::K256ToJwkError), #[error("Failed to build operation payload: {0}")] - PayloadBuildFailed(#[from] crate::did::sidetree::payload::OperationPayloadBuilderError), - #[error("Failed to send request to sidetree: {0}")] - SidetreeRequestFailed(anyhow::Error), - #[error(transparent)] - Other(#[from] anyhow::Error), + PayloadBuildFailed(#[from] crate::did::sidetree::payload::DidCreatePayloadError), + #[error("Failed to parse body: {0}")] + BodyParse(#[from] serde_json::Error), + #[error("Failed to create identifier. response: {0}")] + SidetreeRequestFailed(String), + #[error("Failed to send request: {0}")] + SidetreeHttpClient(StudioClientError), } -impl From for CreateIdentifierError { - fn from(HttpError::Inner(e): HttpError) -> Self { - Self::SidetreeRequestFailed(e) - } +#[derive(Debug, thiserror::Error)] +pub enum FindIdentifierError { + #[error("Failed to send request to sidetree: {0}")] + SidetreeRequestFailed(String), + #[error("Failed to parse body: {0}")] + BodyParse(#[from] serde_json::Error), + #[error("Failed to send request: {0}")] + SidetreeHttpClient(StudioClientError), } #[derive(Debug, thiserror::Error)] -pub enum FindIdentifierError { - #[error("Failed to send request to sidetree: {0}")] - SidetreeRequestFailed(anyhow::Error), - #[error(transparent)] - Other(#[from] anyhow::Error), +pub enum GetPublicKeyError { + #[error("Failed to get public key: {0}")] + PublicKeyNotFound(String), + #[error("Failed to convert from JWK: {0}")] + JwkToK256(#[from] crate::keyring::jwk::JwkToK256Error), + #[error("Failed to convert from JWK: {0}")] + JwkToX25519(#[from] crate::keyring::jwk::JwkToX25519Error), } -impl From for FindIdentifierError { - fn from(HttpError::Inner(e): HttpError) -> Self { - Self::SidetreeRequestFailed(e) - } +fn get_key(key_type: &str, did_document: &DidDocument) -> Result { + let did = &did_document.id; + let public_key = did_document + .public_key + .clone() + .and_then(|pks| pks.into_iter().find(|pk| pk.id == key_type)) + .ok_or(GetPublicKeyError::PublicKeyNotFound(did.to_string()))?; + Ok(public_key.public_key_jwk) +} + +pub fn get_sign_key(did_document: &DidDocument) -> Result { + let public_key = get_key("#signingKey", did_document)?; + Ok(public_key.try_into()?) +} + +pub fn get_encrypt_key( + did_document: &DidDocument, +) -> Result { + let public_key = get_key("#encryptionKey", did_document)?; + Ok(public_key.try_into()?) } -#[async_trait::async_trait] -pub trait DidRepository { +#[trait_variant::make(Send)] +pub trait DidRepository: Sync { + type CreateIdentifierError: std::error::Error + Send + Sync; + type FindIdentifierError: std::error::Error + Send + Sync; async fn create_identifier( &self, keyring: KeyPairing, - ) -> Result; + ) -> Result; async fn find_identifier( &self, did: &str, - ) -> Result, FindIdentifierError>; + ) -> Result, Self::FindIdentifierError>; } -pub struct DidRepositoryImpl { +#[derive(Clone)] +pub struct DidRepositoryImpl { client: C, } -impl Clone for DidRepositoryImpl -where - C: SidetreeHttpClient + Send + Sync + Clone, -{ - fn clone(&self) -> Self { - Self { client: self.client.clone() } - } -} - -impl DidRepositoryImpl { +impl DidRepositoryImpl { pub fn new(client: C) -> Self { Self { client } } } -#[async_trait::async_trait] -impl DidRepository for DidRepositoryImpl { +impl DidRepository for DidRepositoryImpl +where + C: SidetreeHttpClient + Send + Sync, + C::Error: Send + Sync, +{ + type CreateIdentifierError = CreateIdentifierError; + type FindIdentifierError = FindIdentifierError; async fn create_identifier( &self, keyring: KeyPairing, - ) -> Result { - let public = keyring - .sign - .to_public_key("signingKey", &["auth", "general"]) - .map_err(CreateIdentifierError::PublicKeyConvertFailed)?; - - let update = keyring.update.to_jwk(false)?; - let recovery = keyring.recovery.to_jwk(false)?; - let payload = OperationPayloadBuilder::did_create_payload(&DIDCreateRequest { - public_keys: vec![public], - commitment_keys: CommitmentKeys { recovery, update }, - service_endpoints: vec![], - })?; + ) -> Result> { + // https://w3c.github.io/did-spec-registries/#assertionmethod + // FIXME: This purpose property is strange... + // I guess the sidetree protocol this impl uses is too old. + // https://identity.foundation/sidetree/spec/#add-public-keys + // vec!["assertionMethod".to_string()], + let sign = keyring.sign.get_public_key().to_public_key( + "EcdsaSecp256k1VerificationKey2019".to_string(), + "signingKey".to_string(), + vec!["auth".to_string(), "general".to_string()], + )?; + // vec!["keyAgreement".to_string()] + let enc = keyring + .encrypt + .get_public_key() + .to_public_key( + "X25519KeyAgreementKey2019".to_string(), + "encryptionKey".to_string(), + vec!["auth".to_string(), "general".to_string()], + ) + .unwrap(); + let update = keyring.update.get_public_key(); + let recovery = keyring.recovery.get_public_key(); + let document = DidPatchDocument { public_keys: vec![sign, enc], service_endpoints: vec![] }; + let payload = did_create_payload(document, update, recovery)?; - let response = self.client.post_create_identifier(&payload).await?; + let response = self + .client + .post_create_identifier(&payload) + .await + .map_err(CreateIdentifierError::SidetreeHttpClient)?; if response.status_code.is_success() { - let response = serde_json::from_str(&response.body).context("failed to parse body")?; - Ok(response) + Ok(serde_json::from_str(&response.body)?) } else { - Err(CreateIdentifierError::SidetreeRequestFailed(anyhow::anyhow!( - "Failed to create identifier. response: {:?}", - response - ))) + Err(CreateIdentifierError::SidetreeRequestFailed(format!("{:?}", response))) } } async fn find_identifier( &self, did: &str, - ) -> Result, FindIdentifierError> { - let response = self.client.get_find_identifier(did).await?; + ) -> Result, FindIdentifierError> { + let response = self + .client + .get_find_identifier(did) + .await + .map_err(FindIdentifierError::SidetreeHttpClient)?; match response.status_code { - StatusCode::OK => { - Ok(Some(serde_json::from_str(&response.body).context("failed to parse body")?)) - } + StatusCode::OK => Ok(Some(serde_json::from_str(&response.body)?)), StatusCode::NOT_FOUND => Ok(None), - _ => Err(FindIdentifierError::SidetreeRequestFailed(anyhow::anyhow!( - "Failed to find identifier. response: {:?}", - response - ))), + _ => Err(FindIdentifierError::SidetreeRequestFailed(format!("{:?}", response))), } } } @@ -128,7 +166,7 @@ pub mod mocks { use super::*; use crate::{ - did::sidetree::payload::{DIDDocument, DidPublicKey, MethodMetadata}, + did::sidetree::payload::{DidDocument, DidPublicKey, MethodMetadata}, keyring::keypair::KeyPairing, }; @@ -147,32 +185,46 @@ pub mod mocks { } } - #[async_trait::async_trait] + #[derive(Debug, thiserror::Error)] + pub enum DummyError {} + impl DidRepository for MockDidRepository { + type CreateIdentifierError = CreateIdentifierError; + type FindIdentifierError = FindIdentifierError; async fn create_identifier( &self, _keyring: KeyPairing, - ) -> Result { + ) -> Result { unimplemented!() } async fn find_identifier( &self, did: &str, - ) -> Result, FindIdentifierError> { + ) -> Result, Self::FindIdentifierError> { if let Some(keyrings) = self.map.get(did) { let public_keys = keyrings .iter() - .map(|keyring| DidPublicKey { - id: did.to_string() + "#signingKey", - controller: String::new(), - r#type: "EcdsaSecp256k1VerificationKey2019".to_string(), - public_key_jwk: keyring.sign.to_jwk(false).unwrap(), + .flat_map(|keyring| { + vec![ + DidPublicKey { + id: "#signingKey".to_string(), + controller: String::new(), + r#type: "EcdsaSecp256k1VerificationKey2019".to_string(), + public_key_jwk: keyring.sign.get_public_key().try_into().unwrap(), + }, + DidPublicKey { + id: "#encryptionKey".to_string(), + controller: String::new(), + r#type: "X25519KeyAgreementKey2019".to_string(), + public_key_jwk: keyring.encrypt.get_public_key().into(), + }, + ] }) .collect(); - let response = DIDResolutionResponse { + let response = DidResolutionResponse { context: "https://www.w3.org/ns/did-resolution/v1".to_string(), - did_document: DIDDocument { + did_document: DidDocument { id: did.to_string(), public_key: Some(public_keys), service: None, @@ -194,21 +246,22 @@ pub mod mocks { #[derive(Clone, Copy)] pub struct NoPublicKeyDidRepository; - #[async_trait::async_trait] impl DidRepository for NoPublicKeyDidRepository { + type CreateIdentifierError = CreateIdentifierError; + type FindIdentifierError = FindIdentifierError; async fn create_identifier( &self, _keyring: KeyPairing, - ) -> Result { + ) -> Result { unimplemented!() } async fn find_identifier( &self, did: &str, - ) -> Result, FindIdentifierError> { - Ok(Some(DIDResolutionResponse { + ) -> Result, Self::FindIdentifierError> { + Ok(Some(DidResolutionResponse { context: "https://www.w3.org/ns/did-resolution/v1".to_string(), - did_document: DIDDocument { + did_document: DidDocument { id: did.to_string(), public_key: None, service: None, @@ -226,21 +279,22 @@ pub mod mocks { #[derive(Clone, Copy)] pub struct IllegalPublicKeyLengthDidRepository; - #[async_trait::async_trait] impl DidRepository for IllegalPublicKeyLengthDidRepository { + type CreateIdentifierError = CreateIdentifierError; + type FindIdentifierError = FindIdentifierError; async fn create_identifier( &self, _keyring: KeyPairing, - ) -> Result { + ) -> Result { unimplemented!() } async fn find_identifier( &self, did: &str, - ) -> Result, FindIdentifierError> { - Ok(Some(DIDResolutionResponse { + ) -> Result, Self::FindIdentifierError> { + Ok(Some(DidResolutionResponse { context: "https://www.w3.org/ns/did-resolution/v1".to_string(), - did_document: DIDDocument { + did_document: DidDocument { id: did.to_string(), public_key: Some(vec![]), service: None, diff --git a/src/did/sidetree/client.rs b/src/did/sidetree/client.rs index cbc30f7..c8f2d21 100644 --- a/src/did/sidetree/client.rs +++ b/src/did/sidetree/client.rs @@ -1,50 +1,26 @@ use http::StatusCode; -// This type isn't implement std::error::Error because of conflicting -// implementations -#[derive(Debug)] -pub enum HttpError { - Inner(anyhow::Error), -} - -impl From for HttpError -where - E: std::error::Error + Send + Sync + 'static, -{ - fn from(e: E) -> Self { - Self::Inner(anyhow::Error::new(e)) - } -} - #[derive(Clone, Debug)] pub struct SidetreeHttpClientResponse { pub(crate) status_code: StatusCode, pub(crate) body: String, } -#[derive(Debug, thiserror::Error)] -pub enum SidetreeResponseInitializationError { - #[error("Invalid status code: {0}")] - InvalidStatusCode(u16), -} - impl SidetreeHttpClientResponse { - pub fn new( - status_code: u16, - body: String, - ) -> Result { - let status_code = StatusCode::from_u16(status_code) - .map_err(|_| SidetreeResponseInitializationError::InvalidStatusCode(status_code))?; - Ok(Self { status_code, body }) + pub fn new(status_code: StatusCode, body: String) -> Self { + Self { status_code, body } } } -#[async_trait::async_trait] +#[trait_variant::make(Send)] pub trait SidetreeHttpClient { + type Error: std::error::Error; async fn post_create_identifier( &self, body: &str, - ) -> Result; - async fn get_find_identifier(&self, did: &str) - -> Result; + ) -> Result; + async fn get_find_identifier( + &self, + did: &str, + ) -> Result; } diff --git a/src/did/sidetree/mod.rs b/src/did/sidetree/mod.rs index 3ac7dcc..1915d43 100644 --- a/src/did/sidetree/mod.rs +++ b/src/did/sidetree/mod.rs @@ -1,2 +1,3 @@ pub mod client; +pub mod multihash; pub mod payload; diff --git a/src/did/sidetree/multihash.rs b/src/did/sidetree/multihash.rs new file mode 100644 index 0000000..2cde0b8 --- /dev/null +++ b/src/did/sidetree/multihash.rs @@ -0,0 +1,61 @@ +use std::convert::TryInto; + +use data_encoding::BASE64URL_NOPAD; +use sha2::{Digest, Sha256}; + +const MULTIHASH_SHA256_CODE: u8 = 0x12; // 0x12 = 18 + +// [NOTE]: SHA2-256 ONLY +pub fn hash(message: &[u8]) -> Vec { + let mut prefix = Vec::from([MULTIHASH_SHA256_CODE]); + let mut hashed = Sha256::digest(message).to_vec(); + prefix.push(hashed.len().try_into().unwrap()); + prefix.append(&mut hashed); + prefix +} + +pub fn double_hash_encode(message: &[u8]) -> String { + let mes = Sha256::digest(message).to_vec(); + let mes = hash(&mes); + BASE64URL_NOPAD.encode(&mes) +} + +pub fn hash_encode(message: &[u8]) -> String { + let mes = hash(message); + BASE64URL_NOPAD.encode(&mes) +} + +#[cfg(test)] +mod tests { + + use super::*; + + fn message() -> String { + String::from(r#"{"k":"0123456789abcdef"}"#) + } + + #[test] + fn test_hash() { + let result = hash(message().as_bytes()); + assert_eq!( + result, + vec![ + 0x12, 0x20, 0x5f, 0x46, 0x25, 0xd4, 0xf6, 0x1e, 0xdb, 0x52, 0x78, 0x07, 0x45, 0x5f, + 0x48, 0xf8, 0xbe, 0x27, 0x8e, 0x71, 0xe8, 0x4a, 0xa9, 0x4d, 0x23, 0x11, 0x1f, 0xfa, + 0xb3, 0xb6, 0x30, 0x93, 0xa7, 0x13, + ] + ); + } + + #[test] + fn test_double_hash_then_encode() { + let result = double_hash_encode(message().as_bytes()); + assert_eq!(result, String::from("EiAEX1W46vVid7IjJyFY5ibjmyrgepTjW0rYrw-wo4xLCw")); + } + + #[test] + fn test_hash_then_encode() { + let result = hash_encode(message().as_bytes()); + assert_eq!(result, String::from("EiBfRiXU9h7bUngHRV9I-L4njnHoSqlNIxEf-rO2MJOnEw")); + } +} diff --git a/src/did/sidetree/payload.rs b/src/did/sidetree/payload.rs index eb411ae..18b2e6a 100644 --- a/src/did/sidetree/payload.rs +++ b/src/did/sidetree/payload.rs @@ -1,16 +1,15 @@ +use core::convert::TryInto; + +use data_encoding::BASE64URL_NOPAD; +use k256::ecdsa::{signature::Signer, Signature, SigningKey}; use serde::{Deserialize, Serialize}; -use serde_json::json; use thiserror::Error; use crate::{ - common::runtime::{ - base64_url::{Base64Url, PaddingType}, - multihash::Multihash, - }, - keyring::secp256k1::KeyPairSecp256K1, + did::sidetree::multihash, keyring::jwk::Jwk, verifiable_credentials::jws::JwsEncodeError, }; -pub struct OperationPayloadBuilder {} +// TODO: Migrate Sidetree Version #[derive(Debug, Serialize, Deserialize)] pub struct ServiceEndpoint { @@ -39,20 +38,11 @@ pub struct DidPublicKey { pub r#type: String, #[serde(rename = "publicKeyJwk")] - pub public_key_jwk: KeyPairSecp256K1, + pub public_key_jwk: Jwk, } #[derive(Debug, Serialize, Deserialize)] -struct Authentication { - #[serde(rename = "type")] - r#type: String, - - #[serde(rename = "publicKey")] - public_key: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct DIDDocument { +pub struct DidDocument { // TODO: impl parser for mixed type // #[serde(rename = "@context")] // context: String, @@ -79,91 +69,69 @@ pub struct PublicKeyPayload { pub r#type: String, #[serde(rename = "jwk")] - pub jwk: KeyPairSecp256K1, + pub jwk: Jwk, #[serde(rename = "purpose")] pub purpose: Vec, } -// ACTION: add-public-keys -#[allow(dead_code)] -struct DIDAddPublicKeysPayload { - id: String, - r#type: String, - jwk: KeyPairSecp256K1, - purpose: Vec, -} - -#[allow(dead_code)] -struct DIDAddPublicKeysAction { - action: String, //'add-public-keys', - public_keys: Vec, +pub trait ToPublicKey> { + fn to_public_key( + self, + key_type: String, + key_id: String, + purpose: Vec, + ) -> Result; } -// ACTION: remove-public-keys -#[allow(dead_code)] -struct DIDRemovePublicKeysAction { - action: String, // 'remove-public-keys', - ids: Vec, -} - -// ACTION: add-services -#[allow(dead_code)] -struct DIDAddServicesPayload {} - -#[allow(dead_code)] -struct DIDAddServicesAction { - action: String, // 'add-services', - services: Vec, -} - -// ACTION: remove-services -#[allow(dead_code)] -struct DIDRemoveServicesAction { - action: String, // 'remove-services', - ids: Vec, +impl ToPublicKey for T +where + T: TryInto, +{ + fn to_public_key( + self, + key_type: String, + key_id: String, + purpose: Vec, + ) -> Result { + let jwk: Jwk = self.try_into()?; + Ok(PublicKeyPayload { id: key_id, r#type: key_type, jwk, purpose }) + } } -// ACTION: replace #[derive(Debug, Serialize, Deserialize)] -struct DIDReplacePayload { +pub struct DidPatchDocument { #[serde(rename = "public_keys")] - public_keys: Vec, + pub public_keys: Vec, #[serde(rename = "service_endpoints")] - service_endpoints: Vec, + pub service_endpoints: Vec, } #[derive(Debug, Serialize, Deserialize)] -struct DIDReplaceAction { - action: String, // 'replace', - document: DIDReplacePayload, +#[serde(tag = "action")] +pub enum DidAction { + #[serde(rename = "replace")] + Replace { document: DidPatchDocument }, + #[serde(rename = "add-public-keys")] + AddPublicKeys { + #[serde(rename = "public_keys")] + public_keys: Vec, + }, } #[derive(Serialize, Deserialize, Debug)] -struct DIDReplaceDeltaObject { - patches: Vec, +struct DidDeltaObject { + patches: Vec, update_commitment: String, } #[derive(Debug, Serialize, Deserialize)] -struct DIDReplaceSuffixObject { +struct DidSuffixObject { delta_hash: String, recovery_commitment: String, } -// ACTION: ietf-json-patch -#[allow(dead_code)] -struct DIDIetfJsonPatchAction { - action: String, /* 'replace', - * patches: Vec<> */ -} - -#[allow(dead_code)] -struct DIDResolutionRequest { - did: String, -} - #[derive(Debug, Serialize, Deserialize)] pub struct MethodMetadata { #[serde(rename = "published")] @@ -177,153 +145,181 @@ pub struct MethodMetadata { } #[derive(Debug, Serialize, Deserialize)] -pub struct DIDResolutionResponse { +pub struct DidResolutionResponse { #[serde(rename = "@context")] pub context: String, #[serde(rename = "didDocument")] - pub did_document: DIDDocument, + pub did_document: DidDocument, #[serde(rename = "methodMetadata")] pub method_metadata: MethodMetadata, } -#[derive(Clone, Serialize, Deserialize)] -pub struct CommitmentKeys { - #[serde(rename = "recovery")] - pub recovery: KeyPairSecp256K1, - - #[serde(rename = "update")] - pub update: KeyPairSecp256K1, -} - -#[derive(Clone, Serialize, Deserialize)] -pub struct DIDCreateRequest { - #[serde(rename = "publicKeys")] - pub public_keys: Vec, - - #[serde(rename = "commitmentKeys")] - pub commitment_keys: CommitmentKeys, - - #[serde(rename = "serviceEndpoints")] - pub service_endpoints: Vec, -} - #[derive(Debug, Serialize, Deserialize)] -struct DIDCreatePayload { - r#type: String, // 'create', - delta: String, - suffix_data: String, +#[serde(tag = "type")] +enum DidPayload { + #[serde(rename = "create")] + Create { delta: String, suffix_data: String }, + #[serde(rename = "update")] + Update { + delta: String, + // #[serde(rename = "revealValue")] + // reveal_value: String, + #[serde(rename = "did_suffix")] + did_suffix: String, + #[serde(rename = "signed_data")] + signed_data: String, + }, } #[derive(Debug, Serialize, Deserialize)] -pub struct DIDCreateResponse { +pub struct DidCreateResponse { #[serde(rename = "@context")] pub context: String, #[serde(rename = "didDocument")] - pub did_document: DIDDocument, + pub did_document: DidDocument, #[serde(rename = "methodMetadata")] pub method_metadata: MethodMetadata, } -#[allow(dead_code)] -struct DIDUpdateRequest { - // NOT IMPLEMENTED +#[derive(Debug, Error)] +pub enum DidCreatePayloadError { + #[error(transparent)] + SerdeJsonError(#[from] serde_json::Error), + #[error("Failed to convert to JWK: {0}")] + Jwk(#[from] crate::keyring::jwk::K256ToJwkError), +} + +#[inline] +fn canon(value: &T) -> Result, serde_json::Error> +where + T: ?Sized + Serialize, +{ + Ok(serde_jcs::to_string(value)?.into_bytes()) } -#[allow(dead_code)] -struct DIDUpdateResponse { - // NOT IMPLEMENTED +#[inline] +fn commitment_scheme(value: &Jwk) -> Result { + Ok(multihash::double_hash_encode(&canon(value)?)) } -#[allow(dead_code)] -struct DIDRecoverRequest { - // NOT IMPLEMENTED +pub fn did_create_payload( + replace_payload: DidPatchDocument, + update_key: k256::PublicKey, + recovery_key: k256::PublicKey, +) -> Result { + let update_commitment = commitment_scheme(&update_key.try_into()?)?; + let recovery_commitment = commitment_scheme(&recovery_key.try_into()?)?; + let patch = DidAction::Replace { document: replace_payload }; + let delta = DidDeltaObject { patches: vec![patch], update_commitment }; + let delta = canon(&delta)?; + let delta_hash = multihash::hash_encode(&delta); + + let suffix = DidSuffixObject { delta_hash, recovery_commitment }; + let suffix = canon(&suffix)?; + let encoded_delta = BASE64URL_NOPAD.encode(&delta); + let encoded_suffix = BASE64URL_NOPAD.encode(&suffix); + + let payload = DidPayload::Create { delta: encoded_delta, suffix_data: encoded_suffix }; + + Ok(serde_jcs::to_string(&payload)?) } -#[allow(dead_code)] -struct DIDRecoverResponse { - // NOT IMPLEMENTED +pub fn parse_did(did: &str) -> Option<(String, String)> { + let ret: Vec<&str> = did.splitn(3, ':').collect(); + if ret.len() == 3 { Some((ret[1].to_string(), ret[2].to_string())) } else { None } } -#[allow(dead_code)] -struct DIDDeactivateRequest { - // NOT IMPLEMENTED +pub fn get_did_suffix(method_specific_id: &str) -> Option { + let ret: Vec<&str> = method_specific_id.splitn(2, ':').collect(); + if ret.len() == 2 || ret.len() == 1 { Some(ret[0].to_string()) } else { None } } -#[allow(dead_code)] -struct DIDDeactivateResponse { - // NOT IMPLEMENTED +fn sign( + delta_hash: String, + old_public_key: Jwk, + old_secret_key: &k256::SecretKey, +) -> Result { + // NOTE: header + let header = serde_json::json!({ "alg": "ES256K".to_string() }); + let header = serde_jcs::to_string(&header)?; + let header = BASE64URL_NOPAD.encode(header.as_bytes()); + // NOTE: payload + let object = serde_json::json!({"delta_hash": delta_hash, "update_key": old_public_key}); + let payload = BASE64URL_NOPAD.encode(object.to_string().as_bytes()); + // NOTE: message + let message = [header.clone(), payload.clone()].join("."); + let message: &[u8] = message.as_bytes(); + + // NOTE: signature + let signing_key: SigningKey = old_secret_key.into(); + let signature: Signature = signing_key.try_sign(message)?; + let signature = BASE64URL_NOPAD.encode(&signature.to_vec()); + + Ok([header, payload, signature].join(".")) } #[derive(Debug, Error)] -pub enum OperationPayloadBuilderError { +pub enum DidUpdatePayloadError { #[error(transparent)] - MultihashError(#[from] crate::common::runtime::multihash::MultihashError), + SerdeJsonError(#[from] serde_json::Error), + #[error("Failed to convert to JWK: {0}")] + Jwk(#[from] crate::keyring::jwk::K256ToJwkError), + #[error("Failed to parse did")] + DidParse, + #[error("Failed to sign: {0}")] + Sign(#[from] JwsEncodeError), } -impl OperationPayloadBuilder { - pub fn did_create_payload( - params: &DIDCreateRequest, - ) -> Result { - let update = json!(¶ms.commitment_keys.update); - let update_commitment = - Multihash::canonicalize_then_double_hash_then_encode(update.to_string().as_bytes())?; - - let recovery = json!(¶ms.commitment_keys.recovery); - let recovery_commitment = - Multihash::canonicalize_then_double_hash_then_encode(recovery.to_string().as_bytes())?; - - let document: DIDReplacePayload = DIDReplacePayload { - public_keys: params.public_keys.clone(), - service_endpoints: params.service_endpoints.clone(), - }; - let patch: DIDReplaceAction = DIDReplaceAction { action: "replace".to_string(), document }; - - let delta = - json!(DIDReplaceDeltaObject { patches: vec![patch], update_commitment }).to_string(); - - let delta_bytes = delta.as_bytes(); - let delta_hash = Base64Url::encode(&Multihash::hash(delta_bytes), &PaddingType::NoPadding); - - let suffix = json!(DIDReplaceSuffixObject { delta_hash, recovery_commitment }).to_string(); - - let suffix_bytes = suffix.as_bytes(); - - let encoded_delta = Base64Url::encode(delta_bytes, &PaddingType::NoPadding); - let encoded_suffix = Base64Url::encode(suffix_bytes, &PaddingType::NoPadding); - - let payload: DIDCreatePayload = DIDCreatePayload { - r#type: "create".to_string(), - delta: encoded_delta, - suffix_data: encoded_suffix, - }; - - Ok(json!(payload).to_string()) - } +// TODO: Not yet tested because sidetree is broken. +pub fn did_update_payload( + update_payload: Vec, + my_did: &str, + old_update: k256::PublicKey, + old_update_secret: &k256::SecretKey, + new_update: k256::PublicKey, +) -> Result { + let old_update: Jwk = old_update.try_into()?; + let new_update = commitment_scheme(&new_update.try_into()?)?; + let delta = DidDeltaObject { patches: update_payload, update_commitment: new_update }; + let delta = canon(&delta)?; + let delta_hash = multihash::hash_encode(&delta); + let encoded_delta = BASE64URL_NOPAD.encode(&delta); + let (_, suff) = parse_did(my_did).ok_or(DidUpdatePayloadError::DidParse)?; + let suff = get_did_suffix(&suff).ok_or(DidUpdatePayloadError::DidParse)?; + + let payload = DidPayload::Update { + delta: encoded_delta, + did_suffix: suff, + // reveal_value: multihash::hash_encode(&canon(&old_update)?), + signed_data: sign(delta_hash, old_update, old_update_secret)?, + }; + Ok(serde_jcs::to_string(&payload)?) } #[cfg(test)] pub mod tests { + use rand_core::OsRng; + use super::*; - use crate::{keyring, keyring::extension::trng::OSRandomNumberGenerator}; + use crate::{keyring, keyring::keypair::KeyPair}; #[test] pub fn test_did_create_payload() { - let trng: OSRandomNumberGenerator = OSRandomNumberGenerator::default(); - let keyring = keyring::keypair::KeyPairing::create_keyring(&trng).unwrap(); - - let public = keyring.sign.to_public_key("key_id", &[""]).unwrap(); - let update = keyring.recovery.to_jwk(false).unwrap(); - let recovery = keyring.update.to_jwk(false).unwrap(); - - let _result = OperationPayloadBuilder::did_create_payload(&DIDCreateRequest { - public_keys: vec![public], - commitment_keys: CommitmentKeys { recovery, update }, - service_endpoints: vec![], - }) - .unwrap(); + let keyring = keyring::keypair::KeyPairing::create_keyring(OsRng); + let public = keyring + .sign + .get_public_key() + .to_public_key("".to_string(), "key_id".to_string(), vec!["".to_string()]) + .unwrap(); + let update = keyring.recovery.get_public_key(); + let recovery = keyring.update.get_public_key(); + + let document = DidPatchDocument { public_keys: vec![public], service_endpoints: vec![] }; + + let _result = did_create_payload(document, update, recovery).unwrap(); } } diff --git a/src/didcomm/encrypted.rs b/src/didcomm/encrypted.rs index 5089df7..a904963 100644 --- a/src/didcomm/encrypted.rs +++ b/src/didcomm/encrypted.rs @@ -1,234 +1,284 @@ -use anyhow::Context; -use arrayref::array_ref; -use chrono::{DateTime, Utc}; use cuid; -use didcomm_rs::{ - crypto::{CryptoAlgorithm, SignatureAlgorithm}, - AttachmentBuilder, AttachmentDataBuilder, Message, -}; +pub use didcomm_rs; +use didcomm_rs::{crypto::CryptoAlgorithm, AttachmentBuilder, AttachmentDataBuilder, Message}; +pub use serde_json; use serde_json::Value; use thiserror::Error; -use x25519_dalek::{PublicKey, StaticSecret}; use crate::{ - common::runtime, - did::did_repository::{CreateIdentifierError, DidRepository, FindIdentifierError}, - didcomm::types::DIDCommMessage, - keyring::{self, keypair::KeyPairing}, + did::{ + did_repository::{get_encrypt_key, get_sign_key, DidRepository, GetPublicKeyError}, + sidetree::payload::DidDocument, + }, + didcomm::types::{DidCommMessage, FindSenderError}, + keyring::keypair::{KeyPair, KeyPairing}, verifiable_credentials::{ - did_vc::{DIDVCService, DIDVCServiceGenerateError}, + credential_signer::{CredentialSigner, CredentialSignerVerifyError}, + did_vc::DidVcService, types::{VerifiableCredentials, VerifiedContainer}, }, }; -pub struct DIDCommEncryptedService { - vc_service: DIDVCService, - attachment_link: String, +#[trait_variant::make(Send)] +pub trait DidCommEncryptedService: Sync { + type GenerateError: std::error::Error; + type VerifyError: std::error::Error; + async fn generate( + &self, + model: VerifiableCredentials, + from_keyring: &KeyPairing, + to_did: &str, + metadata: Option<&Value>, + ) -> Result; + async fn verify( + &self, + my_keyring: &KeyPairing, + message: &DidCommMessage, + ) -> Result; } -impl Clone for DIDCommEncryptedService -where - R: DidRepository + Clone, - DIDVCService: Clone, -{ - fn clone(&self) -> Self { - Self { vc_service: self.vc_service.clone(), attachment_link: self.attachment_link.clone() } +fn didcomm_generate( + body: &VerifiableCredentials, + from_keyring: &KeyPairing, + to_doc: &DidDocument, + metadata: Option<&Value>, + attachment_link: Option<&str>, +) -> Result< + DidCommMessage, + DidCommEncryptedServiceGenerateError, +> { + let to_did = &to_doc.id; + let from_did = &body.issuer.id; + let body = serde_json::to_string(body)?; + + let mut message = Message::new().from(from_did).to(&[to_did]).body(&body)?; + + if let Some(value) = metadata { + let id = cuid::cuid2(); + + // let media_type = "application/json"; + let data = AttachmentDataBuilder::new().with_json(&value.to_string()); + + let data = if let Some(attachment_link) = attachment_link { + data.with_link(attachment_link) + } else { + data + }; + + message.append_attachment( + AttachmentBuilder::new(true).with_id(&id).with_format("metadata").with_data(data), + ) } + + let public_key = get_encrypt_key(to_doc)?.as_bytes().to_vec(); + let public_key = Some(public_key); + + let seal_message = message + .as_jwe(&CryptoAlgorithm::XC20P, public_key.clone()) + .seal(from_keyring.encrypt.get_secret_key().as_bytes(), Some(vec![public_key]))?; + + Ok(serde_json::from_str::(&seal_message)?) +} + +async fn generate( + did_repository: &R, + vc_service: &V, + model: VerifiableCredentials, + from_keyring: &KeyPairing, + to_did: &str, + metadata: Option<&Value>, + attachment_link: Option<&str>, +) -> Result< + DidCommMessage, + DidCommEncryptedServiceGenerateError, +> { + let body = vc_service + .generate(model, from_keyring) + .map_err(DidCommEncryptedServiceGenerateError::VcService)?; + let to_doc = did_repository + .find_identifier(to_did) + .await + .map_err(DidCommEncryptedServiceGenerateError::SidetreeFindRequestFailed)? + .ok_or(DidCommEncryptedServiceGenerateError::DidDocNotFound(to_did.to_string()))? + .did_document; + + didcomm_generate::(&body, from_keyring, &to_doc, metadata, attachment_link) +} + +fn didcomm_verify( + from_doc: &DidDocument, + my_keyring: &KeyPairing, + message: &DidCommMessage, +) -> Result> { + let public_key = get_encrypt_key(from_doc)?.as_bytes().to_vec(); + let public_key = Some(public_key); + + let message = Message::receive( + &serde_json::to_string(&message)?, + Some(my_keyring.encrypt.get_secret_key().as_bytes().as_ref()), + public_key, + None, + )?; + + let metadata = message.attachment_iter().find(|item| match &item.format { + Some(value) => value == "metadata", + None => false, + }); + + let body = message + .get_body() + .map_err(|e| DidCommEncryptedServiceVerifyError::MetadataBodyNotFound(Some(e)))?; + let body = serde_json::from_str::(&body)?; + + match metadata { + Some(metadata) => { + let metadata = metadata + .data + .json + .as_ref() + .ok_or(DidCommEncryptedServiceVerifyError::MetadataBodyNotFound(None))?; + let metadata = serde_json::from_str::(metadata)?; + Ok(VerifiedContainer { message: body, metadata: Some(metadata) }) + } + None => Ok(VerifiedContainer { message: body, metadata: None }), + } +} + +async fn verify( + did_repository: &R, + my_keyring: &KeyPairing, + message: &DidCommMessage, +) -> Result> { + let other_did = message.find_sender()?; + let other_doc = did_repository + .find_identifier(&other_did) + .await + .map_err(DidCommEncryptedServiceVerifyError::SidetreeFindRequestFailed)? + .ok_or(DidCommEncryptedServiceVerifyError::DidDocNotFound(other_did))? + .did_document; + let mut container = didcomm_verify::(&other_doc, my_keyring, message)?; + // For performance, call low level api + let public_key = get_sign_key(&other_doc)?; + let body = CredentialSigner::verify(container.message, &public_key)?; + container.message = body; + Ok(container) } #[derive(Debug, Error)] -pub enum DIDCommEncryptedServiceGenerateError { - #[error("Secp256k1 error")] - KeyringSecp256k1Error(#[from] keyring::secp256k1::Secp256k1Error), - #[error("Secp256k1 error")] - RuntimeSecp256k1Error(#[from] runtime::secp256k1::Secp256k1Error), - #[error("did not found : {0}")] - DIDNotFound(String), +pub enum DidCommEncryptedServiceGenerateError +where + FindIdentifierError: std::error::Error, + CredentialSignerSignError: std::error::Error, +{ + #[error("failed to get did document: {0}")] + DidDocNotFound(String), #[error("did public key not found. did: {0}")] - DidPublicKeyNotFound(String), - #[error("something went wrong with vc service")] - VCServiceError(#[from] DIDVCServiceGenerateError), - #[error("failed to find identifier")] - SidetreeFindRequestFailed(#[from] FindIdentifierError), - #[error("failed to create identifier")] - SidetreeCreateRequestFailed(#[from] CreateIdentifierError), - #[error("failed to encrypt message")] - EncryptFailed(anyhow::Error), - #[error(transparent)] - Other(#[from] anyhow::Error), + DidPublicKeyNotFound(#[from] GetPublicKeyError), + #[error("something went wrong with vc service: {0}")] + VcService(CredentialSignerSignError), + #[error("failed to create identifier: {0}")] + SidetreeFindRequestFailed(FindIdentifierError), + #[error("failed to encrypt message with error: {0}")] + EncryptFailed(#[from] didcomm_rs::Error), + #[error("failed serialize/deserialize: {0}")] + Json(#[from] serde_json::Error), } #[derive(Debug, Error)] -pub enum DIDCommEncryptedServiceVerifyError { - #[error("Secp256k1 error")] - KeyringSecp256k1Error(#[from] keyring::secp256k1::Secp256k1Error), - #[error("Secp256k1 error")] - RuntimeSecp256k1Error(#[from] runtime::secp256k1::Secp256k1Error), - #[error("did not found : {0}")] - DIDNotFound(String), - #[error("failed to find identifier")] - SidetreeFindRequestFailed(#[from] FindIdentifierError), - #[error("did public key not found : did = {0}")] - DidPublicKeyNotFound(String), - #[error(transparent)] - Other(#[from] anyhow::Error), +pub enum DidCommEncryptedServiceVerifyError { + #[error("failed to get did document: {0}")] + DidDocNotFound(String), + #[error("something went wrong with vc service: {0}")] + VcService(#[from] CredentialSignerVerifyError), + #[error("failed to find identifier: {0}")] + SidetreeFindRequestFailed(FindIdentifierError), + #[error("did public key not found. did: {0}")] + DidPublicKeyNotFound(#[from] GetPublicKeyError), + #[error("failed to decrypt message: {0:?}")] + DecryptFailed(#[from] didcomm_rs::Error), + #[error("failed to get body: {0:?}")] + MetadataBodyNotFound(Option), + #[error("failed serialize/deserialize: {0}")] + Json(#[from] serde_json::Error), + #[error("failed to find sender did: {0}")] + FindSender(#[from] FindSenderError), } -impl DIDCommEncryptedService { - pub fn new(did_repository: R, attachment_link: Option) -> DIDCommEncryptedService { - fn default_attachment_link() -> String { - std::env::var("NODEX_DID_ATTACHMENT_LINK") - .unwrap_or("https://did.getnodex.io".to_string()) - } - - DIDCommEncryptedService { - vc_service: DIDVCService::new(did_repository), - attachment_link: attachment_link.unwrap_or(default_attachment_link()), - } - } - - pub async fn generate( +impl DidCommEncryptedService for R +where + R: DidRepository + DidVcService, +{ + type GenerateError = + DidCommEncryptedServiceGenerateError; + type VerifyError = DidCommEncryptedServiceVerifyError; + async fn generate( &self, - from_did: &str, - to_did: &str, + model: VerifiableCredentials, from_keyring: &KeyPairing, - message: &Value, + to_did: &str, metadata: Option<&Value>, - issuance_date: DateTime, - ) -> Result { - // NOTE: recipient to - let did_document = - self.vc_service.did_repository.find_identifier(to_did).await?.ok_or_else(|| { - DIDCommEncryptedServiceGenerateError::DIDNotFound(to_did.to_string()) - })?; - - let public_keys = did_document.did_document.public_key.ok_or_else(|| { - DIDCommEncryptedServiceGenerateError::DidPublicKeyNotFound(to_did.to_string()) - })?; - - // FIXME: workaround - if public_keys.len() != 1 { - return Err(anyhow::anyhow!("public_keys length must be 1").into()); - } - - let public_key = public_keys[0].clone(); - - let other_key = keyring::secp256k1::Secp256k1::from_jwk(&public_key.public_key_jwk)?; - - // NOTE: ecdh - let shared_key = runtime::secp256k1::Secp256k1::ecdh( - &from_keyring.sign.get_secret_key(), - &other_key.get_public_key(), - )?; - - let sk = StaticSecret::from(array_ref!(shared_key, 0, 32).to_owned()); - let pk = PublicKey::from(&sk); - - // NOTE: message - let body = self.vc_service.generate(from_did, from_keyring, message, issuance_date)?; - let body = serde_json::to_string(&body).context("failed to serialize")?; - - let mut message = - Message::new().from(from_did).to(&[to_did]).body(&body).map_err(|e| { - anyhow::anyhow!("Failed to initialize message with error = {:?}", e) - })?; - - // NOTE: Has attachment - if let Some(value) = metadata { - let id = cuid::cuid2(); - - // let media_type = "application/json"; - let data = AttachmentDataBuilder::new() - .with_link(&self.attachment_link) - .with_json(&value.to_string()); - - message.append_attachment( - AttachmentBuilder::new(true).with_id(&id).with_format("metadata").with_data(data), - ) - } - - let seal_signed_message = message - .as_jwe(&CryptoAlgorithm::XC20P, Some(pk.as_bytes().to_vec())) - .seal_signed( - sk.to_bytes().as_ref(), - Some(vec![Some(pk.as_bytes().to_vec())]), - SignatureAlgorithm::Es256k, - &from_keyring.sign.get_secret_key(), - ) - .map_err(|e| { - DIDCommEncryptedServiceGenerateError::EncryptFailed(anyhow::Error::msg( - e.to_string(), - )) - })?; - - Ok(serde_json::from_str::(&seal_signed_message) - .context("failed to convert to json")?) + ) -> Result { + generate::(self, self, model, from_keyring, to_did, metadata, None).await } - pub async fn verify( + async fn verify( &self, my_keyring: &KeyPairing, - message: &DIDCommMessage, - ) -> Result { - let other_did = message.find_sender()?; - - let did_document = self - .vc_service - .did_repository - .find_identifier(&other_did) - .await? - .ok_or(DIDCommEncryptedServiceVerifyError::DIDNotFound(other_did.to_string()))?; - - let public_keys = did_document.did_document.public_key.ok_or( - DIDCommEncryptedServiceVerifyError::DidPublicKeyNotFound(other_did.to_string()), - )?; - - // FIXME: workaround - if public_keys.len() != 1 { - return Err(anyhow::anyhow!("public_keys length must be 1").into()); - } - - let public_key = public_keys[0].clone(); - - let other_key = keyring::secp256k1::Secp256k1::from_jwk(&public_key.public_key_jwk)?; + message: &DidCommMessage, + ) -> Result { + verify(self, my_keyring, message).await + } +} - // NOTE: ecdh - let shared_key = runtime::secp256k1::Secp256k1::ecdh( - &my_keyring.sign.get_secret_key(), - &other_key.get_public_key(), - )?; +pub struct DidCommServiceWithAttachment +where + R: DidRepository + DidVcService, +{ + vc_service: R, + attachment_link: String, +} - let sk = StaticSecret::from(array_ref!(shared_key, 0, 32).to_owned()); - let pk = PublicKey::from(&sk); +impl DidCommServiceWithAttachment +where + R: DidRepository + DidVcService, +{ + pub fn new(did_repository: R, attachment_link: String) -> Self { + Self { vc_service: did_repository, attachment_link } + } +} - let message = Message::receive( - &serde_json::to_string(&message).context("failed to serialize didcomm message")?, - Some(sk.to_bytes().as_ref()), - Some(pk.as_bytes().to_vec()), - Some(&other_key.get_public_key()), +impl DidCommEncryptedService for DidCommServiceWithAttachment +where + R: DidRepository + DidVcService, +{ + type GenerateError = + DidCommEncryptedServiceGenerateError; + type VerifyError = DidCommEncryptedServiceVerifyError; + async fn generate( + &self, + model: VerifiableCredentials, + from_keyring: &KeyPairing, + to_did: &str, + metadata: Option<&Value>, + ) -> Result { + generate::( + &self.vc_service, + &self.vc_service, + model, + from_keyring, + to_did, + metadata, + Some(&self.attachment_link), ) - .map_err(|e| anyhow::anyhow!("failed to decrypt message : {:?}", e))?; - - let metadata = message.attachment_iter().find(|item| match item.format.clone() { - Some(value) => value == "metadata", - None => false, - }); - - let body = - message.get_body().map_err(|e| anyhow::anyhow!("failed to get body : {:?}", e))?; - let body = - serde_json::from_str::(&body).context("failed to parse body")?; - - match metadata { - Some(metadata) => { - let metadata = - metadata.data.json.as_ref().ok_or(anyhow::anyhow!("metadata not found"))?; - let metadata = serde_json::from_str::(metadata) - .context("failed to parse metadata to json")?; - Ok(VerifiedContainer { message: body, metadata: Some(metadata) }) - } - None => Ok(VerifiedContainer { message: body, metadata: None }), - } + .await + } + + async fn verify( + &self, + my_keyring: &KeyPairing, + message: &DidCommMessage, + ) -> Result { + verify(&self.vc_service, my_keyring, message).await } } @@ -236,13 +286,21 @@ impl DIDCommEncryptedService { mod tests { use std::{collections::BTreeMap, iter::FromIterator as _}; - use serde_json::json; + use chrono::{DateTime, Utc}; + use rand_core::OsRng; + use serde_json::{json, Value}; - use super::*; + // use super::*; + use super::DidCommEncryptedService; use crate::{ - did::did_repository::mocks::MockDidRepository, - didcomm::test_utils::create_random_did, - keyring::{extension::trng::OSRandomNumberGenerator, keypair::KeyPairing}, + did::did_repository::{mocks::MockDidRepository, GetPublicKeyError}, + didcomm::{ + encrypted::{DidCommEncryptedServiceGenerateError, DidCommEncryptedServiceVerifyError}, + test_utils::create_random_did, + types::DidCommMessage, + }, + keyring::keypair::KeyPairing, + verifiable_credentials::types::VerifiableCredentials, }; #[actix_rt::test] @@ -250,26 +308,21 @@ mod tests { let from_did = create_random_did(); let to_did = create_random_did(); - let trng = OSRandomNumberGenerator::default(); - let from_keyring = KeyPairing::create_keyring(&trng).unwrap(); - let to_keyring = KeyPairing::create_keyring(&trng).unwrap(); + let to_keyring = KeyPairing::create_keyring(OsRng); + let from_keyring = KeyPairing::create_keyring(OsRng); let repo = MockDidRepository::from_single(BTreeMap::from_iter([ (from_did.clone(), from_keyring.clone()), (to_did.clone(), to_keyring.clone()), ])); - let service = DIDCommEncryptedService::new(repo, None); - let message = json!({"test": "0123456789abcdef"}); let issuance_date = Utc::now(); - let res = service - .generate(&from_did, &to_did, &from_keyring, &message, None, issuance_date) - .await - .unwrap(); + let model = VerifiableCredentials::new(from_did.clone(), message.clone(), issuance_date); + let res = repo.generate(model, &from_keyring, &to_did, None).await.unwrap(); - let verified = service.verify(&to_keyring, &res).await.unwrap(); + let verified = repo.verify(&to_keyring, &res).await.unwrap(); let verified = verified.message; assert_eq!(verified.issuer.id, from_did); @@ -277,7 +330,6 @@ mod tests { } mod generate_failed { - use self::keyring::secp256k1::Secp256k1; use super::*; use crate::did::did_repository::mocks::NoPublicKeyDidRepository; @@ -286,25 +338,20 @@ mod tests { let from_did = create_random_did(); let to_did = create_random_did(); - let trng = OSRandomNumberGenerator::default(); - let from_keyring = KeyPairing::create_keyring(&trng).unwrap(); + let from_keyring = KeyPairing::create_keyring(OsRng); let repo = MockDidRepository::from_single(BTreeMap::from_iter([( from_did.clone(), from_keyring.clone(), )])); - let service = DIDCommEncryptedService::new(repo, None); - let message = json!({"test": "0123456789abcdef"}); let issuance_date = Utc::now(); - let res = service - .generate(&from_did, &to_did, &from_keyring, &message, None, issuance_date) - .await - .unwrap_err(); + let model = VerifiableCredentials::new(from_did, message, issuance_date); + let res = repo.generate(model, &from_keyring, &to_did, None).await.unwrap_err(); - if let DIDCommEncryptedServiceGenerateError::DIDNotFound(did) = res { + if let DidCommEncryptedServiceGenerateError::DidDocNotFound(did) = res { assert_eq!(did, to_did); } else { panic!("unexpected result: {:?}", res); @@ -316,62 +363,25 @@ mod tests { let from_did = create_random_did(); let to_did = create_random_did(); - let trng = OSRandomNumberGenerator::default(); - let from_keyring = KeyPairing::create_keyring(&trng).unwrap(); + let from_keyring = KeyPairing::create_keyring(OsRng); let repo = NoPublicKeyDidRepository; - let service = DIDCommEncryptedService::new(repo, None); - let message = json!({"test": "0123456789abcdef"}); let issuance_date = Utc::now(); - let res = service - .generate(&from_did, &to_did, &from_keyring, &message, None, issuance_date) - .await - .unwrap_err(); + let model = VerifiableCredentials::new(from_did, message, issuance_date); + let res = repo.generate(model, &from_keyring, &to_did, None).await.unwrap_err(); - if let DIDCommEncryptedServiceGenerateError::DidPublicKeyNotFound(did) = res { + if let DidCommEncryptedServiceGenerateError::DidPublicKeyNotFound( + GetPublicKeyError::PublicKeyNotFound(did), + ) = res + { assert_eq!(did, to_did); } else { panic!("unexpected result: {:?}", res); } } - - #[actix_rt::test] - async fn test_runtime_secp256k1_error() { - let from_did = create_random_did(); - let to_did = create_random_did(); - - let trng = OSRandomNumberGenerator::default(); - let to_keyring = KeyPairing::create_keyring(&trng).unwrap(); - let mut from_keyring = KeyPairing::create_keyring(&trng).unwrap(); - from_keyring.sign = Secp256k1::new( - from_keyring.sign.get_public_key(), - vec![0; from_keyring.sign.get_secret_key().len()], - ) - .unwrap(); - - let repo = MockDidRepository::from_single(BTreeMap::from_iter([ - (from_did.clone(), from_keyring.clone()), - (to_did.clone(), to_keyring.clone()), - ])); - - let service = DIDCommEncryptedService::new(repo, None); - - let message = json!({"test": "0123456789abcdef"}); - let issuance_date = Utc::now(); - - let res = service - .generate(&from_did, &to_did, &from_keyring, &message, None, issuance_date) - .await - .unwrap_err(); - - if let DIDCommEncryptedServiceGenerateError::RuntimeSecp256k1Error(_) = res { - } else { - panic!("unexpected result: {:?}", res); - } - } } mod verify_failed { @@ -386,18 +396,16 @@ mod tests { message: &Value, metadata: Option<&Value>, issuance_date: DateTime, - ) -> DIDCommMessage { + ) -> DidCommMessage { let repo = MockDidRepository::from_single(BTreeMap::from_iter([( to_did.to_string(), to_keyring.clone(), )])); - let service = DIDCommEncryptedService::new(repo, None); + let model = + VerifiableCredentials::new(from_did.to_string(), message.clone(), issuance_date); - service - .generate(from_did, to_did, from_keyring, message, metadata, issuance_date) - .await - .unwrap() + repo.generate(model, from_keyring, to_did, metadata).await.unwrap() } #[actix_rt::test] @@ -405,9 +413,8 @@ mod tests { let from_did = create_random_did(); let to_did = create_random_did(); - let trng = OSRandomNumberGenerator::default(); - let from_keyring = KeyPairing::create_keyring(&trng).unwrap(); - let to_keyring = KeyPairing::create_keyring(&trng).unwrap(); + let to_keyring = KeyPairing::create_keyring(OsRng); + let from_keyring = KeyPairing::create_keyring(OsRng); let message = json!({"test": "0123456789abcdef"}); let issuance_date = Utc::now(); @@ -427,13 +434,48 @@ mod tests { to_did.clone(), to_keyring.clone(), )])); + let res = repo.verify(&from_keyring, &res).await.unwrap_err(); + + if let DidCommEncryptedServiceVerifyError::DidDocNotFound(did) = res { + assert_eq!(did, from_did); + } else { + panic!("unexpected result: {:?}", res); + } + } + + #[actix_rt::test] + async fn test_cannot_steal_message() { + let from_did = create_random_did(); + let to_did = create_random_did(); + let other_did = create_random_did(); - let service = DIDCommEncryptedService::new(repo, None); + let to_keyring = KeyPairing::create_keyring(OsRng); + let from_keyring = KeyPairing::create_keyring(OsRng); + let other_keyring = KeyPairing::create_keyring(OsRng); - let res = service.verify(&from_keyring, &res).await.unwrap_err(); + let message = json!({"test": "0123456789abcdef"}); + let issuance_date = Utc::now(); - if let DIDCommEncryptedServiceVerifyError::DIDNotFound(did) = res { - assert_eq!(did, from_did); + let res = create_didcomm( + &from_did, + &to_did, + &from_keyring, + &to_keyring, + &message, + None, + issuance_date, + ) + .await; + + let repo = MockDidRepository::from_single(BTreeMap::from_iter([ + (from_did.clone(), from_keyring.clone()), + (to_did.clone(), to_keyring.clone()), + (other_did.clone(), other_keyring.clone()), + ])); + + let res = repo.verify(&other_keyring, &res).await.unwrap_err(); + + if let DidCommEncryptedServiceVerifyError::DecryptFailed(_) = res { } else { panic!("unexpected result: {:?}", res); } @@ -444,9 +486,8 @@ mod tests { let from_did = create_random_did(); let to_did = create_random_did(); - let trng = OSRandomNumberGenerator::default(); - let from_keyring = KeyPairing::create_keyring(&trng).unwrap(); - let to_keyring = KeyPairing::create_keyring(&trng).unwrap(); + let to_keyring = KeyPairing::create_keyring(OsRng); + let from_keyring = KeyPairing::create_keyring(OsRng); let message = json!({"test": "0123456789abcdef"}); let issuance_date = Utc::now(); @@ -464,11 +505,12 @@ mod tests { let repo = NoPublicKeyDidRepository; - let service = DIDCommEncryptedService::new(repo, None); - - let res = service.verify(&from_keyring, &res).await.unwrap_err(); + let res = repo.verify(&from_keyring, &res).await.unwrap_err(); - if let DIDCommEncryptedServiceVerifyError::DidPublicKeyNotFound(did) = res { + if let DidCommEncryptedServiceVerifyError::DidPublicKeyNotFound( + GetPublicKeyError::PublicKeyNotFound(did), + ) = res + { assert_eq!(did, from_did); } else { panic!("unexpected result: {:?}", res); diff --git a/src/didcomm/types.rs b/src/didcomm/types.rs index dc6223b..73c02ac 100644 --- a/src/didcomm/types.rs +++ b/src/didcomm/types.rs @@ -1,10 +1,9 @@ -use anyhow::Context as _; +use data_encoding::BASE64URL_NOPAD; use serde::{Deserialize, Serialize}; - -use crate::common::runtime::base64_url::{self, PaddingType}; +use thiserror::Error; #[derive(Deserialize, Serialize, Clone, Debug)] -pub struct DIDCommMessage { +pub struct DidCommMessage { pub ciphertext: String, pub iv: String, pub protected: String, @@ -35,26 +34,35 @@ pub struct Epk { pub x: String, } -impl DIDCommMessage { - pub fn find_receivers(&self) -> anyhow::Result> { - let to_dids = self.recipients.iter().map(|v| v.header.kid.clone()).collect(); +#[derive(Debug, Error)] +pub enum FindSenderError { + #[error("failed serialize/deserialize: {0}")] + Json(#[from] serde_json::Error), + #[error("failed to base64 decode protected: {0}")] + FromUtf8(#[from] std::string::FromUtf8Error), + #[error("failed to base64 decode protected: {0}")] + Decode(#[from] data_encoding::DecodeError), + #[error("skid error")] + Skid, +} - Ok(to_dids) +impl DidCommMessage { + pub fn find_receivers(&self) -> Vec { + self.recipients.iter().map(|v| v.header.kid.clone()).collect() } - pub fn find_sender(&self) -> anyhow::Result { + pub fn find_sender(&self) -> Result { let protected = &self.protected; - let decoded = base64_url::Base64Url::decode_as_string(protected, &PaddingType::NoPadding) - .context("failed to base64 decode protected")?; - let decoded = serde_json::from_str::(&decoded) - .context("failed to decode to json")?; + let decoded = BASE64URL_NOPAD.decode(protected.as_bytes())?; + let decoded = String::from_utf8(decoded)?; + let decoded = serde_json::from_str::(&decoded)?; let from_did = decoded .get("skid") - .context("skid not found")? + .ok_or(FindSenderError::Skid)? .as_str() - .context("failed to serialize skid")? + .ok_or(FindSenderError::Skid)? .to_string(); Ok(from_did) @@ -72,7 +80,7 @@ mod tests { #[test] fn extract_from_did() { - let message: DIDCommMessage = serde_json::from_str(MESSAGE).unwrap(); + let message: DidCommMessage = serde_json::from_str(MESSAGE).unwrap(); let result = message.find_sender().unwrap(); assert_eq!(&result, FROM_DID); } @@ -80,15 +88,15 @@ mod tests { #[test] fn extract_from_did_when_invalid_base64() { let message = include_str!("../../test_resources/invalid_didcomm_message.json"); - let message: DIDCommMessage = serde_json::from_str(message).unwrap(); + let message: DidCommMessage = serde_json::from_str(message).unwrap(); let result = message.find_sender(); assert!(result.is_err()); } #[test] fn extract_to_did() { - let message: DIDCommMessage = serde_json::from_str(MESSAGE).unwrap(); - let result = message.find_receivers().unwrap(); + let message: DidCommMessage = serde_json::from_str(MESSAGE).unwrap(); + let result = message.find_receivers(); let expected_did = vec![TO_DID.to_string()]; assert_eq!(result, expected_did); } diff --git a/src/keyring/extension/trng.rs b/src/keyring/extension/trng.rs index adb7669..a8c8812 100644 --- a/src/keyring/extension/trng.rs +++ b/src/keyring/extension/trng.rs @@ -1,63 +1,2 @@ -use std::{ffi::CStr, num::NonZeroU32}; - -use thiserror::Error; - -use super::Extension; -use crate::common::runtime::random::{Random, RandomError}; - -#[derive(Error, Debug)] -pub enum TrngError { - #[error("Buffer length over")] - BufferLengthOver, - #[error("Library loading error")] - LibraryLoadingError(#[from] libloading::Error), - #[error("External function failed")] - ExternalFunctionFailed(NonZeroU32), - #[error("Random generation failed")] - RandomGenerationFailed(#[from] RandomError), -} - -pub trait Trng { - const MAX_BUFFER_LENGTH: usize = 1024; - fn generate(&self, size: &usize) -> Result, TrngError>; -} - -#[derive(Default)] -pub struct OSRandomNumberGenerator {} - -impl Trng for OSRandomNumberGenerator { - fn generate(&self, size: &usize) -> Result, TrngError> { - Random::bytes(size).map_err(TrngError::RandomGenerationFailed) - } -} - -pub struct ExternalTrng { - extension: Extension, -} - -impl Trng for ExternalTrng { - fn generate(&self, size: &usize) -> Result, TrngError> { - if Self::MAX_BUFFER_LENGTH < *size { - return Err(TrngError::BufferLengthOver); - } - - unsafe { - let buffer = [0u8; Self::MAX_BUFFER_LENGTH + 1]; - let buffer_ptr: *const i8 = buffer.as_ptr().cast(); - - let lib = libloading::Library::new(&self.extension.filename)?; - - let func: libloading::Symbol< - unsafe extern "C" fn(buf: *const i8, bufsize: usize, size: usize) -> u32, - > = lib.get(self.extension.symbol.as_bytes())?; - - let result = func(buffer_ptr, buffer.len(), *size); - - if let Some(exit_status) = NonZeroU32::new(result) { - return Err(TrngError::ExternalFunctionFailed(exit_status)); - } - - Ok(CStr::from_ptr(buffer_ptr as *const core::ffi::c_char).to_bytes().to_vec()) - } - } -} +// TODO: Impl TRNG based CryptoRng trait +// https://rust-random.github.io/rand/src/rand/rngs/std.rs.html#94 diff --git a/src/keyring/jwk.rs b/src/keyring/jwk.rs new file mode 100644 index 0000000..14b1da5 --- /dev/null +++ b/src/keyring/jwk.rs @@ -0,0 +1,139 @@ +use std::convert::{From, Into, TryFrom, TryInto}; + +pub use data_encoding; +use data_encoding::{DecodeError, DecodePartial, BASE64URL_NOPAD}; +use k256::elliptic_curve::sec1::ToEncodedPoint; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Jwk { + #[serde(rename = "kty")] + kty: String, + + #[serde(rename = "crv")] + crv: String, + + #[serde(rename = "x")] + x: String, + + #[serde(rename = "y", skip_serializing_if = "Option::is_none")] + y: Option, +} + +#[derive(Error, Debug)] +pub enum JwkToK256Error { + #[error("missing y")] + MissingY, + #[error("decode error")] + Decode(DecodePartial), + #[error("different crv")] + DifferentCrv, + #[error("crypt error: {0}")] + Crypt(#[from] k256::elliptic_curve::Error), +} + +#[derive(Error, Debug)] +pub enum JwkToX25519Error { + #[error("decode error: {0:?}")] + Decode(Option), + #[error("different crv")] + DifferentCrv, +} + +#[derive(Error, Debug)] +pub enum K256ToJwkError { + #[error("points are invalid")] + PointsInvalid, +} + +fn decode_base64url( + s: &str, +) -> Result, JwkToK256Error> { + let mut result = k256::elliptic_curve::FieldBytes::::default(); + BASE64URL_NOPAD.decode_mut(s.as_bytes(), &mut result).map_err(JwkToK256Error::Decode)?; + Ok(result) +} + +impl TryFrom for k256::PublicKey { + type Error = JwkToK256Error; + fn try_from(value: Jwk) -> Result { + if value.crv != "secp256k1" { + return Err(JwkToK256Error::DifferentCrv); + } + if let Some(y) = value.y { + let x = decode_base64url(&value.x)?; + let y = decode_base64url(&y)?; + let pk = k256::EncodedPoint::from_affine_coordinates(&x, &y, false); + let pk = k256::PublicKey::from_sec1_bytes(pk.as_bytes())?; + Ok(pk) + } else { + Err(JwkToK256Error::MissingY) + } + } +} + +impl TryFrom for Jwk { + type Error = K256ToJwkError; + fn try_from(value: k256::PublicKey) -> Result { + let value = value.to_encoded_point(false); + let kty = "EC".to_string(); + let crv = "secp256k1".to_string(); + match value.coordinates() { + k256::elliptic_curve::sec1::Coordinates::Uncompressed { x, y } => { + let x = BASE64URL_NOPAD.encode(x); + let y = Some(BASE64URL_NOPAD.encode(y)); + Ok(Jwk { kty, crv, x, y }) + } + _ => Err(K256ToJwkError::PointsInvalid), + } + } +} + +impl TryFrom for x25519_dalek::PublicKey { + type Error = JwkToX25519Error; + fn try_from(value: Jwk) -> Result { + if value.crv != "X25519" { + return Err(JwkToX25519Error::DifferentCrv); + } + let pk = BASE64URL_NOPAD + .decode(value.x.as_bytes()) + .map_err(|e| JwkToX25519Error::Decode(Some(e)))?; + let pk: [u8; 32] = pk.try_into().map_err(|_| JwkToX25519Error::Decode(None))?; + Ok(pk.into()) + } +} + +impl From for Jwk { + fn from(value: x25519_dalek::PublicKey) -> Self { + let x = BASE64URL_NOPAD.encode(value.as_bytes()); + let kty = "OKP".to_string(); + let crv = "X25519".to_string(); + Jwk { kty, crv, x, y: None } + } +} + +#[cfg(test)] +pub mod tests { + use rand_core::OsRng; + + use super::*; + + #[test] + pub fn x25519_enc_dec() { + let sk = x25519_dalek::StaticSecret::random_from_rng(OsRng); + let pk = x25519_dalek::PublicKey::from(&sk); + let jwk: Jwk = pk.into(); + let _pk: x25519_dalek::PublicKey = jwk.try_into().unwrap(); + assert_eq!(pk, _pk); + } + + #[test] + pub fn k256_enc_dec() { + let sk = k256::SecretKey::random(&mut OsRng); + let pk = sk.public_key(); + let jwk: Jwk = pk.try_into().unwrap(); + let _pk: k256::PublicKey = jwk.try_into().unwrap(); + assert_eq!(pk, _pk); + } +} diff --git a/src/keyring/keypair.rs b/src/keyring/keypair.rs index 5e51dd0..77b6ce0 100644 --- a/src/keyring/keypair.rs +++ b/src/keyring/keypair.rs @@ -1,66 +1,140 @@ -use std::convert::TryFrom; +use std::convert::{TryFrom, TryInto}; +use hex::FromHexError; +use k256::elliptic_curve::sec1::ToEncodedPoint; +use rand_core::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; use thiserror::Error; +use zeroize::{Zeroize, ZeroizeOnDrop}; -use super::{ - extension::trng::{self, Trng}, - secp256k1::{Secp256k1, Secp256k1Error, Secp256k1HexKeyPair}, -}; -use crate::common::runtime; +#[derive(Clone, Serialize, Deserialize, Zeroize, ZeroizeOnDrop)] +pub struct KeyPairHex { + // MEMO: Matching schema in NodeX config. + public_key: String, + secret_key: String, +} -#[derive(Debug, Clone)] -pub struct KeyPairing { - pub sign: Secp256k1, - pub update: Secp256k1, - pub recovery: Secp256k1, - pub encrypt: Secp256k1, +#[derive(Error, Debug)] +pub enum KeyPairingError { + #[error("from hex error: {0}")] + FromHex(#[from] FromHexError), + #[error("crypt error: {0}")] + Crypt(String), } -#[derive(Debug, Clone, Serialize, Deserialize)] +pub trait KeyPair: Sized { + type Error: std::error::Error; + fn get_secret_key(&self) -> S; + fn get_public_key(&self) -> P; + fn to_hex_key_pair(&self) -> KeyPairHex; + fn from_hex_key_pair(kp: &KeyPairHex) -> Result; +} -pub struct KeyPairingHex { - pub sign: Secp256k1HexKeyPair, - pub update: Secp256k1HexKeyPair, - // MEMO: Matching schema in NodeX config. - pub recover: Secp256k1HexKeyPair, - pub encrypt: Secp256k1HexKeyPair, +#[derive(Clone)] +pub struct K256KeyPair { + secret_key: k256::SecretKey, + public_key: k256::PublicKey, } -#[derive(Error, Debug)] -pub enum KeyPairingError { - #[error("secp256k1 error")] - KeyInitializationFailed(#[from] Secp256k1Error), - #[error("Trng error")] - TrngGenerationFailed(#[from] trng::TrngError), - #[error("BIP32 error")] - BIP32Error(#[from] runtime::bip32::BIP32Error), +impl K256KeyPair { + pub fn new(secret_key: k256::SecretKey) -> Self { + let public_key = secret_key.public_key(); + K256KeyPair { public_key, secret_key } + } } -impl KeyPairing { - const SIGN_DERIVATION_PATH: &'static str = "m/44'/0'/0'/0/10"; - const UPDATE_DERIVATION_PATH: &'static str = "m/44'/0'/0'/0/20"; - const RECOVERY_DERIVATION_PATH: &'static str = "m/44'/0'/0'/0/30"; - const ENCRYPT_DERIVATION_PATH: &'static str = "m/44'/0'/0'/0/40"; +impl KeyPair for K256KeyPair { + type Error = KeyPairingError; + fn get_secret_key(&self) -> k256::SecretKey { + self.secret_key.clone() + } + fn get_public_key(&self) -> k256::PublicKey { + self.public_key + } + fn to_hex_key_pair(&self) -> KeyPairHex { + let sk = self.secret_key.to_bytes(); + let secret_key = hex::encode(sk); + let pk = self.public_key.to_encoded_point(false); + let public_key = hex::encode(pk.as_bytes()); + KeyPairHex { secret_key, public_key } + } + fn from_hex_key_pair(kp: &KeyPairHex) -> Result { + let secret_key = hex::decode(&kp.secret_key)?; + let secret_key = k256::SecretKey::from_slice(&secret_key) + .map_err(|e| KeyPairingError::Crypt(e.to_string()))?; + let public_key = hex::decode(&kp.public_key)?; + let public_key = k256::PublicKey::from_sec1_bytes(&public_key) + .map_err(|e| KeyPairingError::Crypt(e.to_string()))?; + Ok(K256KeyPair { public_key, secret_key }) + } +} - pub fn create_keyring(trng: &T) -> Result { - let seed = trng.generate(&(256 / 8))?; +#[derive(Clone)] +pub struct X25519KeyPair { + secret_key: x25519_dalek::StaticSecret, + public_key: x25519_dalek::PublicKey, +} - let sign = Self::generate_secp256k1(&seed, Self::SIGN_DERIVATION_PATH)?; - let update = Self::generate_secp256k1(&seed, Self::UPDATE_DERIVATION_PATH)?; - let recovery = Self::generate_secp256k1(&seed, Self::RECOVERY_DERIVATION_PATH)?; - let encrypt = Self::generate_secp256k1(&seed, Self::ENCRYPT_DERIVATION_PATH)?; +impl X25519KeyPair { + pub fn new(secret_key: x25519_dalek::StaticSecret) -> Self { + let public_key = x25519_dalek::PublicKey::from(&secret_key); + X25519KeyPair { public_key, secret_key } + } +} - Ok(KeyPairing { sign, update, recovery, encrypt }) +impl KeyPair for X25519KeyPair { + type Error = KeyPairingError; + fn get_secret_key(&self) -> x25519_dalek::StaticSecret { + self.secret_key.clone() + } + fn get_public_key(&self) -> x25519_dalek::PublicKey { + self.public_key + } + fn to_hex_key_pair(&self) -> KeyPairHex { + let sk = self.secret_key.as_bytes(); + let secret_key = hex::encode(sk); + let pk = self.public_key.as_bytes(); + let public_key = hex::encode(pk); + KeyPairHex { secret_key, public_key } + } + fn from_hex_key_pair(kp: &KeyPairHex) -> Result { + let secret_key = hex::decode(&kp.secret_key)?; + let secret_key: [u8; 32] = secret_key.try_into().map_err(|e: Vec| { + KeyPairingError::Crypt(format!("array length mismatch: {}", e.len())) + })?; + let secret_key = x25519_dalek::StaticSecret::from(secret_key); + let public_key = hex::decode(&kp.public_key)?; + let public_key: [u8; 32] = public_key.try_into().map_err(|e: Vec| { + KeyPairingError::Crypt(format!("array length mismatch: {}", e.len())) + })?; + let public_key = x25519_dalek::PublicKey::from(public_key); + Ok(X25519KeyPair { public_key, secret_key }) } +} + +#[derive(Clone)] +pub struct KeyPairing { + pub sign: K256KeyPair, + pub update: K256KeyPair, + pub recovery: K256KeyPair, + pub encrypt: X25519KeyPair, +} - fn generate_secp256k1( - seed: &[u8], - derivation_path: &str, - ) -> Result { - let node = runtime::bip32::BIP32::get_node(seed, derivation_path)?; +#[derive(Clone, Serialize, Deserialize)] +pub struct KeyPairingHex { + pub sign: KeyPairHex, + pub update: KeyPairHex, + pub recovery: KeyPairHex, + pub encrypt: KeyPairHex, +} - Ok(Secp256k1::from_bip32(node)?) +impl KeyPairing { + pub fn create_keyring(mut csprng: T) -> Self { + let sign = K256KeyPair::new(k256::SecretKey::random(&mut csprng)); + let update = K256KeyPair::new(k256::SecretKey::random(&mut csprng)); + let recovery = K256KeyPair::new(k256::SecretKey::random(&mut csprng)); + let encrypt = X25519KeyPair::new(x25519_dalek::StaticSecret::random_from_rng(&mut csprng)); + KeyPairing { sign, update, recovery, encrypt } } } @@ -69,7 +143,7 @@ impl From<&KeyPairing> for KeyPairingHex { KeyPairingHex { sign: keypair.sign.to_hex_key_pair(), update: keypair.update.to_hex_key_pair(), - recover: keypair.recovery.to_hex_key_pair(), + recovery: keypair.recovery.to_hex_key_pair(), encrypt: keypair.encrypt.to_hex_key_pair(), } } @@ -79,10 +153,10 @@ impl TryFrom<&KeyPairingHex> for KeyPairing { type Error = KeyPairingError; fn try_from(hex: &KeyPairingHex) -> Result { - let sign = Secp256k1::from_hex_key_pair(&hex.sign)?; - let update = Secp256k1::from_hex_key_pair(&hex.update)?; - let recovery = Secp256k1::from_hex_key_pair(&hex.recover)?; - let encrypt = Secp256k1::from_hex_key_pair(&hex.encrypt)?; + let sign = K256KeyPair::from_hex_key_pair(&hex.sign)?; + let update = K256KeyPair::from_hex_key_pair(&hex.update)?; + let recovery = K256KeyPair::from_hex_key_pair(&hex.recovery)?; + let encrypt = X25519KeyPair::from_hex_key_pair(&hex.encrypt)?; Ok(KeyPairing { sign, update, recovery, encrypt }) } @@ -90,17 +164,17 @@ impl TryFrom<&KeyPairingHex> for KeyPairing { #[cfg(test)] pub mod tests { + use rand_core::OsRng; + use super::*; - use crate::keyring::extension::trng::OSRandomNumberGenerator; #[test] pub fn test_create_keyring() { - let trng = OSRandomNumberGenerator::default(); - let keyring = KeyPairing::create_keyring(&trng).unwrap(); + let keyring = KeyPairing::create_keyring(OsRng); - assert_eq!(keyring.sign.get_secret_key().len(), 32); - assert_eq!(keyring.update.get_secret_key().len(), 32); - assert_eq!(keyring.recovery.get_secret_key().len(), 32); - assert_eq!(keyring.encrypt.get_secret_key().len(), 32); + assert_eq!(keyring.sign.get_secret_key().to_bytes().len(), 32); + assert_eq!(keyring.update.get_secret_key().to_bytes().len(), 32); + assert_eq!(keyring.recovery.get_secret_key().to_bytes().len(), 32); + assert_eq!(keyring.encrypt.get_secret_key().as_bytes().len(), 32); } } diff --git a/src/keyring/mod.rs b/src/keyring/mod.rs index 8f11f70..d1fd1a0 100644 --- a/src/keyring/mod.rs +++ b/src/keyring/mod.rs @@ -1,3 +1,3 @@ pub mod extension; +pub mod jwk; pub mod keypair; -pub mod secp256k1; diff --git a/src/keyring/secp256k1.rs b/src/keyring/secp256k1.rs deleted file mode 100644 index 1ebb9a6..0000000 --- a/src/keyring/secp256k1.rs +++ /dev/null @@ -1,367 +0,0 @@ -use std::{cmp::Ordering, convert::TryFrom}; - -use hex; -use ibig::{ibig, IBig}; -use serde::{Deserialize, Serialize}; -use thiserror::Error; - -use crate::{ - common::runtime::{self, base64_url::PaddingType, bip32::BIP32Container}, - did::sidetree::payload::PublicKeyPayload, -}; - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct KeyPairSecp256K1 { - #[serde(rename = "kty")] - pub kty: String, - - #[serde(rename = "crv")] - pub crv: String, - - #[serde(rename = "x")] - pub x: String, - - #[serde(rename = "y")] - pub y: String, - - #[serde(rename = "d", skip_serializing_if = "Option::is_none")] - pub d: Option, - - #[serde(rename = "kid", skip_serializing_if = "Option::is_none")] - pub kid: Option, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Secp256k1HexKeyPair { - // MEMO: Matching schema in NodeX config. - pub public_key: String, - pub secret_key: String, -} - -pub(crate) const PRIVATE_KEY_SIZE: usize = 32; // Buffer(PrivateKey (32 = 256 bit)) -pub(crate) const COMPRESSED_PUBLIC_KEY_SIZE: usize = 33; // Buffer(0x04 + PublicKey (32 = 256 bit)) -pub(crate) const UNCOMPRESSED_PUBLIC_KEY_SIZE: usize = 65; // Buffer(0x04 + PublicKey (64 = 512 bit)) - -#[derive(Clone, Debug, PartialEq)] -pub struct Secp256k1 { - public: [u8; UNCOMPRESSED_PUBLIC_KEY_SIZE], - private: [u8; PRIVATE_KEY_SIZE], -} - -impl TryFrom<&Secp256k1HexKeyPair> for Secp256k1 { - type Error = Secp256k1Error; - - fn try_from(value: &Secp256k1HexKeyPair) -> Result { - Self::from_hex_key_pair(value) - } -} - -impl From<&Secp256k1> for Secp256k1HexKeyPair { - fn from(value: &Secp256k1) -> Self { - value.to_hex_key_pair() - } -} - -#[derive(Error, Debug)] -pub enum Secp256k1Error { - #[error("Invalid public key size")] - InvalidSecretKeySize, - #[error("Secp256k1 runtime error : {0:?}")] - Secp256k1RuntimeError(#[from] runtime::secp256k1::Secp256k1Error), - #[error("Invalid public key size")] - InvalidPublicKeySize, - #[error("Base64 decode error")] - Base64UrlError(#[from] runtime::base64_url::Base64UrlError), - #[error("Invalid first bytes")] - InvalidFirstBytes, - #[error("Number parsing error")] - NumberParsingError(#[from] ibig::error::ParseError), - #[error("Validation Failed")] - ValidationFailed, - #[error("Hex Decode failed")] - HexDecodeFailed(#[from] hex::FromHexError), - #[error(transparent)] - Other(#[from] anyhow::Error), -} - -impl Secp256k1 { - pub fn from_bip32(container: BIP32Container) -> Result { - let public = container.public_key; - let private = container.private_key; - - let public = Secp256k1::transform_uncompressed_public_key(&public)?; - - let public = <[u8; UNCOMPRESSED_PUBLIC_KEY_SIZE]>::try_from(public) - .map_err(|_| anyhow::anyhow!("Conversion failed"))?; - - Ok(Secp256k1 { public, private }) - } - - pub fn new(public: Vec, private: Vec) -> Result { - if private.len() != PRIVATE_KEY_SIZE { - return Err(Secp256k1Error::InvalidSecretKeySize); - } - - if public.len() == COMPRESSED_PUBLIC_KEY_SIZE { - let public = Secp256k1::transform_uncompressed_public_key(&public)?; - - let public = <[u8; UNCOMPRESSED_PUBLIC_KEY_SIZE]>::try_from(public) - .map_err(|_| anyhow::anyhow!("Conversion failed"))?; - let private = <[u8; PRIVATE_KEY_SIZE]>::try_from(private) - .map_err(|_| anyhow::anyhow!("Conversion failed"))?; - - if public[0] != 0x04 { - Err(Secp256k1Error::InvalidFirstBytes) - } else { - Ok(Secp256k1 { public, private }) - } - } else if public.len() == UNCOMPRESSED_PUBLIC_KEY_SIZE { - let public = <[u8; UNCOMPRESSED_PUBLIC_KEY_SIZE]>::try_from(public) - .map_err(|_| anyhow::anyhow!("Conversion failed"))?; - let private = <[u8; PRIVATE_KEY_SIZE]>::try_from(private) - .map_err(|_| anyhow::anyhow!("Conversion failed"))?; - - if public[0] != 0x04 { - Err(Secp256k1Error::InvalidFirstBytes) - } else { - Ok(Secp256k1 { public, private }) - } - } else { - Err(Secp256k1Error::InvalidPublicKeySize) - } - } - - pub fn get_public_key(&self) -> Vec { - self.public.to_vec() - } - - pub fn get_secret_key(&self) -> Vec { - self.private.to_vec() - } - - pub fn to_hex_key_pair(&self) -> Secp256k1HexKeyPair { - Secp256k1HexKeyPair { - public_key: hex::encode(self.get_public_key()), - secret_key: hex::encode(self.get_secret_key()), - } - } - - pub fn from_hex_key_pair(hex_key_pair: &Secp256k1HexKeyPair) -> Result { - let public = hex::decode(&hex_key_pair.public_key)?; - let private = hex::decode(&hex_key_pair.secret_key)?; - - Self::new(public, private) - } - - pub fn from_jwk(jwk: &KeyPairSecp256K1) -> Result { - let d = jwk.d.clone().unwrap_or_else(|| { - const EMPTY_PRIVATE_KEY: [u8; PRIVATE_KEY_SIZE] = [0; PRIVATE_KEY_SIZE]; - runtime::base64_url::Base64Url::encode(&EMPTY_PRIVATE_KEY, &PaddingType::NoPadding) - }); - - let x = runtime::base64_url::Base64Url::decode_as_bytes(&jwk.x, &PaddingType::NoPadding)?; - let y = runtime::base64_url::Base64Url::decode_as_bytes(&jwk.y, &PaddingType::NoPadding)?; - - let public = [&[0x04], &x[..], &y[..]].concat(); - let private = runtime::base64_url::Base64Url::decode_as_bytes(&d, &PaddingType::NoPadding)?; - - Self::new(public, private) - } - - pub fn to_jwk(&self, included_private_key: bool) -> Result { - let validated = self.validate_point()?; - - if !validated { - return Err(Secp256k1Error::ValidationFailed); - } - - let d = if included_private_key { - Some(runtime::base64_url::Base64Url::encode( - &self.get_secret_key(), - &PaddingType::NoPadding, - )) - } else { - None - }; - - Ok(KeyPairSecp256K1 { - kty: "EC".to_string(), - crv: "secp256k1".to_string(), - x: runtime::base64_url::Base64Url::encode(self.get_point_x(), &PaddingType::NoPadding), - y: runtime::base64_url::Base64Url::encode(self.get_point_y(), &PaddingType::NoPadding), - d, - kid: None, - }) - } - - pub fn to_public_key( - &self, - key_id: &str, - purpose: &[&str], - ) -> Result { - let validated = self.validate_point()?; - - if !validated { - return Err(Secp256k1Error::ValidationFailed); - } - - let jwk = self.to_jwk(false)?; - - Ok(PublicKeyPayload { - id: key_id.to_string(), - r#type: "EcdsaSecp256k1VerificationKey2019".to_string(), - jwk, - purpose: purpose.to_vec().iter().map(|value| value.to_string()).collect(), - }) - } - - fn get_point_x(&self) -> &[u8] { - &self.public[1..33] - } - - fn get_point_y(&self) -> &[u8] { - &self.public[33..] - } - - pub fn validate_point(&self) -> Result { - let x = self.get_point_x(); - let y = self.get_point_y(); - - let nx = IBig::from_str_radix(&hex::encode(x), 16)?; - let ny = IBig::from_str_radix(&hex::encode(y), 16)?; - let np = IBig::from_str_radix( - "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", - 16, - )?; - - let verified: IBig = (&ny * &ny - &nx * &nx * &nx - 7) % &np; - - Ok(verified.cmp(&ibig!(0)) == Ordering::Equal) - } - - pub fn transform_uncompressed_public_key(compressed: &[u8]) -> Result, Secp256k1Error> { - runtime::secp256k1::Secp256k1::convert_public_key(compressed, false).map_err(|e| e.into()) - } -} - -#[cfg(test)] -pub mod tests { - - use super::*; - - const PRIVATE_KEY: [u8; 32] = [ - 0xc7, 0x39, 0x80, 0x5a, 0xb0, 0x3d, 0xa6, 0x2d, 0xdb, 0xe0, 0x33, 0x90, 0xac, 0xdf, 0x76, - 0x15, 0x64, 0x0a, 0xa6, 0xed, 0x31, 0xb8, 0xf1, 0x82, 0x43, 0xf0, 0x4a, 0x57, 0x2c, 0x52, - 0x8e, 0xdb, - ]; - - const PUBLIC_KEY: [u8; 33] = [ - 0x02, 0x70, 0x96, 0x45, 0x32, 0xf0, 0x83, 0xf4, 0x5f, 0xe8, 0xe8, 0xcc, 0xea, 0x96, 0xa2, - 0x2f, 0x60, 0x18, 0xd4, 0x6a, 0x40, 0x6f, 0x58, 0x3a, 0xb2, 0x26, 0xb1, 0x92, 0x83, 0xaa, - 0x60, 0x5c, 0x44, - ]; - - #[test] - pub fn test_to_hex_key_pair() { - let node = Secp256k1::new(PUBLIC_KEY.to_vec(), PRIVATE_KEY.to_vec()).unwrap(); - - let result = node.to_hex_key_pair(); - - assert_eq!( - result.secret_key, - "c739805ab03da62ddbe03390acdf7615640aa6ed31b8f18243f04a572c528edb" - ); - assert_eq!( - result.public_key, - "0470964532f083f45fe8e8ccea96a22f6018d46a406f583ab226b19283aa605c44851b9274e6a2ce2ad42b4169e37df5f6cb38e81604b3ca2ebe11dd085862b490" - ); - } - - #[test] - pub fn test_get_point_x() { - let node = Secp256k1::new(PUBLIC_KEY.to_vec(), PRIVATE_KEY.to_vec()).unwrap(); - - assert_eq!( - node.get_point_x(), - &[ - 0x70, 0x96, 0x45, 0x32, 0xf0, 0x83, 0xf4, 0x5f, 0xe8, 0xe8, 0xcc, 0xea, 0x96, 0xa2, - 0x2f, 0x60, 0x18, 0xd4, 0x6a, 0x40, 0x6f, 0x58, 0x3a, 0xb2, 0x26, 0xb1, 0x92, 0x83, - 0xaa, 0x60, 0x5c, 0x44, - ] - ) - } - - #[test] - pub fn test_get_point_y() { - let node = Secp256k1::new(PUBLIC_KEY.to_vec(), PRIVATE_KEY.to_vec()).unwrap(); - - assert_eq!( - node.get_point_y(), - &[ - 0x85, 0x1b, 0x92, 0x74, 0xe6, 0xa2, 0xce, 0x2a, 0xd4, 0x2b, 0x41, 0x69, 0xe3, 0x7d, - 0xf5, 0xf6, 0xcb, 0x38, 0xe8, 0x16, 0x04, 0xb3, 0xca, 0x2e, 0xbe, 0x11, 0xdd, 0x08, - 0x58, 0x62, 0xb4, 0x90, - ] - ) - } - - #[test] - pub fn test_validate_point() { - let node = Secp256k1::new(PUBLIC_KEY.to_vec(), PRIVATE_KEY.to_vec()).unwrap(); - - let result = node.validate_point().unwrap(); - - assert!(result) - } - - #[test] - pub fn test_to_jwk_with_private_key() { - let node = Secp256k1::new(PUBLIC_KEY.to_vec(), PRIVATE_KEY.to_vec()).unwrap(); - - let result = node.to_jwk(true).unwrap(); - - assert_eq!(result.kty, "EC"); - assert_eq!(result.crv, "secp256k1"); - assert_eq!(result.x, "cJZFMvCD9F_o6MzqlqIvYBjUakBvWDqyJrGSg6pgXEQ"); - assert_eq!(result.y, "hRuSdOaizirUK0Fp43319ss46BYEs8ouvhHdCFhitJA"); - assert_eq!(result.d, Some("xzmAWrA9pi3b4DOQrN92FWQKpu0xuPGCQ_BKVyxSjts".to_string())); - assert_eq!(result.kid, None); - } - - #[test] - pub fn test_to_jwk_without_private_key() { - let node = Secp256k1::new(PUBLIC_KEY.to_vec(), PRIVATE_KEY.to_vec()).unwrap(); - - let result = node.to_jwk(false).unwrap(); - - assert_eq!(result.kty, "EC"); - assert_eq!(result.crv, "secp256k1"); - assert_eq!(result.x, "cJZFMvCD9F_o6MzqlqIvYBjUakBvWDqyJrGSg6pgXEQ"); - assert_eq!(result.y, "hRuSdOaizirUK0Fp43319ss46BYEs8ouvhHdCFhitJA"); - assert_eq!(result.d, None); - assert_eq!(result.kid, None); - } - - #[test] - pub fn test_from_jwk_with_private_key() { - let node = Secp256k1::new(PUBLIC_KEY.to_vec(), PRIVATE_KEY.to_vec()).unwrap(); - - let jwk = node.to_jwk(false).unwrap(); - - let clone = Secp256k1::from_jwk(&jwk).unwrap(); - - assert_eq!(clone.get_public_key(), node.get_public_key()); - assert_eq!(clone.get_secret_key(), &[0; 32]); - } - - #[test] - pub fn test_from_jwk_without_private_key() { - let node = Secp256k1::new(PUBLIC_KEY.to_vec(), PRIVATE_KEY.to_vec()).unwrap(); - - let jwk = node.to_jwk(true).unwrap(); - - let clone = Secp256k1::from_jwk(&jwk).unwrap(); - - assert_eq!(node, clone); - } -} diff --git a/src/lib.rs b/src/lib.rs index c49cae6..701f61c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,10 @@ -pub(crate) mod common; pub mod did; pub mod didcomm; pub mod keyring; pub mod verifiable_credentials; +pub use http; +pub use k256; +pub use rand_core; +pub use serde_jcs; +pub use sha2; +pub use x25519_dalek; diff --git a/src/verifiable_credentials/credential_signer.rs b/src/verifiable_credentials/credential_signer.rs index aab4659..30b8196 100644 --- a/src/verifiable_credentials/credential_signer.rs +++ b/src/verifiable_credentials/credential_signer.rs @@ -1,41 +1,32 @@ -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; use serde_json::json; use thiserror::Error; use super::types::Proof; use crate::{ - common::{cipher::jws::Jws, utils}, - keyring::secp256k1::Secp256k1, - verifiable_credentials::types::VerifiableCredentials, + keyring::keypair::{K256KeyPair, KeyPair}, + verifiable_credentials::{jws, types::VerifiableCredentials}, }; -#[derive(Debug, Serialize, Deserialize)] -pub struct ProofContext { - #[serde(rename = "proof")] - pub proof: Option, -} - pub struct CredentialSignerSuite<'a> { pub did: &'a str, pub key_id: &'a str, - pub context: &'a Secp256k1, + pub context: &'a K256KeyPair, } #[derive(Debug, Error)] pub enum CredentialSignerSignError { #[error("jws error: {0:?}")] - JwsError(#[from] crate::common::cipher::jws::JwsEncodeError), + Jws(#[from] jws::JwsEncodeError), #[error("json parse error: {0:?}")] - JsonParseError(#[from] serde_json::Error), + Json(#[from] serde_json::Error), } #[derive(Debug, Error)] pub enum CredentialSignerVerifyError { #[error("jws error: {0:?}")] - JwsError(#[from] crate::common::cipher::jws::JwsDecodeError), + Jws(#[from] jws::JwsDecodeError), #[error("json parse error: {0:?}")] - JsonParseError(#[from] serde_json::Error), + Json(#[from] serde_json::Error), #[error("proof not found")] ProofNotFound, } @@ -44,59 +35,45 @@ pub struct CredentialSigner {} impl CredentialSigner { pub fn sign( - object: &VerifiableCredentials, + mut object: VerifiableCredentials, suite: CredentialSignerSuite, - created: DateTime, ) -> Result { - let jws = Jws::encode(&json!(object), suite.context)?; - + let jws = jws::sign(&json!(object), &suite.context.get_secret_key())?; let did = suite.did; let key_id = suite.key_id; - - let proof: ProofContext = ProofContext { - proof: Some(Proof { - r#type: "EcdsaSecp256k1Signature2019".to_string(), - proof_purpose: "authentication".to_string(), - created: created.to_rfc3339(), - verification_method: format!("{}#{}", did, key_id), - jws, - domain: None, - controller: None, - challenge: None, - }), - }; - - // NOTE: sign - let mut signed_object = json!(object); - - utils::json::merge(&mut signed_object, json!(proof)); - - Ok(serde_json::from_value::(signed_object)?) + object.proof = Some(Proof { + r#type: "EcdsaSecp256k1Signature2019".to_string(), + proof_purpose: "authentication".to_string(), + // Assume that object.issuance_date is correct data + created: object.issuance_date, + verification_method: format!("{}#{}", did, key_id), + jws, + domain: None, + controller: None, + challenge: None, + }); + Ok(object) } pub fn verify( mut object: VerifiableCredentials, - context: &Secp256k1, - ) -> Result<(VerifiableCredentials, bool), CredentialSignerVerifyError> { + public_key: &k256::PublicKey, + ) -> Result { let proof = object.proof.take().ok_or(CredentialSignerVerifyError::ProofNotFound)?; - let jws = proof.jws; let payload = serde_json::to_value(&object)?; - - let verified = Jws::verify(&payload, &jws, context)?; - - Ok((object, verified)) + jws::verify(&payload, &jws, public_key)?; + Ok(object) } } #[cfg(test)] pub mod tests { + use chrono::{DateTime, Utc}; + use super::*; - use crate::{ - keyring::{self}, - verifiable_credentials::types::{CredentialSubject, Issuer}, - }; + use crate::verifiable_credentials::types::{CredentialSubject, Issuer}; const PRIVATE_KEY: [u8; 32] = [ 0xc7, 0x39, 0x80, 0x5a, 0xb0, 0x3d, 0xa6, 0x2d, 0xdb, 0xe0, 0x33, 0x90, 0xac, 0xdf, 0x76, @@ -110,17 +87,29 @@ pub mod tests { 0x60, 0x5c, 0x44, ]; + #[test] + pub fn test_public_key() { + let sk = k256::SecretKey::from_slice(&PRIVATE_KEY).unwrap(); + let context = K256KeyPair::new(sk); + assert_eq!( + context.get_public_key(), + k256::PublicKey::from_sec1_bytes(&PUBLIC_KEY).unwrap() + ); + } + #[test] pub fn test_sign() { - let context = - keyring::secp256k1::Secp256k1::new(PUBLIC_KEY.to_vec(), PRIVATE_KEY.to_vec()).unwrap(); + let sk = k256::SecretKey::from_slice(&PRIVATE_KEY).unwrap(); + let context = K256KeyPair::new(sk); let model = VerifiableCredentials { id: None, r#type: vec!["type".to_string()], issuer: Issuer { id: "issuer".to_string() }, context: vec!["context".to_string()], - issuance_date: "issuance_date".to_string(), + issuance_date: DateTime::parse_from_rfc3339("2024-07-19T06:06:51.361316372Z") + .unwrap() + .to_utc(), credential_subject: CredentialSubject { id: None, container: json!(r#"{"k":"0123456789abcdef"}"#), @@ -130,13 +119,12 @@ pub mod tests { }; let result = CredentialSigner::sign( - &model, + model, CredentialSignerSuite { did: "did:nodex:test:000000000000000000000000000000", key_id: "signingKey", context: &context, }, - Utc::now(), ) .unwrap(); @@ -144,7 +132,7 @@ pub mod tests { Some(proof) => { assert_eq!( proof.jws, - "eyJhbGciOiJFUzI1NksiLCJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdfQ..Qc-NyzQu2v735_qPR72j1oqUDK1Ne4XQ7Lc66_x9tlMSeI9xmrgguEA8UmQyTM0cd13xkvpK4g-NEWJBp8_d_w" + "eyJhbGciOiJFUzI1NksiLCJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdfQ..LK8OcOuMgWU4Y5Zpz9jeQ8b5UsgDmjKJTBpuxFepGlp-hGVHVgyZz8QkZseqQRdUXn6JouVYo1jFsQCq_7p7ig" ); assert_eq!(proof.proof_purpose, "authentication"); assert_eq!(proof.r#type, "EcdsaSecp256k1Signature2019"); @@ -159,15 +147,15 @@ pub mod tests { #[test] pub fn test_verify() { - let context = - keyring::secp256k1::Secp256k1::new(PUBLIC_KEY.to_vec(), PRIVATE_KEY.to_vec()).unwrap(); + let sk = k256::SecretKey::from_slice(&PRIVATE_KEY).unwrap(); + let context = K256KeyPair::new(sk); let model = VerifiableCredentials { id: None, r#type: vec!["type".to_string()], issuer: Issuer { id: "issuer".to_string() }, context: vec!["context".to_string()], - issuance_date: "issuance_date".to_string(), + issuance_date: Utc::now(), credential_subject: CredentialSubject { id: None, container: json!(r#"{"k":"0123456789abcdef"}"#), @@ -177,19 +165,17 @@ pub mod tests { }; let vc = CredentialSigner::sign( - &model, + model.clone(), CredentialSignerSuite { did: "did:nodex:test:000000000000000000000000000000", key_id: "signingKey", context: &context, }, - Utc::now(), ) .unwrap(); - let (verified_model, verified) = CredentialSigner::verify(vc, &context).unwrap(); + let verified_model = CredentialSigner::verify(vc, &context.get_public_key()).unwrap(); - assert!(verified); assert_eq!(model, verified_model); } } diff --git a/src/verifiable_credentials/did_vc.rs b/src/verifiable_credentials/did_vc.rs index 7387540..52ab1b7 100644 --- a/src/verifiable_credentials/did_vc.rs +++ b/src/verifiable_credentials/did_vc.rs @@ -1,126 +1,72 @@ -use anyhow::Context as _; -use chrono::{DateTime, Utc}; -use serde_json::Value; use thiserror::Error; use crate::{ - did::did_repository::{DidRepository, FindIdentifierError}, - keyring::{self, keypair}, + did::did_repository::{get_sign_key, DidRepository, GetPublicKeyError}, + keyring::keypair, verifiable_credentials::{ credential_signer::{ CredentialSigner, CredentialSignerSignError, CredentialSignerSuite, CredentialSignerVerifyError, }, - types::{CredentialSubject, Issuer, VerifiableCredentials}, + types::VerifiableCredentials, }, }; -pub struct DIDVCService { - pub(crate) did_repository: R, -} - -impl DIDVCService { - pub fn new(did_repository: R) -> Self { - Self { did_repository } - } -} - -impl Clone for DIDVCService { - fn clone(&self) -> Self { - Self { did_repository: self.did_repository.clone() } - } -} - -#[derive(Debug, Error)] -pub enum DIDVCServiceGenerateError { - #[error("credential signer error")] - SignFailed(#[from] CredentialSignerSignError), +#[trait_variant::make(Send)] +pub trait DidVcService: Sync { + type GenerateError: std::error::Error + Send + Sync; + type VerifyError: std::error::Error + Send + Sync; + fn generate( + &self, + model: VerifiableCredentials, + from_keyring: &keypair::KeyPairing, + ) -> Result; + async fn verify( + &self, + model: VerifiableCredentials, + ) -> Result; } #[derive(Debug, Error)] -pub enum DIDVCServiceVerifyError { - #[error("did not found : {0}")] - DIDNotFound(String), +pub enum DidVcServiceVerifyError { #[error("did public key not found. did: {0}")] - PublicKeyNotFound(String), + PublicKeyNotFound(#[from] GetPublicKeyError), + #[error("failed to get did document: {0}")] + DidDocNotFound(String), + #[error("failed to find identifier: {0}")] + FindIdentifier(FindIdentifierError), #[error("credential signer error")] VerifyFailed(#[from] CredentialSignerVerifyError), - #[error("public_keys length must be 1")] - PublicKeyLengthMismatch, - #[error("sidetree find request failed")] - SidetreeFindRequestFailed(#[from] FindIdentifierError), - #[error("signature is not verified")] - SignatureNotVerified, - #[error(transparent)] - Other(#[from] anyhow::Error), } -impl DIDVCService { - pub fn generate( +impl DidVcService for R { + type GenerateError = CredentialSignerSignError; + type VerifyError = DidVcServiceVerifyError; + fn generate( &self, - from_did: &str, + model: VerifiableCredentials, from_keyring: &keypair::KeyPairing, - message: &Value, - issuance_date: DateTime, - ) -> Result { - let r#type = "VerifiableCredential".to_string(); - let context = "https://www.w3.org/2018/credentials/v1".to_string(); - - let model = VerifiableCredentials { - id: None, - issuer: Issuer { id: from_did.to_string() }, - r#type: vec![r#type], - context: vec![context], - issuance_date: issuance_date.to_rfc3339(), - credential_subject: CredentialSubject { id: None, container: message.clone() }, - expiration_date: None, - proof: None, - }; - - let signed: VerifiableCredentials = CredentialSigner::sign( - &model, - CredentialSignerSuite { - did: from_did, - key_id: "signingKey", - context: &from_keyring.sign, - }, - issuance_date, - )?; - - Ok(signed) + ) -> Result { + let did = &model.issuer.id.clone(); + CredentialSigner::sign( + model, + CredentialSignerSuite { did, key_id: "signingKey", context: &from_keyring.sign }, + ) } - pub async fn verify( + async fn verify( &self, model: VerifiableCredentials, - ) -> Result { + ) -> Result { let did_document = self - .did_repository .find_identifier(&model.issuer.id) - .await? - .ok_or_else(|| DIDVCServiceVerifyError::DIDNotFound(model.issuer.id.clone()))?; - let public_keys = did_document - .did_document - .public_key - .ok_or_else(|| DIDVCServiceVerifyError::PublicKeyNotFound(model.issuer.id.clone()))?; - - // FIXME: workaround - if public_keys.len() != 1 { - return Err(DIDVCServiceVerifyError::PublicKeyLengthMismatch); - } - - let public_key = public_keys[0].clone(); - - let context = keyring::secp256k1::Secp256k1::from_jwk(&public_key.public_key_jwk) - .context("failed to convert key")?; - - let (verified_model, verified) = CredentialSigner::verify(model, &context)?; - - if verified { - Ok(verified_model) - } else { - Err(DIDVCServiceVerifyError::SignatureNotVerified) - } + .await + .map_err(Self::VerifyError::FindIdentifier)?; + let did_document = did_document + .ok_or(DidVcServiceVerifyError::DidDocNotFound(model.issuer.id.clone()))? + .did_document; + let public_key = get_sign_key(&did_document)?; + Ok(CredentialSigner::verify(model, &public_key)?) } } @@ -128,32 +74,34 @@ impl DIDVCService { mod tests { use std::{collections::BTreeMap, iter::FromIterator as _}; - use serde_json::json; + use chrono::{DateTime, Utc}; + use rand_core::OsRng; + use serde_json::{json, Value}; - use super::*; + use super::{DidVcService, DidVcServiceVerifyError, VerifiableCredentials}; use crate::{ did::{did_repository::mocks::MockDidRepository, test_utils::create_random_did}, - keyring::{extension::trng::OSRandomNumberGenerator, keypair::KeyPairing}, + keyring::keypair::KeyPairing, }; #[actix_rt::test] async fn test_generate_and_verify() { let from_did = create_random_did(); - let trng = OSRandomNumberGenerator::default(); - let from_keyring = KeyPairing::create_keyring(&trng).unwrap(); + let from_keyring = KeyPairing::create_keyring(OsRng); let mock_repository = MockDidRepository::from_single(BTreeMap::from_iter([( from_did.clone(), from_keyring.clone(), )])); - let service = DIDVCService::new(mock_repository); + let service = mock_repository; let message = json!({"test": "0123456789abcdef"}); let issuance_date = Utc::now(); - let res = service.generate(&from_did, &from_keyring, &message, issuance_date).unwrap(); + let model = VerifiableCredentials::new(from_did.clone(), message.clone(), issuance_date); + let res = service.generate(model, &from_keyring).unwrap(); let verified = service.verify(res).await.unwrap(); @@ -161,35 +109,7 @@ mod tests { assert_eq!(verified.credential_subject.container, message); } - mod generate_failed { - use super::*; - use crate::keyring::secp256k1::Secp256k1; - - #[actix_rt::test] - async fn test_generate_sign_failed() { - let from_did = create_random_did(); - - let trng = OSRandomNumberGenerator::default(); - let mut illegal_keyring = KeyPairing::create_keyring(&trng).unwrap(); - illegal_keyring.sign = Secp256k1::new( - illegal_keyring.sign.get_public_key(), - vec![0; illegal_keyring.sign.get_secret_key().len()], - ) - .unwrap(); - - let mock_repository = MockDidRepository::from_single(BTreeMap::new()); - - let service = DIDVCService::new(mock_repository); - - let message = json!({"test": "0123456789abcdef"}); - let issuance_date = Utc::now(); - - let res = - service.generate(&from_did, &illegal_keyring, &message, issuance_date).unwrap_err(); - - assert!(matches!(res, DIDVCServiceGenerateError::SignFailed(_))); - } - } + mod generate_failed {} mod verify_failed { use super::*; @@ -203,9 +123,10 @@ mod tests { message: &Value, issuance_date: DateTime, ) -> VerifiableCredentials { - let service = DIDVCService::new(MockDidRepository::from_single(BTreeMap::new())); - - service.generate(from_did, from_keyring, message, issuance_date).unwrap() + let service = MockDidRepository::from_single(BTreeMap::new()); + let model = + VerifiableCredentials::new(from_did.to_string(), message.clone(), issuance_date); + service.generate(model, from_keyring).unwrap() } #[actix_rt::test] @@ -214,18 +135,18 @@ mod tests { let mock_repository = MockDidRepository::from_single(BTreeMap::new()); - let service = DIDVCService::new(mock_repository); + let service = mock_repository; let model = create_did_vc( &from_did, - &KeyPairing::create_keyring(&OSRandomNumberGenerator::default()).unwrap(), + &KeyPairing::create_keyring(OsRng), &json!({}), Utc::now(), ); let res = service.verify(model).await.unwrap_err(); - if let DIDVCServiceVerifyError::DIDNotFound(_) = res { + if let DidVcServiceVerifyError::DidDocNotFound(_) = res { } else { panic!("unexpected error: {:?}", res); } @@ -237,17 +158,17 @@ mod tests { let model = create_did_vc( &from_did, - &KeyPairing::create_keyring(&OSRandomNumberGenerator::default()).unwrap(), + &KeyPairing::create_keyring(OsRng), &json!({}), Utc::now(), ); let mock_repository = NoPublicKeyDidRepository; - let service = DIDVCService::new(mock_repository); + let service = mock_repository; let res = service.verify(model).await.unwrap_err(); - if let DIDVCServiceVerifyError::PublicKeyNotFound(_) = res { + if let DidVcServiceVerifyError::PublicKeyNotFound(_) = res { } else { panic!("unexpected error: {:?}", res); } @@ -259,7 +180,7 @@ mod tests { let mut model = create_did_vc( &from_did, - &KeyPairing::create_keyring(&OSRandomNumberGenerator::default()).unwrap(), + &KeyPairing::create_keyring(OsRng), &json!({}), Utc::now(), ); @@ -268,13 +189,13 @@ mod tests { let mock_repository = MockDidRepository::from_single(BTreeMap::from_iter([( from_did.clone(), - KeyPairing::create_keyring(&OSRandomNumberGenerator::default()).unwrap(), + KeyPairing::create_keyring(OsRng), )])); - let service = DIDVCService::new(mock_repository); + let service = mock_repository; let res = service.verify(model).await.unwrap_err(); - if let DIDVCServiceVerifyError::VerifyFailed(_) = res { + if let DidVcServiceVerifyError::VerifyFailed(_) = res { } else { panic!("unexpected error: {:?}", res); } @@ -286,17 +207,17 @@ mod tests { let model = create_did_vc( &from_did, - &KeyPairing::create_keyring(&OSRandomNumberGenerator::default()).unwrap(), + &KeyPairing::create_keyring(OsRng), &json!({}), Utc::now(), ); let mock_repository = IllegalPublicKeyLengthDidRepository; - let service = DIDVCService::new(mock_repository); + let service = mock_repository; let res = service.verify(model).await.unwrap_err(); - if let DIDVCServiceVerifyError::PublicKeyLengthMismatch = res { + if let DidVcServiceVerifyError::PublicKeyNotFound(_) = res { } else { panic!("unexpected error: {:?}", res); } @@ -308,20 +229,20 @@ mod tests { let model = create_did_vc( &from_did, - &KeyPairing::create_keyring(&OSRandomNumberGenerator::default()).unwrap(), + &KeyPairing::create_keyring(OsRng), &json!({}), Utc::now(), ); let mock_repository = MockDidRepository::from_single(BTreeMap::from_iter([( from_did.clone(), - KeyPairing::create_keyring(&OSRandomNumberGenerator::default()).unwrap(), + KeyPairing::create_keyring(OsRng), )])); - let service = DIDVCService::new(mock_repository); + let service = mock_repository; let res = service.verify(model).await.unwrap_err(); - if let DIDVCServiceVerifyError::SignatureNotVerified = res { + if let DidVcServiceVerifyError::VerifyFailed(_) = res { } else { panic!("unexpected error: {:?}", res); } diff --git a/src/verifiable_credentials/jws.rs b/src/verifiable_credentials/jws.rs new file mode 100644 index 0000000..be6249d --- /dev/null +++ b/src/verifiable_credentials/jws.rs @@ -0,0 +1,165 @@ +use std::convert::TryInto; + +use data_encoding::BASE64URL_NOPAD; +use k256::ecdsa::{ + signature::{Signer, Verifier}, + Signature, SigningKey, VerifyingKey, +}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use thiserror::Error; + +// TODO: Design the interface to have an implementation with accelerators. + +#[derive(Debug, Serialize, Deserialize)] +struct JwsHeader { + alg: String, + b64: bool, + crit: Vec, +} + +#[derive(Debug, Error)] +pub enum JwsEncodeError { + #[error("PublicKeyConvertError : {0:?}")] + SignatureError(#[from] k256::ecdsa::Error), + #[error("CanonicalizeError : {0:?}")] + CanonicalizeError(#[from] serde_json::Error), +} + +#[derive(Debug, Error)] +pub enum JwsDecodeError { + #[error("DecodeError: {0:?}")] + DecodeError(#[from] data_encoding::DecodeError), + #[error(transparent)] + JsonParseError(#[from] serde_json::Error), + #[error("invalid signature length: {0}")] + InvalidSignatureLength(usize), + #[error("InvalidAlgorithm: {0}")] + InvalidAlgorithm(String), + #[error("b64 option is not supported")] + B64NotSupported, + #[error("b64 option is not supported, but contained")] + B64NotSupportedButContained, + #[error("EmptyPayload")] + EmptyPayload, + #[error("InvalidJws : {0}")] + InvalidJws(String), + #[error("CryptError: {0:?}")] + CryptError(#[from] k256::ecdsa::Error), + #[error("FromUtf8Error: {0}")] + FromUtf8Error(#[from] std::string::FromUtf8Error), +} + +pub fn sign(object: &Value, secret_key: &k256::SecretKey) -> Result { + // NOTE: header + let header = JwsHeader { alg: "ES256K".to_string(), b64: false, crit: vec!["b64".to_string()] }; + let header = serde_jcs::to_string(&header)?; + let header = BASE64URL_NOPAD.encode(header.as_bytes()); + // NOTE: payload + let payload = BASE64URL_NOPAD.encode(object.to_string().as_bytes()); + // NOTE: message + let message = [header.clone(), payload].join("."); + let message: &[u8] = message.as_bytes(); + + // NOTE: signature + let signing_key: SigningKey = secret_key.into(); + let signature: Signature = signing_key.try_sign(message)?; + let signature = BASE64URL_NOPAD.encode(&signature.to_vec()); + + Ok([header, "".to_string(), signature].join(".")) +} + +pub fn verify( + object: &Value, + jws: &str, + public_key: &k256::PublicKey, +) -> Result<(), JwsDecodeError> { + let split: Vec = jws.split('.').map(|v| v.to_string()).collect(); + + if split.len() != 3 { + return Err(JwsDecodeError::InvalidJws(jws.to_string())); + } + + let _header = split[0].clone(); + let __payload = split[1].clone(); + let _signature = split[2].clone(); + + // NOTE: header + let decoded = BASE64URL_NOPAD.decode(_header.as_bytes())?; + let decoded = String::from_utf8(decoded)?; + let header = serde_json::from_str::(&decoded)?; + + if header.alg != *"ES256K" { + return Err(JwsDecodeError::InvalidAlgorithm(header.alg)); + } + if header.b64 { + return Err(JwsDecodeError::B64NotSupported); + } + if header.crit.iter().all(|v| v != "b64") { + return Err(JwsDecodeError::B64NotSupportedButContained); + }; + + // NOTE: payload + if __payload != *"".to_string() { + return Err(JwsDecodeError::EmptyPayload); + } + let _payload = BASE64URL_NOPAD.encode(object.to_string().as_bytes()); + + // NOTE: message + let message = [_header, _payload].join("."); + + // NOTE: signature + let signature = BASE64URL_NOPAD.decode(_signature.as_bytes())?; + if signature.len() != 64 { + return Err(JwsDecodeError::InvalidSignatureLength(signature.len())); + } + let r: &[u8; 32] = &signature[0..32].try_into().unwrap(); + let s: &[u8; 32] = &signature[32..].try_into().unwrap(); + let wrapped_signature = Signature::from_scalars(*r, *s)?; + + let verify_key = VerifyingKey::from(public_key); + Ok(verify_key.verify(message.as_bytes(), &wrapped_signature)?) +} + +#[cfg(test)] +pub mod tests { + use super::*; + + const SECRET_KEY: [u8; 32] = [ + 0xc7, 0x39, 0x80, 0x5a, 0xb0, 0x3d, 0xa6, 0x2d, 0xdb, 0xe0, 0x33, 0x90, 0xac, 0xdf, 0x76, + 0x15, 0x64, 0x0a, 0xa6, 0xed, 0x31, 0xb8, 0xf1, 0x82, 0x43, 0xf0, 0x4a, 0x57, 0x2c, 0x52, + 0x8e, 0xdb, + ]; + + const PUBLIC_KEY: [u8; 33] = [ + 0x02, 0x70, 0x96, 0x45, 0x32, 0xf0, 0x83, 0xf4, 0x5f, 0xe8, 0xe8, 0xcc, 0xea, 0x96, 0xa2, + 0x2f, 0x60, 0x18, 0xd4, 0x6a, 0x40, 0x6f, 0x58, 0x3a, 0xb2, 0x26, 0xb1, 0x92, 0x83, 0xaa, + 0x60, 0x5c, 0x44, + ]; + + fn message() -> String { + String::from(r#"{"k":"0123456789abcdef"}"#) + } + + fn signature() -> String { + String::from( + "eyJhbGciOiJFUzI1NksiLCJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdfQ..vuhCrs1zs9Mlhof0TXgN9XQEY9ZJ2g2kZsH4Ef99wn5MR0pQOhkAHvgYZfHBvXOR795WnWKF_rUiE85abp5CAA", + ) + } + + #[test] + pub fn test_encode() { + let sk = k256::SecretKey::from_slice(&SECRET_KEY).unwrap(); + let json: Value = serde_json::from_str(&message()).unwrap(); + let result = sign(&json, &sk).unwrap(); + + assert_eq!(result, signature()) + } + + #[test] + pub fn test_verify() { + let pk = k256::PublicKey::from_sec1_bytes(&PUBLIC_KEY).unwrap(); + let json: Value = serde_json::from_str(&message()).unwrap(); + verify(&json, &signature(), &pk).unwrap(); + } +} diff --git a/src/verifiable_credentials/mod.rs b/src/verifiable_credentials/mod.rs index 5886dd3..28fd00b 100644 --- a/src/verifiable_credentials/mod.rs +++ b/src/verifiable_credentials/mod.rs @@ -1,3 +1,4 @@ pub mod credential_signer; pub mod did_vc; +pub mod jws; pub mod types; diff --git a/src/verifiable_credentials/types.rs b/src/verifiable_credentials/types.rs index 5197183..86ee7ce 100644 --- a/src/verifiable_credentials/types.rs +++ b/src/verifiable_credentials/types.rs @@ -1,3 +1,4 @@ +use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -32,7 +33,7 @@ pub struct Proof { pub proof_purpose: String, #[serde(rename = "created")] - pub created: String, + pub created: DateTime, #[serde(rename = "verificationMethod")] pub verification_method: String, @@ -60,7 +61,7 @@ pub struct VerifiableCredentials { pub issuer: Issuer, #[serde(rename = "issuanceDate")] - pub issuance_date: String, + pub issuance_date: DateTime, #[serde(rename = "expirationDate", skip_serializing_if = "Option::is_none")] pub expiration_date: Option, @@ -77,3 +78,21 @@ pub struct VerifiableCredentials { #[serde(rename = "proof", skip_serializing_if = "Option::is_none")] pub proof: Option, } + +impl VerifiableCredentials { + pub fn new(from_did: String, message: Value, issuance_date: DateTime) -> Self { + let r#type = "VerifiableCredential".to_string(); + let context = "https://www.w3.org/2018/credentials/v1".to_string(); + + VerifiableCredentials { + id: None, + issuer: Issuer { id: from_did }, + r#type: vec![r#type], + context: vec![context], + issuance_date, + credential_subject: CredentialSubject { id: None, container: message }, + expiration_date: None, + proof: None, + } + } +}