@@ -533,6 +533,82 @@ mod tests {
533533 Ok ( ( ) )
534534 }
535535
536+ /// Export a PKCS#12 bundle for `key`+`cert` with the given `cipher_args`,
537+ /// import it into the gpgsm keyring under `home`, and return the imported
538+ /// secret key's fingerprint. Returns `None` when this openssl/gpgsm pair
539+ /// can't round-trip the bundle, so the caller can try another cipher.
540+ #[ cfg( unix) ]
541+ fn gpgsm_import_p12 (
542+ home : & std:: path:: Path ,
543+ key : & std:: path:: Path ,
544+ cert : & std:: path:: Path ,
545+ cipher_args : & [ & str ] ,
546+ ) -> Option < String > {
547+ use std:: process:: Command ;
548+
549+ let p12 = home. join ( "bundle.p12" ) ;
550+ // tolerates failure (unlike the test's `run`) so a failed cipher can
551+ // fall back to another.
552+ let run = |program : & str , args : & [ & str ] | {
553+ Command :: new ( program)
554+ . args ( args)
555+ . env ( "GNUPGHOME" , home)
556+ . output ( )
557+ . unwrap_or_else ( |e| {
558+ panic ! ( "failed to run {program}: {e}" )
559+ } )
560+ } ;
561+
562+ let mut export_args = vec ! [
563+ "pkcs12" ,
564+ "-export" ,
565+ "-inkey" ,
566+ key. to_str( ) . unwrap( ) ,
567+ "-in" ,
568+ cert. to_str( ) . unwrap( ) ,
569+ "-out" ,
570+ p12. to_str( ) . unwrap( ) ,
571+ "-passout" ,
572+ "pass:" ,
573+ ] ;
574+ export_args. extend_from_slice ( cipher_args) ;
575+ if !run ( "openssl" , & export_args) . status . success ( ) {
576+ return None ;
577+ }
578+
579+ run (
580+ "gpgsm" ,
581+ & [
582+ "--batch" ,
583+ "--pinentry-mode" ,
584+ "loopback" ,
585+ "--passphrase" ,
586+ "" ,
587+ "--import" ,
588+ p12. to_str ( ) . unwrap ( ) ,
589+ ] ,
590+ ) ;
591+
592+ // a listed secret key (fpr line) means the import gave us a key.
593+ let listing = run (
594+ "gpgsm" ,
595+ & [ "--batch" , "--with-colons" , "--list-secret-keys" ] ,
596+ ) ;
597+ String :: from_utf8_lossy ( & listing. stdout )
598+ . lines ( )
599+ . filter_map ( |line| line. strip_prefix ( "fpr:" ) )
600+ . find_map ( |rest| {
601+ rest. split ( ':' )
602+ . find ( |field| {
603+ field. len ( ) == 40
604+ && field
605+ . bytes ( )
606+ . all ( |b| b. is_ascii_hexdigit ( ) )
607+ } )
608+ . map ( |field| field. to_string ( ) )
609+ } )
610+ }
611+
536612 /// e2e x509 signing: set up a throwaway `gpgsm` identity, sign a real
537613 /// commit and verify it. Serial + unix-only: uses a process-wide `GNUPGHOME`.
538614 #[ cfg( unix) ]
@@ -609,7 +685,6 @@ mod tests {
609685
610686 let key = home. join ( "key.pem" ) ;
611687 let cert = home. join ( "cert.pem" ) ;
612- let p12 = home. join ( "bundle.p12" ) ;
613688 run (
614689 "openssl" ,
615690 & [
@@ -628,59 +703,24 @@ mod tests {
628703 & format ! ( "/CN=gitui test/emailAddress={email}" ) ,
629704 ] ,
630705 ) ;
631- run (
632- "openssl" ,
633- & [
634- "pkcs12" ,
635- "-export" ,
636- "-inkey" ,
637- key. to_str ( ) . unwrap ( ) ,
638- "-in" ,
639- cert. to_str ( ) . unwrap ( ) ,
640- "-out" ,
641- p12. to_str ( ) . unwrap ( ) ,
642- "-passout" ,
643- "pass:" ,
644- // legacy PBE: gpgsm can't read OpenSSL 3's default PBES2/AES.
645- "-keypbe" ,
646- "PBE-SHA1-3DES" ,
647- "-certpbe" ,
648- "PBE-SHA1-3DES" ,
649- "-macalg" ,
650- "sha1" ,
651- ] ,
652- ) ;
653- run (
654- "gpgsm" ,
655- & [
656- "--batch" ,
657- "--pinentry-mode" ,
658- "loopback" ,
659- "--passphrase" ,
660- "" ,
661- "--import" ,
662- p12. to_str ( ) . unwrap ( ) ,
663- ] ,
664- ) ;
706+ // gpgsm's PKCS#12 reader accepts different ciphers across versions, so
707+ // try a legacy (3DES/SHA1) then a modern (OpenSSL default) bundle and
708+ // fail only if neither imports a usable secret key.
709+ let legacy = & [
710+ "-keypbe" ,
711+ "PBE-SHA1-3DES" ,
712+ "-certpbe" ,
713+ "PBE-SHA1-3DES" ,
714+ "-macalg" ,
715+ "sha1" ,
716+ ] ;
717+ let fingerprint = gpgsm_import_p12 ( home, & key, & cert, legacy)
718+ . or_else ( || gpgsm_import_p12 ( home, & key, & cert, & [ ] ) )
719+ . expect (
720+ "gpgsm could not import a legacy or modern PKCS#12 bundle" ,
721+ ) ;
665722
666723 // trust our self-signed root ("S" relaxes CA checks) so gpgsm will sign.
667- let listing = run (
668- "gpgsm" ,
669- & [ "--batch" , "--with-colons" , "--list-secret-keys" ] ,
670- ) ;
671- let listing = String :: from_utf8_lossy ( & listing. stdout ) ;
672- let fingerprint = listing
673- . lines ( )
674- . filter_map ( |line| line. strip_prefix ( "fpr:" ) )
675- . find_map ( |rest| {
676- rest. split ( ':' ) . find ( |field| {
677- field. len ( ) == 40
678- && field
679- . bytes ( )
680- . all ( |b| b. is_ascii_hexdigit ( ) )
681- } )
682- } )
683- . expect ( "could not determine cert fingerprint" ) ;
684724 std:: fs:: write (
685725 home. join ( "trustlist.txt" ) ,
686726 format ! ( "{fingerprint} S\n " ) ,
0 commit comments