1mod envelope;
21
22#[cfg(test)]
23mod test_helpers;
24
25pub use envelope::detach_payload;
27
28use anyhow::Context;
29use igvm::IgvmFile;
30use igvm::IgvmSerializer;
31use igvm_defs::IgvmPlatformType;
32
33pub fn patch(
80 igvm_data: &[u8],
81 corim_signature: &[u8],
82 platform: IgvmPlatformType,
83 expected_document: Option<&[u8]>,
84) -> anyhow::Result<Vec<u8>> {
85 let igvm_file =
87 IgvmFile::new_from_binary(igvm_data, None).context("parsing input IGVM file")?;
88
89 crate::platform_mask::lookup_compatibility_mask(igvm_file.platforms(), platform)?;
92
93 let mut serializer = IgvmSerializer::new(&igvm_file).context("constructing IGVM serializer")?;
96
97 {
102 let existing_doc = serializer.corim_for(platform).ok_or_else(|| {
103 anyhow::anyhow!(
104 "Cannot patch CoRIM signature for platform {platform:?}: no CoRIM \
105 document present in the IGVM file. The document must be embedded \
106 at IGVM generation time before a signature can be attached."
107 )
108 })?;
109
110 if let Some(expected) = expected_document
116 && expected != existing_doc
117 {
118 anyhow::bail!(
119 "CoRIM document mismatch for platform {platform:?}: the document \
120 carried by the input bundle ({} bytes) does not byte-match the \
121 document embedded in the IGVM file ({} bytes). The bundle was \
122 signed against a different document; re-sign against the \
123 IGVM-embedded document or supply only the detached signature \
124 via `--corim-signature`.",
125 expected.len(),
126 existing_doc.len(),
127 );
128 }
129
130 envelope::verify_corim_signature(corim_signature, existing_doc)
133 .context("verifying CoRIM signature against the in-file document")?;
134 }
135
136 serializer
142 .set_corim_signature(platform, corim_signature.to_vec())
143 .context("staging CoRIM signature replacement")?;
144
145 let mut output = Vec::new();
148 serializer
149 .serialize(&mut output)
150 .context("serializing patched IGVM file")?;
151
152 tracing::info!(
153 original_size = igvm_data.len(),
154 new_size = output.len(),
155 signature_size = corim_signature.len(),
156 platform = ?platform,
157 "Patched CoRIM signature into IGVM file",
158 );
159
160 Ok(output)
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166 use crate::corim_signature::test_helpers::sign_envelope_for;
167 use igvm::IgvmDirectiveHeader;
168 use igvm::IgvmInitializationHeader;
169 use igvm::IgvmPlatformHeader;
170 use igvm::IgvmRevision;
171 use igvm::IgvmSerializer;
172 use igvm::corim::launch_measurement::LaunchMeasurement;
173 use igvm::corim::launch_measurement::MeasurementKind;
174 use igvm_defs::IGVM_FIXED_HEADER;
175 use igvm_defs::IGVM_VHS_SUPPORTED_PLATFORM;
176 use igvm_defs::IgvmPageDataFlags;
177 use igvm_defs::IgvmPageDataType;
178 use test_with_tracing::test;
179 use zerocopy::FromBytes;
180
181 fn new_platform(
182 compatibility_mask: u32,
183 platform_type: IgvmPlatformType,
184 ) -> IgvmPlatformHeader {
185 IgvmPlatformHeader::SupportedPlatform(IGVM_VHS_SUPPORTED_PLATFORM {
186 compatibility_mask,
187 highest_vtl: 0,
188 platform_type,
189 platform_version: 1,
190 shared_gpa_boundary: 0,
191 })
192 }
193
194 fn new_page_data(page: u64, compatibility_mask: u32, data: &[u8]) -> IgvmDirectiveHeader {
195 IgvmDirectiveHeader::PageData {
196 gpa: page * 4096,
197 compatibility_mask,
198 flags: IgvmPageDataFlags::new(),
199 data_type: IgvmPageDataType::NORMAL,
200 data: data.to_vec(),
201 }
202 }
203
204 fn snp_guest_policies(platforms: &[IgvmPlatformHeader]) -> Vec<IgvmInitializationHeader> {
210 platforms
211 .iter()
212 .filter_map(|p| match p {
213 IgvmPlatformHeader::SupportedPlatform(info)
214 if info.platform_type == IgvmPlatformType::SEV_SNP =>
215 {
216 Some(IgvmInitializationHeader::GuestPolicy {
217 policy: 0x30000,
218 compatibility_mask: info.compatibility_mask,
219 })
220 }
221 _ => None,
222 })
223 .collect()
224 }
225
226 fn build_igvm(
228 platforms: Vec<IgvmPlatformHeader>,
229 directives: Vec<IgvmDirectiveHeader>,
230 ) -> Vec<u8> {
231 let initializations = snp_guest_policies(&platforms);
232 let igvm = IgvmFile::new(IgvmRevision::V1, platforms, initializations, directives)
233 .expect("valid IgvmFile");
234 let mut output = Vec::new();
235 igvm.serialize(&mut output).expect("serialize");
236 output
237 }
238
239 fn build_igvm_with_corim_docs(
244 platforms: Vec<IgvmPlatformHeader>,
245 directives: Vec<IgvmDirectiveHeader>,
246 documents: Vec<(u32, Vec<u8>)>,
247 ) -> Vec<u8> {
248 let mut initializations: Vec<IgvmInitializationHeader> = snp_guest_policies(&platforms);
249 initializations.extend(documents.into_iter().map(|(mask, doc)| {
250 IgvmInitializationHeader::CorimDocument {
251 compatibility_mask: mask,
252 document: doc,
253 }
254 }));
255 let igvm = IgvmFile::new(IgvmRevision::V1, platforms, initializations, directives)
256 .expect("valid IgvmFile");
257 let mut output = Vec::new();
258 igvm.serialize(&mut output).expect("serialize");
259 output
260 }
261
262 struct CorimHeaderInfo {
264 compatibility_mask: u32,
265 payload: Vec<u8>,
266 }
267
268 fn extract_corim_headers(data: &[u8]) -> (Vec<CorimHeaderInfo>, Vec<CorimHeaderInfo>) {
271 let igvm = IgvmFile::new_from_binary(data, None).expect("valid IGVM file");
272 let mut documents = Vec::new();
273 let mut signatures = Vec::new();
274
275 for header in igvm.initializations() {
276 match header {
277 IgvmInitializationHeader::CorimDocument {
278 compatibility_mask,
279 document,
280 } => {
281 documents.push(CorimHeaderInfo {
282 compatibility_mask: *compatibility_mask,
283 payload: document.clone(),
284 });
285 }
286 IgvmInitializationHeader::CorimSignature {
287 compatibility_mask,
288 signature,
289 } => {
290 signatures.push(CorimHeaderInfo {
291 compatibility_mask: *compatibility_mask,
292 payload: signature.clone(),
293 });
294 }
295 _ => {}
296 }
297 }
298
299 (documents, signatures)
300 }
301
302 fn count_non_corim_directive_headers(data: &[u8]) -> usize {
304 let igvm = IgvmFile::new_from_binary(data, None).expect("valid IGVM file");
305 igvm.directives().len()
306 }
307
308 fn extract_platform_types(data: &[u8]) -> Vec<(IgvmPlatformType, u32)> {
310 let igvm = IgvmFile::new_from_binary(data, None).expect("valid IGVM file");
311 igvm.platforms()
312 .iter()
313 .map(|p| match p {
314 IgvmPlatformHeader::SupportedPlatform(plat) => {
315 (plat.platform_type, plat.compatibility_mask)
316 }
317 })
318 .collect()
319 }
320
321 #[test]
322 fn test_patch_corim_add_signature() {
323 let page_data = vec![0xCC; 4096];
324 let igvm_data = build_igvm_with_corim_docs(
325 vec![new_platform(0x1, IgvmPlatformType::VSM_ISOLATION)],
326 vec![new_page_data(0, 0x1, &page_data)],
327 vec![(0x1, b"corim-payload".to_vec())],
328 );
329
330 let sig = sign_envelope_for(b"corim-payload", "test");
331 let patched = patch(&igvm_data, &sig, IgvmPlatformType::VSM_ISOLATION, None)
332 .expect("patch should succeed");
333
334 let (docs, sigs) = extract_corim_headers(&patched);
335 assert_eq!(docs.len(), 1);
336 assert_eq!(sigs.len(), 1);
337 assert_eq!(docs[0].payload, b"corim-payload");
338 assert_eq!(sigs[0].payload, sig);
339 assert_eq!(docs[0].compatibility_mask, sigs[0].compatibility_mask);
341 }
342
343 #[test]
344 fn test_patch_corim_preserves_non_corim_directives() {
345 let data1 = vec![0x11; 4096];
346 let data2 = vec![0x22; 4096];
347 let igvm_data = build_igvm_with_corim_docs(
348 vec![new_platform(0x1, IgvmPlatformType::VSM_ISOLATION)],
349 vec![new_page_data(0, 0x1, &data1), new_page_data(1, 0x1, &data2)],
350 vec![(0x1, b"doc".to_vec())],
351 );
352
353 let original_count = count_non_corim_directive_headers(&igvm_data);
354
355 let sig = sign_envelope_for(b"doc", "test");
356 let patched = patch(&igvm_data, &sig, IgvmPlatformType::VSM_ISOLATION, None)
357 .expect("patch should succeed");
358
359 let patched_count = count_non_corim_directive_headers(&patched);
360 assert_eq!(original_count, patched_count);
361 }
362
363 #[test]
364 fn test_patch_corim_preserves_platform_headers() {
365 let data = vec![0x55; 4096];
366 let igvm_data = build_igvm_with_corim_docs(
367 vec![
368 new_platform(0x1, IgvmPlatformType::VSM_ISOLATION),
369 new_platform(0x2, IgvmPlatformType::SEV_SNP),
370 ],
371 vec![new_page_data(0, 0x1, &data), new_page_data(0, 0x2, &data)],
372 vec![(0x1, b"vbs-corim".to_vec())],
373 );
374
375 let original_platforms = extract_platform_types(&igvm_data);
376
377 let sig = sign_envelope_for(b"vbs-corim", "test");
378 let patched = patch(&igvm_data, &sig, IgvmPlatformType::VSM_ISOLATION, None)
379 .expect("patch should succeed");
380
381 let patched_platforms = extract_platform_types(&patched);
382 assert_eq!(original_platforms, patched_platforms);
383 }
384
385 #[test]
386 fn test_patch_corim_uses_correct_mask() {
387 let data = vec![0x55; 4096];
388 let igvm_data = build_igvm_with_corim_docs(
389 vec![
390 new_platform(0x1, IgvmPlatformType::VSM_ISOLATION),
391 new_platform(0x2, IgvmPlatformType::SEV_SNP),
392 ],
393 vec![new_page_data(0, 0x1, &data), new_page_data(0, 0x2, &data)],
394 vec![(0x1, b"vbs-corim".to_vec()), (0x2, b"snp-corim".to_vec())],
395 );
396
397 let sig = sign_envelope_for(b"vbs-corim", "test");
399 let patched = patch(&igvm_data, &sig, IgvmPlatformType::VSM_ISOLATION, None)
400 .expect("patch should succeed");
401
402 let (docs, sigs) = extract_corim_headers(&patched);
403 assert_eq!(docs.len(), 2, "both platform docs preserved");
404 assert_eq!(sigs.len(), 1, "only VBS signature added");
405 assert_eq!(sigs[0].compatibility_mask, 0x1);
406 }
407
408 #[test]
409 fn test_patch_corim_error_platform_not_in_file() {
410 let igvm_data = build_igvm_with_corim_docs(
411 vec![new_platform(0x1, IgvmPlatformType::VSM_ISOLATION)],
412 vec![new_page_data(0, 0x1, &vec![0; 4096])],
413 vec![(0x1, b"doc".to_vec())],
414 );
415
416 let sig = sign_envelope_for(b"doc", "test");
419 let result = patch(
420 &igvm_data,
421 &sig,
422 IgvmPlatformType::SEV_SNP, None,
424 );
425 assert!(result.is_err());
426 let msg = result.unwrap_err().to_string();
427 assert!(
428 msg.contains("not found"),
429 "expected 'not found' error, got: {msg}"
430 );
431 }
432
433 #[test]
434 fn test_patch_corim_error_missing_document() {
435 let igvm_data = build_igvm(
438 vec![new_platform(0x1, IgvmPlatformType::VSM_ISOLATION)],
439 vec![new_page_data(0, 0x1, &vec![0; 4096])],
440 );
441
442 let sig = sign_envelope_for(b"doc", "test");
444 let err = patch(&igvm_data, &sig, IgvmPlatformType::VSM_ISOLATION, None).unwrap_err();
445 let msg = format!("{err:#}");
446 assert!(
447 msg.contains("no CoRIM document"),
448 "expected 'no CoRIM document' error, got: {msg}"
449 );
450 }
451
452 #[test]
453 fn test_patch_corim_output_is_valid_igvm_header() {
454 let page_data = vec![0x77; 4096];
455 let igvm_data = build_igvm_with_corim_docs(
456 vec![new_platform(0x1, IgvmPlatformType::VSM_ISOLATION)],
457 vec![new_page_data(0, 0x1, &page_data)],
458 vec![(0x1, b"round-trip-doc".to_vec())],
459 );
460
461 let sig = sign_envelope_for(b"round-trip-doc", "test");
462 let patched = patch(&igvm_data, &sig, IgvmPlatformType::VSM_ISOLATION, None)
463 .expect("patch should succeed");
464
465 let fixed = IGVM_FIXED_HEADER::read_from_prefix(&patched)
466 .expect("valid fixed header")
467 .0;
468 assert_eq!(fixed.magic, igvm_defs::IGVM_MAGIC_VALUE);
469 assert_eq!(fixed.format_version, 1);
470 assert_eq!(fixed.total_file_size as usize, patched.len());
471
472 IgvmFile::new_from_binary(&patched, None)
476 .expect("patched file must pass IGVM CRC32 validation");
477 }
478
479 #[test]
480 fn test_patch_corim_bundle_document_mismatch() {
481 let page_data = vec![0xAB; 4096];
487 let igvm_data = build_igvm_with_corim_docs(
488 vec![new_platform(0x1, IgvmPlatformType::VSM_ISOLATION)],
489 vec![new_page_data(0, 0x1, &page_data)],
490 vec![(0x1, b"in-file-doc".to_vec())],
491 );
492
493 let sig = sign_envelope_for(b"in-file-doc", "test");
496
497 let err = patch(
498 &igvm_data,
499 &sig,
500 IgvmPlatformType::VSM_ISOLATION,
501 Some(b"different-bundled-doc"),
502 )
503 .unwrap_err();
504 let msg = format!("{err:#}");
505 assert!(
506 msg.contains("does not byte-match"),
507 "expected bundle/in-file mismatch error, got: {msg}"
508 );
509 }
510
511 #[test]
512 fn test_patch_corim_round_trip_reparse() {
513 let page_data = vec![0xDD; 4096];
517 let igvm_data = build_igvm_with_corim_docs(
518 vec![new_platform(0x1, IgvmPlatformType::VSM_ISOLATION)],
519 vec![new_page_data(0, 0x1, &page_data)],
520 vec![(0x1, b"first-doc".to_vec())],
521 );
522
523 let first_sig = sign_envelope_for(b"first-doc", "test");
524 let patched = patch(
525 &igvm_data,
526 &first_sig,
527 IgvmPlatformType::VSM_ISOLATION,
528 None,
529 )
530 .expect("first patch should succeed");
531
532 let second_sig = sign_envelope_for(b"first-doc", "test-alt");
535 let repatched = patch(&patched, &second_sig, IgvmPlatformType::VSM_ISOLATION, None)
536 .expect("re-patching should succeed");
537
538 let (docs, sigs) = extract_corim_headers(&repatched);
539 assert_eq!(docs.len(), 1);
540 assert_eq!(sigs.len(), 1);
541 assert_eq!(docs[0].payload, b"first-doc");
542 assert_eq!(sigs[0].payload, second_sig);
543 }
544
545 #[test]
546 fn test_patch_corim_replace_signature_preserves_document() {
547 let page_data = vec![0xFF; 4096];
548 let igvm_data = build_igvm_with_corim_docs(
549 vec![new_platform(0x1, IgvmPlatformType::VSM_ISOLATION)],
550 vec![new_page_data(0, 0x1, &page_data)],
551 vec![(0x1, b"keep-this-doc".to_vec())],
552 );
553
554 let first_sig = sign_envelope_for(b"keep-this-doc", "test");
556 let with_sig = patch(
557 &igvm_data,
558 &first_sig,
559 IgvmPlatformType::VSM_ISOLATION,
560 None,
561 )
562 .expect("initial signature attach");
563
564 let second_sig = sign_envelope_for(b"keep-this-doc", "test-alt");
566 let updated = patch(
567 &with_sig,
568 &second_sig,
569 IgvmPlatformType::VSM_ISOLATION,
570 None,
571 )
572 .expect("signature replacement");
573
574 let (docs, sigs) = extract_corim_headers(&updated);
575 assert_eq!(docs.len(), 1);
576 assert_eq!(sigs.len(), 1);
577 assert_eq!(docs[0].payload, b"keep-this-doc");
578 assert_eq!(sigs[0].payload, second_sig);
579 }
580
581 fn build_multi_platform_with_corim() -> (Vec<u8>, Vec<u8>, Vec<u8>) {
586 let data = vec![0x55; 4096];
587 let igvm_data = build_igvm_with_corim_docs(
588 vec![
589 new_platform(0x1, IgvmPlatformType::VSM_ISOLATION),
590 new_platform(0x2, IgvmPlatformType::SEV_SNP),
591 ],
592 vec![new_page_data(0, 0x1, &data), new_page_data(0, 0x2, &data)],
593 vec![(0x1, b"vbs-doc".to_vec()), (0x2, b"snp-doc".to_vec())],
594 );
595
596 let vbs_sig = sign_envelope_for(b"vbs-doc", "test");
598 let with_vbs = patch(&igvm_data, &vbs_sig, IgvmPlatformType::VSM_ISOLATION, None)
599 .expect("VBS signature attach");
600
601 let snp_sig = sign_envelope_for(b"snp-doc", "test");
603 let with_both = patch(&with_vbs, &snp_sig, IgvmPlatformType::SEV_SNP, None)
604 .expect("SNP signature attach");
605
606 (with_both, vbs_sig, snp_sig)
607 }
608
609 #[test]
610 fn test_multi_platform_corim_interleaved_ordering_is_valid() {
611 let (with_both, _vbs_sig, _snp_sig) = build_multi_platform_with_corim();
612
613 let (docs, sigs) = extract_corim_headers(&with_both);
614 assert_eq!(docs.len(), 2, "should have docs for both platforms");
615 assert_eq!(sigs.len(), 2, "should have sigs for both platforms");
616
617 let reparsed = IgvmFile::new_from_binary(&with_both, None)
618 .expect("interleaved CoRIM ordering should be parseable");
619
620 let corim_count = reparsed
621 .initializations()
622 .iter()
623 .filter(|h| {
624 matches!(
625 h,
626 IgvmInitializationHeader::CorimDocument { .. }
627 | IgvmInitializationHeader::CorimSignature { .. }
628 )
629 })
630 .count();
631 assert_eq!(corim_count, 4, "should have 4 CoRIM init headers total");
632 }
633
634 #[test]
635 fn test_multi_platform_replace_signature_preserves_other_platform() {
636 let (with_both, vbs_sig, _snp_sig) = build_multi_platform_with_corim();
639
640 let new_snp_sig = sign_envelope_for(b"snp-doc", "test-alt");
641 let updated = patch(&with_both, &new_snp_sig, IgvmPlatformType::SEV_SNP, None)
642 .expect("update SNP signature");
643
644 let (docs, sigs) = extract_corim_headers(&updated);
645 assert_eq!(docs.len(), 2);
646 assert_eq!(sigs.len(), 2);
647
648 let vbs_doc = docs.iter().find(|d| d.compatibility_mask == 0x1).unwrap();
649 let snp_doc = docs.iter().find(|d| d.compatibility_mask == 0x2).unwrap();
650 let vbs_sig_after = sigs.iter().find(|s| s.compatibility_mask == 0x1).unwrap();
651 let snp_sig_after = sigs.iter().find(|s| s.compatibility_mask == 0x2).unwrap();
652
653 assert_eq!(vbs_doc.payload, b"vbs-doc", "VBS doc must be unchanged");
654 assert_eq!(snp_doc.payload, b"snp-doc", "SNP doc preserved");
655 assert_eq!(vbs_sig_after.payload, vbs_sig, "VBS sig must be unchanged");
656 assert_eq!(
657 snp_sig_after.payload, new_snp_sig,
658 "SNP sig must be the new one"
659 );
660
661 IgvmFile::new_from_binary(&updated, None).expect("output should be valid IGVM");
662 }
663
664 #[test]
665 fn test_multi_platform_sequential_updates_both_platforms() {
666 let (with_both, _vbs_sig, _snp_sig) = build_multi_platform_with_corim();
669
670 let new_vbs_sig = sign_envelope_for(b"vbs-doc", "test-alt");
672 let after_vbs = patch(
673 &with_both,
674 &new_vbs_sig,
675 IgvmPlatformType::VSM_ISOLATION,
676 None,
677 )
678 .expect("VBS update");
679
680 IgvmFile::new_from_binary(&after_vbs, None).expect("valid after VBS update");
681
682 let new_snp_sig = sign_envelope_for(b"snp-doc", "test-alt");
684 let after_snp =
685 patch(&after_vbs, &new_snp_sig, IgvmPlatformType::SEV_SNP, None).expect("SNP update");
686
687 let (docs, sigs) = extract_corim_headers(&after_snp);
688 assert_eq!(docs.len(), 2);
689 assert_eq!(sigs.len(), 2);
690
691 let vbs_doc = docs.iter().find(|d| d.compatibility_mask == 0x1).unwrap();
692 let snp_doc = docs.iter().find(|d| d.compatibility_mask == 0x2).unwrap();
693 let vbs_sig = sigs.iter().find(|s| s.compatibility_mask == 0x1).unwrap();
694 let snp_sig = sigs.iter().find(|s| s.compatibility_mask == 0x2).unwrap();
695
696 assert_eq!(vbs_doc.payload, b"vbs-doc", "VBS doc preserved");
697 assert_eq!(snp_doc.payload, b"snp-doc", "SNP doc preserved");
698 assert_eq!(vbs_sig.payload, new_vbs_sig, "VBS sig from step 1");
699 assert_eq!(snp_sig.payload, new_snp_sig, "SNP sig from step 2");
700
701 IgvmFile::new_from_binary(&after_snp, None).expect("valid after both updates");
702 }
703
704 #[test]
712 fn test_e2e_real_corim_build_and_patch() {
713 let platform = IgvmPlatformType::VSM_ISOLATION;
714 let mask = 0x1;
715
716 let page_data = vec![0xAA; 4096];
718 let base = build_igvm(
719 vec![new_platform(mask, platform)],
720 vec![new_page_data(0, mask, &page_data)],
721 );
722
723 let parsed = IgvmFile::new_from_binary(&base, None).expect("parse base IGVM");
726 let mut serializer = IgvmSerializer::new(&parsed).expect("construct serializer");
727 let mut le = LaunchMeasurement::for_platform(platform).expect("launch endorsement");
728 le.set_measurement(MeasurementKind::Launch)
729 .expect("set measurement kind");
730 le.endorse(1)
731 .with(MeasurementKind::Launch)
732 .expect("CES with")
733 .finish()
734 .expect("CES finish");
735 let real_corim = serializer
736 .add_corim(platform, le.build())
737 .expect("add_corim")
738 .to_vec();
739
740 let mut with_doc = Vec::new();
741 serializer.serialize(&mut with_doc).expect("serialize");
742
743 let (docs, sigs) = extract_corim_headers(&with_doc);
746 assert_eq!(docs.len(), 1, "one CoRIM document embedded");
747 assert!(sigs.is_empty(), "no signature before patch");
748 assert_eq!(
749 docs[0].payload, real_corim,
750 "embedded doc matches add_corim return"
751 );
752
753 let signature = sign_envelope_for(&real_corim, "e2e-test");
756 let patched = patch(&with_doc, &signature, platform, None).expect("patch signature");
757
758 IgvmFile::new_from_binary(&patched, None).expect("patched file parses");
761 let (docs, sigs) = extract_corim_headers(&patched);
762 assert_eq!(docs.len(), 1, "one CoRIM document after patch");
763 assert_eq!(sigs.len(), 1, "one CoRIM signature after patch");
764 assert_eq!(docs[0].compatibility_mask, mask);
765 assert_eq!(sigs[0].compatibility_mask, mask);
766 assert_eq!(docs[0].payload, real_corim, "real CoRIM doc preserved");
767 assert_eq!(sigs[0].payload, signature, "real signature attached");
768 }
769}