Skip to content

Commit

Permalink
impl using rpgp
Browse files Browse the repository at this point in the history
add note regarding bug
pgp::composed::signed_key::parse::from_armor_many seems to yield only one key when multiple are present

use feature resolver v2 (necessary since nettle will not compile on wasm32)
https://doc.rust-lang.org/cargo/reference/features.html#feature-resolver-version-2

add notes on pgp specific dependencies
  • Loading branch information
fairingrey committed Jan 17, 2022
1 parent 38e33d7 commit d0d964a
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 4 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ license = "Apache-2.0"
description = "Core library for Verifiable Credentials and Decentralized Identifiers."
repository = "https://github.com/spruceid/ssi/"
documentation = "https://docs.rs/ssi/"
resolver = "2"

exclude = ["json-ld-api/*", "json-ld-normalization/*"]

Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,20 @@ packages.
```
clang
openssl-devel
```

If using feature `did-webkey/sequoia-openpgp` for PGP support, the following
dependencies are also needed:

```
nettle-dev
capnproto
```

If using feature
[`did-webkey/crypto-cng`](https://gitlab.com/sequoia-pgp/sequoia#cryptography),
only `capnproto` is needed.

## Install

### Crates.io
Expand Down
17 changes: 14 additions & 3 deletions did-webkey/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,25 @@ ssi = { version = "0.3", path = "../", features = [
], default-features = false }
async-trait = "0.1"
reqwest = { version = "0.11", features = ["json"] }
hex = "0.4"
http = "0.2"
sequoia-openpgp = { version = "1.7", features = [
"compression-deflate",
], default-features = false }
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
sshkeys = "0.3"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
sequoia-openpgp = { version = "1.7", features = [
"compression-deflate",
], default-features = false, optional = true }
# HACK: temp, point to crates once pgp publishes a version that doesn't require zeroize=1.3.0
pgp = { git = "https://github.com/rpgp/rpgp", rev = "21081b6aaaaa5750ab937cfef30bae879a740d23", optional = true }

[target.'cfg(target_arch = "wasm32")'.dependencies]
# HACK: same thing as above
pgp = { git = "https://github.com/rpgp/rpgp", rev = "21081b6aaaaa5750ab937cfef30bae879a740d23", features = [
"wasm",
] }

[target.'cfg(target_os = "android")'.dependencies.reqwest]
version = "0.11"
features = ["json", "native-tls-vendored"]
Expand Down
80 changes: 79 additions & 1 deletion did-webkey/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@ use core::str::FromStr;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};

#[cfg(all(not(target_arch = "wasm32"), feature = "sequoia-openpgp"))]
use openpgp::{
cert::prelude::*,
parse::{PacketParser, Parse},
serialize::SerializeInto,
};
#[cfg(any(target_arch = "wasm32", feature = "pgp"))]
use pgp::{
composed::{PublicOrSecret, SignedPublicKey},
errors::Error as PgpError,
types::KeyTrait,
};
#[cfg(all(not(target_arch = "wasm32"), feature = "sequoia-openpgp"))]
use sequoia_openpgp as openpgp;
use sshkeys::PublicKeyKind;
use ssi::did::{DIDMethod, Document, VerificationMethod, VerificationMethodMap, DIDURL};
Expand All @@ -16,6 +24,11 @@ use ssi::did_resolve::{
};
use ssi::ssh::ssh_pkk_to_jwk;

#[cfg(all(feature = "sequoia-openpgp", feature = "pgp"))]
compile_error!(
"Feature \"sequoia-openpgp\" and feature \"pgp\" cannot be enabled at the same time"
);

// For testing, enable handling requests at localhost.
#[cfg(test)]
use std::cell::RefCell;
Expand Down Expand Up @@ -44,6 +57,7 @@ impl FromStr for DIDWebKeyType {
}
}

#[cfg(all(not(target_arch = "wasm32"), feature = "sequoia-openpgp"))]
fn parse_pubkeys_gpg(
did: &str,
bytes: Vec<u8>,
Expand All @@ -68,6 +82,7 @@ fn parse_pubkeys_gpg(
Ok((vm_maps, did_urls))
}

#[cfg(all(not(target_arch = "wasm32"), feature = "sequoia-openpgp"))]
fn gpg_pk_to_vm(did: &str, cert: Cert) -> Result<(VerificationMethodMap, DIDURL), String> {
let vm_url = DIDURL {
did: did.to_string(),
Expand All @@ -92,6 +107,68 @@ fn gpg_pk_to_vm(did: &str, cert: Cert) -> Result<(VerificationMethodMap, DIDURL)
Ok((vm_map, vm_url))
}

#[cfg(any(target_arch = "wasm32", feature = "pgp"))]
fn parse_pubkeys_gpg(
did: &str,
bytes: Vec<u8>,
) -> Result<(Vec<VerificationMethodMap>, Vec<DIDURL>), String> {
use std::io::Cursor;

let mut did_urls = Vec::new();
let mut vm_maps = Vec::new();

let c = Cursor::new(bytes);
// BUG: This seems to yield only one key.
let keys = pgp::composed::signed_key::parse::from_armor_many(c)
.map_err(|e| format!("Unable to parse GPG keyring: {}", e))?
.0
.collect::<Result<Vec<PublicOrSecret>, PgpError>>()
.map_err(|e| format!("Unable to parse GPG keyring: {}", e))?;

for key in keys {
// ignore if secret key (which shouldn't happen)
if let PublicOrSecret::Public(pk) = key {
let (vm_map, did_url) = gpg_pk_to_vm(did, pk).map_err(|e| {
format!(
"Unable to convert GPG public key to verification method: {}",
e
)
})?;
vm_maps.push(vm_map);
did_urls.push(did_url);
}
}

Ok((vm_maps, did_urls))
}

#[cfg(any(target_arch = "wasm32", feature = "pgp"))]
fn gpg_pk_to_vm(
did: &str,
key: SignedPublicKey,
) -> Result<(VerificationMethodMap, DIDURL), String> {
let fingerprint: String = hex::encode_upper(key.fingerprint());

let vm_url = DIDURL {
did: did.to_string(),
fragment: Some(fingerprint),
..Default::default()
};

let armored_pgp = key
.to_armored_string(None)
.map_err(|e| format!("Failed to re-serialize cert: {}", e))?;

let vm_map = VerificationMethodMap {
id: vm_url.to_string(),
type_: "PgpVerificationKey2021".to_string(),
public_key_pgp: Some(armored_pgp),
controller: did.to_string(),
..Default::default()
};
Ok((vm_map, vm_url))
}

fn pk_to_vm_ed25519(
did: &str,
pk: sshkeys::Ed25519PublicKey,
Expand Down Expand Up @@ -420,7 +497,7 @@ mod tests {
let (mut parts, body) = Response::<Body>::default().into_parts();
parts.status = hyper::StatusCode::NOT_FOUND;
let response = Response::from_parts(parts, body);
return Ok::<_, hyper::Error>(response);
Ok::<_, hyper::Error>(response)
}))
});
let server = Server::try_bind(&addr)?.serve(make_svc);
Expand Down Expand Up @@ -526,6 +603,7 @@ mod tests {
)
.await;
assert_eq!(res_meta.error, None);
// NOTE: sequoia-pgp and rpgp will likely produce slightly different output
let value_expected = json!({
"@context": "https://www.w3.org/ns/did/v1",
"assertionMethod": [
Expand Down

0 comments on commit d0d964a

Please sign in to comment.