diff --git a/Cargo.lock b/Cargo.lock index 625605f..5faf6d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,9 +85,9 @@ checksum = "1485d4d2cc45e7b201ee3767015c96faa5904387c9d87c6efdd0fb511f12d305" [[package]] name = "arbitrary" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a7924531f38b1970ff630f03eb20a2fde69db5c590c93b0f3482e95dcc5fd60" +checksum = "8931eb436ab9bf1980c6cb2b9d1ba5390cd6793b2c6e2d2ea8147da3570c2a2e" [[package]] name = "arrayvec" @@ -161,9 +161,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "base64ct" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdca834647821e0b13d9539a8634eb62d3501b6b6c2cec1722786ee6671b851" +checksum = "ea2b2456fd614d856680dcd9fcc660a51a820fa09daef2e49772b56a193c8474" [[package]] name = "beef" @@ -276,7 +276,7 @@ dependencies = [ "lalrpop-util", "leb128", "logos", - "num-bigint", + "num-bigint 0.4.3", "num-traits", "num_enum", "paste", @@ -320,9 +320,9 @@ checksum = "17cc5e6b5ab06331c33589842070416baa137e8b0eb912b008cfd4a78ada7919" [[package]] name = "clap" -version = "3.2.17" +version = "3.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29e724a68d9319343bb3328c9cc2dfde263f4b3142ee1059a9980580171c954b" +checksum = "b15f2ea93df33549dbe2e8eecd1ca55269d63ae0b3ba1f55db030817d1c2867f" dependencies = [ "atty", "bitflags", @@ -337,9 +337,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.2.17" +version = "3.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13547f7012c01ab4a0e8f8967730ada8f9fdf419e8b6c792788f39cf4e46eefa" +checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" dependencies = [ "heck 0.4.0", "proc-macro-error", @@ -408,9 +408,9 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cpufeatures" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "dc948ebb96241bb40ab73effeb80d9f93afaad49359d159a5e61be51619fe813" dependencies = [ "libc", ] @@ -788,30 +788,30 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bfc52cbddcfd745bf1740338492bb0bd83d76c67b445f91c5fb29fae29ecaa1" +checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2acedae88d38235936c3922476b10fced7b2b68136f5e3c03c2d5be348a1115" +checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" [[package]] name = "futures-io" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93a66fc6d035a26a3ae255a6d2bca35eda63ae4c5512bef54449113f7a1228e5" +checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" [[package]] name = "futures-macro" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0db9cce532b0eae2ccf2766ab246f114b56b9cf6d445e00c2549fbc100ca045d" +checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" dependencies = [ "proc-macro2", "quote", @@ -820,21 +820,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca0bae1fe9752cf7fd9b0064c674ae63f97b37bc714d745cbde0afb7ec4e6765" +checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" [[package]] name = "futures-task" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "842fc63b931f4056a24d59de13fb1272134ce261816e063e634ad0c15cdc5306" +checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" [[package]] name = "futures-util" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0828a5471e340229c11c77ca80017937ce3c58cb788a17e5f1c2d5c485a9577" +checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" dependencies = [ "futures-core", "futures-io", @@ -1070,9 +1070,24 @@ dependencies = [ "url", ] +[[package]] +name = "ic-identity-hsm" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b8c272724cd8b723746b55336a4b2aeba86d20f4d28d7e8ddfb0f975431cd7" +dependencies = [ + "hex", + "ic-agent", + "num-bigint 0.4.3", + "pkcs11", + "sha2 0.10.2", + "simple_asn1", + "thiserror", +] + [[package]] name = "ic-repl" -version = "0.3.0" +version = "0.3.1" dependencies = [ "ansi_term", "anyhow", @@ -1084,6 +1099,7 @@ dependencies = [ "garcon", "hex", "ic-agent", + "ic-identity-hsm", "ic-wasm", "image", "inferno", @@ -1099,6 +1115,7 @@ dependencies = [ "qrcode", "rand", "ring", + "rpassword", "rustyline", "rustyline-derive", "serde", @@ -1330,11 +1347,21 @@ dependencies = [ "rle-decode-fast", ] +[[package]] +name = "libloading" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" +dependencies = [ + "cc", + "winapi", +] + [[package]] name = "lock_api" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" dependencies = [ "autocfg", "scopeguard", @@ -1470,6 +1497,17 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -1710,9 +1748,9 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pest" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69486e2b8c2d2aeb9762db7b4e00b0331156393555cff467f4163ff06821eef8" +checksum = "4b0560d531d1febc25a3c9398a62a71256c0178f2e3443baedd9ad4bb8c9deb4" dependencies = [ "thiserror", "ucd-trie", @@ -1742,9 +1780,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b13570633aff33c6d22ce47dd566b10a3b9122c2fe9d8e7501895905be532b91" +checksum = "905708f7f674518498c1f8d644481440f476d39ca6ecae83319bba7c6c12da91" dependencies = [ "pest", "pest_generator", @@ -1752,9 +1790,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3c567e5702efdc79fb18859ea74c3eb36e14c43da7b8c1f098a4ed6514ec7a0" +checksum = "5803d8284a629cc999094ecd630f55e91b561a1d1ba75e233b00ae13b91a69ad" dependencies = [ "pest", "pest_meta", @@ -1765,9 +1803,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb32be5ee3bbdafa8c7a18b0a8a8d962b66cfa2ceee4037f49267a50ee821fe" +checksum = "1538eb784f07615c6d9a8ab061089c6c54a344c5b4301db51990ca1c241e8c04" dependencies = [ "once_cell", "pest", @@ -1811,6 +1849,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs11" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3aca6d67e4c8613bfe455599d0233d00735f85df2001f6bfd9bb7ac0496b10af" +dependencies = [ + "libloading", + "num-bigint 0.2.6", +] + [[package]] name = "pkcs8" version = "0.9.0" @@ -2121,6 +2169,16 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" +[[package]] +name = "rpassword" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b763cb66df1c928432cc35053f8bd4cec3335d8559fc16010017d16b3c1680" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "rustls" version = "0.20.6" @@ -2265,9 +2323,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.143" +version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553" +checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" dependencies = [ "serde_derive", ] @@ -2293,9 +2351,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.143" +version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391" +checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" dependencies = [ "proc-macro2", "quote", @@ -2317,9 +2375,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.83" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" +checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ "itoa 1.0.3", "ryu", @@ -2407,7 +2465,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" dependencies = [ - "num-bigint", + "num-bigint 0.4.3", "num-traits", "thiserror", "time", @@ -2436,9 +2494,9 @@ checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" [[package]] name = "socket2" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "10c98bba371b9b22a71a9414e420f92ddeb2369239af08200816169d5e2dd7aa" dependencies = [ "libc", "winapi", @@ -2572,9 +2630,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db76ff9fa4b1458b3c7f077f3ff9887394058460d21e634355b273aaf11eea45" +checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b" dependencies = [ "itoa 1.0.3", "libc", diff --git a/Cargo.toml b/Cargo.toml index 5cf0098..160c07a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ic-repl" -version = "0.3.0" +version = "0.3.1" authors = ["DFINITY Team"] edition = "2018" @@ -20,6 +20,7 @@ pretty = "0.10.0" pem = "1.0" shellexpand = "2.1" ic-agent = "0.20.0" +ic-identity-hsm = "0.20.0" ic-wasm = "0.1" inferno = "0.11" walrus = "0.19" @@ -31,6 +32,7 @@ logos = "0.12" lalrpop-util = "0.19" clap = { version = "3.2", features = ["derive"] } ring = "0.16" +rpassword = "7.0" serde = "1.0" serde_json = "1.0" hex = { version = "0.4", features = ["serde"] } diff --git a/README.md b/README.md index 9616205..15f48dd 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ ic-repl [--replica [local|ic|url] | --offline [--format [ascii|png]]] --config < | // show the value of | assert // assertion | fetch // fetch the HTTP endpoint of `canister//` - | identity ? // switch to identity , with optional pem file + | identity ( | record { slot_index = ; key_id = })? // switch to identity , with optional pem file or HSM config | function ( ,* ) { ;* } // define a function := | // any candid value diff --git a/src/command.rs b/src/command.rs index 7a83a62..77225fc 100644 --- a/src/command.rs +++ b/src/command.rs @@ -22,7 +22,7 @@ pub enum Command { Export(String), Import(String, Principal, Option), Load(String), - Identity(String, Option), + Identity(String, IdentityConfig), Fetch(String, String), Func { name: String, @@ -30,6 +30,12 @@ pub enum Command { body: Vec, }, } +#[derive(Debug, Clone)] +pub enum IdentityConfig { + Empty, + Pem(String), + Hsm { slot_index: usize, key_id: String }, +} #[allow(clippy::enum_variant_names)] #[derive(Debug, Clone)] pub enum BinOp { @@ -102,37 +108,49 @@ impl Command { let res = fetch_metadata(&helper.agent, id, &path)?; println!("{}", pretty_hex::pretty_hex(&res)); } - Command::Identity(id, opt_pem) => { - use ic_agent::identity::{BasicIdentity, Secp256k1Identity}; + Command::Identity(id, config) => { + use ic_agent::identity::{BasicIdentity, Identity, Secp256k1Identity}; use ring::signature::Ed25519KeyPair; - if let Some(pem_path) = &opt_pem { - let pem_path = resolve_path(&helper.base_path, pem_path); - match Secp256k1Identity::from_pem_file(&pem_path) { - Ok(identity) => { - helper - .identity_map - .0 - .insert(id.to_string(), Arc::from(identity)); - } - Err(_) => { - let identity = BasicIdentity::from_pem_file(&pem_path)?; - helper - .identity_map - .0 - .insert(id.to_string(), Arc::from(identity)); + let identity: Arc = match &config { + IdentityConfig::Hsm { slot_index, key_id } => { + #[cfg(target_os = "macos")] + const PKCS11_LIBPATH: &str = "/Library/OpenSC/lib/pkcs11/opensc-pkcs11.so"; + #[cfg(target_os = "linux")] + const PKCS11_LIBPATH: &str = "/usr/lib/x86_64-linux-gnu/opensc-pkcs11.so"; + #[cfg(target_os = "windows")] + const PKCS11_LIBPATH: &str = + "C:/Program Files/OpenSC Project/OpenSC/pkcs11/opensc-pkcs11.dll"; + let lib_path = std::env::var("PKCS11_LIBPATH") + .unwrap_or_else(|_| PKCS11_LIBPATH.to_string()); + Arc::from(ic_identity_hsm::HardwareIdentity::new( + lib_path, + *slot_index, + key_id, + get_dfx_hsm_pin, + )?) + } + IdentityConfig::Pem(pem_path) => { + let pem_path = resolve_path(&helper.base_path, pem_path); + match Secp256k1Identity::from_pem_file(&pem_path) { + Ok(identity) => Arc::from(identity), + Err(_) => Arc::from(BasicIdentity::from_pem_file(&pem_path)?), } } - } else if helper.identity_map.0.get(&id).is_none() { - let rng = ring::rand::SystemRandom::new(); - let pkcs8_bytes = Ed25519KeyPair::generate_pkcs8(&rng)?.as_ref().to_vec(); - let keypair = Ed25519KeyPair::from_pkcs8(&pkcs8_bytes)?; - let identity = BasicIdentity::from_key_pair(keypair); - helper - .identity_map - .0 - .insert(id.to_string(), Arc::from(identity)); + IdentityConfig::Empty => match helper.identity_map.0.get(&id) { + Some(identity) => identity.clone(), + None => { + let rng = ring::rand::SystemRandom::new(); + let pkcs8_bytes = + Ed25519KeyPair::generate_pkcs8(&rng)?.as_ref().to_vec(); + let keypair = Ed25519KeyPair::from_pkcs8(&pkcs8_bytes)?; + Arc::from(BasicIdentity::from_key_pair(keypair)) + } + }, }; - let identity = helper.identity_map.0.get(&id).unwrap(); + helper + .identity_map + .0 + .insert(id.to_string(), identity.clone()); let sender = identity.sender().map_err(|e| anyhow!("{}", e))?; println!("Current identity {}", sender); @@ -205,3 +223,11 @@ pub fn resolve_path(base: &Path, file: &str) -> PathBuf { base.join(file) } } + +fn get_dfx_hsm_pin() -> Result { + std::env::var("DFX_HSM_PIN").or_else(|_| { + rpassword::prompt_password("HSM PIN: ") + .context("No DFX_HSM_PIN environment variable and cannot read HSM PIN from tty") + .map_err(|e| e.to_string()) + }) +} diff --git a/src/grammar.lalrpop b/src/grammar.lalrpop index 05381eb..4f6cfc9 100644 --- a/src/grammar.lalrpop +++ b/src/grammar.lalrpop @@ -78,7 +78,18 @@ pub Command: Command = { Ok(Command::Import(id, principal, did)) }, "fetch" => Command::Fetch(id, path), - "identity" => Command::Identity(id, path), + "identity" ?> =>? { + use super::command::IdentityConfig::*; + Ok(match config { + None => Command::Identity(id, Empty), + Some((Exp::Text(path), _)) => Command::Identity(id, Pem(path)), + Some((Exp::Record(fs), pos)) => match fs.as_slice() { + [Field { id: key, val: Exp::Text(key_id) }, Field { id: slot, val: Exp::Number(slot_index) }] if *slot == Label::Named("slot_index".to_string()) && *key == Label::Named("key_id".to_string()) => Command::Identity(id, Hsm{ key_id: key_id.to_string(), slot_index: slot_index.parse::().map_err(|_| error2("slot_index cannot convert to usize", pos))? }), + _ => return Err(error2("only expect record { slot_index : nat; key_id : text }", pos)), + }, + Some((_, pos)) => return Err(error2("Identity can either be a .pem file or HSM slot_index and key_id record", pos)), + }) + }, "function" "(" > ")" "{" > "}" => Command::Func {name,args,body}, }