1919 */
2020
2121use std:: {
22- ffi:: { c_void, CStr } ,
22+ ffi:: { c_void, CStr , CString } ,
2323 fs:: File ,
2424 io:: { prelude:: * , BufReader } ,
2525 mem:: ManuallyDrop ,
@@ -33,7 +33,7 @@ use openssl::{
3333 stack:: { self , Stack } ,
3434 x509:: {
3535 store:: { X509Store , X509StoreBuilder } ,
36- X509 ,
36+ X509Ref , X509 ,
3737 } ,
3838} ;
3939
@@ -43,10 +43,13 @@ use clam_sigutil::{
4343 SigType , Signature ,
4444} ;
4545
46- use log:: { debug, error , warn} ;
46+ use log:: { debug, warn} ;
4747
4848use crate :: { ffi_error, ffi_util:: FFIError , sys:: cl_retflevel, validate_str_param} ;
4949
50+ const CLAMSIGN_HEADER_PREFIX : & str = "#clamsign-" ;
51+ const CLAMSIGN_VERSION : & str = "1.0" ;
52+
5053#[ derive( Debug , thiserror:: Error ) ]
5154pub enum Error {
5255 #[ error( "Can't verify: {0}" ) ]
@@ -82,6 +85,38 @@ pub enum Error {
8285 IncorrectPublicKey ,
8386}
8487
88+ fn validate_clamsign_header ( line : & str ) -> Result < ( ) , Error > {
89+ let version = line. strip_prefix ( CLAMSIGN_HEADER_PREFIX ) . ok_or_else ( || {
90+ Error :: CannotVerify ( format ! (
91+ "Unsupported signature file format, expected first line to start with '{}{}'" ,
92+ CLAMSIGN_HEADER_PREFIX , CLAMSIGN_VERSION
93+ ) )
94+ } ) ?;
95+
96+ if version != CLAMSIGN_VERSION {
97+ return Err ( Error :: CannotVerify ( format ! (
98+ "Unsupported signature file version, expected '{}'" ,
99+ CLAMSIGN_VERSION
100+ ) ) ) ;
101+ }
102+
103+ Ok ( ( ) )
104+ }
105+
106+ fn certificate_common_name ( cert : & X509Ref ) -> Result < String , String > {
107+ let common_name = cert
108+ . subject_name ( )
109+ . entries ( )
110+ . find ( |name_entry| name_entry. object ( ) . nid ( ) == openssl:: nid:: Nid :: COMMONNAME )
111+ . ok_or_else ( || "certificate subject is missing a common name" . to_string ( ) ) ?;
112+
113+ common_name
114+ . data ( )
115+ . as_utf8 ( )
116+ . map ( |name| name. to_string ( ) )
117+ . map_err ( |e| format ! ( "certificate common name is not valid UTF-8: {}" , e) )
118+ }
119+
85120/// C interface for verify_signed_file() which verifies a file's external digital signature.
86121/// Handles all the unsafe ffi stuff.
87122///
@@ -306,7 +341,15 @@ pub unsafe extern "C" fn codesign_verify_file(
306341 Ok ( signer) => {
307342 debug ! ( "CVD verified successfully" ) ;
308343 // convert the signer_name to a CString and store it in the output parameter
309- let signer_cstr = std:: ffi:: CString :: new ( signer) . unwrap ( ) ;
344+ let signer_cstr = match CString :: new ( signer) {
345+ Ok ( signer_cstr) => signer_cstr,
346+ Err ( e) => {
347+ return ffi_error ! (
348+ err = err,
349+ Error :: CannotVerify ( format!( "Invalid signer name: {}" , e) )
350+ ) ;
351+ }
352+ } ;
310353 * signer_name = signer_cstr. into_raw ( ) ;
311354 true
312355 }
@@ -407,19 +450,7 @@ pub fn verify_signed_file(
407450 // First line should be "#clamsign-MAJOR.MINOR"
408451 if index == 0 {
409452 let line = line?;
410- if !line. starts_with ( "#clamsign" ) {
411- return Err ( Error :: CannotVerify (
412- "Unsupported signature file format, expected first line start with '#clamsign-1.0'" . to_string ( ) ,
413- ) ) ;
414- }
415-
416- // Check clamsign version
417- let version = line. split ( '-' ) . nth ( 1 ) . unwrap ( ) ;
418- if version != "1.0" {
419- return Err ( Error :: CannotVerify (
420- "Unsupported signature file version, expected '1.0'" . to_string ( ) ,
421- ) ) ;
422- }
453+ validate_clamsign_header ( & line) ?;
423454
424455 continue ;
425456 }
@@ -441,7 +472,12 @@ pub fn verify_signed_file(
441472
442473 match parse_from_cvd_with_meta ( SigType :: DigitalSignature , & data. into ( ) ) {
443474 Ok ( ( sig, meta) ) => {
444- let sig = sig. downcast :: < DigitalSig > ( ) . unwrap ( ) ;
475+ let sig = sig. downcast :: < DigitalSig > ( ) . map_err ( |_| {
476+ Error :: CannotVerify ( format ! (
477+ "{:?}:{}: Signature did not parse as a digital signature" ,
478+ signature_file_path, index
479+ ) )
480+ } ) ?;
445481
446482 sig. validate ( & meta) . map_err ( |e| {
447483 Error :: CannotVerify ( format ! (
@@ -452,7 +488,12 @@ pub fn verify_signed_file(
452488
453489 // verify the flevel bounds of this signature compared with the current flevel
454490 let current_flevel = unsafe { cl_retflevel ( ) } ;
455- let sig_flevel_range = meta. f_level . unwrap ( ) ;
491+ let sig_flevel_range = meta. f_level . ok_or_else ( || {
492+ Error :: CannotVerify ( format ! (
493+ "{:?}:{}: Digital signature is missing feature-level metadata" ,
494+ signature_file_path, index
495+ ) )
496+ } ) ?;
456497 if !sig_flevel_range. contains ( & current_flevel) {
457498 debug ! (
458499 "{:?}:{}: Signature feature level range {:?} does not include current feature level {}" ,
@@ -574,25 +615,27 @@ impl Verifier {
574615 let file = file?;
575616 let path = file. path ( ) ;
576617 if path. is_file ( ) {
577- let ext = path. extension ( ) ;
578- if ext. is_some ( ) && ( ext. unwrap ( ) == "pem" || ext. unwrap ( ) == "crt" ) {
579- let read_result = std:: fs:: read ( & path) ;
580- if let Err ( e) = read_result {
581- debug ! ( "Error reading certificate file '{:?}': {}" , path, e) ;
582- continue ;
583- }
584- let certs_in_file = X509 :: stack_from_pem ( & read_result. unwrap ( ) ) ?;
618+ let is_certificate = match path. extension ( ) {
619+ Some ( ext) => ext == "pem" || ext == "crt" ,
620+ None => false ,
621+ } ;
622+ if is_certificate {
623+ let cert_bytes = match std:: fs:: read ( & path) {
624+ Ok ( cert_bytes) => cert_bytes,
625+ Err ( e) => {
626+ debug ! ( "Error reading certificate file '{:?}': {}" , path, e) ;
627+ continue ;
628+ }
629+ } ;
630+ let certs_in_file = X509 :: stack_from_pem ( & cert_bytes) ?;
585631
586632 for cert in certs_in_file {
587- // get cert common name
588- let common_name = cert
589- . subject_name ( )
590- . entries ( )
591- . find ( |name_entry| {
592- name_entry. object ( ) . nid ( ) == openssl:: nid:: Nid :: COMMONNAME
593- } )
594- . map ( |name_entry| name_entry. data ( ) . as_utf8 ( ) . unwrap ( ) . to_string ( ) )
595- . unwrap ( ) ;
633+ let common_name = certificate_common_name ( & cert) . map_err ( |e| {
634+ Error :: CertificateStore ( format ! (
635+ "Invalid certificate {:?}: {}" ,
636+ path, e
637+ ) )
638+ } ) ?;
596639
597640 if root_common_names. contains ( & common_name) {
598641 return Err ( Error :: CertificateStore ( format ! (
@@ -628,15 +671,11 @@ impl Verifier {
628671 let signers: Vec < String > = signer
629672 . iter ( )
630673 . map ( |cert| {
631- cert. subject_name ( )
632- . entries ( )
633- . find ( |name_entry| {
634- name_entry. object ( ) . nid ( ) == openssl:: nid:: Nid :: COMMONNAME
635- } )
636- . map ( |name_entry| name_entry. data ( ) . as_utf8 ( ) . unwrap ( ) . to_string ( ) )
637- . unwrap ( )
674+ certificate_common_name ( cert) . map_err ( |e| {
675+ Error :: InvalidDigitalSignature ( format ! ( "Invalid signer certificate: {}" , e) )
676+ } )
638677 } )
639- . collect ( ) ;
678+ . collect :: < Result < _ , _ > > ( ) ? ;
640679
641680 match result {
642681 Ok ( ( ) ) => {
@@ -656,3 +695,29 @@ impl Verifier {
656695 }
657696 }
658697}
698+
699+ #[ cfg( test) ]
700+ mod tests {
701+ use super :: * ;
702+
703+ #[ test]
704+ fn clamsign_header_accepts_supported_version ( ) {
705+ validate_clamsign_header ( "#clamsign-1.0" ) . expect ( "valid clamsign header" ) ;
706+ }
707+
708+ #[ test]
709+ fn clamsign_header_rejects_missing_separator ( ) {
710+ assert ! ( matches!(
711+ validate_clamsign_header( "#clamsign" ) ,
712+ Err ( Error :: CannotVerify ( _) )
713+ ) ) ;
714+ }
715+
716+ #[ test]
717+ fn clamsign_header_rejects_unsupported_version ( ) {
718+ assert ! ( matches!(
719+ validate_clamsign_header( "#clamsign-2.0" ) ,
720+ Err ( Error :: CannotVerify ( _) )
721+ ) ) ;
722+ }
723+ }
0 commit comments