use base64::Engine;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use ed25519_dalek::{Signer, SigningKey};
use serde_json::json;
use crate::error::AppError;
use crate::keys::seed_store::SeedStore;
use crate::operations::internal_authority::InternalAuthority;
use crate::store::KeyspaceHandle;
use vta_sdk::did_key::decode_private_key_multibase;
const APPROVAL_TTL_SECS: u64 = 300;
pub fn step_up_policy_approve(_holder_did: &str, _rp_did: &str) -> bool {
true
}
pub fn build_vta_approval_token(
vta_did: &str,
holder_did: &str,
rp_did: &str,
nonce: &str,
iat: u64,
signing_key: &SigningKey,
) -> Result<String, AppError> {
let header = json!({
"alg": "EdDSA",
"typ": "JWT",
"kid": format!("{vta_did}#key-0"),
});
let payload = json!({
"iss": vta_did,
"sub": holder_did,
"aud": rp_did,
"nonce": nonce,
"iat": iat,
"exp": iat + APPROVAL_TTL_SECS,
});
let header_b64 = URL_SAFE_NO_PAD.encode(
serde_json::to_vec(&header)
.map_err(|e| AppError::Internal(format!("serialize approval header: {e}")))?,
);
let payload_b64 = URL_SAFE_NO_PAD.encode(
serde_json::to_vec(&payload)
.map_err(|e| AppError::Internal(format!("serialize approval payload: {e}")))?,
);
let signing_input = format!("{header_b64}.{payload_b64}");
let signature = signing_key.sign(signing_input.as_bytes());
let sig_b64 = URL_SAFE_NO_PAD.encode(signature.to_bytes());
Ok(format!("{signing_input}.{sig_b64}"))
}
pub async fn load_vta_key0_signing_key(
keys_ks: &KeyspaceHandle,
imported_ks: &KeyspaceHandle,
seed_store: &dyn SeedStore,
audit_ks: &KeyspaceHandle,
vta_did: &str,
) -> Result<SigningKey, AppError> {
let key_id = format!("{vta_did}#key-0");
let authority = InternalAuthority::new("step-up-approval");
let resp = crate::operations::keys::get_key_secret_internal(
keys_ks,
imported_ks,
seed_store,
audit_ks,
authority,
&key_id,
"step-up-approval-internal",
)
.await?;
let seed: [u8; 32] = decode_private_key_multibase(&resp.private_key_multibase)
.map_err(|e| AppError::Internal(format!("decode VTA key-0 seed for {key_id}: {e}")))?;
Ok(SigningKey::from_bytes(&seed))
}
#[cfg(test)]
mod tests {
use super::*;
use ed25519_dalek::{Signature, Verifier, VerifyingKey};
#[test]
fn approval_token_matches_contract_and_verifies() {
let signing_key = SigningKey::from_bytes(&[7u8; 32]);
let vta_did = "did:webvh:Q1:vta.example.com:agent";
let holder = "did:key:z6MkHolder";
let rp = "did:key:z6MkRelyingParty";
let nonce = "deadbeefcafe";
let iat = 1_700_000_000u64;
let token = build_vta_approval_token(vta_did, holder, rp, nonce, iat, &signing_key)
.expect("token builds");
let parts: Vec<&str> = token.split('.').collect();
assert_eq!(parts.len(), 3, "compact JWS has three parts");
let header: serde_json::Value =
serde_json::from_slice(&URL_SAFE_NO_PAD.decode(parts[0]).unwrap()).unwrap();
assert_eq!(header["alg"], "EdDSA");
assert_eq!(header["typ"], "JWT");
assert_eq!(header["kid"], format!("{vta_did}#key-0"));
let payload: serde_json::Value =
serde_json::from_slice(&URL_SAFE_NO_PAD.decode(parts[1]).unwrap()).unwrap();
assert_eq!(payload["iss"], vta_did);
assert_eq!(payload["sub"], holder);
assert_eq!(payload["aud"], rp);
assert_eq!(payload["nonce"], nonce);
assert_eq!(payload["iat"], iat);
assert_eq!(payload["exp"], iat + 300);
let kid = header["kid"].as_str().unwrap();
let kid_base = kid.split('#').next().unwrap();
assert_eq!(kid_base, payload["iss"].as_str().unwrap());
let vk: VerifyingKey = signing_key.verifying_key();
let signing_input = format!("{}.{}", parts[0], parts[1]);
let sig = Signature::from_slice(&URL_SAFE_NO_PAD.decode(parts[2]).unwrap()).unwrap();
vk.verify(signing_input.as_bytes(), &sig)
.expect("signature must verify");
}
#[test]
fn policy_approves_by_default() {
assert!(step_up_policy_approve("did:key:zHolder", "did:key:zRp"));
}
}