Skip to content

Commit

Permalink
Implement signing using ssh-agent
Browse files Browse the repository at this point in the history
  • Loading branch information
clehner committed May 20, 2021
1 parent 8e3cd5a commit dcf81b2
Show file tree
Hide file tree
Showing 5 changed files with 379 additions and 13 deletions.
4 changes: 4 additions & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ serde_json = "1.0"
structopt = "0.3"
did-method-key = { version = "0.1", path = "../../ssi/did-key" }
ssi = { version = "0.2", path = "../../ssi", default-features = false }
thiserror = "1.0"
bytes = "1.0"
base64 = "0.12"
sshkeys = "0.3"

[dev-dependencies]
tokio = { version = "1.0", features = ["macros", "process"] }
Expand Down
20 changes: 18 additions & 2 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,18 @@ The proof type is set automatically based on the key file provided. JWK paramete
#### Options

- `-r, --did-resolver <url>` - [DID resolver HTTP(S) endpoint][did-resolution-https-binding], used for DID resolution and DID URL dereferencing for non-built-in DID Methods. Equivalent to environmental variable `DID_RESOLVER`.
- `-k, --key-path <key>` (required, conflicts with jwk) - Filename of JWK for signing.
- `-k, --key-path <file>` - Filename of JWK file for signing. Conflicts with `-j`.
- `-j, --jwk <jwk>` - JWK for signing. Conflicts with `-k`.
- `-S, --ssh-agent` - Use SSH agent for signing instead of JWK private key. See the section on SSH Agent below for more info.

One of `-k` (`--key-path`), `-j` (`--jwk`) or `-S` (`--ssh-agent`) is required.

The following options correspond to linked data [proof options][] as specified in [ld-proofs][] and [vc-http-api][]:

- `-C, --challenge <challenge>` - [challenge][] property of the proof
- `-c, --created <created>` - [created][] property of the proof. ISO8601 datetime. Defaults to the current time.
time.
- `-d, --domain <domain>` - [domain][] property of the proof
- `-j, --jwk <jwk>` (required, conflicts with key-path) - JWK for signing.
- `-p, --proof-purpose <proof-purpose>` [proofPurpose][] property of the proof.
- `-v, --verification-method <verification-method>` [verificationMethod][]
property of the proof. URI for proof verification information, e.g. a public key identifier.
Expand All @@ -73,6 +76,19 @@ The following options correspond to linked data [proof options][] as specified i
- `RSA`
- `OKP` (`curve`: `Ed25519`)

#### SSH Agent

DIDKit can use [SSH Agent][] for signing, as an alternative to signing with a JWK private key.
If the `-S` (`--ssh-agent`) CLI option is used, DIDKit will attempt to connect to a local instance of `ssh-agent`, via the [UNIX socket][] refered to by environmental variable `SSH_AUTH_SOCK`, following the [SSH Agent Protocol][].

##### Key selection

When `-S, `--ssh-agent` is used, the JWK referred to by `-k, --key-file` or `-j, --jwk` is treated as a public key and used to select which key from SSH Agent to use for signing. If no JWK option is used, then the SSH Agent is expected to have only one key, and that key is used for signing.

[SSH Agent]: https://en.wikipedia.org/wiki/Ssh-agent
[UNIX socket]: https://en.wikipedia.org/wiki/Unix_domain_socket
[SSH Agent Protocol]: https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent-04

### `didkit vc-verify-credential`

Verify a verifiable credential. Reads verifiable credential on standard input, and outputs verification result. Returns exit status zero if credential successfully verified, or non-zero if errors were encountered.
Expand Down
1 change: 1 addition & 0 deletions cli/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod opts;
pub mod ssh_agent;
63 changes: 52 additions & 11 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,17 +164,23 @@ pub struct KeyArg {
help = "WARNING: you should not use this through the CLI in a production environment, prefer its environment variable."
)]
jwk: Option<JWK>,
#[structopt(short = "S", long, group = "key_group")]
ssh_agent: bool,
}

impl KeyArg {
fn get_jwk(&self) -> JWK {
self.get_jwk_opt()
.expect("Key path or JWK option is required")
}
fn get_jwk_opt(&self) -> Option<JWK> {
match &self.key_path {
Some(p) => {
let key_file = File::open(p).unwrap();
let key_reader = BufReader::new(key_file);
serde_json::from_reader(key_reader).unwrap()
Some(serde_json::from_reader(key_reader).unwrap())
}
None => self.jwk.clone().unwrap(),
None => self.jwk.clone(),
}
}
}
Expand Down Expand Up @@ -275,6 +281,47 @@ mod tests {
}
}

#[derive(thiserror::Error, Debug)]
pub enum GenerateProofError {
#[error("Unable to sign: {0}")]
Sign(#[from] didkit_cli::ssh_agent::SignError),
#[error("SSI: {0}")]
SSI(#[from] ssi::error::Error),
#[error("IO: {0}")]
IO(#[from] std::io::Error),
}

async fn generate_proof(
document: &(dyn ssi::ldp::LinkedDataDocument + Sync),
key: &KeyArg,
proof_options: ProofOptions,
) -> Result<ssi::vc::Proof, GenerateProofError> {
let options = LinkedDataProofOptions::from(proof_options);
let jwk_opt: Option<JWK> = key.get_jwk_opt();
use didkit_cli::ssh_agent::sign_with_pk;
use ssi::ldp::LinkedDataProofs;
let proof = if key.ssh_agent {
use tokio::net::UnixStream;
let mut ssh_agent_sock = if let Ok(sock_path) = std::env::var("SSH_AUTH_SOCK") {
UnixStream::connect(sock_path).await?
} else {
eprintln!(
r#"didkit: missing SSH_AUTH_SOCK environmental variable for SSH Agent usage.
To use DIDKit with SSH Agent, ssh-agent must be running and $SSH_AUTH_SOCK
set. For more info, see the manual for ssh-agent(1) and ssh-add(1).
"#
);
std::process::exit(1);
};
sign_with_pk(document, options, jwk_opt.as_ref(), &mut ssh_agent_sock).await?
} else {
let jwk = jwk_opt.expect("JWK, Key Path, or SSH Agent option is required.");
LinkedDataProofs::sign(document, &options, &jwk).await?
};

Ok(proof)
}

fn main() {
let rt = runtime::get().unwrap();
let opt = DIDKit::from_args();
Expand Down Expand Up @@ -335,13 +382,11 @@ fn main() {
}

DIDKit::VCIssueCredential { key, proof_options } => {
let key: JWK = key.get_jwk();
let credential_reader = BufReader::new(stdin());
let mut credential: VerifiableCredential =
serde_json::from_reader(credential_reader).unwrap();
let options = LinkedDataProofOptions::from(proof_options);
let proof = rt
.block_on(credential.generate_proof(&key, &options))
.block_on(generate_proof(&credential, &key, proof_options))
.unwrap();
credential.add_proof(proof);
let stdout_writer = BufWriter::new(stdout());
Expand All @@ -367,13 +412,11 @@ fn main() {
}

DIDKit::VCIssuePresentation { key, proof_options } => {
let key: JWK = key.get_jwk();
let presentation_reader = BufReader::new(stdin());
let mut presentation: VerifiablePresentation =
serde_json::from_reader(presentation_reader).unwrap();
let options = LinkedDataProofOptions::from(proof_options);
let proof = rt
.block_on(presentation.generate_proof(&key, &options))
.block_on(generate_proof(&presentation, &key, proof_options))
.unwrap();
presentation.add_proof(proof);
let stdout_writer = BufWriter::new(stdout());
Expand Down Expand Up @@ -469,12 +512,10 @@ fn main() {
holder,
proof_options,
} => {
let key: JWK = key.get_jwk();
let mut presentation = VerifiablePresentation::default();
presentation.holder = Some(ssi::vc::URI::String(holder));
let options = LinkedDataProofOptions::from(proof_options);
let proof = rt
.block_on(presentation.generate_proof(&key, &options))
.block_on(generate_proof(&presentation, &key, proof_options))
.unwrap();
presentation.add_proof(proof);
let stdout_writer = BufWriter::new(stdout());
Expand Down
Loading

0 comments on commit dcf81b2

Please sign in to comment.