How to sign rpms with GPG

Updated -

RPM package signatures can be used to implement cryptographic integrity checks for RPM packages. This approach is end-to-end in the sense that the package build infrastructure at the vendor can use an offline or half-online private key (such as one stored in hardware security module), and the final system which consumes these packages can directly verify the signatures because they are built into the .rpm package files. Intermediates such as proxies and caches (which are sometimes used to separate production servers from the Internet) cannot tamper with these signatures.

The following steps describes the process of generating a GPG key and signing RPMs with the key.

  • First generate a gpg key pair on the machine.
[root@localhost ~]# gpg --gen-key
GnuPG needs to construct a user ID to identify your key.
...
Real name: Package Manager
Email address: pmanager@example.com
Comment: RPM Signing Key
You selected this USER-ID:
    "Package Manager (RPM Signing Key) <pmanager@example.com>"
...
gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0  valid:   2  signed:   0  trust: 0-, 0q, 0n, 0m, 0f, 2u
pub   2048R/B74246CE 2017-03-22
      Key fingerprint = BCE7 1F72 7D86 E857 2B3F  1790 7255 CF3E B742 46CE
uid                  Package Manager (RPM Signing Key) <pmanager@example.com>
sub   2048R/D8FA7BFF 2017-03-22
  • Confirm and see the generated key.
[root@localhost ~]# gpg --list-keys
/root/.gnupg/pubring.gpg
------------------------
pub   2048R/B74246CE 2017-03-22
uid                  Package Manager (RPM Signing Key) <pmanager@example.com>
sub   2048R/D8FA7BFF 2017-03-22
  • Export the Public key.
[root@localhost ~]# gpg --export -a 'Package Manager' > RPM-GPG-KEY-pmanager
[root@localhost ~]# cat RPM-GPG-KEY-pmanager
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v2.0.22 (GNU/Linux)

mQENBFjSgckBCADob86RSLTExhr8T/P69vz4MjE2EDHUmCN+tFYUaDgmkpZBpU40
Qs6WdOuBUvXevPQfKCj1OaRTouzGGGsvv7W/hHIKZ1IdY5HOXHbHZdkcghnaAGTj
VdctgkXyLj+qMI9mvRAMaHiiTbD8AoPsT/uGMeg94R3FhO5hgXDQc5xADhvXU3+8
MzeBmaYxAYRZ65Vc/8XgAPMDK+kIZZ0VQMB0Gzxio2OCMTy7uNg9QOqkYhPuVYh8
GarrP6bZNzeImT8k615/+9rYo9Ys0mY4tq0N4RlZCjhJ/PIMoRjhEgtGe643DXUh
EMpk477niWvolaE3PxZjFXRGxQN/VjjWoy9vABEBAAG0OFB1c2hwZW5kcmEgQ2hh
dmFuIChSUE0gU2lnbmluZyBLZXkpIDxwY2hhdmFuQHJlZGhhdC5jb20+iQE5BBMB
AgAjBQJY0oHJAhsDBwsJCAcDAgEGFQgCCQoLBBYCAwECHgECF4AACgkQclXPPrdC
Rs4mxwf6AwPEHK3ZWCUTjj9uJlVIM3lU+9F5aFOgns2cY6msR3Y+spOVVkBqq97V
v3bgRbhU2W+0RXIRxnyOMn9LrBkcPCjujfPZ7gIBpfrJpOpH26PZEPyVvj07ZRv5
5DFaJgccnr7LKwp22pn8byK1yK5BDZMg6/U9mK5MoRsfmc/fC4I25gT3aIBxV7hj
swb7gbNOx3ZzQT7dJwQIFkznpY2XuKAwaZrpZNwttF74xW8oYjXatNsGNSNmNgFh
7rtqAwBWTN54zg+qojk+eX4zMmldpWqvHATlh8cc4Wzj40bBI8Ifaq/QTl0dY3t2
nzd1afgbwx4lTt5VjBQ8KJzSNSn0lbkBDQRY0oHJAQgApsX9jRxstyJMhaQr57Re
voOkIT6z6mz8OpdGf2QHiXyVFYiRVv5njDl+hv9jtQ3XNmnInwi/Xs4OL5j6mE8r
cetr4ngPowVeCbwqcZeP53S5/K4Klg+7G7Sut1oNQlh7qPbut/glgiu3zqwbl4rO
Vs5iMGoQtZazQxHKby611Gka2yky/JSrnVLb7Hxsoe9/yT1+cmeelzv2hZA7cEqx
XHykDAoiULepcfau3scUatZexyTNMne3nlLFSnVxz4PRy9/+l0jb2m2lA5Yl9U4G
45JD+FJ/jflcSGW9wFD8B4Sgb8QAhVffbjzHrof0Bbxbxwc5ilYExXBtuSJao1Cd
bwARAQABiQEfBBgBAgAJBQJY0oHJAhsMAAoJEHJVzz63QkbOIs4IALXiE2B+I2/D
9//YhHGAXy7VQ3qKZBxuS0zxTHnzD+gLs+xIeCrLKJxpJ3beTxSdBRge/DQUkMXq
F/j9vKHFFTf0Hx/IGj2ujdUdQ2ILoTJMsfQXprqPTAJ0t5oPGwIF+fBxmJjQ6DgM
YbbQS2qMpK3YFCmKQHZQhwvn3r7jERWZpJr6pYYYkwqhmj1MXIxuJDGDgTH3VZl0
g9FkYYhGy8dU4CFSoRGuTtcVX+VzI8/lU544K1HE7uLwkz40PP+zBpQ3QqxLC2MV
voKkk10tyrv3xf+PimsnQmV3GKgfMoRLVTP0pP2YN8SS+aSGNPDwfKWKNkNRNc3/
ZVDQUeETOh4=
=aTMv
-----END PGP PUBLIC KEY BLOCK-----
  • Import the exported public key into rpm database as follows.
[root@localhost ~]# rpm --import RPM-GPG-KEY-pmanager 

[root@localhost ~]# rpm -q gpg-pubkey --qf '%{name}-%{version}-%{release} --> %{summary}\n'
        gpg-pubkey-fd431d51-4ae0493b --> gpg(Red Hat, Inc. (release key 2) <security@redhat.com>)
        gpg-pubkey-2fa658e0-45700c69 --> gpg(Red Hat, Inc. (auxiliary key) <security@redhat.com>)
        gpg-pubkey-b74246ce-58d281c9 --> gpg(Package Manager (RPM Signing Key) <pmanager@example.com>)
  • Edit the file ~/.rpmmacros in order to utilize the key.
[root@localhost ~]# vi .rpmmacros 
[root@localhost ~]# cat .rpmmacros 
%_signature gpg
%_gpg_path /root/.gnupg
%_gpg_name Package Manager
%_gpgbin /usr/bin/gpg2
  • Verify that the __gpg_sign_cmd is defined in --showrc option. Its not required to define this parameter in the ~/.rpmmacros above since its already defined in default macros unless there is a requirement to tweak the options for gpg singing command.
[root@localhost ~]# rpm --showrc | grep __gpg_sign_cmd -A 5
-13: __gpg_sign_cmd %{__gpg} 
    gpg --no-verbose --no-armor 
    %{?_gpg_digest_algo:--digest-algo %{_gpg_digest_algo}} 
    --no-secmem-warning 
    %{?_gpg_sign_cmd_extra_args:%{_gpg_sign_cmd_extra_args}} 
    -u "%{_gpg_name}" -sbo %{__signature_filename} %{__plaintext_filename}
  • Sign the rpm.
[root@localhost ~]# rpm --addsign test-1-0.x86_64.rpm 
Enter pass phrase: 
Pass phrase is good.
test-1-0.x86_64.rpm:
gpg: writing to `/var/tmp/rpm-tmp.JO74Hd.sig'
gpg: RSA/SHA256 signature from: "B74246CE Package Manager (RPM Signing Key) <pmanager@example.com>"
gpg: writing to `/var/tmp/rpm-tmp.hG5gUG.sig'
gpg: RSA/SHA256 signature from: "B74246CE Package Manager (RPM Signing Key) <pmanager@example.com>"

[root@localhost ~]# rpm --checksig test-1-0.x86_64.rpm 
test-1-0.x86_64.rpm: rsa sha1 (md5) pgp md5 OK

[root@localhost ~]# rpm -qp --qf '%|DSAHEADER?{%{DSAHEADER:pgpsig}}:{%|RSAHEADER?{%{RSAHEADER:pgpsig}}:{(none)}|}|\n' test-1-0.x86_64.rpm 
RSA/SHA256, Wed 22 Mar 2017 07:57:29 AM MDT, Key ID 7255cf3eb74246ce
  • rpm -K still shows generic output because SHA1 (and MD5) digests are an integral part of rpm packages,
  • Use the above command in order to confirm the sign algorithm.

8 Comments

This is a good guide, but its worth mentioning that the key-creation step creates excess key material, particularly an encryption subkey. And encryption subkey would never be needed for a package signing key and might open the user up to forming a bad habbit of using it because it exists--understanding they could trivially add an encryption or authentication key at any time. A more correct approach would be to use the --full-gen-key option and chose options 3 or 4 to create a, "sign only" key:

[coster@localhost ~]$ gpg2 --full-gen-key
gpg (GnuPG) 2.2.11; Copyright (C) 2018 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Please select what kind of key you want:
   (1) RSA and RSA (default)
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
Your selection? 4
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 
Requested keysize is 2048 bits
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 
Key does not expire at all
Is this correct? (y/N) y

GnuPG needs to construct a user ID to identify your key.

Real name: Package Manager
Email address: pmanager@example.com
Comment: 
You selected this USER-ID:
    "Package Manager <pmanager@example.com>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
gpg: key 279015F596B3A227 marked as ultimately trusted
gpg: revocation certificate stored as '/home/coster/.gnupg/openpgp-revocs.d/B9FB4C7DFF9E7991BAE2124F279015F596B3A227.rev'
public and secret key created and signed.
  • This creates a considerably smaller key:
[coster@localhost ~]$ gpg2 -a --export pmanager@example.com
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQENBFxQaekBCADoDt8tFdlQvgoS7/jOeCGhDMV05lFupmeK9gYvKYDqRY5badUo
YFPbWBppid2Bm/nc0xG74tUWi61HdH6yWyPoO6MTum73HZluysRPqyhlx0pXjlBp
VezHFpDTceUqg1ZREo0kZxrv2XiotHnK3MtJIrLBdeN0Uz/nBtV/mZB5AIkezEaK
6xPba8SobT4YF0VLl9GbgVp2wxaYQiLVjP7U6WMBMH1sV0Mi/La4XF9H84GjUU94
1Kh8VpL5NaU6rsyMNVtlrWG0n0ZDOJpsBupmGAFkVqngljB0vgYfJCV6fbuhoIQq
1JMD2sBkbCBH49wEsb5Dwg62hfGoc5bLgIR7ABEBAAG0JlBhY2thZ2UgTWFuYWdl
ciA8cG1hbmFnZXJAZXhhbXBsZS5jb20+iQFOBBMBCAA4FiEEuftMff+eeZG64hJP
J5AV9ZazoicFAlxQaekCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQJ5AV
9Zazoics4Af/fG0TUs2O3RiHJflhfoXr+3Qm1j4k+CCFGCbdTyvSs2GHmoPcRnG/
BVSovQmfMnuN5bRGRu9aGkw+YN2S9Ua6DvclCBmgvwHkJV32urni3NrcO5PcAQM5
36UvUoYWQ9HFt4I1hU+AHC8BnA+/28GEAGrIgx7oxFcMguu0woAiQ2CtVXbNuPaK
bgyml53DLlfkhqdOFveFteRy0p5QIjLkFJFR6vYq3K7mZgRFKrzejdTZUmN5qN5v
FlTYFTxKt32TGvCAeaVlikj+SzDD+MvMVjiomsHLxqLMUf1DoFzDcXfffKlWykns
e9gw9GO96/wSZy13A9L3guFl3maFgNh50g==
=zLG0
-----END PGP PUBLIC KEY BLOCK-----

This key is valid for signature and certification (signing other keys), as evidenced by the [SC] below:

[coster@localhost ~]$ gpg2 --list-keys pmanager@example.com
pub   rsa2048 2019-01-29 [SC]
      B9FB4C7DFF9E7991BAE2124F279015F596B3A227
uid           [ultimate] Package Manager <pmanager@example.com>

One would think that you should trim down further and create a key incapable of signing other keys, however, GnuPG just won't let you do that because it has to sign itself.

For anyone running this on an Ubuntu 18.04 WSL, I received the following error:

 gpg: Fatal: passphrase-fd is invalid: Bad file descriptor

Removing the '--passphrase-fd 3' option from the .rpmmacros file worked for me.

~ cat .rpmmacros %_signature gpg %_gpg_path /home/johnpeery/.gnupg %_gpg_name Package Manager %_gpgbin /usr/bin/gpg %__gpg_sign_cmd %{__gpg} gpg --force-v3-sigs --batch --verbose --no-armor --no-secmem-warning -u "%{_gpg_name}" -sbo %{__signature_filename} --digest-algo sha256 %{__plaintext_filename}' ~

Here's the reference where I found this information:

https://github.com/Homebrew/homebrew-core/issues/39120

wiggin15 commented on Nov 30, 2019 For anyone coming here through a search engine query (like me): The problem here is related to the configuration set in ~/.rpmmacros. Using --passphrase-fd 3 (to tell gpg to read the passphrase from "stdin") apparently worked in older versions of "gpg" (where "stdin" was file descriptor 3, it seems) but does not work in newer versions of "gpg" (we used rpmsign on Ubuntu, and upgrading from Ubuntu 14.04 with gpg v1.4.16 to Ubuntu 18.04 with gpg v2.2.4 caused the same issue). For us, removing this flag altogether from ~/.rpmmacros fixed the issue.

This was also an issue for me on RHEL 8.2 (as of 10/01/2020). Thanks for the comment as this fixed it for me on EL8 as well!

Hi

this solution is working for one, maybe two rpms. But what if it should be used for thousands?

The gpg implementation in CentOS/Redhat does not support the pinentry-mode and gpg-preset-passphrase - so how to do it?

For several (to squeeze within line length) packages: rpm --addsign package-1.rpm package-2.rpm ... Nasty solution would be to put passphrase into ~/.rpmmacros in plaintext...

You might do something like that in ~/.rpmmacros:

%_signature gpg
%_gpg_name <ADD_YOUR_KEY_ID_HERE>
%__gpg_check_password_cmd /bin/true
%_gpgbin /usr/bin/gpg2
%__gpg_sign_cmd     %{__gpg} \
   gpg \
     --force-v3-sigs \
     --pinentry-mode loopback \
     --batch \
     --verbose \
     --no-armor \
     --no-secmem-warning \
     --passphrase-file <PATH_TO_YOUR_SECURE_FILE_CONTAINGIN_PASSPHRASE> \
     --digest-algo sha512 \
     -u "%{_gpg_name}" \
     -sbo %{__signature_filename} %{__plaintext_filename}

(do not forget to replace the key and pass file)

Can the apostrophe use in this document be double-checked?

For example, the following one at the end has no starter. Does it need to exist?
{__signature_filename} --digest-algo sha256 %{__plaintext_filename}'

Also, there's an apostrophe and backtick here that looks like a copy paste error:
gpg: writing to `/var/tmp/rpm-tmp.JO74Hd.sig'

Confirming as others have, that it is necessary to remove '--passphrase-fd 3' from the .rpmmacros. Setting it to '--passphrase-fd 0' still causes the issue. It appears from the error response (as shown below) that the '--passphrase-fd 3' may be bugged and setting V4 sigs (just guessing from the error message) instead of the v3 sigs option from the argument? I can also confirm I removed the unnecessary ' from the end of the same argument line as noted by Lyndon Lapierre's comment.

#.rpmmacros AS SHOWN IN THE DOC
[root@rhel8test ~]# rpm --addsign my.rpm
my.rpm:
gpg: writing to 'my.rpm.sig'
gpg: pinentry launched (42727 curses 1.1.0 /dev/pts/0 xterm -)
gpg: RSA/SHA256 signature from: "AB239C392DEFG219 GpgKey (GPG SIGNING) <no_reply@email.com>"
gpg: writing to 'my.rpm.sig'
gpg: RSA/SHA256 signature from: "AB239C392DEFG219 GpgKey (GPG SIGNING) <no_reply@email.com>"
[root@rhel8test ~]# rpm -q --qf '%{SIGPGP:pgpsig} %{SIGGPG:pgpsig}\n' -p my.rpm
error: my.rpm: Header V4 RSA/SHA256 Signature, key ID 2defg219: BAD
error: my.rpm: not an rpm package (or package manifest)
[root@rhel8test ~]# rpm -qp --qf '%|DSAHEADER?{%{DSAHEADER:pgpsig}}:{%|RSAHEADER?{%{RSAHEADER:pgpsig}}:{(none)}|}|\n' my.rpm
error: my.rpm: Header V4 RSA/SHA256 Signature, key ID 2defg219: BAD
error: my.rpm: not an rpm package (or package manifest)

#.rpmmacros WITH PASSPHRASE-FD OPTION REMOVED
[root@rhel8test ~]# rpm --addsign my.rpm
my.rpm:
gpg: writing to 'my.rpm.sig'
gpg: pinentry launched (42881 curses 1.1.0 /dev/pts/0 xterm -)
gpg: RSA/SHA256 signature from: "AB239C392DEFG219 GpgKey (GPG SIGNING) <no_reply@email.com>"
gpg: writing to 'my.rpm.sig'
gpg: RSA/SHA256 signature from: "AB239C392DEFG219 GpgKey (GPG SIGNING) <no_reply@email.com>"
[root@rhel8test ~]# rpm -qp --qf '%|DSAHEADER?{%{DSAHEADER:pgpsig}}:{%|RSAHEADER?{%{RSAHEADER:pgpsig}}:{(none)}|}|\n' my.rpm
RSA/SHA256, Wed 12 Jul 2023 01:41:21 PM UTC, Key ID ab239c392defg219
[root@rhel8test ~]# rpm -q --qf '%{SIGPGP:pgpsig} %{SIGGPG:pgpsig}\n' -p my.rpm
RSA/SHA256, Wed 12 Jul 2023 01:41:32 PM UTC, Key ID ab239c392defg219 (none)