use chio_core::canonical::canonical_json_bytes;
use chio_core::crypto::{
sign_canonical_with_backend, PublicKey, Signature, SigningAlgorithm, SigningBackend,
};
use serde::{Deserialize, Serialize};
use crate::receipt_support::KernelCryptoFloor;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SessionComplianceCertificateBody {
pub session_id: String,
pub policy_hash: String,
pub receipt_count: u64,
pub all_signatures_valid: bool,
pub crypto_floor: String,
pub issued_at: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionComplianceCertificate {
pub body: SessionComplianceCertificateBody,
pub signer_key: PublicKey,
pub algorithm: SigningAlgorithm,
pub signature: Signature,
}
#[derive(Debug, thiserror::Error)]
pub enum ComplianceCertificateError {
#[error(
"compliance certificate crypto_floor field {body_floor} disagrees with issuance floor {issuance_floor}"
)]
CryptoFloorMismatch {
body_floor: String,
issuance_floor: String,
},
#[error("compliance certificate signing failed: {0}")]
SigningFailed(String),
#[error("compliance certificate signature verification failed")]
SignatureVerificationFailed,
#[error(
"compliance certificate algorithm field {declared} disagrees with signature algorithm {actual}"
)]
AlgorithmMismatch {
declared: &'static str,
actual: &'static str,
},
#[error(
"compliance certificate rejected by crypto_floor={floor}: signature algorithm {actual} not permitted"
)]
RejectedByCryptoFloor {
floor: &'static str,
actual: &'static str,
},
}
pub fn issue_session_compliance_certificate(
body: SessionComplianceCertificateBody,
floor: KernelCryptoFloor,
backend: &dyn SigningBackend,
) -> Result<SessionComplianceCertificate, ComplianceCertificateError> {
if body.crypto_floor != floor.as_str() {
return Err(ComplianceCertificateError::CryptoFloorMismatch {
body_floor: body.crypto_floor.clone(),
issuance_floor: floor.as_str().to_string(),
});
}
let (signature, _bytes) = sign_canonical_with_backend(backend, &body)
.map_err(|error| ComplianceCertificateError::SigningFailed(error.to_string()))?;
Ok(SessionComplianceCertificate {
body,
signer_key: backend.public_key(),
algorithm: backend.algorithm(),
signature,
})
}
pub fn verify_session_compliance_certificate(
cert: &SessionComplianceCertificate,
floor: KernelCryptoFloor,
) -> Result<(), ComplianceCertificateError> {
let signature_algorithm = cert.signature.algorithm();
if cert.algorithm != signature_algorithm {
return Err(ComplianceCertificateError::AlgorithmMismatch {
declared: signing_algorithm_label(cert.algorithm),
actual: signing_algorithm_label(signature_algorithm),
});
}
let is_hybrid = matches!(signature_algorithm, SigningAlgorithm::Hybrid);
let allowed = if is_hybrid {
floor.allows_hybrid()
} else {
floor.allows_classical_only()
};
if !allowed {
return Err(ComplianceCertificateError::RejectedByCryptoFloor {
floor: floor.as_str(),
actual: signing_algorithm_label(signature_algorithm),
});
}
if cert.body.crypto_floor != floor.as_str() {
return Err(ComplianceCertificateError::CryptoFloorMismatch {
body_floor: cert.body.crypto_floor.clone(),
issuance_floor: floor.as_str().to_string(),
});
}
let bytes = canonical_json_bytes(&cert.body)
.map_err(|error| ComplianceCertificateError::SigningFailed(error.to_string()))?;
if !cert.signer_key.verify(&bytes, &cert.signature) {
return Err(ComplianceCertificateError::SignatureVerificationFailed);
}
Ok(())
}
fn signing_algorithm_label(alg: SigningAlgorithm) -> &'static str {
match alg {
SigningAlgorithm::Ed25519 => "ed25519",
SigningAlgorithm::P256 => "p256",
SigningAlgorithm::P384 => "p384",
SigningAlgorithm::Hybrid => "hybrid",
}
}