@@ -743,4 +743,125 @@ mod tests {
743743 std:: env:: remove_var ( "GNUPGHOME" ) ;
744744 Ok ( ( ) )
745745 }
746+
747+ /// e2e openpgp signing: generate a throwaway `gpg` key, sign a real
748+ /// commit and verify it. Serial + unix-only: uses a process-wide `GNUPGHOME`.
749+ #[ cfg( unix) ]
750+ #[ test]
751+ #[ serial]
752+ fn test_openpgp_sign_and_verify_e2e ( ) -> Result < ( ) > {
753+ use std:: os:: unix:: fs:: PermissionsExt ;
754+ use std:: process:: Command ;
755+
756+ fn tool_available ( bin : & str ) -> bool {
757+ Command :: new ( bin)
758+ . arg ( "--version" )
759+ . stdout ( std:: process:: Stdio :: null ( ) )
760+ . stderr ( std:: process:: Stdio :: null ( ) )
761+ . status ( )
762+ . map ( |s| s. success ( ) )
763+ . unwrap_or ( false )
764+ }
765+
766+ assert ! (
767+ tool_available( "gpg" ) ,
768+ "gpg is required for the openpgp e2e test"
769+ ) ;
770+
771+ let email = "gitui-openpgp-test@example.com" ;
772+ let gnupg = tempfile:: tempdir ( ) ?;
773+ let home = gnupg. path ( ) ;
774+ std:: fs:: set_permissions (
775+ home,
776+ std:: fs:: Permissions :: from_mode ( 0o700 ) ,
777+ ) ?;
778+
779+ // GPGSign inherits env, so point the child gpg at our keyring.
780+ std:: env:: set_var ( "GNUPGHOME" , home) ;
781+
782+ let run = |program : & str , args : & [ & str ] | {
783+ let out = Command :: new ( program)
784+ . args ( args)
785+ . env ( "GNUPGHOME" , home)
786+ . output ( )
787+ . unwrap_or_else ( |e| {
788+ panic ! ( "failed to run {program}: {e}" )
789+ } ) ;
790+ assert ! (
791+ out. status. success( ) ,
792+ "{program} {args:?} failed: {}" ,
793+ String :: from_utf8_lossy( & out. stderr)
794+ ) ;
795+ out
796+ } ;
797+
798+ // unattended keygen: %no-protection => no passphrase, so no pinentry
799+ // and no agent trust dance are needed (unlike the x509/gpgsm path).
800+ let params = home. join ( "keyparams" ) ;
801+ std:: fs:: write (
802+ & params,
803+ format ! (
804+ "%no-protection\n Key-Type: RSA\n Key-Length: 2048\n Subkey-Type: RSA\n Subkey-Length: 2048\n Name-Real: gitui test\n Name-Email: {email}\n Expire-Date: 0\n %commit\n "
805+ ) ,
806+ ) ?;
807+ run (
808+ "gpg" ,
809+ & [ "--batch" , "--gen-key" , params. to_str ( ) . unwrap ( ) ] ,
810+ ) ;
811+
812+ let ( _tmp_dir, repo) = repo_init_empty ( ) ?;
813+ {
814+ let mut config = repo. config ( ) ?;
815+ config. set_str ( "gpg.format" , "openpgp" ) ?;
816+ config. set_str ( "user.signingKey" , email) ?;
817+ }
818+ let signer =
819+ SignBuilder :: from_gitconfig ( & repo, & repo. config ( ) ?) ?;
820+ assert_eq ! ( "gpg" , signer. program( ) ) ;
821+
822+ let sig = git2:: Signature :: now ( "gitui test" , email) ?;
823+ let tree = {
824+ let mut index = repo. index ( ) ?;
825+ let tree_id = index. write_tree ( ) ?;
826+ repo. find_tree ( tree_id) ?
827+ } ;
828+ let commit_id = create_signed_commit (
829+ & repo,
830+ & * signer,
831+ & sig,
832+ & sig,
833+ "openpgp signed commit" ,
834+ & tree,
835+ & [ ] ,
836+ ) ?;
837+
838+ let ( signature, signed_data) =
839+ repo. extract_signature ( & commit_id, None ) ?;
840+ let signature = std:: str:: from_utf8 ( & signature) . unwrap ( ) ;
841+ assert ! (
842+ signature. contains( "BEGIN PGP SIGNATURE" ) ,
843+ "expected an armored OpenPGP signature, got: {signature}"
844+ ) ;
845+
846+ let sig_file = home. join ( "commit.sig" ) ;
847+ let data_file = home. join ( "commit.data" ) ;
848+ std:: fs:: write ( & sig_file, signature) ?;
849+ std:: fs:: write ( & data_file, & * signed_data) ?;
850+ let verify = run (
851+ "gpg" ,
852+ & [
853+ "--verify" ,
854+ sig_file. to_str ( ) . unwrap ( ) ,
855+ data_file. to_str ( ) . unwrap ( ) ,
856+ ] ,
857+ ) ;
858+ let verify_err = String :: from_utf8_lossy ( & verify. stderr ) ;
859+ assert ! (
860+ verify_err. contains( "Good signature" ) ,
861+ "gpg did not accept the signature: {verify_err}"
862+ ) ;
863+
864+ std:: env:: remove_var ( "GNUPGHOME" ) ;
865+ Ok ( ( ) )
866+ }
746867}
0 commit comments