use ed25519_dalek::{Signer, SigningKey, Verifier, VerifyingKey};
use thiserror::Error;
use crate::signing::{b64decode, b64encode};
#[derive(Debug, Error, PartialEq, Eq)]
pub enum CertError {
#[error("certificate base64 decode failed")]
BadEncoding,
#[error("certificate length is not 64 bytes")]
BadLength,
#[error("public key length is not 32 bytes")]
BadKey,
#[error("signature did not verify")]
Rejected,
}
pub fn sign_did_cert(signing_key: &[u8], payload_did: &str) -> Result<String, CertError> {
if signing_key.len() < 32 {
return Err(CertError::BadKey);
}
let mut sk_bytes = [0u8; 32];
sk_bytes.copy_from_slice(&signing_key[..32]);
let sk = SigningKey::from_bytes(&sk_bytes);
let sig = sk.sign(payload_did.as_bytes());
Ok(b64encode(&sig.to_bytes()))
}
pub fn verify_op_cert(
op_pubkey: &[u8],
op_cert_b64: &str,
session_did: &str,
) -> Result<(), CertError> {
verify_did_cert(op_pubkey, op_cert_b64, session_did)
}
pub fn verify_member_cert(
org_pubkey: &[u8],
member_cert_b64: &str,
op_did: &str,
) -> Result<(), CertError> {
verify_did_cert(org_pubkey, member_cert_b64, op_did)
}
fn succession_payload(kind: &str, old_did: &str, new_did: &str) -> String {
format!("wire-succession-v1|{kind}|{old_did}|{new_did}")
}
pub fn sign_succession_cert(
old_signing_key: &[u8],
kind: &str,
old_did: &str,
new_did: &str,
) -> Result<String, CertError> {
sign_did_cert(old_signing_key, &succession_payload(kind, old_did, new_did))
}
pub fn verify_succession_cert(
old_pubkey: &[u8],
cert_b64: &str,
kind: &str,
old_did: &str,
new_did: &str,
) -> Result<(), CertError> {
verify_did_cert(
old_pubkey,
cert_b64,
&succession_payload(kind, old_did, new_did),
)
}
fn verify_did_cert(pubkey: &[u8], cert_b64: &str, payload_did: &str) -> Result<(), CertError> {
if pubkey.len() != 32 {
return Err(CertError::BadKey);
}
let mut pk_arr = [0u8; 32];
pk_arr.copy_from_slice(pubkey);
let vk = VerifyingKey::from_bytes(&pk_arr).map_err(|_| CertError::BadKey)?;
let sig_bytes = b64decode(cert_b64).map_err(|_| CertError::BadEncoding)?;
if sig_bytes.len() != 64 {
return Err(CertError::BadLength);
}
let mut sig_arr = [0u8; 64];
sig_arr.copy_from_slice(&sig_bytes);
let sig = ed25519_dalek::Signature::from_bytes(&sig_arr);
vk.verify(payload_did.as_bytes(), &sig)
.map_err(|_| CertError::Rejected)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::agent_card::{did_for_op, did_for_org, did_for_with_key};
use crate::signing::generate_keypair;
#[test]
fn sign_verify_op_cert_roundtrip() {
let (op_sk, op_pk) = generate_keypair();
let (_, session_pk) = generate_keypair();
let session_did = did_for_with_key("vesper-valley", &session_pk);
let cert = sign_did_cert(&op_sk, &session_did).unwrap();
verify_op_cert(&op_pk, &cert, &session_did).unwrap();
}
#[test]
fn sign_verify_member_cert_roundtrip() {
let (org_sk, org_pk) = generate_keypair();
let (_, op_pk) = generate_keypair();
let op_did = did_for_op("darby", &op_pk);
let cert = sign_did_cert(&org_sk, &op_did).unwrap();
verify_member_cert(&org_pk, &cert, &op_did).unwrap();
}
#[test]
fn verify_op_cert_rejects_wrong_session_did() {
let (op_sk, op_pk) = generate_keypair();
let (_, sk_a) = generate_keypair();
let (_, sk_b) = generate_keypair();
let did_a = did_for_with_key("session-a", &sk_a);
let did_b = did_for_with_key("session-b", &sk_b);
let cert = sign_did_cert(&op_sk, &did_a).unwrap();
assert_eq!(
verify_op_cert(&op_pk, &cert, &did_b),
Err(CertError::Rejected)
);
}
#[test]
fn verify_member_cert_rejects_wrong_op_did() {
let (org_sk, org_pk) = generate_keypair();
let (_, op_a_pk) = generate_keypair();
let (_, op_b_pk) = generate_keypair();
let op_a = did_for_op("darby", &op_a_pk);
let op_b = did_for_op("willard", &op_b_pk);
let cert = sign_did_cert(&org_sk, &op_a).unwrap();
assert_eq!(
verify_member_cert(&org_pk, &cert, &op_b),
Err(CertError::Rejected)
);
}
#[test]
fn verify_op_cert_rejects_wrong_op_key() {
let (alice_sk, _) = generate_keypair();
let (_, bob_pk) = generate_keypair();
let (_, session_pk) = generate_keypair();
let session_did = did_for_with_key("s", &session_pk);
let cert = sign_did_cert(&alice_sk, &session_did).unwrap();
assert_eq!(
verify_op_cert(&bob_pk, &cert, &session_did),
Err(CertError::Rejected)
);
}
#[test]
fn verify_op_cert_rejects_bad_base64() {
let (_, pk) = generate_keypair();
assert_eq!(
verify_op_cert(&pk, "not-base64!", "did:wire:s"),
Err(CertError::BadEncoding)
);
}
#[test]
fn verify_op_cert_rejects_short_cert() {
let (_, pk) = generate_keypair();
let short = b64encode(&[0u8; 32]);
assert_eq!(
verify_op_cert(&pk, &short, "did:wire:s"),
Err(CertError::BadLength)
);
}
#[test]
fn verify_op_cert_rejects_short_pubkey() {
let (sk, _) = generate_keypair();
let cert = sign_did_cert(&sk, "did:wire:s").unwrap();
let short_pk = vec![0u8; 16];
assert_eq!(
verify_op_cert(&short_pk, &cert, "did:wire:s"),
Err(CertError::BadKey)
);
}
#[test]
fn sign_did_cert_rejects_short_signing_key() {
let short_sk = vec![0u8; 16];
assert_eq!(
sign_did_cert(&short_sk, "did:wire:s"),
Err(CertError::BadKey)
);
}
#[test]
fn op_and_org_cert_signing_are_indistinguishable_at_byte_level() {
let (op_sk, _op_pk) = generate_keypair();
let (_, session_pk) = generate_keypair();
let session_did = did_for_with_key("s", &session_pk);
let (org_sk, _org_pk) = generate_keypair();
let (_, op_pk) = generate_keypair();
let op_did = did_for_op("darby", &op_pk);
let op_cert = sign_did_cert(&op_sk, &session_did).unwrap();
let member_cert = sign_did_cert(&org_sk, &op_did).unwrap();
assert_eq!(b64decode(&op_cert).unwrap().len(), 64);
assert_eq!(b64decode(&member_cert).unwrap().len(), 64);
}
#[test]
fn succession_cert_roundtrip_and_binding() {
let (old_sk, old_pk) = generate_keypair();
let (_, new_pk) = generate_keypair();
let old_did = did_for_op("darby", &old_pk);
let new_did = did_for_op("darby", &new_pk);
let cert = sign_succession_cert(&old_sk, "op", &old_did, &new_did).unwrap();
verify_succession_cert(&old_pk, &cert, "op", &old_did, &new_did).unwrap();
let (_, attacker_pk) = generate_keypair();
let attacker_did = did_for_op("darby", &attacker_pk);
assert_eq!(
verify_succession_cert(&old_pk, &cert, "op", &old_did, &attacker_did),
Err(CertError::Rejected)
);
assert_eq!(
verify_succession_cert(&old_pk, &cert, "org", &old_did, &new_did),
Err(CertError::Rejected)
);
assert_eq!(
verify_succession_cert(&new_pk, &cert, "op", &old_did, &new_did),
Err(CertError::Rejected)
);
}
#[test]
fn succession_cert_is_domain_separated_from_op_cert() {
let (old_sk, old_pk) = generate_keypair();
let (_, new_pk) = generate_keypair();
let old_did = did_for_op("darby", &old_pk);
let new_did = did_for_op("darby", &new_pk);
let succ = sign_succession_cert(&old_sk, "op", &old_did, &new_did).unwrap();
assert_eq!(
verify_op_cert(&old_pk, &succ, &new_did),
Err(CertError::Rejected)
);
let op_cert = sign_did_cert(&old_sk, &new_did).unwrap();
assert_eq!(
verify_succession_cert(&old_pk, &op_cert, "op", &old_did, &new_did),
Err(CertError::Rejected)
);
}
#[test]
fn org_did_payload_is_not_confused_with_member_cert_subject() {
let (org_sk, org_pk) = generate_keypair();
let (_, org_pk_for_did) = generate_keypair();
let org_did = did_for_org("slanchaai", &org_pk_for_did);
let (_, op_pk) = generate_keypair();
let op_did = did_for_op("darby", &op_pk);
let bogus = sign_did_cert(&org_sk, &org_did).unwrap();
assert_eq!(
verify_member_cert(&org_pk, &bogus, &op_did),
Err(CertError::Rejected)
);
}
}