use serde_json::Value;
use vti_common::vault::VaultSecret;
use crate::error::AppError;
use crate::keys::seed_store::SeedStore;
use crate::store::KeyspaceHandle;
pub struct SignedEnvelope {
pub signed: Value,
pub principal_did: String,
}
pub enum SignTrustTaskError {
NotSignable { kind: &'static str },
EnvelopeNotObject,
EnvelopeMissingField { field: &'static str },
IssuerNotString,
AlreadyProofed,
IssuerMismatch {
envelope_issuer: String,
expected: String,
},
ExpiresAtNotRfc3339 { value: String },
Expired { value: String },
App(AppError),
}
impl From<AppError> for SignTrustTaskError {
fn from(e: AppError) -> Self {
SignTrustTaskError::App(e)
}
}
pub async fn sign_envelope(
keys_ks: &KeyspaceHandle,
imported_ks: &KeyspaceHandle,
audit_ks: &KeyspaceHandle,
seed_store: &dyn SeedStore,
secret: &VaultSecret,
unsigned_envelope: &Value,
) -> Result<SignedEnvelope, SignTrustTaskError> {
let (principal_did, signing_key_id) = match secret {
VaultSecret::DidSelfIssued {
did,
signing_key_id,
..
}
| VaultSecret::DidcommPeer {
peer_did: did,
signing_key_id,
..
} => (did.clone(), signing_key_id.clone()),
other => {
return Err(SignTrustTaskError::NotSignable {
kind: super::secret_kind_label(other.kind()),
});
}
};
let envelope_obj = unsigned_envelope
.as_object()
.ok_or(SignTrustTaskError::EnvelopeNotObject)?;
for field in ["id", "type", "issuer", "recipient", "issuedAt", "payload"] {
if !envelope_obj.contains_key(field) {
return Err(SignTrustTaskError::EnvelopeMissingField { field });
}
}
if envelope_obj.contains_key("proof") {
return Err(SignTrustTaskError::AlreadyProofed);
}
let envelope_issuer = envelope_obj
.get("issuer")
.and_then(|v| v.as_str())
.ok_or(SignTrustTaskError::IssuerNotString)?;
if envelope_issuer != principal_did {
return Err(SignTrustTaskError::IssuerMismatch {
envelope_issuer: envelope_issuer.to_string(),
expected: principal_did,
});
}
if let Some(exp_v) = envelope_obj.get("expiresAt") {
let exp_str = exp_v.as_str().unwrap_or_default();
match chrono::DateTime::parse_from_rfc3339(exp_str) {
Ok(exp) if exp < chrono::Utc::now() => {
return Err(SignTrustTaskError::Expired {
value: exp_str.to_string(),
});
}
Ok(_) => {}
Err(_) => {
return Err(SignTrustTaskError::ExpiresAtNotRfc3339 {
value: exp_str.to_string(),
});
}
}
}
let secret_key = super::load_signing_secret_by_id(
keys_ks,
imported_ks,
seed_store,
audit_ks,
&signing_key_id,
)
.await?;
let proof = affinidi_data_integrity::DataIntegrityProof::sign(
unsigned_envelope,
&secret_key,
affinidi_data_integrity::SignOptions::new(),
)
.await
.map_err(|e| AppError::Internal(format!("DataIntegrityProof sign failed: {e}")))?;
let proof_value = serde_json::to_value(&proof)
.map_err(|e| AppError::Internal(format!("serialize proof: {e}")))?;
let mut signed = unsigned_envelope.clone();
signed
.as_object_mut()
.expect("envelope is an object — checked above")
.insert("proof".to_string(), proof_value);
Ok(SignedEnvelope {
signed,
principal_did,
})
}