1use ed25519_dalek::{Signer, SigningKey, Verifier, VerifyingKey};
29use thiserror::Error;
30
31use crate::signing::{b64decode, b64encode};
32
33#[derive(Debug, Error, PartialEq, Eq)]
34pub enum CertError {
35 #[error("certificate base64 decode failed")]
36 BadEncoding,
37 #[error("certificate length is not 64 bytes")]
38 BadLength,
39 #[error("public key length is not 32 bytes")]
40 BadKey,
41 #[error("signature did not verify")]
42 Rejected,
43}
44
45pub fn sign_did_cert(signing_key: &[u8], payload_did: &str) -> Result<String, CertError> {
51 if signing_key.len() < 32 {
52 return Err(CertError::BadKey);
53 }
54 let mut sk_bytes = [0u8; 32];
55 sk_bytes.copy_from_slice(&signing_key[..32]);
56 let sk = SigningKey::from_bytes(&sk_bytes);
57 let sig = sk.sign(payload_did.as_bytes());
58 Ok(b64encode(&sig.to_bytes()))
59}
60
61pub fn verify_op_cert(
67 op_pubkey: &[u8],
68 op_cert_b64: &str,
69 session_did: &str,
70) -> Result<(), CertError> {
71 verify_did_cert(op_pubkey, op_cert_b64, session_did)
72}
73
74pub fn verify_member_cert(
79 org_pubkey: &[u8],
80 member_cert_b64: &str,
81 op_did: &str,
82) -> Result<(), CertError> {
83 verify_did_cert(org_pubkey, member_cert_b64, op_did)
84}
85
86fn verify_did_cert(pubkey: &[u8], cert_b64: &str, payload_did: &str) -> Result<(), CertError> {
87 if pubkey.len() != 32 {
88 return Err(CertError::BadKey);
89 }
90 let mut pk_arr = [0u8; 32];
91 pk_arr.copy_from_slice(pubkey);
92 let vk = VerifyingKey::from_bytes(&pk_arr).map_err(|_| CertError::BadKey)?;
93
94 let sig_bytes = b64decode(cert_b64).map_err(|_| CertError::BadEncoding)?;
95 if sig_bytes.len() != 64 {
96 return Err(CertError::BadLength);
97 }
98 let mut sig_arr = [0u8; 64];
99 sig_arr.copy_from_slice(&sig_bytes);
100 let sig = ed25519_dalek::Signature::from_bytes(&sig_arr);
101
102 vk.verify(payload_did.as_bytes(), &sig)
103 .map_err(|_| CertError::Rejected)
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109 use crate::agent_card::{did_for_op, did_for_org, did_for_with_key};
110 use crate::signing::generate_keypair;
111
112 #[test]
113 fn sign_verify_op_cert_roundtrip() {
114 let (op_sk, op_pk) = generate_keypair();
115 let (_, session_pk) = generate_keypair();
116 let session_did = did_for_with_key("vesper-valley", &session_pk);
117 let cert = sign_did_cert(&op_sk, &session_did).unwrap();
118 verify_op_cert(&op_pk, &cert, &session_did).unwrap();
119 }
120
121 #[test]
122 fn sign_verify_member_cert_roundtrip() {
123 let (org_sk, org_pk) = generate_keypair();
124 let (_, op_pk) = generate_keypair();
125 let op_did = did_for_op("darby", &op_pk);
126 let cert = sign_did_cert(&org_sk, &op_did).unwrap();
127 verify_member_cert(&org_pk, &cert, &op_did).unwrap();
128 }
129
130 #[test]
131 fn verify_op_cert_rejects_wrong_session_did() {
132 let (op_sk, op_pk) = generate_keypair();
136 let (_, sk_a) = generate_keypair();
137 let (_, sk_b) = generate_keypair();
138 let did_a = did_for_with_key("session-a", &sk_a);
139 let did_b = did_for_with_key("session-b", &sk_b);
140 let cert = sign_did_cert(&op_sk, &did_a).unwrap();
141 assert_eq!(
142 verify_op_cert(&op_pk, &cert, &did_b),
143 Err(CertError::Rejected)
144 );
145 }
146
147 #[test]
148 fn verify_member_cert_rejects_wrong_op_did() {
149 let (org_sk, org_pk) = generate_keypair();
153 let (_, op_a_pk) = generate_keypair();
154 let (_, op_b_pk) = generate_keypair();
155 let op_a = did_for_op("darby", &op_a_pk);
156 let op_b = did_for_op("willard", &op_b_pk);
157 let cert = sign_did_cert(&org_sk, &op_a).unwrap();
158 assert_eq!(
159 verify_member_cert(&org_pk, &cert, &op_b),
160 Err(CertError::Rejected)
161 );
162 }
163
164 #[test]
165 fn verify_op_cert_rejects_wrong_op_key() {
166 let (alice_sk, _) = generate_keypair();
169 let (_, bob_pk) = generate_keypair();
170 let (_, session_pk) = generate_keypair();
171 let session_did = did_for_with_key("s", &session_pk);
172 let cert = sign_did_cert(&alice_sk, &session_did).unwrap();
173 assert_eq!(
174 verify_op_cert(&bob_pk, &cert, &session_did),
175 Err(CertError::Rejected)
176 );
177 }
178
179 #[test]
180 fn verify_op_cert_rejects_bad_base64() {
181 let (_, pk) = generate_keypair();
182 assert_eq!(
183 verify_op_cert(&pk, "not-base64!", "did:wire:s"),
184 Err(CertError::BadEncoding)
185 );
186 }
187
188 #[test]
189 fn verify_op_cert_rejects_short_cert() {
190 let (_, pk) = generate_keypair();
191 let short = b64encode(&[0u8; 32]);
192 assert_eq!(
193 verify_op_cert(&pk, &short, "did:wire:s"),
194 Err(CertError::BadLength)
195 );
196 }
197
198 #[test]
199 fn verify_op_cert_rejects_short_pubkey() {
200 let (sk, _) = generate_keypair();
201 let cert = sign_did_cert(&sk, "did:wire:s").unwrap();
202 let short_pk = vec![0u8; 16];
203 assert_eq!(
204 verify_op_cert(&short_pk, &cert, "did:wire:s"),
205 Err(CertError::BadKey)
206 );
207 }
208
209 #[test]
210 fn sign_did_cert_rejects_short_signing_key() {
211 let short_sk = vec![0u8; 16];
212 assert_eq!(
213 sign_did_cert(&short_sk, "did:wire:s"),
214 Err(CertError::BadKey)
215 );
216 }
217
218 #[test]
219 fn op_and_org_cert_signing_are_indistinguishable_at_byte_level() {
220 let (op_sk, _op_pk) = generate_keypair();
226 let (_, session_pk) = generate_keypair();
227 let session_did = did_for_with_key("s", &session_pk);
228
229 let (org_sk, _org_pk) = generate_keypair();
230 let (_, op_pk) = generate_keypair();
231 let op_did = did_for_op("darby", &op_pk);
232
233 let op_cert = sign_did_cert(&op_sk, &session_did).unwrap();
234 let member_cert = sign_did_cert(&org_sk, &op_did).unwrap();
235
236 assert_eq!(b64decode(&op_cert).unwrap().len(), 64);
238 assert_eq!(b64decode(&member_cert).unwrap().len(), 64);
239 }
240
241 #[test]
242 fn org_did_payload_is_not_confused_with_member_cert_subject() {
243 let (org_sk, org_pk) = generate_keypair();
248 let (_, org_pk_for_did) = generate_keypair();
249 let org_did = did_for_org("slanchaai", &org_pk_for_did);
250 let (_, op_pk) = generate_keypair();
251 let op_did = did_for_op("darby", &op_pk);
252
253 let bogus = sign_did_cert(&org_sk, &org_did).unwrap();
256 assert_eq!(
257 verify_member_cert(&org_pk, &bogus, &op_did),
258 Err(CertError::Rejected)
259 );
260 }
261}