use crate::did::{
Context, DEFAULT_MAX_LEN, Did, DidDocument, DidError, DocumentError, MethodType,
ResolutionError, ResolutionErrorKind, ResolutionOptions, ResolutionResult,
VerificationMaterial, VerificationMethod, VerificationRelationship,
};
use crate::jose::Jwk;
use crate::resolver::{DidProducer, DidResolver, Hosting, MethodCapabilities, ProduceError};
use super::check_accept;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct DidJwk;
impl DidJwk {
pub const METHOD: &'static str = "jwk";
pub const FRAGMENT: &'static str = "0";
pub const MAX_DID_LEN: usize = DEFAULT_MAX_LEN - 1 - Self::FRAGMENT.len();
pub const CONTEXT: [&'static str; 2] = [
Context::DID_V1,
"https://w3id.org/security/suites/jws-2020/v1",
];
pub fn did(jwk: &Jwk) -> Result<Did, DidError> {
let value = serde_json::to_value(jwk.public()).expect("a JWK serializes");
let json =
super::jcs::to_string(&value).map_err(|unsupported| DidError::UnsupportedNumber {
number: unsupported.0,
})?;
let id = crate::jose::base64url::encode(json.as_bytes());
Did::parse_with_max_len(&format!("did:jwk:{id}"), Self::MAX_DID_LEN)
}
pub fn document(did: &Did) -> Result<DidDocument, ResolutionError> {
let invalid = |detail: &str| {
ResolutionError::new(ResolutionErrorKind::InvalidDid).with_detail(detail)
};
if did.method() != Self::METHOD {
return Err(
ResolutionError::new(ResolutionErrorKind::MethodNotSupported)
.with_detail(format!("`{did}` is not a did:jwk")),
);
}
if did.as_str().len() > Self::MAX_DID_LEN {
return Err(invalid(&format!(
"the DID is longer than {} bytes",
Self::MAX_DID_LEN
)));
}
let json = crate::jose::base64url::decode(did.method_specific_id()).map_err(|error| {
invalid("the method-specific id is not base64url").with_source(error)
})?;
let jwk: Jwk = serde_json::from_slice(&json).map_err(|error| {
invalid("the method-specific id does not encode a JSON Web Key").with_source(error)
})?;
if jwk.is_private() {
return Err(invalid("the JSON Web Key carries private parameters"));
}
build(did, jwk)
.map_err(|error| invalid("the JSON Web Key yields no DID document").with_source(error))
}
}
fn build(did: &Did, jwk: Jwk) -> Result<DidDocument, DocumentError> {
let relationships: &[VerificationRelationship] = match jwk.key_use() {
Some("sig") => &[
VerificationRelationship::Authentication,
VerificationRelationship::AssertionMethod,
VerificationRelationship::CapabilityInvocation,
VerificationRelationship::CapabilityDelegation,
],
Some("enc") => &[VerificationRelationship::KeyAgreement],
_ => VerificationRelationship::ALL,
};
let method = VerificationMethod::new(
did.to_url().with_fragment(DidJwk::FRAGMENT),
MethodType::JSON_WEB_KEY_2020,
did.clone(),
VerificationMaterial::PublicKeyJwk(Box::new(jwk)),
);
DidDocument::builder(did.clone())
.context(Context::uris(DidJwk::CONTEXT))
.verification_method(method, relationships.iter().copied())
.build()
}
#[async_trait::async_trait]
impl DidResolver for DidJwk {
fn method(&self) -> &'static str {
Self::METHOD
}
async fn resolve(
&self,
did: &Did,
options: &ResolutionOptions,
) -> Result<ResolutionResult, ResolutionError> {
check_accept(options)?;
Self::document(did).map(ResolutionResult::new)
}
}
#[async_trait::async_trait]
impl DidProducer for DidJwk {
type Params = ();
fn method(&self) -> &'static str {
Self::METHOD
}
fn capabilities(&self) -> MethodCapabilities {
MethodCapabilities::new(Hosting::Embedded)
.with_single_key(true)
.with_fixed_fragment(Self::FRAGMENT)
}
async fn produce(&self, keys: &[Jwk], _: ()) -> Result<DidDocument, ProduceError> {
let jwk = super::single_key(keys)?;
let unsupported = |reason: String| ProduceError::UnsupportedKey { index: 0, reason };
let did = Self::did(jwk).map_err(|error| unsupported(error.to_string()))?;
Self::document(&did).map_err(|error| unsupported(error.to_string()))
}
}