diff --git a/Cargo.lock b/Cargo.lock index 4b4f6c5ed..5f9036403 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1424,6 +1424,7 @@ dependencies = [ "base64 0.21.7", "ceramic-car", "ceramic-core", + "chrono", "cid 0.11.1", "expect-test", "ipld-core", diff --git a/core/src/jwk.rs b/core/src/jwk.rs index 69e76855a..31f803df9 100644 --- a/core/src/jwk.rs +++ b/core/src/jwk.rs @@ -2,7 +2,9 @@ use crate::DidDocument; use cid::multibase; use once_cell::sync::Lazy; use ssi::did::{Resource, VerificationMethod}; -use ssi::did_resolve::{dereference, Content, DIDResolver, DereferencingInputMetadata}; +use ssi::did_resolve::{ + dereference, Content, DIDResolver, DereferencingInputMetadata, ResolutionInputMetadata, +}; use ssi::jwk::{Base64urlUInt, OctetParams, Params, JWK}; use std::ops::Deref; @@ -65,6 +67,29 @@ impl Jwk { jwk.key_id.clone_from(&self.key_id); Ok(Self(jwk)) } + + /// Resolve a DID and return the DIDDocument + /// + /// The DID Document contains the public key. For more info, see: + /// https://www.w3.org/TR/did-core/#dfn-did-documents + pub async fn resolve_did(did: &str) -> Option { + if !did.starts_with("did:key:") && !did.starts_with("did:pkh:") { + return None; + } + let input_metadata = ResolutionInputMetadata::default(); + let base_did = did.split_once('#').map_or(did, |(b, _)| b); + + let resolver: &dyn DIDResolver = if base_did.starts_with("did:key:") { + &did_method_key::DIDKey + } else if base_did.starts_with("did:pkh:") { + &did_pkh::DIDPKH + } else { + return None; + }; + + let (_res, did_document, _metadata) = resolver.resolve(base_did, &input_metadata).await; + did_document + } } impl From for Jwk { diff --git a/event/Cargo.toml b/event/Cargo.toml index b687968a4..44b9d8195 100644 --- a/event/Cargo.toml +++ b/event/Cargo.toml @@ -24,6 +24,7 @@ serde_json.workspace = true ssi.workspace = true tokio.workspace = true tracing.workspace = true +chrono = "0.4" [dev-dependencies] multibase.workspace = true diff --git a/event/src/unvalidated/signed/cacao.rs b/event/src/unvalidated/signed/cacao.rs index 773ae08b8..f6a25a0f1 100644 --- a/event/src/unvalidated/signed/cacao.rs +++ b/event/src/unvalidated/signed/cacao.rs @@ -1,155 +1,167 @@ //! Structures for encoding and decoding CACAO capability objects. use serde::{Deserialize, Serialize}; +use ssi::jwk::Algorithm; +use std::collections::HashMap; /// Capability object, see https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-74.md -#[derive(Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Capability { + /// Header for capability #[serde(rename = "h")] - header: Header, + pub header: Header, + /// Payload for capability #[serde(rename = "p")] - payload: Payload, + pub payload: Payload, + /// Signature for capability #[serde(rename = "s")] - signature: Signature, + pub signature: Signature, } -impl Capability { - /// Get the header - pub fn header(&self) -> &Header { - &self.header - } - - /// Get the payload - pub fn payload(&self) -> &Payload { - &self.payload - } - - /// Get the signature - pub fn signature(&self) -> &Signature { - &self.signature - } +/// Type of Capability Header +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum HeaderType { + /// EIP-4361 Capability + #[serde(rename = "eip4361")] + EIP4361, + /// CAIP-122 Capability + #[serde(rename = "caip122")] + CAIP122, } -/// Header for a CACAO -#[derive(Debug, Serialize, Deserialize)] + +/// Header for a Capability +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Header { + /// Type of the Capability Header #[serde(rename = "t")] - r#type: String, + pub r#type: HeaderType, } -impl Header { - /// Get the type of the CACAO - pub fn r#type(&self) -> &str { - &self.r#type - } -} +/// Time format for capability +pub type CapabilityTime = chrono::DateTime; /// Payload for a CACAO -#[derive(Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Payload { - domain: String, - - #[serde(rename = "iss")] - issuer: String, - + /// Audience for payload #[serde(rename = "aud")] - audience: String, + pub audience: String, - version: String, + /// Domain for payload + pub domain: String, - nonce: String, + /// Expiration time + #[serde(rename = "exp", skip_serializing_if = "Option::is_none")] + pub expiration: Option, + /// Issued at time #[serde(rename = "iat")] - issued_at: String, + pub issued_at: CapabilityTime, - #[serde(rename = "nbf", skip_serializing_if = "Option::is_none")] - not_before: Option, + /// Issuer for payload. For capability will be DID in URI format + #[serde(rename = "iss")] + pub issuer: String, - #[serde(rename = "exp", skip_serializing_if = "Option::is_none")] - expiration: Option, + /// Not before time + #[serde(rename = "nbf", skip_serializing_if = "Option::is_none")] + pub not_before: Option, - #[serde(skip_serializing_if = "Option::is_none")] - statement: Option, + /// Nonce of payload + pub nonce: String, + /// Request ID #[serde(rename = "requestId", skip_serializing_if = "Option::is_none")] - request_id: Option, + pub request_id: Option, + /// Resources #[serde(skip_serializing_if = "Option::is_none")] - resources: Option>, -} - -impl Payload { - /// Get the domain - pub fn domain(&self) -> &str { - &self.domain - } - - /// Get the issuer as a DID pkh string - pub fn issuer(&self) -> &str { - &self.issuer - } - - /// Get the audience as a URI - pub fn audience(&self) -> &str { - &self.audience - } + pub resources: Option>, - /// Get the version - pub fn version(&self) -> &str { - &self.version - } - - /// Get the nonce - pub fn nonce(&self) -> &str { - &self.nonce - } - - /// Get the issued at date and time as a RFC3339 string - pub fn issued_at(&self) -> &str { - &self.issued_at - } + /// Subject of payload + #[serde(skip_serializing_if = "Option::is_none")] + pub statement: Option, - /// Get the not before date and time as a RFC3339 string - pub fn not_before(&self) -> Option<&String> { - self.not_before.as_ref() - } + /// Version of payload + pub version: String, +} - /// Get the expiration date and time as a RFC3339 string - pub fn expiration(&self) -> Option<&String> { - self.expiration.as_ref() - } +/// Type of Signature +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum SignatureType { + /// EIP-191 Signature + #[serde(rename = "eip191")] + EIP191, + /// EIP-1271 Signature + #[serde(rename = "eip1271")] + EIP1271, + /// ED25519 signature for solana + #[serde(rename = "solana:ed25519")] + SolanaED25519, + /// ED25519 signature for tezos + #[serde(rename = "tezos:ed25519")] + TezosED25519, + /// SECP256K1 signature for stacks + #[serde(rename = "stacks:secp256k1")] + StacksSECP256K1, + /// SECP256K1 signature for webauthn + #[serde(rename = "webauthn:p256")] + WebAuthNP256, + /// JWS signature + #[serde(rename = "jws")] + JWS, +} - /// Get the statement - pub fn statement(&self) -> Option<&String> { - self.statement.as_ref() +impl SignatureType { + /// Convert signature type to algorithm + pub fn algorithm(&self) -> Algorithm { + match self { + SignatureType::EIP191 => Algorithm::ES256, + SignatureType::EIP1271 => Algorithm::ES256, + SignatureType::SolanaED25519 => Algorithm::EdDSA, + SignatureType::TezosED25519 => Algorithm::EdDSA, + SignatureType::StacksSECP256K1 => Algorithm::ES256K, + SignatureType::WebAuthNP256 => Algorithm::ES256, + SignatureType::JWS => Algorithm::ES256, + } } +} - /// Get the request Id - pub fn request_id(&self) -> Option<&String> { - self.request_id.as_ref() - } +/// Values for unknown metadata +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum MetadataValue { + /// Boolean value + Boolean(bool), + /// Integer value + Integer(i64), + /// Null value + Null, + /// String value + String(String), +} - /// Get the resources - pub fn resources(&self) -> Option<&[String]> { - self.resources.as_ref().map(|r| &r[..]) - } +/// Metadata for signature +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct SignatureMetadata { + /// Algorithm for signature + pub alg: String, + /// Key ID for signature + pub kid: String, + /// Other metadata + #[serde(flatten)] + pub rest: HashMap, } /// Signature of a CACAO -#[derive(Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Signature { + /// Metadata for signature + #[serde(rename = "m", skip_serializing_if = "Option::is_none")] + pub metadata: Option, + /// Type of signature #[serde(rename = "t")] - r#type: String, + pub r#type: SignatureType, + /// Signature bytes #[serde(rename = "s")] - signature: String, -} - -impl Signature { - /// Get the type of the signature - pub fn r#type(&self) -> &str { - &self.r#type - } - /// Get the signature bytes as hex encoded string prefixed with 0x - pub fn signature(&self) -> &str { - &self.signature - } + pub signature: String, } diff --git a/event/src/unvalidated/signed/mod.rs b/event/src/unvalidated/signed/mod.rs index fc493b10d..776743c4a 100644 --- a/event/src/unvalidated/signed/mod.rs +++ b/event/src/unvalidated/signed/mod.rs @@ -68,6 +68,16 @@ impl Event { &self.payload } + /// Get the Envelope + pub fn envelope(&self) -> &Envelope { + &self.envelope + } + + /// Get the capability + pub fn capability(&self) -> Option<&Capability> { + self.capability.as_ref().map(|(_, ref c)| c) + } + /// Constructs a signed event by signing a given event payload. pub fn from_payload(payload: Payload, signer: impl Signer) -> anyhow::Result { let payload_cid = cid_from_dag_cbor(&serde_ipld_dagcbor::to_vec(&payload)?); @@ -192,6 +202,34 @@ impl Envelope { }) }) } + + /// Get the signature + pub fn signature(&self) -> &[Signature] { + &self.signatures + } + + /// Get the signed payload + pub fn payload(&self) -> &Bytes { + &self.payload + } + + /// Construct the jws header from the signature protected bytes + pub fn jws_header(&self) -> Result { + let (protected, _signature) = match self.signatures.first() { + Some(sig) => ( + sig.protected + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Missing protected field"))? + .as_slice(), + sig.signature.as_ref(), + ), + None => { + anyhow::bail!("signature is missing") + } + }; + let header: ssi::jws::Header = serde_json::from_slice(protected)?; + Ok(header) + } } /// A signature part of a JSON Web Signature.