use crate::lib::std::str::FromStr;
use crate::lib::std::vec::Vec;
use crate::{
did::Did,
did_doc::{
DidDocument, DidDocumentBuilder, PublicKey, PublicKeyBuilder, PublicKeyEncoded,
PublicKeyType, Service, ServiceEndpoint, VerificationMethod, KEY_FORMATS,
},
};
use json::JsonValue;
#[cfg(feature = "std")]
use regex::Regex;
pub const GENERIC_DID_CTX: &str = "https://www.w3.org/2019/did/v1";
const CONTEXT_PROP: &str = "@context";
const SUBJECT_PROP: &str = "id";
const CREATED_PROP: &str = "created";
const UPDATED_PROP: &str = "updated";
const PUBKEYS_PROP: &str = "publicKey";
const AUTHN_PROP: &str = "authentication";
const SERVICE_PROP: &str = "service";
const SVCENDP_PROP: &str = "serviceEndpoint";
const ID_PROP: &str = "id";
const TYPE_PROP: &str = "type";
const CTRL_PROP: &str = "controller";
#[cfg(feature = "std")]
lazy_static! {
static ref DATETIME_REGEX: Regex = Regex::new(
r"(?x)
-?([1-9][0-9]{3,}|0[0-9]{3})
-(0[1-9]|1[0-2])
-(0[1-9]|[12][0-9]|3[01])
T(([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\.[0-9]+)?|(24:00:00(\.0+)?))
Z
"
)
.unwrap();
}
#[inline]
fn parse_str<'a>(json: &'a JsonValue, prop: &str, err: &'a str) -> Result<&'a str, &'a str> {
json[prop].as_str().ok_or(err)
}
#[inline]
fn parse_str_then<'a, F: FnOnce(&'a str) -> Result<&'a str, &'a str>>(
json: &'a JsonValue,
prop: &str,
err: &'a str,
op: F,
) -> Result<&'a str, &'a str> {
parse_str(json, prop, err).and_then(op)
}
fn parse_did_context(json: &JsonValue) -> Result<&str, &str> {
parse_str_then(json, CONTEXT_PROP, "missing DID context", |ctx| {
if ctx == GENERIC_DID_CTX {
Ok(GENERIC_DID_CTX)
} else {
Err("invalid DID context")
}
})
}
fn parse_did_subject(json: &JsonValue) -> Result<&str, &str> {
parse_str_then(json, SUBJECT_PROP, "missing DID subject", |sub| {
if Did::is_valid(sub) {
Ok(sub)
} else {
Err("invalid DID subject")
}
})
}
#[cfg(feature = "std")]
fn validate_datetime(input: &str) -> bool {
DATETIME_REGEX.is_match(input)
}
#[cfg(not(feature = "std"))]
fn validate_datetime(_input: &str) -> bool {
true
}
fn parse_did_created(json: &JsonValue) -> Result<Option<&str>, &str> {
match json[CREATED_PROP].as_str() {
Some(created) if validate_datetime(created) => Ok(Some(created)),
Some(_) => Err("invalid created timestamp"),
None => Ok(None),
}
}
fn parse_did_updated(json: &JsonValue) -> Result<Option<&str>, &str> {
match json[UPDATED_PROP].as_str() {
#[cfg(feature = "std")]
Some(created) if validate_datetime(created) => Ok(Some(created)),
Some(_) => Err("invalid updated timestamp"),
None => Ok(None),
}
}
fn parse_did_pubkey_list<'a>(json: &'a JsonValue) -> Result<Vec<PublicKey>, &'a str> {
let mut keys: Vec<PublicKey> = vec![];
for i in 0..json[PUBKEYS_PROP].len() {
let key = &json[PUBKEYS_PROP][i];
if key.is_null() {
break;
}
let pubkey = parse_did_pubkey(key, &keys)?;
keys.push(pubkey);
}
Ok(keys)
}
fn parse_did_pubkey<'a>(key: &'a JsonValue, keys: &[PublicKey]) -> Result<PublicKey<'a>, &'a str> {
let key_id = parse_did_pubkey_id(key)?;
if keys.iter().any(|k| k.id() == key_id) {
return Err("duplicate DID public key id");
}
let key_type = parse_did_pubkey_type(key)?;
let key_ctrl = parse_did_pubkey_ctrl(key)?;
let key_format = parse_did_pubkey_format(key)?;
let key_encoded = parse_did_pubkey_encoded(key, key_format)?;
let key = PublicKeyBuilder::new(key_id, key_type, key_ctrl)
.with_encoded_key(key_encoded)
.build();
Ok(key)
}
fn parse_did_pubkey_id(key: &JsonValue) -> Result<&str, &str> {
parse_str_then(key, ID_PROP, "missing DID public key id", |id| {
if Did::is_valid(&id) {
Ok(id)
} else {
Err("invalid DID public key id")
}
})
}
fn parse_did_pubkey_type(key: &JsonValue) -> Result<PublicKeyType, &str> {
parse_str(key, TYPE_PROP, "missing DID public key type").and_then(|r#type| {
match PublicKeyType::from_str(&r#type) {
Ok(key_type) => Ok(key_type),
Err(_) => Err("invalid DID public key type"),
}
})
}
fn parse_did_pubkey_ctrl(key: &JsonValue) -> Result<&str, &str> {
parse_str(key, CTRL_PROP, "missing DID public key controller")
}
fn parse_did_pubkey_format(key: &JsonValue) -> Result<&str, &str> {
match KEY_FORMATS.iter().find(|f| key.has_key(f)) {
Some(&kf) => Ok(kf),
None => Err("missing DID public key property"),
}
}
fn parse_did_pubkey_encoded<'a>(
key: &'a JsonValue,
key_format: &'a str,
) -> Result<PublicKeyEncoded<'a>, &'a str> {
parse_str(key, key_format, "missing DID public key controller").and_then(|key_enc| {
match PublicKeyEncoded::from((key_format, key_enc)) {
PublicKeyEncoded::Unsupported => Err("unknown DID public key format"),
supported => Ok(supported),
}
})
}
fn parse_did_auth_list<'a>(
json: &'a JsonValue,
pub_keys: &[PublicKey],
) -> Result<Vec<VerificationMethod<'a>>, &'a str> {
json[AUTHN_PROP]
.members()
.map(|vm| parse_auth_verif_method(vm, pub_keys))
.collect()
}
fn parse_auth_verif_method<'a>(
json: &'a JsonValue,
pub_keys: &[PublicKey],
) -> Result<VerificationMethod<'a>, &'a str> {
if json.is_string() {
let did = parse_auth_verif_method_ref(json)?;
if !pub_keys.iter().any(|k| k.id() == did) {
return Err("unknown reference verification method");
}
Ok(VerificationMethod::Reference(did))
} else if json.is_object() {
let key = parse_did_pubkey(json, Vec::<PublicKey>::new().as_slice())?;
if pub_keys.iter().any(|k| k.id() == key.id()) {
return Err("duplicate public key id from embedded verification method");
}
Ok(VerificationMethod::Embedded(key))
} else {
Err("invalid embedded verification method")
}
}
fn parse_auth_verif_method_ref(json: &JsonValue) -> Result<&str, &str> {
json.as_str()
.ok_or("invalid reference verification method")
.and_then(|did| {
if Did::is_valid(did) {
Ok(did)
} else {
Err("invalid reference verification method")
}
})
}
fn parse_did_service_list<'a>(json: &'a JsonValue) -> Result<Vec<Service<'a>>, &'a str> {
json[SERVICE_PROP]
.members()
.map(parse_did_svc_endpoint)
.collect()
}
fn parse_did_svc_endpoint(json: &JsonValue) -> Result<Service<'_>, &str> {
let svc_id = parse_did_svc_endpoint_id(json)?;
let svc_type = parse_did_svc_endpoint_type(json)?;
let svc_endpoint = parse_did_svc_endpoint_value(json)?;
Ok(Service::new(svc_id, svc_type, svc_endpoint))
}
fn parse_did_svc_endpoint_id(key: &JsonValue) -> Result<&str, &str> {
parse_str_then(key, ID_PROP, "missing service endpoint id", |id| {
if Did::is_valid(&id) {
Ok(id)
} else {
Err("invalid service endpoint id")
}
})
}
fn parse_did_svc_endpoint_type(json: &JsonValue) -> Result<&str, &str> {
parse_str(json, TYPE_PROP, "missing service endpoint type")
}
fn parse_did_svc_endpoint_value(json: &JsonValue) -> Result<ServiceEndpoint, &str> {
if json[SVCENDP_PROP].is_string() {
parse_str(json, SVCENDP_PROP, "invalid service endpoint URI")
.and_then(|uri| Ok(ServiceEndpoint::Uri(uri)))
} else if json.is_object() {
Err("invalid service endpoint JSON-LD object : unimplemented")
} else {
Err("invalid service endpoint : unknown format")
}
}
pub fn parse_did_doc(json: &JsonValue) -> Result<DidDocument<'_>, &str> {
let _ctx = parse_did_context(json)?; let sub = parse_did_subject(json)?;
let created = parse_did_created(json)?;
let updated = parse_did_updated(json)?;
let keys = parse_did_pubkey_list(json)?;
let auth = parse_did_auth_list(json, &keys[..])?;
let services = parse_did_service_list(json)?;
let mut did_doc = DidDocumentBuilder::new(sub)
.with_authentication(auth)
.with_pubkeys(keys)
.with_services(services);
if let Some(created) = created {
did_doc = did_doc.created_on(created);
}
if let Some(updated) = updated {
did_doc = did_doc.updated_on(updated);
}
Ok(did_doc.build())
}