diff --git a/Cargo.lock b/Cargo.lock index 5e35c1f357..06f531fd3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -387,6 +387,11 @@ name = "autocfg" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "awc" version = "0.2.8" @@ -894,6 +899,7 @@ dependencies = [ "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "hotwatch 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "ic-http-agent 0.1.0", + "ic-identity-manager 0.1.0", "indicatif 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy-init 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1508,7 +1514,7 @@ dependencies = [ "hex 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "leb128 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "mockito 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl 0.10.24 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.10.28 (registry+https://github.com/rust-lang/crates.io-index)", "proptest 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1519,6 +1525,18 @@ dependencies = [ "url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ic-identity-manager" +version = "0.1.0" +dependencies = [ + "ic-http-agent 0.1.0", + "openssl 0.10.28 (registry+https://github.com/rust-lang/crates.io-index)", + "pem 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ring 0.16.11 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_cbor 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "idna" version = "0.1.5" @@ -1881,9 +1899,9 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl 0.10.24 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.10.28 (registry+https://github.com/rust-lang/crates.io-index)", "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.52 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)", "schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", "security-framework 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "security-framework-sys 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2008,7 +2026,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "openssl" -version = "0.10.24" +version = "0.10.28" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2016,7 +2034,7 @@ dependencies = [ "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", - "openssl-sys 0.9.52 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2026,10 +2044,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "openssl-sys" -version = "0.9.52" +version = "0.9.54" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", "pkg-config 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2132,6 +2150,16 @@ dependencies = [ "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "pem" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "percent-encoding" version = "1.0.1" @@ -2649,6 +2677,20 @@ name = "rgb" version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "ring" +version = "0.16.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.45 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", + "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "web-sys 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rle-decode-fast" version = "1.0.1" @@ -2941,6 +2983,11 @@ name = "sourcefile" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "string" version = "0.2.1" @@ -3466,6 +3513,11 @@ name = "unicode-xid" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "untrusted" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "url" version = "1.7.2" @@ -3828,6 +3880,7 @@ dependencies = [ "checksum assert-json-diff 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "32946b6d31d50d0e35896c864907f9cb7e47b52bd875fa3c058618601cfdefb1" "checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" "checksum autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875" +"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" "checksum awc 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "5e995283278dd3bf0449e7534e77184adb1570c0de8b6a50bf7c9d01ad8db8c4" "checksum backtrace 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)" = "690a62be8920ccf773ee00ef0968649b0e724cda8bd5b12286302b4ae955fdf5" "checksum backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b" @@ -4001,9 +4054,9 @@ dependencies = [ "checksum num_enum_derive 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "47b3a6cd5b8ac2ca83258cbaf693f740aca5681818b4720497305fd642447eb0" "checksum number_prefix 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b02fc0ff9a9e4b35b3342880f48e896ebf69f2967921fe8646bf5b7125956a" "checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" -"checksum openssl 0.10.24 (registry+https://github.com/rust-lang/crates.io-index)" = "8152bb5a9b5b721538462336e3bef9a539f892715e5037fda0f984577311af15" +"checksum openssl 0.10.28 (registry+https://github.com/rust-lang/crates.io-index)" = "973293749822d7dd6370d6da1e523b0d1db19f06c459134c658b2a4261378b52" "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" -"checksum openssl-sys 0.9.52 (registry+https://github.com/rust-lang/crates.io-index)" = "c977d08e1312e2f7e4b86f9ebaa0ed3b19d1daff75fae88bbb88108afbd801fc" +"checksum openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)" = "1024c0a59774200a555087a6da3f253a9095a5f344e353b212ac4c8b8e450986" "checksum ordermap 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a86ed3f5f244b372d6b1a00b72ef7f8876d0bc6a78a4c9985c53614041512063" "checksum parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "92e98c49ab0b7ce5b222f2cc9193fc4efe11c6d0bd4f648e374684a6857b1cfc" "checksum parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fa7767817701cce701d5585b9c4db3cdd02086398322c1d7e8bf5094a96a2ce7" @@ -4013,6 +4066,7 @@ dependencies = [ "checksum parking_lot_core 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7582838484df45743c8434fbff785e8edf260c28748353d44bc0da32e0ceabf1" "checksum paste 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "423a519e1c6e828f1e73b720f9d9ed2fa643dce8a7737fb43235ce0b41eeaa49" "checksum paste-impl 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "4214c9e912ef61bf42b81ba9a47e8aad1b2ffaf739ab162bf96d1e011f54e6c5" +"checksum pem 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a1581760c757a756a41f0ee3ff01256227bdf64cb752839779b95ffb01c59793" "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" "checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" "checksum petgraph 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3659d1ee90221741f65dd128d9998311b0e40c5d3c23a62445938214abce4f" @@ -4067,6 +4121,7 @@ dependencies = [ "checksum reqwest 0.9.20 (registry+https://github.com/rust-lang/crates.io-index)" = "0f6d896143a583047512e59ac54a215cb203c29cc941917343edea3be8df9c78" "checksum resolv-conf 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b263b4aa1b5de9ffc0054a2386f96992058bb6870aab516f8cdeb8a667d56dcb" "checksum rgb 0.8.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2089e4031214d129e201f8c3c8c2fe97cd7322478a0d1cdf78e7029b0042efdb" +"checksum ring 0.16.11 (registry+https://github.com/rust-lang/crates.io-index)" = "741ba1704ae21999c00942f9f5944f801e977f54302af346b596287599ad1862" "checksum rle-decode-fast 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac" "checksum rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf" "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" @@ -4101,6 +4156,7 @@ dependencies = [ "checksum smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44e59e0c9fa00817912ae6e4e6e3c4fe04455e75699d06eedc7d85917ed8e8f4" "checksum socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85" "checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3" +"checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" "checksum string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d" "checksum string_cache 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "96ccb3a75a3caf2d7f2eb9ada86ec1fbbd4c74ad2bd8dc00a96a0c2f93509ef0" "checksum string_cache_codegen 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f45ed1b65bf9a4bf2f7b7dc59212d1926e9eaf00fa998988e420fd124467c6" @@ -4155,6 +4211,7 @@ dependencies = [ "checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +"checksum untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60369ef7a31de49bcb3f6ca728d4ba7300d9a1658f94c727d4cab8c8d9f4aece" "checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" "checksum url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61" "checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" diff --git a/Cargo.toml b/Cargo.toml index cb973af530..492de2f414 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,5 +4,6 @@ members = [ "src/dfx_derive", "src/dfx_info", "src/ic_http_agent", + "src/ic_identity_manager", "src/serde_idl", ] diff --git a/src/dfx/Cargo.toml b/src/dfx/Cargo.toml index 42ac7b11a0..cb925693c0 100644 --- a/src/dfx/Cargo.toml +++ b/src/dfx/Cargo.toml @@ -29,6 +29,7 @@ flate2 = "1.0.11" futures = "0.1.28" hex = "0.3.2" ic-http-agent = { path = "../ic_http_agent" } +ic-identity-manager = { path = "../ic_identity_manager" } indicatif = "0.13.0" lazy-init = "0.3.0" lazy_static = "1.4.0" diff --git a/src/dfx/src/lib/environment.rs b/src/dfx/src/lib/environment.rs index 1da4343fca..5ed7ad1552 100644 --- a/src/dfx/src/lib/environment.rs +++ b/src/dfx/src/lib/environment.rs @@ -2,12 +2,14 @@ use crate::config::cache::{Cache, DiskBasedCache}; use crate::config::dfinity::Config; use crate::config::dfx_version; use crate::lib::error::DfxResult; +use crate::lib::identity_interface::Identity; use crate::lib::progress_bar::ProgressBar; use ic_http_agent::{Agent, AgentConfig}; use lazy_init::Lazy; use semver::Version; use slog::Record; +use std::fs::create_dir_all; use std::path::{Path, PathBuf}; use std::rc::Rc; @@ -75,7 +77,7 @@ impl EnvironmentImpl { .into_path(), Some(c) => c.get_path().parent().unwrap().join(".dfx"), }; - std::fs::create_dir_all(&temp_dir)?; + create_dir_all(&temp_dir)?; // Figure out which version of DFX we should be running. This will use the following // fallback sequence: @@ -156,9 +158,15 @@ impl Environment for EnvironmentImpl { let start = config.get_config().get_defaults().get_start(); let address = start.get_address("localhost"); let port = start.get_port(8000); + let dfx_root = self.get_temp_dir(); + let local_project_identity = dfx_root.join("identity").join("default"); + if create_dir_all(&local_project_identity).is_err() { + return None; + } Agent::new(AgentConfig { url: format!("http://{}:{}", address, port).as_str(), + signer: Box::new(Identity::new(local_project_identity)), ..AgentConfig::default() }) .ok() @@ -195,13 +203,19 @@ pub struct AgentEnvironment<'a> { impl<'a> AgentEnvironment<'a> { pub fn new(backend: &'a dyn Environment, agent_url: &str) -> Self { + // We do not expose the path directly for now. + let dfx_root = backend.get_temp_dir(); + let local_project_identity = dfx_root.join("identity").join("default"); + // N.B. Do not assume the existence of this directory yet. + create_dir_all(&local_project_identity).expect("Failed to construct identity profile"); AgentEnvironment { backend, agent: Agent::new(AgentConfig { url: agent_url, + signer: Box::new(Identity::new(local_project_identity)), ..AgentConfig::default() }) - .unwrap(), + .expect("Failed to construct agent"), } } } diff --git a/src/dfx/src/lib/identity_interface.rs b/src/dfx/src/lib/identity_interface.rs new file mode 100644 index 0000000000..4b8ea364d3 --- /dev/null +++ b/src/dfx/src/lib/identity_interface.rs @@ -0,0 +1,75 @@ +use ic_http_agent::to_request_id; +use ic_http_agent::AgentError; +use ic_http_agent::Blob; +use ic_http_agent::RequestId; +use ic_http_agent::SignedMessage; +use ic_http_agent::Signer; +use std::path::PathBuf; + +pub struct Identity(ic_identity_manager::Identity); + +impl Identity { + /// Construct a new identity handling object, providing given + /// configuration. + pub fn new(identity_config_path: PathBuf) -> Self { + Self( + // We panic as discussed, as that should not be the + // case. I personally prefer this to be an error. + ic_identity_manager::Identity::new(identity_config_path) + .expect("Expected a valid identity configuration"), + ) + } +} + +impl Signer for Identity { + fn sign<'a>( + &self, + request: Box<(dyn erased_serde::Serialize + Send + Sync + 'a)>, + ) -> Result< + ( + RequestId, + Box, + ), + AgentError, + > { + let request_id = to_request_id(&request).map_err(AgentError::from)?; + let signature_tuple = self + .0 + .sign(Blob::from(request_id).as_slice()) + .map_err(|e| AgentError::SigningError(e.to_string()))?; + let signature = Blob::from(signature_tuple.signature.clone()); + let sender_pubkey = Blob::from(signature_tuple.public_key); + let signed_request = SignedMessage { + request_with_sender: request, + signature, + sender_pubkey, + }; + Ok((request_id, Box::new(signed_request))) + } +} + +#[cfg(test)] +mod test { + use super::*; + use proptest::prelude::*; + use serde::Serialize; + use tempfile::tempdir; + + // Dummy proptest checking request id is correct for now. + proptest! { + #[test] + fn request_id_identity(request: String) { + let dir = tempdir().unwrap(); + + #[derive(Clone,Serialize)] + struct TestAPI { inner : String} + let request = TestAPI { inner: request}; + + let request_with_sender = request.clone(); + let actual_request_id = to_request_id(&request_with_sender).expect("Failed to produce request id"); + + let signer = Identity::new(dir.into_path()); + let request_id = signer.sign(Box::new(request)).expect("Failed to sign").0; + assert_eq!(request_id, actual_request_id) + }} +} diff --git a/src/dfx/src/lib/mod.rs b/src/dfx/src/lib/mod.rs index 79a5299b7b..3968e75559 100644 --- a/src/dfx/src/lib/mod.rs +++ b/src/dfx/src/lib/mod.rs @@ -1,6 +1,7 @@ pub mod canister_info; pub mod environment; pub mod error; +pub mod identity_interface; pub mod logger; pub mod message; pub mod progress_bar; diff --git a/src/ic_http_agent/src/agent/agent_error.rs b/src/ic_http_agent/src/agent/agent_error.rs index a88fda44ed..ef3a0e2ac8 100644 --- a/src/ic_http_agent/src/agent/agent_error.rs +++ b/src/ic_http_agent/src/agent/agent_error.rs @@ -10,6 +10,8 @@ pub enum AgentError { ClientError(u16, String), TimeoutWaitingForResponse, + SigningError(String), + InvalidCborData(serde_cbor::Error), ReqwestError(reqwest::Error), SerdeError(SerdeError), diff --git a/src/ic_identity_manager/Cargo.toml b/src/ic_identity_manager/Cargo.toml new file mode 100644 index 0000000000..3865d6d943 --- /dev/null +++ b/src/ic_identity_manager/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "ic-identity-manager" +version = "0.1.0" +authors = ["DFINITY Stiftung"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +openssl = "0.10.28" +pem = "0.7.0" +ring = "0.16.11" +serde = { version = "1.0", features = ["derive"] } +ic-http-agent = { path = "../ic_http_agent" } + + +[dev-dependencies] +serde_cbor = "0.10" \ No newline at end of file diff --git a/src/ic_identity_manager/examples/main.rs b/src/ic_identity_manager/examples/main.rs new file mode 100644 index 0000000000..2bf7a0caba --- /dev/null +++ b/src/ic_identity_manager/examples/main.rs @@ -0,0 +1,38 @@ +use ic_http_agent::Principal; +use ic_identity_manager::crypto_error::Result; +use ic_identity_manager::Identity; +use ring::signature::{self, KeyPair}; +use std::env::current_dir; +use std::fs; + +fn main() -> Result<()> { + const MESSAGE: &[u8] = b"Hello World!! This is an example test"; + let pwd = current_dir()?; + let identity = Identity::new(pwd)?; + let signed_message = identity.sign(MESSAGE)?; + + let pkcs8_bytes = pem::parse(fs::read("creds.pem").unwrap()).unwrap().contents; + let key_pair = signature::Ed25519KeyPair::from_pkcs8(pkcs8_bytes.as_ref())?; + let sig = key_pair.sign(MESSAGE); + assert_eq!(sig.as_ref().to_vec(), signed_message.signature); + assert_eq!( + Principal::self_authenticating(&key_pair.public_key()), + signed_message.signer + ); + assert_eq!( + key_pair.public_key().as_ref().to_vec(), + signed_message.public_key + ); + + let peer_public_key_bytes = key_pair.public_key().as_ref(); + let peer_public_key = + signature::UnparsedPublicKey::new(&signature::ED25519, peer_public_key_bytes); + peer_public_key.verify(MESSAGE, sig.as_ref())?; + + let signed_message_2 = identity.sign(MESSAGE)?; + assert_eq!(*signed_message_2.signature, *signed_message.signature); + assert_eq!(signed_message_2.public_key, signed_message.public_key); + assert_eq!(signed_message_2.signer, signed_message.signer); + + Ok(()) +} diff --git a/src/ic_identity_manager/src/basic.rs b/src/ic_identity_manager/src/basic.rs new file mode 100644 index 0000000000..9b6f802ed7 --- /dev/null +++ b/src/ic_identity_manager/src/basic.rs @@ -0,0 +1,117 @@ +//! Provides a basic example provider that utilizes unencrypted PEM +//! files. This is provided as a basic stepping stone to provide +//! further functionality. Note that working with unencrypted PEM is +//! not the best idea. +//! +//! However, there are two options: i) prompt the user per call, as +//! the agent is "stateless" or ii) provide long-running service +//! providers -- such as PGP, ssh-agent. +use crate::crypto_error::{Error, Result}; +use crate::types::Signature; + +use ic_http_agent::Principal; +use pem::{encode, Pem}; +use ring::signature::Ed25519KeyPair; +use ring::{ + rand, + signature::{self, KeyPair}, +}; +use std::fs; +use std::path::{Path, PathBuf}; + +// This module should not be re-exported. We want to ensure +// construction and handling of keys is done only here. +use self::private::BasicSignerReady; + +#[derive(Clone)] +pub struct BasicSigner { + path: PathBuf, +} + +impl BasicSigner { + pub fn new(path: PathBuf) -> Result { + if !path.is_dir() { + return Err(Error::ProviderFailedToInitialize); + } + Ok(Self { path }) + } +} + +fn generate(profile_path: &impl AsRef) -> Result { + let rng = rand::SystemRandom::new(); + let pkcs8_bytes = signature::Ed25519KeyPair::generate_pkcs8(&rng)?; + // We create a temporary file that gets overwritten every time + // we create a new provider for now. + let pem_file = profile_path.as_ref().join("creds.pem"); + fs::write(&pem_file, encode_pem_private_key(&(*pkcs8_bytes.as_ref())))?; + + assert_eq!( + pem::parse(fs::read(&pem_file)?)?.contents, + pkcs8_bytes.as_ref() + ); + Ok(pem_file) +} + +impl BasicSigner { + pub fn provide(&self) -> Result { + let mut dir = fs::read_dir(&self.path)?; + let name: std::ffi::OsString = "creds.pem".to_owned().into(); + let pem_file = if dir.any(|n| match n { + Ok(n) => n.file_name() == name, + Err(_) => false, + }) { + self.path.join("creds.pem") + } else { + generate(&self.path)? + }; + + let pkcs8_bytes = pem::parse(fs::read(pem_file)?)?.contents; + let key_pair = signature::Ed25519KeyPair::from_pkcs8(pkcs8_bytes.as_ref())?; + + Ok(BasicSignerReady { key_pair }) + } +} + +// The contents of this module while public, that is can be known and +// handled as of the new Rust iteration by other modules in the crate, +// the type constructor and associated functions shall be visible only +// by the parent module, and should not be re-exported. This is +// essentially a sealed type. +mod private { + use super::*; + /// We enforce a state transition, reading the key as necessary, only + /// to sign. TODO(eftychis): We should erase pin and erase the key + /// from memory afterwards. + pub struct BasicSignerReady { + pub key_pair: Ed25519KeyPair, + } + + impl BasicSignerReady { + pub fn sign(&self, msg: &[u8]) -> Result { + let signature = self.key_pair.sign(msg); + // At this point we shall validate the signature in this first + // skeleton version. + let public_key_bytes = self.key_pair.public_key().as_ref(); + + let public_key = + signature::UnparsedPublicKey::new(&signature::ED25519, public_key_bytes); + public_key.verify(msg, signature.as_ref())?; + Ok(Signature { + signer: self.principal(), + signature: signature.as_ref().to_vec(), + public_key: public_key_bytes.to_vec(), + }) + } + fn principal(&self) -> Principal { + Principal::self_authenticating(&self.key_pair.public_key()) + } + } +} + +fn encode_pem_private_key(key: &[u8]) -> String { + let pem = Pem { + tag: "PRIVATE KEY".to_owned(), + contents: key.to_vec(), + }; + encode(&pem) +} diff --git a/src/ic_identity_manager/src/crypto_error.rs b/src/ic_identity_manager/src/crypto_error.rs new file mode 100644 index 0000000000..b8cc656441 --- /dev/null +++ b/src/ic_identity_manager/src/crypto_error.rs @@ -0,0 +1,58 @@ +use std::{error, result}; + +#[derive(Debug)] +/// Error type for Identity operations. This can involve system +/// runtime faults, setup or cryptography failures. +pub enum Error { + /// A CryptoError is isomorphic to unit on purpose. In case of + /// such a failure, as Rust is eager in general so we don't have + /// to worry about lazy evaluation of errors. + CryptoError, + /// No provider was found. + NoProvider, + /// Failed to initialize. + IdentityFailedToInitialize, + /// Failed to initialize provider. + ProviderFailedToInitialize, + /// Failed to parse provided PEM input string. + PemError(pem::PemError), + /// Failed to access file. + IOError(std::io::Error), +} + +impl From for Error { + fn from(_: ring::error::Unspecified) -> Self { + Error::CryptoError + } +} + +impl From for Error { + fn from(_: ring::error::KeyRejected) -> Self { + Error::CryptoError + } +} + +impl From for Error { + fn from(e: pem::PemError) -> Self { + Error::PemError(e) + } +} + +impl From for Error { + fn from(e: std::io::Error) -> Self { + Error::IOError(e) + } +} + +impl error::Error for Error { + // We do not need source for now. +} + +impl std::fmt::Display for Error { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // Currently Display = Debug for all intents and purposes. + write!(fmt, "{:?}", self) + } +} + +pub type Result = result::Result; diff --git a/src/ic_identity_manager/src/lib.rs b/src/ic_identity_manager/src/lib.rs new file mode 100644 index 0000000000..d8ba15a514 --- /dev/null +++ b/src/ic_identity_manager/src/lib.rs @@ -0,0 +1,53 @@ +//! # Usage +//! +//! We expose a single type [`Identity`], currently providing only +//! signing and principal. +//! +//! # Examples +//! ```not_run +//! use ic_identity_manager::identity::Identity; +//! +//! let identity = +//! Identity::new(std::path::PathBuf::from("temp_dir")).expect("Failed to construct an identity object"); +//! let _signed_message = identity.sign(b"Hello World! This is Bob").expect("Signing failed"); +//! ``` + +/// Provides basic error type and messages. +pub mod crypto_error; + +mod basic; +mod types; + +use crate::basic::BasicSigner; +use crate::crypto_error::Error; +use crate::crypto_error::Result; +use crate::types::Signature; + +use std::path::PathBuf; + +/// An identity is a construct that denotes the set of claims of an +/// entity about itself. Namely it collects principals, under which +/// the owner of this object can authenticate and provides basic +/// operations. +pub struct Identity { + inner: BasicSigner, +} + +impl Identity { + /// Return a corresponding provided a profile path. We pass a simple + /// configuration for now, but this might change in the future. + pub fn new(path: PathBuf) -> Result { + let basic_provider = BasicSigner::new(path)?; + Ok(Self { + inner: basic_provider, + }) + } + /// Sign the provided message assuming a certain principal. + pub fn sign(&self, msg: &[u8]) -> Result { + let identity = self + .inner + .provide() + .map_err(|_| Error::IdentityFailedToInitialize)?; + identity.sign(msg) + } +} diff --git a/src/ic_identity_manager/src/types.rs b/src/ic_identity_manager/src/types.rs new file mode 100644 index 0000000000..aeaf7c96ff --- /dev/null +++ b/src/ic_identity_manager/src/types.rs @@ -0,0 +1,10 @@ +use ic_http_agent::Principal; + +// Note perhaps in the future we will need to indicate the schema +// type. +#[derive(Clone)] +pub struct Signature { + pub signer: Principal, + pub public_key: Vec, + pub signature: Vec, +}