pub mod error;
#[cfg(feature = "jolo")]
pub mod jolo;
#[cfg(feature = "keriox")]
pub mod keri;
#[cfg(feature = "didkey")]
pub mod key;
#[cfg(feature = "keriox")]
use crate::keri::DidKeriResolver;
#[cfg(feature = "didkey")]
use key::DidKeyResolver;
use base58::FromBase58;
pub use did_key::{Document, KeyFormat, VerificationMethod};
use error::Error;
use lazy_static::lazy_static;
use regex::Regex;
use serde::{Deserialize, Serialize};
lazy_static! {
static ref DID_REGEX: Regex = Regex::new(
r"(?x)(?P<prefix>[did]{3}):(?P<method>[a-z]*):(?P<key_id>[-_a-zA-Z0-9]*)([:?/]?)(S)*??",
)
.unwrap();
}
pub trait DdoResolver {
fn resolve(&self, did_url: &str) -> Result<Document, Error>;
}
pub trait DdoParser {
fn find_key_agreement(&self, pattern: &str) -> Option<KeyAgreement>;
fn find_public_key_for_curve(&self, curve: &str) -> Option<Vec<u8>>;
fn find_public_key_id_for_curve(&self, curve: &str) -> Option<String>;
fn find_public_key_controller_for_curve(&self, curve: &str) -> Option<String>;
}
impl DdoParser for Document {
fn find_key_agreement(&self, pattern: &str) -> Option<KeyAgreement> {
let agreements = self.key_agreement.clone();
if agreements.is_none() {
return None;
}
match agreements.unwrap().iter().find(|a| a.contains(pattern)) {
Some(a) => serde_json::from_str(a).unwrap_or(None),
None => None,
}
}
fn find_public_key_for_curve(&self, curve: &str) -> Option<Vec<u8>> {
if let Some(k) = self
.verification_method
.iter()
.find(|m| m.key_type.contains(curve))
{
if let Some(key) = k.public_key.clone() {
match key {
KeyFormat::Base58(value) => Some(value.from_base58().unwrap()),
KeyFormat::Multibase(value) => Some(value),
KeyFormat::JWK(_value) => todo!(), }
} else {
None
}
} else {
None
}
}
fn find_public_key_id_for_curve(&self, curve: &str) -> Option<String> {
match get_public_key(self, curve) {
Some(kf) => match kf {
KeyFormat::JWK(key) => key.key_id,
_ => None,
},
None => None,
}
}
fn find_public_key_controller_for_curve(&self, curve: &str) -> Option<String> {
match self
.verification_method
.iter()
.find(|vm| vm.key_type.contains(curve))
{
Some(vm) => Some(vm.controller.to_owned()),
None => None,
}
}
}
pub fn try_resolve_any(did_url: &str) -> Result<Document, Error> {
let re = regex::Regex::new(r"^((?P<prefix>did){1}:(?P<method>[-_A-Za-z0-9]*){1}:(?P<id>.+?))((?P<kerlid>\?kerl=)(?P<kerl>[a-zA-Z0-9]+?))?$").unwrap();
match re.captures(did_url) {
Some(caps) => {
match &caps["method"] {
#[cfg(feature = "didkey")]
"key" => DidKeyResolver {}
.resolve(did_url)
.map_err(|e| error::Error::DidKeyError(e.to_string())),
#[cfg(feature = "keriox")]
"keri" => match &caps["kerlid"] {
"" => Err(error::Error::DidKeriError("kerl id not found".into())),
_ => match &caps["kerl"] {
"" => Err(error::Error::DidKeriError("kerl not found".into())),
_ => DidKeriResolver::new(&String::from_utf8_lossy(&base64_url::decode(
&caps["kerl"],
)?))
.resolve(&format!("did:keri:{}", &caps["id"])),
},
},
_ => Err(error::Error::DidKeyError("not supported key url".into())), }
}
None => Err(error::Error::DidKeyError("not a did url".into())), }
}
pub fn resolve_any(did_url: &str) -> Option<Document> {
let re = regex::Regex::new(r"^((?P<prefix>did){1}:(?P<method>[-_a-zA-Z0-9]*){1}:(?P<id>.+?))((?P<kerlid>\?kerl=)(?P<kerl>[a-zA-Z0-9]+?))?$").unwrap();
match re.captures(did_url) {
Some(caps) => {
let resolver: Box<dyn DdoResolver> = match &caps["method"] {
#[cfg(feature = "didkey")]
"key" => Box::new(DidKeyResolver {}),
#[cfg(feature = "keriox")]
"keri" => Box::new(DidKeriResolver::new(&String::from_utf8_lossy(
&base64_url::decode(&caps["kerl"]).unwrap_or(vec![]),
))),
#[cfg(feature = "didjolo")]
"jolo" => {}
#[cfg(feature = "didweb")]
"web" => {}
_ => return None,
};
let parsed_url = format!("{}:{}:{}", &caps["prefix"], &caps["method"], &caps["id"]);
match resolver.resolve(&parsed_url) {
Ok(doc) => Some(doc),
Err(_) => None,
}
}
None => None,
}
}
pub fn get_sign_and_crypto_keys<'a>(ddo: &'a Document) -> (Option<&'a [u8]>, Option<&'a [u8]>) {
let _sign_key = ddo.verification_method.iter().fold(None, |_, vm| {
vm.public_key.iter().find(|k| match k {
KeyFormat::JWK(key) => key.curve == "Ed25519",
_ => false,
})
});
let _crypto_key = ddo
.verification_method
.iter()
.find(|vm| vm.key_type == "X25519");
(None, None)
}
pub(crate) fn get_public_key(doc: &Document, curve: &str) -> Option<KeyFormat> {
match doc
.verification_method
.iter()
.find(|m| match &m.public_key {
Some(KeyFormat::JWK(jwk)) => jwk.curve.contains(curve),
_ => false,
}) {
Some(vm) => vm.public_key.clone(),
None => None,
}
}
pub(crate) fn key_id_from_didurl(url: &str) -> String {
match DID_REGEX.captures(url) {
Some(s) => match s.name("key_id") {
Some(name) => format!("#{}", name.as_str()),
None => String::default(),
},
None => String::default(),
}
}
pub fn did_id_from_url(url: &str) -> Option<String> {
let captures = DID_REGEX.captures(url)?;
Some(format!(
"{}:{}:{}",
captures.name("prefix")?.as_str(),
captures.name("method")?.as_str(),
captures.name("key_id")?.as_str()
))
}
#[cfg(feature = "didkey")]
#[derive(Serialize, Deserialize, Debug)]
pub struct KeyAgreement {
pub id: String,
pub r#type: String,
pub controller: String,
#[serde(rename = "publicKeyBase58")]
pub public_key_base58: String,
}
#[test]
fn did_id_from_url_test() {
let keri = "did:keri:someiderNTIFIER2345432?bunch_of_niose!_$(#)";
let keri_sym = "did:keri:D1bkcOzM-YwEXKPc5yHbMzkHRrZS3O6QAVEpGsS0XpF_E";
let key = "did:key:bu03rlnth4gpk09y4cr3DEGCTHUDGc45RCGUCH?again_some_rubbish";
let long = "did:verylongid:BXDHCG8765678THDIYFGCRNWMBXIF34543HDGC?MOREnoise_?";
let not_a_did = "thisisnot_a_did";
assert_eq!(
&did_id_from_url(keri).unwrap(),
"did:keri:someiderNTIFIER2345432"
);
assert_eq!(
&did_id_from_url(keri_sym).unwrap(),
"did:keri:D1bkcOzM-YwEXKPc5yHbMzkHRrZS3O6QAVEpGsS0XpF_E"
);
assert_eq!(
&did_id_from_url(key).unwrap(),
"did:key:bu03rlnth4gpk09y4cr3DEGCTHUDGc45RCGUCH"
);
assert_eq!(
&did_id_from_url(long).unwrap(),
"did:verylongid:BXDHCG8765678THDIYFGCRNWMBXIF34543HDGC"
);
assert!(did_id_from_url(not_a_did).is_none());
}