Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: modify cacao, unvalidated module, jwk to add functionality needed for event validation #492

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 26 additions & 1 deletion core/src/jwk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<DidDocument> {
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<JWK> for Jwk {
Expand Down
1 change: 1 addition & 0 deletions event/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
232 changes: 122 additions & 110 deletions event/src/unvalidated/signed/cacao.rs
Original file line number Diff line number Diff line change
@@ -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<chrono::Utc>;

/// 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<CapabilityTime>,

/// 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<String>,
/// 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<String>,
/// Not before time
#[serde(rename = "nbf", skip_serializing_if = "Option::is_none")]
pub not_before: Option<CapabilityTime>,

#[serde(skip_serializing_if = "Option::is_none")]
statement: Option<String>,
/// Nonce of payload
pub nonce: String,

/// Request ID
#[serde(rename = "requestId", skip_serializing_if = "Option::is_none")]
request_id: Option<String>,
pub request_id: Option<String>,

/// Resources
#[serde(skip_serializing_if = "Option::is_none")]
resources: Option<Vec<String>>,
}

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<Vec<String>>,

/// 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<String>,

/// 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<String, MetadataValue>,
}

/// 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<SignatureMetadata>,
/// 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,
}
38 changes: 38 additions & 0 deletions event/src/unvalidated/signed/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,16 @@ impl<D: serde::Serialize> Event<D> {
&self.payload
}

/// Get the Envelope
pub fn envelope(&self) -> &Envelope {
dav1do marked this conversation as resolved.
Show resolved Hide resolved
&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<D>, signer: impl Signer) -> anyhow::Result<Self> {
let payload_cid = cid_from_dag_cbor(&serde_ipld_dagcbor::to_vec(&payload)?);
Expand Down Expand Up @@ -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<ssi::jws::Header, anyhow::Error> {
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.
Expand Down
Loading