use affinidi_tdk::secrets_resolver::secrets::Secret;
use serde_json::Value;
use trust_tasks_proof::affinidi::{CryptoSuite, SignOptions, sign_trust_task};
use crate::error::AppError;
use crate::server::AppState;
pub fn control_signing_secret(state: &AppState, control_did: &str) -> Result<Secret, AppError> {
let identity = state.identity.as_deref().ok_or_else(|| {
AppError::Internal(
"service identity not loaded; cannot sign the outbound request document".into(),
)
})?;
did_hosting_common::server::trust_tasks::identity_signing_secret(identity, control_did)
.map_err(|e| AppError::Internal(e.to_string()))
}
pub fn require_signing_identity(
identity: Option<&did_hosting_common::server::identity::ServiceIdentity>,
server_did: Option<&str>,
) -> Result<(), AppError> {
let did = server_did.ok_or_else(|| {
AppError::Config(
"server_did is not configured; the control plane signs every reply with its own \
DID and cannot start without one"
.into(),
)
})?;
let identity = identity.ok_or_else(|| {
AppError::Config(format!(
"the service identity for {did} could not be loaded; the control plane cannot sign \
its replies and will not start"
))
})?;
did_hosting_common::server::trust_tasks::identity_signing_secret(identity, did)
.map(|_| ())
.map_err(|e| {
AppError::Config(format!(
"the control plane cannot sign as {did} ({e}) and will not start"
))
})
}
pub async fn sign_trust_task_document(
unsigned: Value,
signing_secret: &Secret,
) -> Result<Value, AppError> {
debug_assert!(
unsigned.get("proof").is_none(),
"document to sign must not already carry a proof"
);
sign_trust_task(
&unsigned,
signing_secret,
SignOptions::new()
.with_proof_purpose(
did_hosting_common::server::trust_tasks::verifier::OPERATIONAL_PROOF_PURPOSE,
)
.with_cryptosuite(CryptoSuite::EddsaJcs2022),
)
.await
.map_err(|e| AppError::Internal(format!("sign trust task document: {e}")))
}
#[cfg(test)]
mod startup_tests {
use super::*;
use did_hosting_common::server::identity::ServiceIdentity;
const DID: &str = "did:webvh:test:control.example.com";
#[tokio::test]
async fn a_control_plane_needs_a_signing_identity_to_start() {
let identity = ServiceIdentity::generated_for(DID).await.unwrap();
require_signing_identity(Some(&identity), Some(DID)).expect("can sign, so starts");
assert!(
require_signing_identity(None, None).is_err(),
"no server_did"
);
assert!(
require_signing_identity(None, Some(DID)).is_err(),
"no identity"
);
assert!(
require_signing_identity(Some(&identity), Some("did:webvh:test:other.example.com"))
.is_err(),
"identity for a different DID"
);
}
}
#[cfg(test)]
pub(crate) mod test_util {
use super::*;
pub(crate) fn did_key_signer(seed: &[u8; 32]) -> (String, Secret) {
let secret = Secret::generate_ed25519(None, Some(seed));
let pk_mb = secret.get_public_keymultibase().expect("multibase pubkey");
let did = format!("did:key:{pk_mb}");
let mut signer = secret;
signer.id = format!("{did}#{pk_mb}");
(did, signer)
}
pub(crate) async fn sign_operational(unsigned: Value, signer: &Secret) -> Value {
let doc: trust_tasks_rs::TrustTask<Value> =
serde_json::from_value(unsigned).expect("unsigned document parses");
let signed = did_hosting_common::server::trust_tasks::sign_document(&doc, signer)
.await
.expect("sign");
serde_json::to_value(&signed).expect("signed document serialises")
}
}