use std::sync::Arc;
use auths_core::signing::{SecureSigner, StorageSigner};
use auths_core::storage::keychain::KeyAlias;
use auths_crypto::CurveType;
use auths_keri::KeriPublicKey;
use base64::Engine;
use base64::engine::general_purpose::STANDARD as BASE64;
use serde::{Deserialize, Serialize};
use crate::context::AuthsContext;
use crate::domains::compliance::frameworks::{FrameworkReport, INTOTO_STATEMENT_TYPE};
use crate::domains::compliance::query::{
ComplianceQueryError, EvidencePack, RowVerdict, verify_evidence_pack_offline,
};
use auths_verifier::{Ed25519PublicKey, IdentityDID};
pub const DSSE_INTOTO_PAYLOAD_TYPE: &str = "application/vnd.in-toto+json";
fn pae(payload_type: &str, payload: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(payload.len() + payload_type.len() + 32);
out.extend_from_slice(b"DSSEv1 ");
out.extend_from_slice(payload_type.len().to_string().as_bytes());
out.push(b' ');
out.extend_from_slice(payload_type.as_bytes());
out.push(b' ');
out.extend_from_slice(payload.len().to_string().as_bytes());
out.push(b' ');
out.extend_from_slice(payload);
out
}
fn curve_tag(curve: CurveType) -> &'static str {
match curve {
CurveType::Ed25519 => "ed25519",
CurveType::P256 => "p256",
}
}
fn curve_from_tag(tag: &str) -> CurveType {
match tag {
"ed25519" => CurveType::Ed25519,
_ => CurveType::P256,
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DsseSignature {
pub keyid: String,
pub curve: String,
pub sig: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DsseEnvelope {
#[serde(rename = "payloadType")]
pub payload_type: String,
pub payload: String,
pub signatures: Vec<DsseSignature>,
}
impl DsseEnvelope {
pub fn decoded_payload(&self) -> Result<Vec<u8>, ComplianceQueryError> {
BASE64
.decode(self.payload.as_bytes())
.map_err(|e| ComplianceQueryError::Decode(format!("dsse payload base64: {e}")))
}
pub fn to_canonical_json(&self) -> Result<String, ComplianceQueryError> {
json_canon::to_string(self).map_err(|e| ComplianceQueryError::Canonicalize(e.to_string()))
}
pub fn from_json(json: &str) -> Result<Self, ComplianceQueryError> {
serde_json::from_str(json).map_err(|e| ComplianceQueryError::Decode(e.to_string()))
}
pub fn verify(
&self,
org_public_key: &[u8],
org_curve: CurveType,
) -> Result<(), ComplianceQueryError> {
let payload = self.decoded_payload()?;
let to_verify = pae(&self.payload_type, &payload);
let key = KeriPublicKey::from_verkey_bytes(org_public_key, org_curve)
.map_err(|e| ComplianceQueryError::Verification(e.to_string()))?;
for s in &self.signatures {
if curve_from_tag(&s.curve) != org_curve {
continue;
}
let sig = BASE64
.decode(s.sig.as_bytes())
.map_err(|e| ComplianceQueryError::Decode(format!("dsse sig base64: {e}")))?;
if key.verify_signature(&to_verify, &sig).is_ok() {
return Ok(());
}
}
Err(ComplianceQueryError::Verification(
"no DSSE signature verified against the org key".into(),
))
}
}
#[derive(Debug, Clone, Serialize)]
pub struct VerifiedEvidencePack {
pub pack: EvidencePack,
pub org_kel_seq: u128,
pub verdicts: Vec<RowVerdict>,
}
impl VerifiedEvidencePack {
pub fn authentic(&self) -> bool {
self.verdicts.iter().all(|v| {
v.authority_consistent
&& v.transparency_verified.unwrap_or(true)
&& v.checkpoint_attested.unwrap_or(true)
})
}
}
pub fn verify_signed_evidence_pack_offline(
envelope_json: &str,
pinned_roots: &[IdentityDID],
pinned_log_key: Option<&Ed25519PublicKey>,
) -> Result<VerifiedEvidencePack, ComplianceQueryError> {
let envelope = DsseEnvelope::from_json(envelope_json)?;
let payload = envelope.decoded_payload()?;
let statement: serde_json::Value = serde_json::from_slice(&payload)
.map_err(|e| ComplianceQueryError::Decode(format!("dsse payload is not JSON: {e}")))?;
if statement["_type"] != INTOTO_STATEMENT_TYPE {
return Err(ComplianceQueryError::Decode(format!(
"dsse payload is not an in-toto Statement ({INTOTO_STATEMENT_TYPE})"
)));
}
let pack: EvidencePack =
serde_json::from_value(statement["predicate"].clone()).map_err(|e| {
ComplianceQueryError::Decode(format!(
"statement predicate is not an evidence pack: {e}"
))
})?;
let bundle = pack.org_bundle.as_ref().ok_or_else(|| {
ComplianceQueryError::OfflineVerification(
"pack carries no embedded org bundle — not an offline-verifiable pack".into(),
)
})?;
let state = bundle
.authenticated_org_state()
.map_err(|e| ComplianceQueryError::OfflineVerification(e.to_string()))?;
let cesr = state.current_key().ok_or_else(|| {
ComplianceQueryError::Verification("org KEL resolves to no current key".into())
})?;
let org_key = KeriPublicKey::parse(cesr.as_str())
.map_err(|e| ComplianceQueryError::Decode(format!("org verkey decode: {e}")))?;
let org_curve = match org_key {
KeriPublicKey::Ed25519 { .. } => CurveType::Ed25519,
KeriPublicKey::P256 { .. } => CurveType::P256,
};
envelope.verify(org_key.as_bytes(), org_curve)?;
let verdicts = verify_evidence_pack_offline(&pack, pinned_roots, pinned_log_key)?;
Ok(VerifiedEvidencePack {
org_kel_seq: state.sequence,
pack,
verdicts,
})
}
pub fn sign_evidence_pack(
ctx: &AuthsContext,
org_did: &str,
org_alias: &KeyAlias,
org_curve: CurveType,
pack: &EvidencePack,
) -> Result<DsseEnvelope, ComplianceQueryError> {
sign_intoto_statement(
ctx,
org_did,
org_alias,
org_curve,
&pack.to_intoto_statement()?,
)
}
pub fn sign_framework_report(
ctx: &AuthsContext,
org_did: &str,
org_alias: &KeyAlias,
org_curve: CurveType,
report: &FrameworkReport,
) -> Result<DsseEnvelope, ComplianceQueryError> {
sign_intoto_statement(
ctx,
org_did,
org_alias,
org_curve,
&report.to_intoto_statement()?,
)
}
fn sign_intoto_statement(
ctx: &AuthsContext,
org_did: &str,
org_alias: &KeyAlias,
org_curve: CurveType,
statement: &str,
) -> Result<DsseEnvelope, ComplianceQueryError> {
let payload = statement.as_bytes();
let to_sign = pae(DSSE_INTOTO_PAYLOAD_TYPE, payload);
let signer = StorageSigner::new(Arc::clone(&ctx.key_storage));
let sig = signer
.sign_with_alias(org_alias, ctx.passphrase_provider.as_ref(), &to_sign)
.map_err(|e| ComplianceQueryError::Signing(e.to_string()))?;
Ok(DsseEnvelope {
payload_type: DSSE_INTOTO_PAYLOAD_TYPE.to_string(),
payload: BASE64.encode(payload),
signatures: vec![DsseSignature {
keyid: org_did.to_string(),
curve: curve_tag(org_curve).to_string(),
sig: BASE64.encode(&sig),
}],
})
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn pae_matches_dsse_spec_layout() {
let got = pae("demo", b"hello");
assert_eq!(got, b"DSSEv1 4 demo 5 hello");
}
#[test]
fn pae_is_length_prefixed_not_delimiter_ambiguous() {
let a = pae("t", b"a b");
let b = pae("t", b"a b");
assert_ne!(
a, b,
"PAE length-prefix must distinguish differing payloads"
);
}
#[test]
fn curve_tag_round_trips() {
assert_eq!(
curve_from_tag(curve_tag(CurveType::Ed25519)),
CurveType::Ed25519
);
assert_eq!(curve_from_tag(curve_tag(CurveType::P256)), CurveType::P256);
assert_eq!(curve_from_tag("unknown"), CurveType::P256);
}
}