From 3730b543933b09ec7e0e7005ce9c5e7b608b1ddb Mon Sep 17 00:00:00 2001 From: Simon Bihel Date: Wed, 16 Feb 2022 15:27:39 +0000 Subject: [PATCH] Add key generation subcommands (#259) Also: - move to clap v3; and - breaking change for did-auth, use `-H` instead of `-h` because of its conflict with the help flag. --- cli/Cargo.toml | 2 +- cli/src/main.rs | 133 +++++++++++++++++++++++++++++-------------- cli/src/opts.rs | 6 +- cli/tests/cli.rs | 2 +- cli/tests/example.sh | 2 +- http/Cargo.toml | 2 +- http/src/main.rs | 16 +++--- 7 files changed, 105 insertions(+), 58 deletions(-) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index b62194d5..309c8ac1 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -26,7 +26,7 @@ tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } didkit = { version = "0.3", path = "../lib", features = ["http-did"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -structopt = "0.3" +clap = { version = "3.0", features = ["derive", "env"] } did-method-key = { version = "0.1", path = "../../ssi/did-key" } ssi = { version = "0.3", path = "../../ssi", default-features = false } thiserror = "1.0" diff --git a/cli/src/main.rs b/cli/src/main.rs index 67644f88..a58a4fa5 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -4,10 +4,10 @@ use std::path::PathBuf; use std::str::FromStr; use chrono::prelude::*; +use clap::{AppSettings, ArgGroup, Parser, StructOpt}; use serde::Serialize; use serde_json::Value; use sshkeys::PublicKey; -use structopt::{clap::AppSettings, clap::ArgGroup, StructOpt}; use did_method_key::DIDKey; use didkit::generate_proof; @@ -22,11 +22,15 @@ use didkit_cli::opts::ResolverOptions; #[derive(StructOpt, Debug)] pub enum DIDKit { /// Generate and output a Ed25519 keypair in JWK format + #[clap(setting(clap::AppSettings::Hidden))] GenerateEd25519Key, + /// Subcommand for keypair operations + #[clap(subcommand)] + Key(KeyCmd), /// Output a did:key DID for a JWK. Deprecated in favor of key-to-did. - #[structopt(setting = AppSettings::Hidden)] + #[clap(setting = AppSettings::Hidden)] KeyToDIDKey { - #[structopt(flatten)] + #[clap(flatten)] key: KeyArg, }, /// Output a DID for a given JWK according to the provided DID method name or pattern @@ -42,21 +46,21 @@ pub enum DIDKit { /// begins with `did:pkh:tz`. KeyToDID { /// DID method name or pattern. e.g. `key`, `tz`, or `pkh:tz` - #[structopt(default_value = "key")] + #[clap(default_value = "key")] method_pattern: String, - #[structopt(flatten)] + #[clap(flatten)] key: KeyArg, }, /// Output a verificationMethod DID URL for a JWK and DID method name/pattern KeyToVerificationMethod { /// DID method id or pattern. e.g. `key`, `tz`, or `pkh:tz` method_pattern: Option, - #[structopt(flatten)] + #[clap(flatten)] key: KeyArg, }, /// Convert a SSH public key to a JWK SshPkToJwk { - #[structopt(parse(try_from_str=PublicKey::from_string))] + #[clap(parse(try_from_str=PublicKey::from_string))] /// SSH Public Key ssh_pk: PublicKey, }, @@ -69,36 +73,36 @@ pub enum DIDKit { /// Resolve a DID to a DID Document. DIDResolve { did: String, - #[structopt(short = "m", long)] + #[clap(short = 'm', long)] /// Return resolution result with metadata with_metadata: bool, - #[structopt(short = "i", name = "name=value")] + #[clap(short = 'i', name = "name=value")] /// DID resolution input metadata input_metadata: Vec, - #[structopt(flatten)] + #[clap(flatten)] resolver_options: ResolverOptions, }, /// Dereference a DID URL to a resource. DIDDereference { did_url: String, - #[structopt(short = "m", long)] + #[clap(short = 'm', long)] /// Return resolution result with metadata with_metadata: bool, - #[structopt(short = "i", name = "name=value")] + #[clap(short = 'i', name = "name=value")] /// DID dereferencing input metadata input_metadata: Vec, - #[structopt(flatten)] + #[clap(flatten)] resolver_options: ResolverOptions, }, /// Authenticate with a DID. DIDAuth { - #[structopt(flatten)] + #[clap(flatten)] key: KeyArg, - #[structopt(short = "h", long)] + #[clap(short = 'H', long)] holder: String, - #[structopt(flatten)] + #[clap(flatten)] proof_options: ProofOptions, - #[structopt(flatten)] + #[clap(flatten)] resolver_options: ResolverOptions, }, /* @@ -114,46 +118,46 @@ pub enum DIDKit { // VC Functionality /// Issue Credential VCIssueCredential { - #[structopt(flatten)] + #[clap(flatten)] key: KeyArg, - #[structopt(flatten)] + #[clap(flatten)] proof_options: ProofOptions, - #[structopt(flatten)] + #[clap(flatten)] resolver_options: ResolverOptions, }, /// Verify Credential VCVerifyCredential { - #[structopt(flatten)] + #[clap(flatten)] proof_options: ProofOptions, - #[structopt(flatten)] + #[clap(flatten)] resolver_options: ResolverOptions, }, /// Issue Presentation VCIssuePresentation { - #[structopt(flatten)] + #[clap(flatten)] key: KeyArg, - #[structopt(flatten)] + #[clap(flatten)] proof_options: ProofOptions, - #[structopt(flatten)] + #[clap(flatten)] resolver_options: ResolverOptions, }, /// Verify Presentation VCVerifyPresentation { - #[structopt(flatten)] + #[clap(flatten)] resolver_options: ResolverOptions, - #[structopt(flatten)] + #[clap(flatten)] proof_options: ProofOptions, }, /// Convert JSON-LD to URDNA2015-canonicalized RDF N-Quads ToRdfURDNA2015 { /// Base IRI - #[structopt(short = "b", long)] + #[clap(short = 'b', long)] base: Option, /// IRI for expandContext option - #[structopt(short = "c", long)] + #[clap(short = 'c', long)] expand_context: Option, /// Additional values for JSON-LD @context property. - #[structopt(short = "C", long)] + #[clap(short = 'C', long)] more_context_json: Option, }, /* @@ -176,42 +180,42 @@ pub enum DIDKit { #[non_exhaustive] pub struct ProofOptions { // Options as in vc-api (vc-http-api) - #[structopt(env, short, long)] + #[clap(env, short, long)] pub type_: Option, - #[structopt(env, short, long)] + #[clap(env, short, long)] pub verification_method: Option, - #[structopt(env, short, long)] + #[clap(env, short, long)] pub proof_purpose: Option, - #[structopt(env, short, long)] + #[clap(env, short, long)] pub created: Option>, - #[structopt(env, short = "C", long)] + #[clap(env, short = 'C', long)] pub challenge: Option, - #[structopt(env, short, long)] + #[clap(env, short, long)] pub domain: Option, // Non-standard options - #[structopt(env, default_value, short = "f", long)] + #[clap(env, default_value_t, short = 'f', long)] pub proof_format: ProofFormat, } #[derive(StructOpt, Debug)] -#[structopt(group = ArgGroup::with_name("key_group").multiple(true).required(true))] +#[clap(group = ArgGroup::new("key_group").multiple(true).required(true))] pub struct KeyArg { - #[structopt(env, short, long, parse(from_os_str), group = "key_group")] + #[clap(env, short, long, parse(from_os_str), group = "key_group")] key_path: Option, - #[structopt( + #[clap( env, short, long, parse(try_from_str = serde_json::from_str), hide_env_values = true, - conflicts_with = "key_path", + conflicts_with = "key-path", group = "key_group", help = "WARNING: you should not use this through the CLI in a production environment, prefer its environment variable." )] jwk: Option, /// Request signature using SSH Agent - #[structopt(short = "S", long, group = "key_group")] + #[clap(short = 'S', long, group = "key_group")] ssh_agent: bool, } @@ -247,6 +251,23 @@ impl From for LinkedDataProofOptions { } } +#[derive(StructOpt, Debug)] +pub enum KeyCmd { + /// Generate and output a keypair in JWK format + #[clap(subcommand)] + Generate(KeyGenerateCmd), +} + +#[derive(StructOpt, Debug)] +pub enum KeyGenerateCmd { + /// Generate and output a Ed25519 keypair in JWK format + Ed25519, + /// Generate and output a K-256 keypair in JWK format + Secp256k1, + /// Generate and output a P-256 keypair in JWK format + Secp256r1, +} + #[derive(Debug, Serialize)] /// Subset of [DID Metadata Structure][metadata] that is just a string property name and string value. /// [metadata]: https://w3c.github.io/did-core/#metadata-structure @@ -328,6 +349,12 @@ mod tests { let meta = metadata_properties_to_value(props).unwrap(); assert_eq!(meta, json!({"name": ["value1", "value2"]})); } + + #[test] + fn verify_app() { + use clap::IntoApp; + DIDKit::into_app().debug_assert() + } } fn get_ssh_agent_sock() -> String { @@ -349,7 +376,7 @@ set. For more info, see the manual for ssh-agent(1) and ssh-add(1). fn main() { let rt = runtime::get().unwrap(); - let opt = DIDKit::from_args(); + let opt = DIDKit::parse(); let ssh_agent_sock; match opt { @@ -359,6 +386,26 @@ fn main() { println!("{}", jwk_str); } + DIDKit::Key(cmd) => match cmd { + KeyCmd::Generate(cmd_generate) => { + let jwk_str = match cmd_generate { + KeyGenerateCmd::Ed25519 => { + let jwk = JWK::generate_ed25519().unwrap(); + serde_json::to_string(&jwk).unwrap() + } + KeyGenerateCmd::Secp256k1 => { + let jwk = JWK::generate_secp256k1().unwrap(); + serde_json::to_string(&jwk).unwrap() + } + KeyGenerateCmd::Secp256r1 => { + let jwk = JWK::generate_p256().unwrap(); + serde_json::to_string(&jwk).unwrap() + } + }; + println!("{}", jwk_str); + } + }, + DIDKit::KeyToDIDKey { key } => { // Deprecated in favor of KeyToDID eprintln!("didkit: use key-to-did instead of key-to-did-key"); diff --git a/cli/src/opts.rs b/cli/src/opts.rs index df7d6624..68d8f9c7 100644 --- a/cli/src/opts.rs +++ b/cli/src/opts.rs @@ -1,13 +1,13 @@ -use structopt::StructOpt; +use clap::StructOpt; use didkit::{HTTPDIDResolver, SeriesResolver, DID_METHODS}; #[derive(StructOpt, Debug, Clone, Default)] pub struct ResolverOptions { - #[structopt(env, short = "r", long, parse(from_str = HTTPDIDResolver::new))] + #[clap(env, short = 'r', long, parse(from_str = HTTPDIDResolver::new))] /// Fallback DID Resolver HTTP(S) endpoint, for non-built-in DID methods. pub did_resolver: Option, - #[structopt(env, short = "R", long, parse(from_str = HTTPDIDResolver::new))] + #[clap(env, short = 'R', long, parse(from_str = HTTPDIDResolver::new))] /// Override DID Resolver HTTP(S) endpoint, for all DID methods. pub did_resolver_override: Option, } diff --git a/cli/tests/cli.rs b/cli/tests/cli.rs index 6d6e1bce..40d70020 100644 --- a/cli/tests/cli.rs +++ b/cli/tests/cli.rs @@ -189,7 +189,7 @@ fn didkit_cli() { "did-auth", "-k", "tests/ed25519-key.jwk", - "-h", + "--holder", &did.to_string(), "-v", &verification_method.trim(), diff --git a/cli/tests/example.sh b/cli/tests/example.sh index d88863cb..7735b481 100755 --- a/cli/tests/example.sh +++ b/cli/tests/example.sh @@ -197,7 +197,7 @@ then fi if ! didkit did-auth \ -k key.jwk \ - -h "$did" \ + --holder "$did" \ -p authentication \ -C "$challenge" \ -v "$verification_method" \ diff --git a/http/Cargo.toml b/http/Cargo.toml index 54a29ac5..d8d808ca 100644 --- a/http/Cargo.toml +++ b/http/Cargo.toml @@ -24,7 +24,7 @@ ring = ["ssi/ring"] didkit = { version = "0.3", path = "../lib", features = ["http-did"] } didkit-cli = { version = "^0.1.1", path = "../cli" } tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } -structopt = "0.3" +clap = { version = "3.0", features = ["derive", "env"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_urlencoded = "0.7" diff --git a/http/src/main.rs b/http/src/main.rs index e52dc5de..416efe4e 100644 --- a/http/src/main.rs +++ b/http/src/main.rs @@ -2,8 +2,8 @@ use std::fs::File; use std::io::BufReader; use std::path::PathBuf; +use clap::{AppSettings, ArgGroup, Parser, StructOpt}; use hyper::Server; -use structopt::StructOpt; use didkit::JWK; use didkit_cli::opts::ResolverOptions; @@ -13,28 +13,28 @@ use didkit_http::Error; #[derive(StructOpt, Debug)] pub struct DIDKitHttpOpts { /// Port to listen on - #[structopt(env, short, long)] + #[clap(env, short, long)] port: Option, /// Hostname to listen on - #[structopt(env, short = "s", long)] + #[clap(env, short = 's', long)] host: Option, /// JWK to use for issuing - #[structopt(flatten)] + #[clap(flatten)] key: KeyArg, - #[structopt(flatten)] + #[clap(flatten)] resolver_options: ResolverOptions, } #[derive(StructOpt, Debug)] pub struct KeyArg { - #[structopt(env, short, long, parse(from_os_str), group = "key_group")] + #[clap(env, short, long, parse(from_os_str), group = "key_group")] key_path: Option>, - #[structopt( + #[clap( env, short, long, parse(try_from_str = serde_json::from_str), - conflicts_with = "key_path", + conflicts_with = "key-path", group = "key_group", help = "WARNING: you should not use this through the CLI in a production environment, prefer its environment variable." )]