Skip to content

Commit

Permalink
implement webkey gpg parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
fairingrey committed Jan 11, 2022
1 parent de7fbfc commit 479cde7
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 13 deletions.
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
70 changes: 64 additions & 6 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 @@ -476,12 +533,13 @@ mod tests {
});
let (res_meta, doc_opt, _doc_meta) = DIDWebKey
.resolve(
"did:webkey:gpg:localhost:user.keys",
"did:webkey:gpg:localhost:user.gpg",
&ResolutionInputMetadata::default(),
)
.await;
assert_eq!(res_meta.error, None);
// let value_expected = unimplemented!();
// TODO: put correct JSON here
let value_expected = json!({});
let doc = doc_opt.unwrap();
let doc_value = serde_json::to_value(doc).unwrap();
eprintln!("doc {}", serde_json::to_string_pretty(&doc_value).unwrap());
Expand Down
67 changes: 67 additions & 0 deletions src/gpg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
use crate::jwk::{Base64urlUInt, Params as JWKParams, JWK};
use openpgp::{
crypto::mpi::PublicKey,
packet::{
key::{KeyParts, KeyRole},
Key,
},
types::{Curve, PublicKeyAlgorithm},
};
use sequoia_openpgp as openpgp;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum GpgKeyToJWKError {
#[error("Unsupported GPG key type")]
UnsupportedGpgKeyType,
#[error("Unsupported GPG public key algorithm")]
UnsupportedGpgPkAlgorithm,
#[error("P-256 parse error: {0}")]
P256Parse(String),
#[error("Unsupported ECDSA key type: {0}")]
UnsupportedEcdsaKey(String),
#[error("Missing features: {0}")]
MissingFeatures(&'static str),
}

/// Convert a GPG public key to a JWK.
pub fn gpg_pkk_to_jwk<P: KeyParts, R: KeyRole>(pkk: &Key<P, R>) -> Result<JWK, GpgKeyToJWKError> {
// https://datatracker.ietf.org/doc/html/rfc4253#section-6.6
if let Key::V4(key) = pkk {
match key.pk_algo() {
PublicKeyAlgorithm::RSAEncryptSign
| PublicKeyAlgorithm::ECDSA
| PublicKeyAlgorithm::EdDSA => match key.mpis() {
PublicKey::RSA { e, n } => Ok(JWK::from(JWKParams::RSA(
crate::jwk::RSAParams::new_public(e.value(), n.value()),
))),
PublicKey::ECDSA { curve, q } => {
if curve == &Curve::NistP256 {
#[cfg(not(feature = "p256"))]
{
Err(GpgKeyToJWKError::MissingFeatures("p256"))
}
#[cfg(feature = "p256")]
{
crate::jwk::p256_parse(q.value())
.map_err(|e| GpgKeyToJWKError::P256Parse(e.to_string()))
}
} else {
Err(GpgKeyToJWKError::UnsupportedEcdsaKey(curve.to_string()))
}
}
PublicKey::EdDSA { curve, q } => {
Ok(JWK::from(JWKParams::OKP(crate::jwk::OctetParams {
curve: curve.to_string(),
public_key: Base64urlUInt(q.value().to_vec()),
private_key: None,
})))
}
_ => panic!("Something went wrong"),
},
_ => Err(GpgKeyToJWKError::UnsupportedGpgPkAlgorithm),
}
} else {
Err(GpgKeyToJWKError::UnsupportedGpgKeyType)
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod did_resolve;
#[cfg(feature = "keccak-hash")]
pub mod eip712;
pub mod error;
pub mod gpg;
pub mod hash;
pub mod jsonld;
pub mod jwk;
Expand Down

0 comments on commit 479cde7

Please sign in to comment.