1use cms::content_info::ContentInfo;
11use cms::signed_data::{
12 CertificateSet, DigestAlgorithmIdentifiers, EncapsulatedContentInfo, SignedData, SignerInfo,
13 SignerInfos,
14};
15use der_v08::asn1::{ObjectIdentifier, OctetString, SetOfVec};
16use der_v08::{Any, Decode, Encode};
17use sha2::{Digest, Sha256};
18use x509_cert_v03::attr::{Attribute, AttributeValue, Attributes};
19use x509_cert_v03::certificate::Certificate;
20
21use crate::error::{Result, SignError};
22use crate::ml_dsa_bridge::{ML_DSA_44_OID, ML_DSA_65_OID, ML_DSA_87_OID};
23
24const ID_SIGNED_DATA: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.7.2");
26const ID_CT_DATA: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.7.1");
27const ID_CONTENT_TYPE: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.9.3");
28const ID_MESSAGE_DIGEST: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.9.4");
29const ID_SHA256: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.1");
30const ID_ECDSA_SHA256: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.2");
31
32#[derive(Clone)]
34pub enum SealKey {
35 EcdsaP256(p256::ecdsa::SigningKey),
36 MlDsa44(ml_dsa::SigningKey<ml_dsa::MlDsa44>),
37 MlDsa65(ml_dsa::SigningKey<ml_dsa::MlDsa65>),
38 MlDsa87(ml_dsa::SigningKey<ml_dsa::MlDsa87>),
39}
40
41impl SealKey {
42 fn sign_attrs(&self, attrs_der: &[u8]) -> Result<Vec<u8>> {
43 match self {
44 SealKey::EcdsaP256(key) => {
45 use p256::ecdsa::signature::hazmat::PrehashSigner;
46 let hash = Sha256::digest(attrs_der);
47 let sig: p256::ecdsa::Signature = key
48 .sign_prehash(&hash)
49 .map_err(|e| SignError::Seal(format!("ECDSA sign: {e}")))?;
50 Ok(ecdsa_raw_to_der(&sig.to_bytes()))
51 }
52 SealKey::MlDsa44(key) => sign_ml_dsa(key, attrs_der),
53 SealKey::MlDsa65(key) => sign_ml_dsa(key, attrs_der),
54 SealKey::MlDsa87(key) => sign_ml_dsa(key, attrs_der),
55 }
56 }
57
58 fn algorithm_oid(&self) -> ObjectIdentifier {
59 match self {
60 SealKey::EcdsaP256(_) => ID_ECDSA_SHA256,
61 SealKey::MlDsa44(_) => ObjectIdentifier::new_unwrap(ML_DSA_44_OID),
62 SealKey::MlDsa65(_) => ObjectIdentifier::new_unwrap(ML_DSA_65_OID),
63 SealKey::MlDsa87(_) => ObjectIdentifier::new_unwrap(ML_DSA_87_OID),
64 }
65 }
66
67 fn digest_algorithm_oid(&self) -> ObjectIdentifier {
68 match self {
69 SealKey::EcdsaP256(_) => ID_SHA256,
70 SealKey::MlDsa44(_) | SealKey::MlDsa65(_) | SealKey::MlDsa87(_) => ID_SHA256,
72 }
73 }
74}
75
76fn sign_ml_dsa<P>(key: &ml_dsa::SigningKey<P>, data: &[u8]) -> Result<Vec<u8>>
77where
78 P: ml_dsa::MlDsaParams,
79 ml_dsa::SigningKey<P>: ml_dsa::Signer<ml_dsa::Signature<P>>,
80{
81 use ml_dsa::Signer as Signer3;
82 let sig: ml_dsa::Signature<P> = key
83 .try_sign(data)
84 .map_err(|e| SignError::Seal(format!("ML-DSA sign: {e}")))?;
85 let encoded = sig.encode();
86 let bytes: &[u8] = encoded.as_ref();
87 Ok(bytes.to_vec())
88}
89
90pub fn create_cms_seal(
95 manifest_bytes: &[u8],
96 seal_key: &SealKey,
97 seal_cert_der: &[u8],
98) -> Result<Vec<u8>> {
99 let manifest_digest = Sha256::digest(manifest_bytes);
100
101 let cert = Certificate::from_der(seal_cert_der)
102 .map_err(|e| SignError::Seal(format!("parse seal cert: {e}")))?;
103
104 let content_type_attr = build_content_type_attr()?;
105 let message_digest_attr = build_message_digest_attr(&manifest_digest)?;
106
107 let signed_attrs = Attributes::try_from(vec![content_type_attr, message_digest_attr])
108 .map_err(|e| SignError::Seal(format!("build signed attrs: {e}")))?;
109
110 let signed_attrs_der = signed_attrs
111 .to_der()
112 .map_err(|e| SignError::Seal(format!("encode signed attrs: {e}")))?;
113
114 let sig_bytes = seal_key.sign_attrs(&signed_attrs_der)?;
115
116 let signer_info = SignerInfo {
117 version: cms::content_info::CmsVersion::V1,
118 sid: cms::signed_data::SignerIdentifier::from(&cert),
119 digest_alg: spki_v08::AlgorithmIdentifierOwned {
120 oid: seal_key.digest_algorithm_oid(),
121 parameters: None,
122 },
123 signed_attrs: Some(signed_attrs),
124 signature_algorithm: spki_v08::AlgorithmIdentifierOwned {
125 oid: seal_key.algorithm_oid(),
126 parameters: None,
127 },
128 signature: OctetString::new(sig_bytes)
129 .map_err(|e| SignError::Seal(format!("sig octet string: {e}")))?,
130 unsigned_attrs: None,
131 };
132
133 let digest_alg = spki_v08::AlgorithmIdentifierOwned {
134 oid: ID_SHA256,
135 parameters: None,
136 };
137 let digest_algorithms = DigestAlgorithmIdentifiers::try_from(vec![digest_alg])
138 .map_err(|e| SignError::Seal(format!("digest algs: {e}")))?;
139
140 let cert_set = CertificateSet::try_from(vec![cms::cert::CertificateChoices::Certificate(cert)])
141 .map_err(|e| SignError::Seal(format!("cert set: {e}")))?;
142
143 let signer_infos = SignerInfos::try_from(vec![signer_info])
144 .map_err(|e| SignError::Seal(format!("signer infos: {e}")))?;
145
146 let encap_content_info = EncapsulatedContentInfo {
148 econtent_type: ID_CT_DATA,
149 econtent: None,
150 };
151
152 let signed_data = SignedData {
153 version: cms::content_info::CmsVersion::V1,
154 digest_algorithms,
155 encap_content_info,
156 certificates: Some(cert_set),
157 crls: None,
158 signer_infos,
159 };
160
161 let signed_data_der = signed_data
163 .to_der()
164 .map_err(|e| SignError::Seal(format!("encode SignedData: {e}")))?;
165
166 let content_info = ContentInfo {
167 content_type: ID_SIGNED_DATA,
168 content: Any::from_der(&signed_data_der)
169 .map_err(|e| SignError::Seal(format!("wrap ContentInfo: {e}")))?,
170 };
171
172 content_info
173 .to_der()
174 .map_err(|e| SignError::Seal(format!("encode ContentInfo: {e}")))
175}
176
177fn build_content_type_attr() -> Result<Attribute> {
178 let oid_der = ID_CT_DATA
179 .to_der()
180 .map_err(|e| SignError::Seal(format!("encode content type OID: {e}")))?;
181 let attr_val = AttributeValue::from_der(&oid_der)
182 .map_err(|e| SignError::Seal(format!("attr value from OID: {e}")))?;
183 let mut values = SetOfVec::new();
184 values
185 .insert(attr_val)
186 .map_err(|e| SignError::Seal(format!("insert content type: {e}")))?;
187 Ok(Attribute {
188 oid: ID_CONTENT_TYPE,
189 values,
190 })
191}
192
193fn build_message_digest_attr(digest: &[u8]) -> Result<Attribute> {
194 let octet_string = OctetString::new(digest.to_vec())
195 .map_err(|e| SignError::Seal(format!("digest octet string: {e}")))?;
196 let octet_der = octet_string
197 .to_der()
198 .map_err(|e| SignError::Seal(format!("encode digest: {e}")))?;
199 let attr_val = AttributeValue::from_der(&octet_der)
200 .map_err(|e| SignError::Seal(format!("attr value from digest: {e}")))?;
201 let mut values = SetOfVec::new();
202 values
203 .insert(attr_val)
204 .map_err(|e| SignError::Seal(format!("insert message digest: {e}")))?;
205 Ok(Attribute {
206 oid: ID_MESSAGE_DIGEST,
207 values,
208 })
209}
210
211fn ecdsa_raw_to_der(raw: &[u8]) -> Vec<u8> {
213 if raw.len() != 64 {
214 return raw.to_vec();
215 }
216 let r = &raw[..32];
217 let s = &raw[32..];
218
219 fn encode_integer(val: &[u8]) -> Vec<u8> {
220 let start = val.iter().position(|&b| b != 0).unwrap_or(val.len() - 1);
221 let val = &val[start..];
222 let needs_pad = val.first().is_some_and(|&b| b & 0x80 != 0);
223 let len = val.len() + usize::from(needs_pad);
224 let mut out = vec![0x02, len as u8];
225 if needs_pad {
226 out.push(0x00);
227 }
228 out.extend_from_slice(val);
229 out
230 }
231
232 let r_enc = encode_integer(r);
233 let s_enc = encode_integer(s);
234 let total = r_enc.len() + s_enc.len();
235 let mut out = vec![0x30, total as u8];
236 out.extend_from_slice(&r_enc);
237 out.extend_from_slice(&s_enc);
238 out
239}
240
241pub fn generate_seal_cert(signing_key: &p256::ecdsa::SigningKey) -> Result<Vec<u8>> {
243 generate_seal_cert_for_key(&SealKey::EcdsaP256(signing_key.clone()))
244}
245
246pub fn generate_seal_cert_for_key(seal_key: &SealKey) -> Result<Vec<u8>> {
248 let (pub_key_bytes, alg_id_der) = match seal_key {
249 SealKey::EcdsaP256(key) => {
250 let vk = p256::ecdsa::VerifyingKey::from(key);
251 let pub_key = p256::PublicKey::from(vk).to_sec1_bytes().to_vec();
252 (pub_key, encode_ecdsa_alg_id())
253 }
254 SealKey::MlDsa44(key) => {
255 use ml_dsa::Keypair;
256 (
257 key.verifying_key().encode().to_vec(),
258 encode_ml_dsa_alg_id(ML_DSA_44_OID),
259 )
260 }
261 SealKey::MlDsa65(key) => {
262 use ml_dsa::Keypair;
263 (
264 key.verifying_key().encode().to_vec(),
265 encode_ml_dsa_alg_id(ML_DSA_65_OID),
266 )
267 }
268 SealKey::MlDsa87(key) => {
269 use ml_dsa::Keypair;
270 (
271 key.verifying_key().encode().to_vec(),
272 encode_ml_dsa_alg_id(ML_DSA_87_OID),
273 )
274 }
275 };
276
277 let spki_der = match seal_key {
278 SealKey::EcdsaP256(_) => encode_ecdsa_spki(&pub_key_bytes),
279 _ => encode_ml_dsa_spki(&pub_key_bytes, &alg_id_der),
280 };
281
282 let mut tbs = Vec::new();
283 tbs.extend_from_slice(&[0xA0, 0x03, 0x02, 0x01, 0x02]);
284 tbs.extend_from_slice(&[0x02, 0x01, 0x01]);
285 tbs.extend_from_slice(&alg_id_der);
286 let name = encode_cn("hoike-seal");
287 tbs.extend_from_slice(&name);
288 tbs.extend_from_slice(&encode_validity());
289 tbs.extend_from_slice(&name);
290 tbs.extend_from_slice(&spki_der);
291 let tbs_seq = wrap_seq(&tbs);
292
293 let sig_bytes = seal_key.sign_attrs(&tbs_seq)?;
294
295 let mut cert = Vec::new();
296 cert.extend_from_slice(&tbs_seq);
297 cert.extend_from_slice(&alg_id_der);
298 cert.extend_from_slice(&encode_bitstring(&sig_bytes));
299
300 Ok(wrap_seq(&cert))
301}
302
303fn encode_bitstring(data: &[u8]) -> Vec<u8> {
304 let bs_len = data.len() + 1; let mut bs = vec![0x03];
306 encode_der_length(&mut bs, bs_len);
307 bs.push(0x00); bs.extend_from_slice(data);
309 bs
310}
311
312fn encode_der_length(out: &mut Vec<u8>, len: usize) {
314 if len < 128 {
315 out.push(len as u8);
316 } else if len < 256 {
317 out.push(0x81);
318 out.push(len as u8);
319 } else if len < 65536 {
320 out.push(0x82);
321 out.push((len >> 8) as u8);
322 out.push((len & 0xFF) as u8);
323 } else {
324 out.push(0x83);
325 out.push((len >> 16) as u8);
326 out.push(((len >> 8) & 0xFF) as u8);
327 out.push((len & 0xFF) as u8);
328 }
329}
330
331fn encode_ecdsa_alg_id() -> Vec<u8> {
332 let oid = &[0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02];
333 wrap_seq(oid)
334}
335
336fn encode_ml_dsa_alg_id(oid_str: &str) -> Vec<u8> {
337 let oid = ObjectIdentifier::new_unwrap(oid_str);
338 let oid_der = oid.to_der().unwrap();
339 wrap_seq(&oid_der)
340}
341
342fn encode_cn(cn: &str) -> Vec<u8> {
343 let cn_oid = &[0x06, 0x03, 0x55, 0x04, 0x03];
344 let cn_val = tlv(0x0C, cn.as_bytes());
345 let mut attr = Vec::new();
346 attr.extend_from_slice(cn_oid);
347 attr.extend_from_slice(&cn_val);
348 let attr_seq = wrap_seq(&attr);
349 let rdn_set = tlv(0x31, &attr_seq);
350 wrap_seq(&rdn_set)
351}
352
353fn encode_validity() -> Vec<u8> {
354 let nb = tlv(0x18, b"20240101000000Z");
355 let na = tlv(0x18, b"20341231235959Z");
356 let mut v = Vec::new();
357 v.extend_from_slice(&nb);
358 v.extend_from_slice(&na);
359 wrap_seq(&v)
360}
361
362fn encode_ecdsa_spki(pub_key_uncompressed: &[u8]) -> Vec<u8> {
363 let ec_oid = &[0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01];
364 let p256_oid = &[0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07];
365 let mut alg = Vec::new();
366 alg.extend_from_slice(ec_oid);
367 alg.extend_from_slice(p256_oid);
368 let alg_seq = wrap_seq(&alg);
369
370 let mut spki = Vec::new();
371 spki.extend_from_slice(&alg_seq);
372 spki.extend_from_slice(&encode_bitstring(pub_key_uncompressed));
373 wrap_seq(&spki)
374}
375
376fn encode_ml_dsa_spki(pub_key_bytes: &[u8], alg_id_der: &[u8]) -> Vec<u8> {
377 let mut spki = Vec::new();
378 spki.extend_from_slice(alg_id_der);
379 spki.extend_from_slice(&encode_bitstring(pub_key_bytes));
380 wrap_seq(&spki)
381}
382
383fn wrap_seq(content: &[u8]) -> Vec<u8> {
384 tlv(0x30, content)
385}
386
387fn tlv(tag: u8, content: &[u8]) -> Vec<u8> {
388 let mut out = vec![tag];
389 encode_der_length(&mut out, content.len());
390 out.extend_from_slice(content);
391 out
392}
393
394#[cfg(test)]
395mod tests {
396 use super::*;
397
398 fn test_key() -> p256::ecdsa::SigningKey {
399 let secret = [7u8; 32];
400 p256::ecdsa::SigningKey::from_bytes((&secret).into()).unwrap()
401 }
402
403 #[test]
404 fn generate_seal_cert_parseable() {
405 let key = test_key();
406 let cert_der = generate_seal_cert(&key).unwrap();
407 let cert = Certificate::from_der(&cert_der).unwrap();
408 assert_eq!(cert.tbs_certificate().serial_number().as_bytes(), &[0x01]);
409 }
410
411 #[test]
412 fn seal_round_trip() {
413 let key = test_key();
414 let seal_key = SealKey::EcdsaP256(key.clone());
415 let cert_der = generate_seal_cert(&key).unwrap();
416 let manifest = b"test manifest data for seal verification";
417
418 let seal = create_cms_seal(manifest, &seal_key, &cert_der).unwrap();
419 assert!(!seal.is_empty());
420
421 let ci = ContentInfo::from_der(&seal).unwrap();
422 assert_eq!(ci.content_type, ID_SIGNED_DATA);
423
424 let sd = ci.content.decode_as::<SignedData>().unwrap();
425 assert!(sd.certificates.is_some());
426 assert_eq!(sd.signer_infos.0.len(), 1);
427 assert!(sd.encap_content_info.econtent.is_none());
428 }
429
430 #[test]
431 fn seal_has_message_digest_attr() {
432 let key = test_key();
433 let seal_key = SealKey::EcdsaP256(key.clone());
434 let cert_der = generate_seal_cert(&key).unwrap();
435 let manifest = b"manifest";
436
437 let seal = create_cms_seal(manifest, &seal_key, &cert_der).unwrap();
438 let ci = ContentInfo::from_der(&seal).unwrap();
439 let sd = ci.content.decode_as::<SignedData>().unwrap();
440 let si = &sd.signer_infos.0.as_slice()[0];
441
442 let attrs = si.signed_attrs.as_ref().expect("signed_attrs required");
443 assert!(
444 attrs.iter().any(|a| a.oid == ID_MESSAGE_DIGEST),
445 "must contain message-digest attribute"
446 );
447 assert!(
448 attrs.iter().any(|a| a.oid == ID_CONTENT_TYPE),
449 "must contain content-type attribute"
450 );
451 }
452
453 #[test]
454 fn ml_dsa_87_seal_cert_parseable() {
455 let sk = ml_dsa::SigningKey::<ml_dsa::MlDsa87>::from_seed((&[42u8; 32]).into());
456 let seal_key = SealKey::MlDsa87(sk);
457 let cert_der = generate_seal_cert_for_key(&seal_key).unwrap();
458 let cert = Certificate::from_der(&cert_der).unwrap();
459 assert_eq!(cert.tbs_certificate().serial_number().as_bytes(), &[0x01]);
460 }
461
462 #[test]
463 fn ml_dsa_87_seal_round_trip() {
464 let sk = ml_dsa::SigningKey::<ml_dsa::MlDsa87>::from_seed((&[99u8; 32]).into());
465 let seal_key = SealKey::MlDsa87(sk);
466 let cert_der = generate_seal_cert_for_key(&seal_key).unwrap();
467 let manifest = b"ML-DSA sealed manifest";
468
469 let seal = create_cms_seal(manifest, &seal_key, &cert_der).unwrap();
470 assert!(!seal.is_empty());
471
472 let ci = ContentInfo::from_der(&seal).unwrap();
473 let sd = ci.content.decode_as::<SignedData>().unwrap();
474 let si = &sd.signer_infos.0.as_slice()[0];
475
476 let sig_alg = si.signature_algorithm.oid.to_string();
477 assert_eq!(sig_alg, ML_DSA_87_OID);
478 assert!(
479 si.signature.as_bytes().len() > 4000,
480 "ML-DSA-87 sig should be ~4627 bytes"
481 );
482 }
483}