Skip to content

Commit

Permalink
Add key generation subcommands (#259)
Browse files Browse the repository at this point in the history
Also:
- move to clap v3; and
- breaking change for did-auth, use `-H` instead of `-h` because of its conflict with the help flag.
  • Loading branch information
sbihel authored Feb 16, 2022
1 parent 6524e20 commit 3730b54
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 58 deletions.
2 changes: 1 addition & 1 deletion cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
133 changes: 90 additions & 43 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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<String>,
#[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,
},
Expand All @@ -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<MetadataProperty>,
#[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<MetadataProperty>,
#[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,
},
/*
Expand All @@ -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<String>,
/// IRI for expandContext option
#[structopt(short = "c", long)]
#[clap(short = 'c', long)]
expand_context: Option<String>,
/// Additional values for JSON-LD @context property.
#[structopt(short = "C", long)]
#[clap(short = 'C', long)]
more_context_json: Option<String>,
},
/*
Expand All @@ -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<String>,
#[structopt(env, short, long)]
#[clap(env, short, long)]
pub verification_method: Option<URI>,
#[structopt(env, short, long)]
#[clap(env, short, long)]
pub proof_purpose: Option<ProofPurpose>,
#[structopt(env, short, long)]
#[clap(env, short, long)]
pub created: Option<DateTime<Utc>>,
#[structopt(env, short = "C", long)]
#[clap(env, short = 'C', long)]
pub challenge: Option<String>,
#[structopt(env, short, long)]
#[clap(env, short, long)]
pub domain: Option<String>,

// 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<PathBuf>,
#[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<JWK>,
/// Request signature using SSH Agent
#[structopt(short = "S", long, group = "key_group")]
#[clap(short = 'S', long, group = "key_group")]
ssh_agent: bool,
}

Expand Down Expand Up @@ -247,6 +251,23 @@ impl From<ProofOptions> 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
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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");
Expand Down
6 changes: 3 additions & 3 deletions cli/src/opts.rs
Original file line number Diff line number Diff line change
@@ -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<HTTPDIDResolver>,
#[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<HTTPDIDResolver>,
}
Expand Down
2 changes: 1 addition & 1 deletion cli/tests/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ fn didkit_cli() {
"did-auth",
"-k",
"tests/ed25519-key.jwk",
"-h",
"--holder",
&did.to_string(),
"-v",
&verification_method.trim(),
Expand Down
2 changes: 1 addition & 1 deletion cli/tests/example.sh
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ then
fi
if ! didkit did-auth \
-k key.jwk \
-h "$did" \
--holder "$did" \
-p authentication \
-C "$challenge" \
-v "$verification_method" \
Expand Down
2 changes: 1 addition & 1 deletion http/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
16 changes: 8 additions & 8 deletions http/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<u16>,
/// Hostname to listen on
#[structopt(env, short = "s", long)]
#[clap(env, short = 's', long)]
host: Option<std::net::IpAddr>,
/// 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<Vec<PathBuf>>,
#[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."
)]
Expand Down

0 comments on commit 3730b54

Please sign in to comment.