use crate::audit::{PolicyDecisionRecord, SignAudit, SignAuditRecord, SignOutcome, now};
use crate::guardrails::{Guardrails, PolicyDecision, SignContext};
use crate::keys::{KeyInfo, KeyRef, Scheme, Signature, Subject};
use crate::payload::{Intent, Payload};
use crate::port::KeySigner;
use crate::port::SignerError;
pub struct GuardedSigner<S, G, A> {
signer: S,
guardrails: G,
audit: A,
}
impl<S, G, A> GuardedSigner<S, G, A> {
#[must_use]
pub fn new(signer: S, guardrails: G, audit: A) -> Self {
Self {
signer,
guardrails,
audit,
}
}
}
impl<S, G, A> GuardedSigner<S, G, A>
where
S: KeySigner,
G: Guardrails,
A: SignAudit,
{
pub async fn create_key(
&self,
subject: &Subject,
scheme: Scheme,
label: &str,
) -> Result<KeyInfo, SignerError> {
self.signer.create_key(subject, scheme, label).await
}
pub async fn key(&self, key_ref: &KeyRef) -> Result<KeyInfo, SignerError> {
self.signer.key(key_ref).await
}
pub async fn sign(
&self,
subject: &Subject,
key_ref: &KeyRef,
payload: &Payload,
) -> Result<Signature, SignerError> {
let (intent, payload_hash) = match payload
.intent()
.and_then(|intent| Ok((intent, payload.payload_hash()?)))
{
Ok(pair) => pair,
Err(err) => {
self.write(
subject,
key_ref,
[0_u8; 32],
None,
PolicyDecisionRecord::Deny {
reason: format!("the payload did not decode: {err}"),
},
SignOutcome::NotAttempted,
)
.await?;
return Err(err);
}
};
let decision = match self
.guardrails
.check(&SignContext {
subject: subject.clone(),
key_ref: key_ref.clone(),
payload_hash,
intent: intent.clone(),
})
.await
{
Ok(decision) => decision,
Err(err) => {
self.write(
subject,
key_ref,
payload_hash,
Some(intent),
PolicyDecisionRecord::Deny {
reason: format!("the guardrails failed: {err}"),
},
SignOutcome::NotAttempted,
)
.await?;
return Err(err);
}
};
if let PolicyDecision::Deny { reason } = &decision {
self.write(
subject,
key_ref,
payload_hash,
Some(intent),
PolicyDecisionRecord::from(&decision),
SignOutcome::NotAttempted,
)
.await?;
return Err(SignerError::Denied {
reason: reason.clone(),
});
}
self.write(
subject,
key_ref,
payload_hash,
Some(intent.clone()),
PolicyDecisionRecord::Allow,
SignOutcome::NotAttempted,
)
.await?;
match self.signer.sign(key_ref, payload).await {
Ok(signature) => {
self.write(
subject,
key_ref,
payload_hash,
Some(intent),
PolicyDecisionRecord::Allow,
SignOutcome::Signed,
)
.await?;
Ok(signature)
}
Err(err) => {
let error = err.to_string();
self.write(
subject,
key_ref,
payload_hash,
Some(intent),
PolicyDecisionRecord::Allow,
SignOutcome::ProviderError { error },
)
.await?;
Err(err)
}
}
}
async fn write(
&self,
subject: &Subject,
key_ref: &KeyRef,
payload_hash: [u8; 32],
intent: Option<Intent>,
decision: PolicyDecisionRecord,
outcome: SignOutcome,
) -> Result<(), SignerError> {
self.audit
.record(&SignAuditRecord {
subject: subject.clone(),
key_ref: key_ref.clone(),
payload_hash: format!("0x{}", hex::encode(payload_hash)),
intent,
decision,
outcome,
at: now(),
})
.await
.map_err(|err| {
SignerError::Audit(format!(
"the audit trail refused the record, so the signature did not happen: {err}"
))
})
}
}