use crate::agent_key::VerificationKey;
use crate::error::{Error, Result};
use crate::message::{Jwe, Jws, SecurityMode};
use async_trait::async_trait;
use serde::de::DeserializeOwned;
use serde::Serialize;
use serde_json::Value;
use std::any::Any;
use std::fmt::Debug;
use std::sync::Arc;
use tap_msg::didcomm::{PlainMessage, PlainMessageExt};
use tap_msg::message::TapMessage;
use uuid::Uuid;
#[derive(Debug, Clone)]
pub struct UnpackedMessage {
pub plain_message: PlainMessage,
pub tap_message: Option<TapMessage>,
}
impl UnpackedMessage {
pub fn new(plain_message: PlainMessage) -> Self {
let tap_message = TapMessage::from_plain_message(&plain_message).ok();
Self {
plain_message,
tap_message,
}
}
pub fn as_typed<T: tap_msg::TapMessageBody>(&self) -> Result<PlainMessage<T>> {
self.plain_message
.clone()
.parse_as()
.map_err(|e| Error::Serialization(e.to_string()))
}
pub fn into_typed(self) -> PlainMessage<Value> {
self.plain_message.into_typed()
}
}
#[derive(Debug, thiserror::Error)]
pub enum MessageError {
#[error("Serialization error: {0}")]
Serialization(#[from] serde_json::Error),
#[error("Key manager error: {0}")]
KeyManager(String),
#[error("Crypto operation failed: {0}")]
Crypto(String),
#[error("Invalid message format: {0}")]
InvalidFormat(String),
#[error("Unsupported security mode: {0:?}")]
UnsupportedSecurityMode(SecurityMode),
#[error("Missing required parameter: {0}")]
MissingParameter(String),
#[error("Key not found: {0}")]
KeyNotFound(String),
#[error("Verification failed")]
VerificationFailed,
#[error("Decryption failed")]
DecryptionFailed,
}
impl From<MessageError> for Error {
fn from(err: MessageError) -> Self {
match err {
MessageError::Serialization(e) => Error::Serialization(e.to_string()),
MessageError::KeyManager(e) => Error::Cryptography(e),
MessageError::Crypto(e) => Error::Cryptography(e),
MessageError::InvalidFormat(e) => Error::Validation(e),
MessageError::UnsupportedSecurityMode(mode) => {
Error::Validation(format!("Unsupported security mode: {:?}", mode))
}
MessageError::MissingParameter(e) => {
Error::Validation(format!("Missing parameter: {}", e))
}
MessageError::KeyNotFound(e) => Error::Cryptography(format!("Key not found: {}", e)),
MessageError::VerificationFailed => {
Error::Cryptography("Verification failed".to_string())
}
MessageError::DecryptionFailed => Error::Cryptography("Decryption failed".to_string()),
}
}
}
#[derive(Debug, Clone)]
pub struct PackOptions {
pub security_mode: SecurityMode,
pub recipient_kid: Option<String>,
pub sender_kid: Option<String>,
}
impl Default for PackOptions {
fn default() -> Self {
Self::new()
}
}
impl PackOptions {
pub fn new() -> Self {
Self {
security_mode: SecurityMode::Plain,
recipient_kid: None,
sender_kid: None,
}
}
pub fn with_plain(mut self) -> Self {
self.security_mode = SecurityMode::Plain;
self
}
pub fn with_sign(mut self, sender_kid: &str) -> Self {
self.security_mode = SecurityMode::Signed;
self.sender_kid = Some(sender_kid.to_string());
self
}
pub fn with_auth_crypt(mut self, sender_kid: &str, recipient_jwk: &serde_json::Value) -> Self {
self.security_mode = SecurityMode::AuthCrypt;
self.sender_kid = Some(sender_kid.to_string());
if let Some(kid) = recipient_jwk.get("kid").and_then(|k| k.as_str()) {
self.recipient_kid = Some(kid.to_string());
}
self
}
pub fn security_mode(&self) -> SecurityMode {
self.security_mode
}
}
#[derive(Debug, Clone)]
pub struct UnpackOptions {
pub expected_security_mode: SecurityMode,
pub expected_recipient_kid: Option<String>,
pub require_signature: bool,
}
impl Default for UnpackOptions {
fn default() -> Self {
Self::new()
}
}
impl UnpackOptions {
pub fn new() -> Self {
Self {
expected_security_mode: SecurityMode::Any,
expected_recipient_kid: None,
require_signature: false,
}
}
pub fn with_require_signature(mut self, require: bool) -> Self {
self.require_signature = require;
self
}
}
#[async_trait]
pub trait Packable<Output = String>: Sized {
async fn pack(
&self,
key_manager: &(impl KeyManagerPacking + ?Sized),
options: PackOptions,
) -> Result<Output>;
}
#[async_trait]
pub trait Unpackable<Input, Output = PlainMessage>: Sized {
async fn unpack(
packed_message: &Input,
key_manager: &(impl KeyManagerPacking + ?Sized),
options: UnpackOptions,
) -> Result<Output>;
}
#[async_trait]
pub trait KeyManagerPacking: Send + Sync + Debug {
async fn get_signing_key(
&self,
kid: &str,
) -> Result<Arc<dyn crate::agent_key::SigningKey + Send + Sync>>;
async fn get_encryption_key(
&self,
kid: &str,
) -> Result<Arc<dyn crate::agent_key::EncryptionKey + Send + Sync>>;
async fn get_decryption_key(
&self,
kid: &str,
) -> Result<Arc<dyn crate::agent_key::DecryptionKey + Send + Sync>>;
async fn resolve_verification_key(
&self,
kid: &str,
) -> Result<Arc<dyn VerificationKey + Send + Sync>>;
}
#[async_trait]
impl Packable for PlainMessage {
async fn pack(
&self,
key_manager: &(impl KeyManagerPacking + ?Sized),
options: PackOptions,
) -> Result<String> {
match options.security_mode {
SecurityMode::Plain => {
serde_json::to_string(self).map_err(|e| Error::Serialization(e.to_string()))
}
SecurityMode::Signed => {
let sender_kid = options.sender_kid.clone().ok_or_else(|| {
Error::Validation("Signed mode requires sender_kid".to_string())
})?;
let signing_key = key_manager.get_signing_key(&sender_kid).await?;
let payload =
serde_json::to_string(self).map_err(|e| Error::Serialization(e.to_string()))?;
let protected_header = crate::message::JwsProtected {
typ: crate::message::DIDCOMM_SIGNED.to_string(),
alg: String::new(), kid: sender_kid.clone(),
};
let jws = signing_key
.create_jws(payload.as_bytes(), Some(protected_header))
.await
.map_err(|e| Error::Cryptography(format!("Failed to create JWS: {}", e)))?;
serde_json::to_string(&jws).map_err(|e| Error::Serialization(e.to_string()))
}
SecurityMode::AuthCrypt => {
let sender_kid = options.sender_kid.clone().ok_or_else(|| {
Error::Validation("AuthCrypt mode requires sender_kid".to_string())
})?;
let recipient_kid = options.recipient_kid.clone().ok_or_else(|| {
Error::Validation("AuthCrypt mode requires recipient_kid".to_string())
})?;
let encryption_key = key_manager.get_encryption_key(&sender_kid).await?;
let recipient_key = key_manager.resolve_verification_key(&recipient_kid).await?;
let plaintext =
serde_json::to_string(self).map_err(|e| Error::Serialization(e.to_string()))?;
let jwe = encryption_key
.create_jwe(plaintext.as_bytes(), &[recipient_key], None)
.await
.map_err(|e| Error::Cryptography(format!("Failed to create JWE: {}", e)))?;
serde_json::to_string(&jwe).map_err(|e| Error::Serialization(e.to_string()))
}
SecurityMode::AnonCrypt => {
let recipient_kid = options.recipient_kid.clone().ok_or_else(|| {
Error::Validation("AnonCrypt mode requires recipient_kid".to_string())
})?;
let encryption_key = if let Some(sender_kid) = &options.sender_kid {
key_manager.get_encryption_key(sender_kid).await?
} else {
return Err(Error::Validation(
"AnonCrypt mode requires a temporary encryption key".to_string(),
));
};
let recipient_key = key_manager.resolve_verification_key(&recipient_kid).await?;
let plaintext =
serde_json::to_string(self).map_err(|e| Error::Serialization(e.to_string()))?;
let jwe = encryption_key
.create_jwe(plaintext.as_bytes(), &[recipient_key], None)
.await
.map_err(|e| Error::Cryptography(format!("Failed to create JWE: {}", e)))?;
serde_json::to_string(&jwe).map_err(|e| Error::Serialization(e.to_string()))
}
SecurityMode::Any => {
Err(Error::Validation(
"SecurityMode::Any is not valid for packing".to_string(),
))
}
}
}
}
pub async fn pack_any<T>(
obj: &T,
key_manager: &(impl KeyManagerPacking + ?Sized),
options: PackOptions,
) -> Result<String>
where
T: Serialize + Send + Sync + std::fmt::Debug + 'static + Sized,
{
if obj.type_id() == std::any::TypeId::of::<PlainMessage>() {
let value = serde_json::to_value(obj).map_err(|e| Error::Serialization(e.to_string()))?;
let plain_msg: PlainMessage =
serde_json::from_value(value).map_err(|e| Error::Serialization(e.to_string()))?;
return plain_msg.pack(key_manager, options).await;
}
match options.security_mode {
SecurityMode::Plain => {
serde_json::to_string(obj).map_err(|e| Error::Serialization(e.to_string()))
}
SecurityMode::Signed => {
let sender_kid = options
.sender_kid
.clone()
.ok_or_else(|| Error::Validation("Signed mode requires sender_kid".to_string()))?;
let signing_key = key_manager.get_signing_key(&sender_kid).await?;
let value =
serde_json::to_value(obj).map_err(|e| Error::Serialization(e.to_string()))?;
let obj = value
.as_object()
.ok_or_else(|| Error::Validation("Message is not a JSON object".to_string()))?;
let id_string = obj
.get("id")
.map(|v| v.as_str().unwrap_or_default().to_string())
.unwrap_or_else(|| Uuid::new_v4().to_string());
let id = id_string.as_str();
let msg_type = obj
.get("type")
.and_then(|v| v.as_str())
.unwrap_or("https://tap.rsvp/schema/1.0/message");
let from = options.sender_kid.as_ref().map(|kid| {
kid.split('#').next().unwrap_or(kid).to_string()
});
let to = if let Some(kid) = &options.recipient_kid {
let did = kid.split('#').next().unwrap_or(kid).to_string();
vec![did]
} else {
vec![]
};
let plain_message = PlainMessage {
id: id.to_string(),
typ: "application/didcomm-plain+json".to_string(),
type_: msg_type.to_string(),
body: value,
from: from.unwrap_or_default(),
to,
thid: None,
pthid: None,
created_time: Some(chrono::Utc::now().timestamp() as u64),
expires_time: None,
from_prior: None,
attachments: None,
extra_headers: std::collections::HashMap::new(),
};
let payload = serde_json::to_string(&plain_message)
.map_err(|e| Error::Serialization(e.to_string()))?;
let protected_header = crate::message::JwsProtected {
typ: crate::message::DIDCOMM_SIGNED.to_string(),
alg: String::new(), kid: sender_kid.clone(),
};
let jws = signing_key
.create_jws(payload.as_bytes(), Some(protected_header))
.await
.map_err(|e| Error::Cryptography(format!("Failed to create JWS: {}", e)))?;
serde_json::to_string(&jws).map_err(|e| Error::Serialization(e.to_string()))
}
SecurityMode::AuthCrypt => {
let sender_kid = options.sender_kid.clone().ok_or_else(|| {
Error::Validation("AuthCrypt mode requires sender_kid".to_string())
})?;
let recipient_kid = options.recipient_kid.clone().ok_or_else(|| {
Error::Validation("AuthCrypt mode requires recipient_kid".to_string())
})?;
let encryption_key = key_manager.get_encryption_key(&sender_kid).await?;
let recipient_key = key_manager.resolve_verification_key(&recipient_kid).await?;
let value =
serde_json::to_value(obj).map_err(|e| Error::Serialization(e.to_string()))?;
let obj = value
.as_object()
.ok_or_else(|| Error::Validation("Message is not a JSON object".to_string()))?;
let id_string = obj
.get("id")
.map(|v| v.as_str().unwrap_or_default().to_string())
.unwrap_or_else(|| Uuid::new_v4().to_string());
let id = id_string.as_str();
let msg_type = obj
.get("type")
.and_then(|v| v.as_str())
.unwrap_or("https://tap.rsvp/schema/1.0/message");
let from = options.sender_kid.as_ref().map(|kid| {
kid.split('#').next().unwrap_or(kid).to_string()
});
let to = if let Some(kid) = &options.recipient_kid {
let did = kid.split('#').next().unwrap_or(kid).to_string();
vec![did]
} else {
vec![]
};
let plain_message = PlainMessage {
id: id.to_string(),
typ: "application/didcomm-plain+json".to_string(),
type_: msg_type.to_string(),
body: value,
from: from.unwrap_or_default(),
to,
thid: None,
pthid: None,
created_time: Some(chrono::Utc::now().timestamp() as u64),
expires_time: None,
from_prior: None,
attachments: None,
extra_headers: std::collections::HashMap::new(),
};
let plaintext = serde_json::to_string(&plain_message)
.map_err(|e| Error::Serialization(e.to_string()))?;
let jwe = encryption_key
.create_jwe(plaintext.as_bytes(), &[recipient_key], None)
.await
.map_err(|e| Error::Cryptography(format!("Failed to create JWE: {}", e)))?;
serde_json::to_string(&jwe).map_err(|e| Error::Serialization(e.to_string()))
}
SecurityMode::AnonCrypt => {
let recipient_kid = options.recipient_kid.clone().ok_or_else(|| {
Error::Validation("AnonCrypt mode requires recipient_kid".to_string())
})?;
let encryption_key = if let Some(sender_kid) = &options.sender_kid {
key_manager.get_encryption_key(sender_kid).await?
} else {
return Err(Error::Validation(
"AnonCrypt mode requires a temporary encryption key".to_string(),
));
};
let recipient_key = key_manager.resolve_verification_key(&recipient_kid).await?;
let value =
serde_json::to_value(obj).map_err(|e| Error::Serialization(e.to_string()))?;
let obj = value
.as_object()
.ok_or_else(|| Error::Validation("Message is not a JSON object".to_string()))?;
let id_string = obj
.get("id")
.map(|v| v.as_str().unwrap_or_default().to_string())
.unwrap_or_else(|| Uuid::new_v4().to_string());
let msg_type = obj
.get("type")
.and_then(|v| v.as_str())
.unwrap_or("https://tap.rsvp/schema/1.0/message");
let to = if let Some(kid) = &options.recipient_kid {
let did = kid.split('#').next().unwrap_or(kid).to_string();
vec![did]
} else {
vec![]
};
let plain_message = PlainMessage {
id: id_string,
typ: "application/didcomm-plain+json".to_string(),
type_: msg_type.to_string(),
body: value,
from: String::new(), to,
thid: None,
pthid: None,
created_time: Some(chrono::Utc::now().timestamp() as u64),
expires_time: None,
from_prior: None,
attachments: None,
extra_headers: std::collections::HashMap::new(),
};
let plaintext = serde_json::to_string(&plain_message)
.map_err(|e| Error::Serialization(e.to_string()))?;
let jwe = encryption_key
.create_jwe(plaintext.as_bytes(), &[recipient_key], None)
.await
.map_err(|e| Error::Cryptography(format!("Failed to create JWE: {}", e)))?;
serde_json::to_string(&jwe).map_err(|e| Error::Serialization(e.to_string()))
}
SecurityMode::Any => {
Err(Error::Validation(
"SecurityMode::Any is not valid for packing".to_string(),
))
}
}
}
#[async_trait]
impl<T: DeserializeOwned + Send + 'static> Unpackable<Jws, T> for Jws {
async fn unpack(
packed_message: &Jws,
key_manager: &(impl KeyManagerPacking + ?Sized),
_options: UnpackOptions,
) -> Result<T> {
let payload_bytes = crate::message::base64_decode_flexible(&packed_message.payload)
.map_err(|e| Error::Cryptography(format!("Failed to decode JWS payload: {}", e)))?;
let payload_str = String::from_utf8(payload_bytes)
.map_err(|e| Error::Validation(format!("Invalid UTF-8 in payload: {}", e)))?;
let plain_message: PlainMessage =
serde_json::from_str(&payload_str).map_err(|e| Error::Serialization(e.to_string()))?;
let mut verified = false;
for signature in &packed_message.signatures {
let protected_bytes = crate::message::base64_decode_flexible(&signature.protected)
.map_err(|e| {
Error::Cryptography(format!("Failed to decode protected header: {}", e))
})?;
let protected: crate::message::JwsProtected = serde_json::from_slice(&protected_bytes)
.map_err(|e| {
Error::Serialization(format!("Failed to parse protected header: {}", e))
})?;
let kid = match signature.get_kid() {
Some(kid) => kid,
None => continue, };
let verification_key = match key_manager.resolve_verification_key(&kid).await {
Ok(key) => key,
Err(_) => continue, };
let signature_bytes = crate::message::base64_decode_flexible(&signature.signature)
.map_err(|e| Error::Cryptography(format!("Failed to decode signature: {}", e)))?;
let signing_input = format!("{}.{}", signature.protected, packed_message.payload);
match verification_key
.verify_signature(signing_input.as_bytes(), &signature_bytes, &protected)
.await
{
Ok(true) => {
verified = true;
break;
}
_ => continue,
}
}
if !verified {
return Err(Error::Cryptography(
"Signature verification failed".to_string(),
));
}
if std::any::TypeId::of::<T>() == std::any::TypeId::of::<PlainMessage>() {
let result = serde_json::to_value(plain_message).unwrap();
return serde_json::from_value(result).map_err(|e| Error::Serialization(e.to_string()));
}
serde_json::from_value(plain_message.body).map_err(|e| Error::Serialization(e.to_string()))
}
}
#[async_trait]
impl<T: DeserializeOwned + Send + 'static> Unpackable<Jwe, T> for Jwe {
async fn unpack(
packed_message: &Jwe,
key_manager: &(impl KeyManagerPacking + ?Sized),
options: UnpackOptions,
) -> Result<T> {
let recipients = if let Some(kid) = &options.expected_recipient_kid {
packed_message
.recipients
.iter()
.filter(|r| r.header.kid == *kid)
.collect::<Vec<_>>()
} else {
packed_message.recipients.iter().collect::<Vec<_>>()
};
let mut last_error = None;
for recipient in recipients {
let kid = &recipient.header.kid;
let decryption_key = match key_manager.get_decryption_key(kid).await {
Ok(key) => key,
Err(e) => {
last_error = Some(format!("Key lookup failed for {}: {}", kid, e));
continue;
}
};
match decryption_key.unwrap_jwe(packed_message).await {
Ok(plaintext) => {
let plaintext_str = String::from_utf8(plaintext).map_err(|e| {
Error::Validation(format!("Invalid UTF-8 in plaintext: {}", e))
})?;
let plain_message: PlainMessage = match serde_json::from_str(&plaintext_str) {
Ok(msg) => msg,
Err(e) => {
return Err(Error::Serialization(e.to_string()));
}
};
if std::any::TypeId::of::<T>() == std::any::TypeId::of::<PlainMessage>() {
let result = serde_json::to_value(plain_message).unwrap();
return serde_json::from_value(result)
.map_err(|e| Error::Serialization(e.to_string()));
}
return serde_json::from_value(plain_message.body)
.map_err(|e| Error::Serialization(e.to_string()));
}
Err(e) => {
last_error = Some(format!("Decryption failed for {}: {}", kid, e));
continue;
}
}
}
Err(Error::Cryptography(format!(
"Failed to decrypt JWE for any of {} recipients{}",
packed_message.recipients.len(),
last_error.map(|e| format!(": {}", e)).unwrap_or_default()
)))
}
}
#[async_trait]
impl<T: DeserializeOwned + Send + 'static> Unpackable<String, T> for String {
async fn unpack(
packed_message: &String,
key_manager: &(impl KeyManagerPacking + ?Sized),
options: UnpackOptions,
) -> Result<T> {
if let Ok(value) = serde_json::from_str::<Value>(packed_message) {
if value.get("payload").is_some()
&& (value.get("signatures").is_some() || value.get("signature").is_some())
{
let jws: Jws = serde_json::from_str(packed_message)
.map_err(|e| Error::Serialization(e.to_string()))?;
return Jws::unpack(&jws, key_manager, options).await;
}
if value.get("ciphertext").is_some()
&& value.get("protected").is_some()
&& value.get("recipients").is_some()
{
let jwe: Jwe = serde_json::from_str(packed_message)
.map_err(|e| Error::Serialization(e.to_string()))?;
return Jwe::unpack(&jwe, key_manager, options).await;
}
if value.get("body").is_some() && value.get("type").is_some() {
let plain: PlainMessage = serde_json::from_str(packed_message)
.map_err(|e| Error::Serialization(e.to_string()))?;
if std::any::TypeId::of::<T>() == std::any::TypeId::of::<PlainMessage>() {
let result = serde_json::to_value(plain).unwrap();
return serde_json::from_value(result)
.map_err(|e| Error::Serialization(e.to_string()));
}
return serde_json::from_value(plain.body)
.map_err(|e| Error::Serialization(e.to_string()));
}
return serde_json::from_value(value).map_err(|e| Error::Serialization(e.to_string()));
}
Err(Error::Validation("Message is not valid JSON".to_string()))
}
}
#[async_trait]
impl Unpackable<String, UnpackedMessage> for String {
async fn unpack(
packed_message: &String,
key_manager: &(impl KeyManagerPacking + ?Sized),
options: UnpackOptions,
) -> Result<UnpackedMessage> {
let plain_message: PlainMessage =
String::unpack(packed_message, key_manager, options).await?;
Ok(UnpackedMessage::new(plain_message))
}
}
#[async_trait]
impl Unpackable<Jws, UnpackedMessage> for Jws {
async fn unpack(
packed_message: &Jws,
key_manager: &(impl KeyManagerPacking + ?Sized),
options: UnpackOptions,
) -> Result<UnpackedMessage> {
let plain_message: PlainMessage = Jws::unpack(packed_message, key_manager, options).await?;
Ok(UnpackedMessage::new(plain_message))
}
}
#[async_trait]
impl Unpackable<Jwe, UnpackedMessage> for Jwe {
async fn unpack(
packed_message: &Jwe,
key_manager: &(impl KeyManagerPacking + ?Sized),
options: UnpackOptions,
) -> Result<UnpackedMessage> {
let plain_message: PlainMessage = Jwe::unpack(packed_message, key_manager, options).await?;
Ok(UnpackedMessage::new(plain_message))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::agent_key_manager::AgentKeyManagerBuilder;
use crate::did::{DIDGenerationOptions, KeyType};
use crate::key_manager::KeyManager;
use std::sync::Arc;
use tap_msg::didcomm::PlainMessage;
use tap_msg::message::agent::TapParticipant;
#[tokio::test]
async fn test_plain_message_pack_unpack() {
let key_manager = Arc::new(AgentKeyManagerBuilder::new().build().unwrap());
let key = key_manager
.generate_key(DIDGenerationOptions {
key_type: KeyType::Ed25519,
})
.unwrap();
let message = PlainMessage {
id: "test-message-1".to_string(),
typ: "application/didcomm-plain+json".to_string(),
type_: "https://example.org/test".to_string(),
body: serde_json::json!({
"content": "Hello, World!"
}),
from: key.did.clone(),
to: vec!["did:example:bob".to_string()],
thid: None,
pthid: None,
created_time: Some(1234567890),
expires_time: None,
from_prior: None,
attachments: None,
extra_headers: Default::default(),
};
let pack_options = PackOptions::new().with_plain();
let packed = message.pack(&*key_manager, pack_options).await.unwrap();
let unpack_options = UnpackOptions::new();
let unpacked: PlainMessage = String::unpack(&packed, &*key_manager, unpack_options)
.await
.unwrap();
assert_eq!(unpacked.id, message.id);
assert_eq!(unpacked.type_, message.type_);
assert_eq!(unpacked.body, message.body);
assert_eq!(unpacked.from, message.from);
assert_eq!(unpacked.to, message.to);
}
#[tokio::test]
async fn test_jws_message_pack_unpack() {
let key_manager = Arc::new(AgentKeyManagerBuilder::new().build().unwrap());
let key = key_manager
.generate_key(DIDGenerationOptions {
key_type: KeyType::Ed25519,
})
.unwrap();
let sender_kid = key.did_doc.verification_method[0].id.clone();
let message = PlainMessage {
id: "test-message-2".to_string(),
typ: "application/didcomm-plain+json".to_string(),
type_: "https://example.org/test".to_string(),
body: serde_json::json!({
"content": "Signed message"
}),
from: key.did.clone(),
to: vec!["did:example:bob".to_string()],
thid: None,
pthid: None,
created_time: Some(1234567890),
expires_time: None,
from_prior: None,
attachments: None,
extra_headers: Default::default(),
};
let pack_options = PackOptions::new().with_sign(&sender_kid);
let packed = message.pack(&*key_manager, pack_options).await.unwrap();
let jws: Jws = serde_json::from_str(&packed).unwrap();
assert!(!jws.signatures.is_empty());
let protected_header = jws.signatures[0].get_protected_header().unwrap();
assert_eq!(protected_header.kid, sender_kid);
assert_eq!(protected_header.typ, "application/didcomm-signed+json");
assert_eq!(protected_header.alg, "EdDSA");
let unpack_options = UnpackOptions::new();
let unpacked: PlainMessage = String::unpack(&packed, &*key_manager, unpack_options)
.await
.unwrap();
assert_eq!(unpacked.id, message.id);
assert_eq!(unpacked.type_, message.type_);
assert_eq!(unpacked.body, message.body);
assert_eq!(unpacked.from, message.from);
assert_eq!(unpacked.to, message.to);
}
#[tokio::test]
async fn test_different_key_types_jws() {
let key_types = vec![KeyType::Ed25519];
for key_type in key_types {
let key_manager = Arc::new(AgentKeyManagerBuilder::new().build().unwrap());
let key = key_manager
.generate_key(DIDGenerationOptions { key_type })
.unwrap();
let sender_kid = key.did_doc.verification_method[0].id.clone();
let message = PlainMessage {
id: format!("test-{:?}", key_type),
typ: "application/didcomm-plain+json".to_string(),
type_: "https://example.org/test".to_string(),
body: serde_json::json!({
"content": format!("Signed with {:?}", key_type)
}),
from: key.did.clone(),
to: vec!["did:example:bob".to_string()],
thid: None,
pthid: None,
created_time: Some(1234567890),
expires_time: None,
from_prior: None,
attachments: None,
extra_headers: Default::default(),
};
let pack_options = PackOptions::new().with_sign(&sender_kid);
let packed = message.pack(&*key_manager, pack_options).await.unwrap();
let jws: Jws = serde_json::from_str(&packed).unwrap();
assert!(!jws.signatures.is_empty());
let protected_header = jws.signatures[0].get_protected_header().unwrap();
assert_eq!(protected_header.kid, sender_kid);
let expected_alg = match key_type {
#[cfg(feature = "crypto-ed25519")]
KeyType::Ed25519 => "EdDSA",
#[cfg(feature = "crypto-p256")]
KeyType::P256 => "ES256",
#[cfg(feature = "crypto-secp256k1")]
KeyType::Secp256k1 => "ES256K",
};
assert_eq!(protected_header.alg, expected_alg);
let unpack_options = UnpackOptions::new();
let unpacked: PlainMessage = String::unpack(&packed, &*key_manager, unpack_options)
.await
.unwrap();
assert_eq!(unpacked.id, message.id);
assert_eq!(unpacked.body, message.body);
}
}
#[tokio::test]
async fn test_unpack_with_wrong_signature() {
let key_manager1 = Arc::new(AgentKeyManagerBuilder::new().build().unwrap());
let key1 = key_manager1
.generate_key(DIDGenerationOptions {
key_type: KeyType::Ed25519,
})
.unwrap();
let message = PlainMessage {
id: "test-wrong-sig".to_string(),
typ: "application/didcomm-plain+json".to_string(),
type_: "https://example.org/test".to_string(),
body: serde_json::json!({
"content": "Test wrong signature"
}),
from: key1.did.clone(),
to: vec!["did:example:bob".to_string()],
thid: None,
pthid: None,
created_time: Some(1234567890),
expires_time: None,
from_prior: None,
attachments: None,
extra_headers: Default::default(),
};
let sender_kid = key1.did_doc.verification_method[0].id.clone();
let pack_options = PackOptions::new().with_sign(&sender_kid);
let packed = message.pack(&*key_manager1, pack_options).await.unwrap();
let mut jws: crate::message::Jws = serde_json::from_str(&packed).unwrap();
jws.signatures[0].signature = "AAAA_invalid_signature_AAAA".to_string();
let tampered = serde_json::to_string(&jws).unwrap();
let unpack_options = UnpackOptions::new();
let result: Result<PlainMessage> =
String::unpack(&tampered, &*key_manager1, unpack_options).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_unpack_cross_agent_with_did_key() {
let key_manager1 = Arc::new(AgentKeyManagerBuilder::new().build().unwrap());
let key1 = key_manager1
.generate_key(DIDGenerationOptions {
key_type: KeyType::Ed25519,
})
.unwrap();
let key_manager2 = Arc::new(AgentKeyManagerBuilder::new().build().unwrap());
let _key2 = key_manager2
.generate_key(DIDGenerationOptions {
key_type: KeyType::Ed25519,
})
.unwrap();
let message = PlainMessage {
id: "test-cross-agent".to_string(),
typ: "application/didcomm-plain+json".to_string(),
type_: "https://example.org/test".to_string(),
body: serde_json::json!({
"content": "Cross-agent verification"
}),
from: key1.did.clone(),
to: vec!["did:example:bob".to_string()],
thid: None,
pthid: None,
created_time: Some(1234567890),
expires_time: None,
from_prior: None,
attachments: None,
extra_headers: Default::default(),
};
let sender_kid = key1.did_doc.verification_method[0].id.clone();
let pack_options = PackOptions::new().with_sign(&sender_kid);
let packed = message.pack(&*key_manager1, pack_options).await.unwrap();
let unpack_options = UnpackOptions::new();
let result: PlainMessage = String::unpack(&packed, &*key_manager2, unpack_options)
.await
.unwrap();
assert_eq!(result.id, "test-cross-agent");
assert_eq!(
result.body,
serde_json::json!({"content": "Cross-agent verification"})
);
}
#[tokio::test]
async fn test_unpack_to_unpacked_message() {
let key_manager = Arc::new(AgentKeyManagerBuilder::new().build().unwrap());
let key = key_manager
.generate_key(DIDGenerationOptions {
key_type: KeyType::Ed25519,
})
.unwrap();
let message = PlainMessage {
id: "test-transfer-1".to_string(),
typ: "application/didcomm-plain+json".to_string(),
type_: "https://tap.rsvp/schema/1.0#Transfer".to_string(),
body: serde_json::json!({
"@type": "https://tap.rsvp/schema/1.0#Transfer",
"transaction_id": "test-tx-123",
"asset": "eip155:1/slip44:60",
"originator": {
"@id": key.did.clone()
},
"amount": "100",
"agents": [],
"memo": null,
"beneficiary": {
"@id": "did:example:bob"
},
"settlement_id": null,
"connection_id": null,
"metadata": {}
}),
from: key.did.clone(),
to: vec!["did:example:bob".to_string()],
thid: None,
pthid: None,
created_time: Some(1234567890),
expires_time: None,
from_prior: None,
attachments: None,
extra_headers: Default::default(),
};
let pack_options = PackOptions::new().with_plain();
let packed = message.pack(&*key_manager, pack_options).await.unwrap();
let unpack_options = UnpackOptions::new();
let unpacked: UnpackedMessage = String::unpack(&packed, &*key_manager, unpack_options)
.await
.unwrap();
assert_eq!(unpacked.plain_message.id, message.id);
assert_eq!(unpacked.plain_message.type_, message.type_);
if unpacked.tap_message.is_none() {
println!(
"TAP message parsing failed for body: {}",
serde_json::to_string_pretty(&unpacked.plain_message.body).unwrap()
);
}
assert!(unpacked.tap_message.is_some());
match unpacked.tap_message.unwrap() {
TapMessage::Transfer(transfer) => {
assert_eq!(transfer.amount, "100");
assert_eq!(transfer.originator.as_ref().unwrap().id(), key.did);
}
_ => panic!("Expected Transfer message"),
}
}
#[tokio::test]
async fn test_unpack_invalid_tap_message() {
let key_manager = Arc::new(AgentKeyManagerBuilder::new().build().unwrap());
let key = key_manager
.generate_key(DIDGenerationOptions {
key_type: KeyType::Ed25519,
})
.unwrap();
let message = PlainMessage {
id: "test-unknown-1".to_string(),
typ: "application/didcomm-plain+json".to_string(),
type_: "https://example.org/unknown#message".to_string(),
body: serde_json::json!({
"content": "Unknown message type"
}),
from: key.did.clone(),
to: vec!["did:example:bob".to_string()],
thid: None,
pthid: None,
created_time: Some(1234567890),
expires_time: None,
from_prior: None,
attachments: None,
extra_headers: Default::default(),
};
let pack_options = PackOptions::new().with_plain();
let packed = message.pack(&*key_manager, pack_options).await.unwrap();
let unpack_options = UnpackOptions::new();
let unpacked: UnpackedMessage = String::unpack(&packed, &*key_manager, unpack_options)
.await
.unwrap();
assert_eq!(unpacked.plain_message.id, message.id);
assert!(unpacked.tap_message.is_none());
}
}