From 3710daf281b9a0311d8ba44e3f6ce756f381bdc9 Mon Sep 17 00:00:00 2001 From: Taylor Thomas Date: Thu, 16 Dec 2021 15:18:13 -0700 Subject: [PATCH 1/2] feat(*): Adds support for wasm32 targets This will allow anyone who wants to use the nkeys library in a `wasm32-unknown-unknown` target to do so (such as in wasmcloud actors). It also enables users to generate their keys using different sources of random data Signed-off-by: Taylor Thomas --- .gitignore | 3 +- Cargo.toml | 15 ++++++---- src/bin/nk/main.rs | 7 ++--- src/lib.rs | 70 ++++++++++++++++++++++++++++++++++++---------- 4 files changed, 70 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 293dd90..b2195be 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /target **/*.rs.bk Cargo.lock -.idea \ No newline at end of file +.idea +.vscode/ \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 268a6dc..0bbbc3a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "nkeys" -version = "0.1.0" +version = "0.2.0" authors = ["Kevin Hoffman "] -edition = "2018" +edition = "2021" description = "Rust implementation of the NATS nkeys library" license = "Apache-2.0" homepage = "https://github.com/encabulators/nkeys" @@ -23,9 +23,9 @@ name = "nk" required-features = ["cli"] [dependencies] -signatory = "0.21" +signatory = "0.23" ed25519-dalek = { version = "1.0.1", default-features = false, features = ["u64_backend"] } -rand = "0.7.3" +rand = "0.8" byteorder = "1.3.4" data-encoding = "2.3.0" log = "0.4.11" @@ -35,5 +35,10 @@ quicli = { version = "0.4", optional = true } structopt = { version = "0.3.17", optional = true } term-table = { version = "1.3.0", optional = true } exitfailure = { version = "0.5.1", optional =true } -env_logger = { version = "0.7.1", optional = true } +env_logger = { version = "0.9", optional = true } serde_json = { version = "1.0", optional = true } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +# DIRTY HACK WARNING: We don't actually use this, but if we don't specify custom, things die because +# wasm32-unknown-unknown isn't supported +getrandom = { version = "0.2", default-features = false, features = ["custom"] } diff --git a/src/bin/nk/main.rs b/src/bin/nk/main.rs index 41208cb..67ffbbe 100644 --- a/src/bin/nk/main.rs +++ b/src/bin/nk/main.rs @@ -39,7 +39,7 @@ enum Command { #[derive(StructOpt, Debug, Clone)] enum Output { Text, - JSON, + Json, } impl FromStr for Output { @@ -47,7 +47,7 @@ impl FromStr for Output { fn from_str(s: &str) -> Result { match s { - "json" => Ok(Output::JSON), + "json" => Ok(Output::Json), "text" => Ok(Output::Text), _ => Err(OutputParseErr), } @@ -63,7 +63,6 @@ impl fmt::Display for OutputParseErr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "{}", "error parsing output type, see help for the list of accepted outputs" ) } @@ -91,7 +90,7 @@ fn generate(kt: &KeyPairType, output_type: &Output) { kp.seed().unwrap() ); } - Output::JSON => { + Output::Json => { let output = json!({ "public_key": kp.public_key(), "seed": kp.seed().unwrap(), diff --git a/src/lib.rs b/src/lib.rs index adef67f..5802e23 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,11 +45,11 @@ #![allow(dead_code)] +use std::fmt::{self, Debug}; + use crc::{extract_crc, push_crc, valid_checksum}; use ed25519_dalek::{ExpandedSecretKey, PublicKey, SecretKey, Signature, Verifier}; use rand::prelude::*; -use std::fmt; -use std::fmt::Debug; const ENCODED_SEED_LENGTH: usize = 58; @@ -145,57 +145,95 @@ impl From for KeyPairType { } impl KeyPair { - /// Creates a new key pair of the given type + /// Creates a new key pair of the given type. + /// + /// NOTE: This is not available if using on a wasm32-unknown-unknown target due to the lack of + /// rand support. Use [`new_from_raw`](KeyPair::new_from_raw) instead + #[cfg(not(target_arch = "wasm32"))] pub fn new(kp_type: KeyPairType) -> KeyPair { // If this unwrap fails, then the library is invalid, so the unwrap is OK here - let s = create_seed().unwrap(); - KeyPair { + Self::new_from_raw(kp_type, generate_seed_rand()).unwrap() + } + + /// Create a new keypair using a pre-existing set of random bytes. + /// + /// Returns an error if there is an issue using the bytes to generate the key + /// NOTE: These bytes should be generated from a cryptographically secure random source. + pub fn new_from_raw(kp_type: KeyPairType, random_bytes: [u8; 32]) -> Result { + let s = create_seed(random_bytes)?; + Ok(KeyPair { kp_type, pk: pk_from_seed(&s), sk: Some(s), - } + }) } /// Creates a new user key pair with a seed that has a **U** prefix + /// + /// NOTE: This is not available if using on a wasm32-unknown-unknown target due to the lack of + /// rand support. Use [`new_from_raw`](KeyPair::new_from_raw) instead + #[cfg(not(target_arch = "wasm32"))] pub fn new_user() -> KeyPair { Self::new(KeyPairType::User) } /// Creates a new account key pair with a seed that has an **A** prefix + /// + /// NOTE: This is not available if using on a wasm32-unknown-unknown target due to the lack of + /// rand support. Use [`new_from_raw`](KeyPair::new_from_raw) instead + #[cfg(not(target_arch = "wasm32"))] pub fn new_account() -> KeyPair { Self::new(KeyPairType::Account) } /// Creates a new operator key pair with a seed that has an **O** prefix + /// + /// NOTE: This is not available if using on a wasm32-unknown-unknown target due to the lack of + /// rand support. Use [`new_from_raw`](KeyPair::new_from_raw) instead + #[cfg(not(target_arch = "wasm32"))] pub fn new_operator() -> KeyPair { Self::new(KeyPairType::Operator) } /// Creates a new cluster key pair with a seed that has the **C** prefix + /// + /// NOTE: This is not available if using on a wasm32-unknown-unknown target due to the lack of + /// rand support. Use [`new_from_raw`](KeyPair::new_from_raw) instead + #[cfg(not(target_arch = "wasm32"))] pub fn new_cluster() -> KeyPair { Self::new(KeyPairType::Cluster) } /// Creates a new server key pair with a seed that has the **N** prefix + /// + /// NOTE: This is not available if using on a wasm32-unknown-unknown target due to the lack of + /// rand support. Use [`new_from_raw`](KeyPair::new_from_raw) instead + #[cfg(not(target_arch = "wasm32"))] pub fn new_server() -> KeyPair { Self::new(KeyPairType::Server) } /// Creates a new module (e.g. WebAssembly) key pair with a seed that has the **M** prefix + /// + /// NOTE: This is not available if using on a wasm32-unknown-unknown target due to the lack of + /// rand support. Use [`new_from_raw`](KeyPair::new_from_raw) instead + #[cfg(not(target_arch = "wasm32"))] pub fn new_module() -> KeyPair { Self::new(KeyPairType::Module) } /// Creates a new service / service provider key pair with a seed that has the **V** prefix + /// + /// NOTE: This is not available if using on a wasm32-unknown-unknown target due to the lack of + /// rand support. Use [`new_from_raw`](KeyPair::new_from_raw) instead + #[cfg(not(target_arch = "wasm32"))] pub fn new_service() -> KeyPair { Self::new(KeyPairType::Service) } /// Returns the encoded, human-readable public key of this key pair pub fn public_key(&self) -> String { - let mut raw = vec![]; - - raw.push(get_prefix_byte(&self.kp_type)); + let mut raw = vec![get_prefix_byte(&self.kp_type)]; raw.extend(self.pk.as_bytes()); @@ -216,9 +254,9 @@ impl KeyPair { /// Attempts to verify that the given signature is valid for the given input pub fn verify(&self, input: &[u8], sig: &[u8]) -> Result<()> { - let mut fixedsig = [0; ed25519_dalek::ed25519::SIGNATURE_LENGTH]; + let mut fixedsig = [0; ed25519_dalek::Signature::BYTE_SIZE]; fixedsig.copy_from_slice(sig); - let insig = ed25519_dalek::Signature::new(fixedsig); + let insig = ed25519_dalek::Signature::from_bytes(&fixedsig)?; match self.pk.verify(input, &insig) { Ok(()) => Ok(()), @@ -288,7 +326,7 @@ impl KeyPair { Err(err!( InvalidPrefix, "Incorrect byte prefix: {}", - source.chars().nth(0).unwrap() + source.chars().next().unwrap() )) } else { let b2 = (raw[0] & 7) << 5 | ((raw[1] & 248) >> 3); @@ -323,11 +361,13 @@ fn decode_raw(raw: &[u8]) -> Result> { } } -fn create_seed() -> Result { +fn generate_seed_rand() -> [u8; 32] { let mut rng = rand::thread_rng(); - let rnd_bytes = rng.gen::<[u8; 32]>(); + rng.gen::<[u8; 32]>() +} - SecretKey::from_bytes(&rnd_bytes[..]).map_err(|e| e.into()) +fn create_seed(rand_bytes: [u8; 32]) -> Result { + SecretKey::from_bytes(&rand_bytes[..]).map_err(|e| e.into()) } fn get_prefix_byte(kp_type: &KeyPairType) -> u8 { From 0b3d45ada20681de74a23e0e927eb1817225a0f1 Mon Sep 17 00:00:00 2001 From: Taylor Thomas Date: Thu, 16 Dec 2021 16:15:23 -0700 Subject: [PATCH 2/2] Removes old things from Cargo.toml Signed-off-by: Taylor Thomas --- Cargo.toml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0bbbc3a..7d615c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,20 +1,17 @@ [package] name = "nkeys" version = "0.2.0" -authors = ["Kevin Hoffman "] +authors = ["wasmCloud Team"] edition = "2021" description = "Rust implementation of the NATS nkeys library" license = "Apache-2.0" -homepage = "https://github.com/encabulators/nkeys" +homepage = "https://github.com/wasmcloud/nkeys" documentation = "https://docs.rs/nkeys" -repository = "https://github.com/encabulators/nkeys" +repository = "https://github.com/wasmcloud/nkeys" readme = "README.md" keywords = ["crypto", "nats", "ed25519", "cryptography"] categories = ["cryptography", "authentication"] -[badges] -travis-ci = { repository = "encabulators/nkeys", branch = "master" } - [features] cli = ["quicli", "structopt", "term-table", "exitfailure", "env_logger", "serde_json"] @@ -39,6 +36,10 @@ env_logger = { version = "0.9", optional = true } serde_json = { version = "1.0", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] -# DIRTY HACK WARNING: We don't actually use this, but if we don't specify custom, things die because -# wasm32-unknown-unknown isn't supported +# NOTE: We need this due to an underlying dependency being pulled in by +# `ed25519-dalek`. Even if we exclude `rand`, that crate pulls it in. `rand` pulls in the low level +# `getrandom` library, which explicitly doesn't support wasm32-unknown-unknown. This is a hack to +# get around that by enabling the `custom` feature of getrandom (even though we don't actually use +# the library). This makes `rand` compile in the dalek library even though we aren't actually using +# the `rand` part of it. getrandom = { version = "0.2", default-features = false, features = ["custom"] }