Skip to content

Commit

Permalink
implement webkey gpg parsing to did
Browse files Browse the repository at this point in the history
  • Loading branch information
fairingrey committed Jan 11, 2022
1 parent 130cbee commit 62e45a4
Show file tree
Hide file tree
Showing 7 changed files with 295 additions and 23 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
/target
Cargo.lock

.vscode
17 changes: 10 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,12 @@ description = "Core library for Verifiable Credentials and Decentralized Identif
repository = "https://github.com/spruceid/ssi/"
documentation = "https://docs.rs/ssi/"

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

[features]
default = ["ring"]
http-did = ["hyper", "hyper-tls", "http", "percent-encoding", "tokio"]
libsecp256k1 = ["secp256k1"] # backward compatibility
libsecp256k1 = ["secp256k1"] # backward compatibility
secp256k1 = ["k256", "rand", "k256/keccak256"]
secp256r1 = ["p256", "rand"]
ripemd-160 = ["ripemd160", "secp256k1"]
Expand Down Expand Up @@ -58,7 +55,12 @@ lazy_static = "1.4"
combination = "0.1"
sha2 = { version = "0.9", optional = true }
sha2_old = { package = "sha2", version = "0.8" }
hyper = { version = "0.14", optional = true, features = ["server", "client", "http1", "stream"] }
hyper = { version = "0.14", optional = true, features = [
"server",
"client",
"http1",
"stream",
] }
hyper-tls = { version = "0.5", optional = true }
http = { version = "0.2", optional = true }
hex = "0.4"
Expand All @@ -77,6 +79,7 @@ p256 = { version = "0.8", optional = true, features = ["zeroize", "ecdsa"] }
ssi-contexts = { version = "0.1.2", path = "contexts/" }
ripemd160 = { version = "0.9", optional = true }
sshkeys = "0.3"
sequoia-openpgp = "1.7"
reqwest = { version = "0.11", features = ["json"] }
flate2 = "1.0"
bitvec = "0.20"
Expand Down Expand Up @@ -106,7 +109,7 @@ members = [
]

[dev-dependencies]
blake2 = "0.8" # for bbs doctest
blake2 = "0.8" # for bbs doctest
uuid = { version = "0.8", features = ["v4", "serde"] }
difference = "2.0"
did-method-key = { path = "./did-key" }
Expand Down
7 changes: 6 additions & 1 deletion did-webkey/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,15 @@ documentation = "https://docs.rs/did-webkey/"
p256 = ["ssi/p256"]

[dependencies]
ssi = { version = "0.3", path = "../", default-features = false }
ssi = { version = "0.3", path = "../", features = [
"rand",
"ring",
"p256",
], default-features = false }
async-trait = "0.1"
reqwest = { version = "0.11", features = ["json"] }
http = "0.2"
sequoia-openpgp = "1.7"
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
sshkeys = "0.3"
Expand Down
165 changes: 150 additions & 15 deletions did-webkey/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,21 @@ use core::str::FromStr;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};

use openpgp::Packet;
use openpgp::{
packet::{
key::{KeyParts, KeyRole},
Key,
},
parse::{PacketParser, PacketParserResult, Parse},
};
use sequoia_openpgp as openpgp;
use sshkeys::PublicKeyKind;
use ssi::did::{DIDMethod, Document, VerificationMethod, VerificationMethodMap, DIDURL};
use ssi::did_resolve::{
DIDResolver, DocumentMetadata, ResolutionInputMetadata, ResolutionMetadata, ERROR_INVALID_DID,
};
use ssi::gpg::gpg_pkk_to_jwk;
use ssi::ssh::ssh_pkk_to_jwk;

// For testing, enable handling requests at localhost.
Expand Down Expand Up @@ -39,11 +49,58 @@ impl FromStr for DIDWebKeyType {
}

fn parse_pubkeys_gpg(
_did: &str,
_bytes: Vec<u8>,
did: &str,
bytes: Vec<u8>,
) -> Result<(Vec<VerificationMethodMap>, Vec<DIDURL>), String> {
// TODO
Err(String::from("GPG Key Type Not Implemented"))
let mut ppr = PacketParser::from_bytes(&bytes)
.map_err(|e| format!("Unable to parse GPG keyring: {}", e))?;
let mut did_urls = Vec::new();
let mut vm_maps = Vec::new();

while let PacketParserResult::Some(pp) = ppr {
let (packet, next_ppr) = pp
.recurse()
.map_err(|e| format!("Error occured parsing keyring: {}", e))?;
ppr = next_ppr;

// packet is expected to be a public key
if let Packet::PublicKey(pk) = packet {
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))
}

fn gpg_pk_to_vm<P: KeyParts, R: KeyRole>(
did: &str,
pk: Key<P, R>,
) -> Result<(VerificationMethodMap, DIDURL), String> {
let jwk =
gpg_pkk_to_jwk(&pk).map_err(|e| format!("Unable to convert GPG key to JWK: {}", e))?;
let thumbprint = jwk
.thumbprint()
.map_err(|e| format!("Unable to calculate JWK thumbprint: {}", e))?;
let vm_url = DIDURL {
did: did.to_string(),
fragment: Some(thumbprint),
..Default::default()
};
let vm_map = VerificationMethodMap {
id: vm_url.to_string(),
type_: "PgpVerificationKey2021".to_string(),
public_key_jwk: Some(jwk),
controller: did.to_string(),
..Default::default()
};
Ok((vm_map, vm_url))
}

fn pk_to_vm_ed25519(
Expand Down Expand Up @@ -348,23 +405,22 @@ mod tests {
);
}

// TODO: use JWK fingerprint
const DID_URL: &str = "https://localhost/user.keys";
const PUBKEYS: &str = include_str!("../tests/ssh_keys");
// localhost web server for serving did:web DID documents.
// TODO: pass arguments here instead of using const
fn web_server() -> Result<(String, impl FnOnce() -> Result<(), ()>), hyper::Error> {
fn web_server(
did_url: &'static str,
pubkeys: &'static str,
) -> Result<(String, impl FnOnce() -> Result<(), ()>), hyper::Error> {
use http::header::{HeaderValue, CONTENT_TYPE};
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Response, Server};
let addr = ([127, 0, 0, 1], 0).into();
let make_svc = make_service_fn(|_| async move {
Ok::<_, hyper::Error>(service_fn(|req| async move {
let make_svc = make_service_fn(move |_| async move {
Ok::<_, hyper::Error>(service_fn(move |req| async move {
let uri = req.uri();
// Skip leading slash
let proxied_url: String = uri.path().chars().skip(1).collect();
if proxied_url == DID_URL {
let body = Body::from(PUBKEYS);
if proxied_url == did_url {
let body = Body::from(pubkeys);
let mut response = Response::new(body);
response
.headers_mut()
Expand Down Expand Up @@ -392,8 +448,12 @@ mod tests {
}

#[tokio::test]
async fn from_did_webkey() {
let (url, shutdown) = web_server().unwrap();
async fn from_did_webkey_ssh() {
// TODO: use JWK fingerprint
let did_url: &str = "https://localhost/user.keys";
let pubkeys: &str = include_str!("../tests/ssh_keys");

let (url, shutdown) = web_server(did_url, pubkeys).unwrap();
PROXY.with(|proxy| {
proxy.replace(Some(url));
});
Expand Down Expand Up @@ -460,4 +520,79 @@ mod tests {
});
shutdown().ok();
}

#[tokio::test]
async fn from_did_webkey_gpg() {
// TODO: use JWK fingerprint
let did_url: &str = "https://localhost/user.gpg";
let pubkeys: &str = include_str!("../tests/user.gpg");

let (url, shutdown) = web_server(did_url, pubkeys).unwrap();
PROXY.with(|proxy| {
proxy.replace(Some(url));
});
let (res_meta, doc_opt, _doc_meta) = DIDWebKey
.resolve(
"did:webkey:gpg:localhost:user.gpg",
&ResolutionInputMetadata::default(),
)
.await;
assert_eq!(res_meta.error, None);
// TODO: put correct JSON here
let value_expected = json!({
"@context": "https://www.w3.org/ns/did/v1",
"assertionMethod": [
"did:webkey:gpg:localhost:user.gpg#2rrcnZOu6TbB8LBJ2In_xSs3KDTEcLcY1-5GN-W7DPQ",
"did:webkey:gpg:localhost:user.gpg#dlTBirGD0Ryr28lw1aYOskbbTXBNo2Vn06KrxBFjFUc",
"did:webkey:gpg:localhost:user.gpg#PrLd4Isj-nX1BsSOOkNksmgkLofC3waM1tZQ3tTTnmo"
],
"authentication": [
"did:webkey:gpg:localhost:user.gpg#2rrcnZOu6TbB8LBJ2In_xSs3KDTEcLcY1-5GN-W7DPQ",
"did:webkey:gpg:localhost:user.gpg#dlTBirGD0Ryr28lw1aYOskbbTXBNo2Vn06KrxBFjFUc",
"did:webkey:gpg:localhost:user.gpg#PrLd4Isj-nX1BsSOOkNksmgkLofC3waM1tZQ3tTTnmo"
],
"id": "did:webkey:gpg:localhost:user.gpg",
"verificationMethod": [
{
"controller": "did:webkey:gpg:localhost:user.gpg",
"id": "did:webkey:gpg:localhost:user.gpg#2rrcnZOu6TbB8LBJ2In_xSs3KDTEcLcY1-5GN-W7DPQ",
"publicKeyJwk": {
"e": "AQAB",
"kty": "RSA",
"n": "qJPWfS1nljM-ZlhcrWf1fSku0XOEqsqL87J9ZRbiXraMFP4jXilLFdjMp4kQErkCq6OwrJ85rOjTPM4wib1V2gfQVckwFzcolbzdi9BXtVAtjhQaZQvCJPl-ewoBlcAA1XyAkdZAM7JE1XbgRvJ-Ib1Wt49OgnvItmI9DYCb15BgxDugIssM_T6yqgctZcfsbSWTmr8qdNtP-d1hQf0JUIMsrEvow4Xa0dpAl6CpVYAZmokdXBtMDED-HzxKM4cfxk8jJ1ialA-5bKA6gtyQo2IrOfkuye5-8TJaHO3dIHZN3coNVqn1uC1_8ZzfV1Frb-bBacnFBtlErv1QfH-uNhzemPRgHFxs0elLvzEHha2yFvpL1So-4gZYa8xMN3I3LQ6pXBvt1R_QoNxeCN-HHxmeI2rP7k8ozZcAMwTyjMgBmSJM_qkwYadq_jQGtdsI_iDNgC2w34GRzhYz9ajp2Z03obju54keM6V88Qy03qHvd6Qk32s04c5ilPVQk_Oh"
},
"type": "PgpVerificationKey2021"
},
{
"controller": "did:webkey:gpg:localhost:user.gpg",
"id": "did:webkey:gpg:localhost:user.gpg#dlTBirGD0Ryr28lw1aYOskbbTXBNo2Vn06KrxBFjFUc",
"publicKeyJwk": {
"crv": "P-256",
"kty": "EC",
"x": "YZyZg4g9-S8yV7LZ-MwTH6Wr1AkpGM8iSiGB3jsFuAQ",
"y": "V74AX9l1zPMmfHw7XGd29g4VxxU0DPscxvvE8HswNwQ"
},
"type": "PgpVerificationKey2021"
},
{
"controller": "did:webkey:gpg:localhost:user.gpg",
"id": "did:webkey:gpg:localhost:user.gpg#PrLd4Isj-nX1BsSOOkNksmgkLofC3waM1tZQ3tTTnmo",
"publicKeyJwk": {
"crv": "Ed25519",
"kty": "OKP",
"x": "QKe-eoFmW2Qeusk445-dg8lLwsVIBRu2kiqmGOyhtirw"
},
"type": "PgpVerificationKey2021"
}
]
});
let doc = doc_opt.unwrap();
let doc_value = serde_json::to_value(doc).unwrap();
eprintln!("doc {}", serde_json::to_string_pretty(&doc_value).unwrap());
assert_eq!(doc_value, value_expected);
PROXY.with(|proxy| {
proxy.replace(None);
});
shutdown().ok();
}
}
59 changes: 59 additions & 0 deletions did-webkey/tests/user.gpg
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

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
IVJ3eaA6olem6Y7IEWPqGhiTIAg=
=yjPH
-----END PGP PUBLIC KEY BLOCK-----
Loading

0 comments on commit 62e45a4

Please sign in to comment.