Verifying RPM signatures

Like other package formats, RPMs can be cryptographically verified to ensure packages are still intact before they are installed. This post is going to go over how you can verify packages.

Why verify RPMs?

RPMs are normally sourced from the Internet, this presents two problems:

  • RPMs can become truncated or corrupt during transit.
  • RPMs could be maliciously modified as part of a man in the middle attack.

To mitigate these problems RPMs contain a signature section; the section includes a checksum and optionally a cryptographic signature. Checksums ensure truncated or corrupt packages are not installed. The signature (if included) can be used to verify the contents of the RPM has not been modified, since the author signed the package. RPMs normally use GPG public-key cryptography to achieve this.

GPG public keys

On Red Hat based distributions like CentOS, you can normally find GPG public keys in /etc/pki/rpm-gpg/. The files in the directory should be ASCII Armoured GPG public keys.

Although keys are normally deployed to /etc/pki/rpm-gpg/, they are actually imported to an RPM database before they are used. You can query which RPMs have been imported by running rpm -q gpg-pubkey:

$ rpm -q gpg-pubkey
gpg-pubkey-f4a80eb5-53a7ff4b

Note: the RPM database files can be found in /var/lib/rpm.

Importing keys with YUM

When installing packages with YUM, if the gpgcheck option has been enabled, YUM will automatically try to verify packages against imported public keys. If the key has not yet been imported you will see something like this:

Retrieving key from file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
Importing GPG key 0xF4A80EB5:
 Userid     : "CentOS-7 Key (CentOS 7 Official Signing Key) <security@centos.org>"
 Fingerprint: 6341 ab27 53d7 8a78 a7c2 7bb1 24c6 a8a7 f4a8 0eb5
 Package    : centos-release-7-1.1503.el7.centos.2.8.x86_64 (@anaconda)
 From       : /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
Is this ok [y/N]:

Saying yes will import the public key and continue the installation. The path to the public key which should be imported is managed on a per repository basis. For example the base repository for CentOS 7 has the following configuration:

[base]
name=CentOS-$releasever - Base
mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=os&infra=$infra
#baseurl=http://mirror.centos.org/centos/$releasever/os/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7

Note: other protocols like https can be used instead of file, refer to the yum.conf(5) man page for more details.

Importing keys with RPM

Alternatively rpm --import can be used to import public keys:

$ rpm -q gpg-pubkey
package gpg-pubkey is not installed
$ sudo rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
$ rpm -q gpg-pubkey
gpg-pubkey-f4a80eb5-53a7ff4b

Listing imported keys

The rpm command can be used to list GPG keys currently imported into the RPM database:

$ rpm -q --qf "%{version}-%{release} %{summary}\n" gpg-pubkey
f4a80eb5-53a7ff4b gpg(CentOS-7 Key (CentOS 7 Official Signing Key) <security@centos.org>)
b6792c39-53c4fbdd gpg(CentOS-7 Debug (CentOS-7 Debuginfo RPMS) <security@centos.org>)
8fae34bd-538f1e51 gpg(CentOS-7 Testing (CentOS 7 Testing content) <security@centos.org>)

Note: a query string is used in the example above to make the keys easier to identify.

Removing keys

To remove GPG keys from the RPM database you can use rpm -e followed by gpg-pubkey-<version>-<release>. For example:

$ sudo rpm -e gpg-pubkey-b6792c39-53c4fbdd

Version and release?

GPG public keys have a key id and a POSIX timestamp. You can list these using the gpg command:

$ gpg --list-packets /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
:public key packet:
                version 4, algo 1, created 1403518795, expires 0
                pkey[0]: [4096 bits]
                pkey[1]: [17 bits]
                keyid: 24C6A8A7F4A80EB5
:user ID packet: "CentOS-7 Key (CentOS 7 Official Signing Key) <security@centos.org>"
:signature packet: algo 1, keyid 24C6A8A7F4A80EB5
                version 4, created 1403518795, md5len 0, sigclass 0x13
                digest algo 2, begin of digest 4c dd
                hashed subpkt 2 len 4 (sig created 2014-06-23)
                hashed subpkt 27 len 1 (key flags: 03)
                hashed subpkt 11 len 5 (pref-sym-algos: 9 8 7 3 2)
                hashed subpkt 21 len 3 (pref-hash-algos: 2 8 3)
                hashed subpkt 22 len 2 (pref-zip-algos: 2 1)
                hashed subpkt 30 len 1 (features: 01)
                hashed subpkt 23 len 1 (key server preferences: 80)
                subpkt 16 len 8 (issuer key ID 24C6A8A7F4A80EB5)
                data: [4095 bits]

Note: the details of how the key id is calculated are described in RFC 4880.

So what does this have to do with the seemingly arbitrary version and release of gpg-pubkey entries in the RPM database? Well the version maps directly to the last eight characters of the GPG key id, and the release is the hexadecimal version of the creation timestamp.

Verifying packages

So long as the gpgcheck option has been enabled, YUM will automatically check the GPG signature of packages it downloads. If the signature matches, YUM will carry on as normal. If the package doesn't match the configured public key you will get a message similar to the following and the package won't be installed:

The GPG keys listed for the "CentOS-7 - Base" repository are already
installed but they are not correct for this package.

Check that the correct key URLs are configured for this repository.

Verifying packages with rpm

You can also manually verify RPM packages using the rpm command. If both the signature and the checksum are correct you'll get output similar to the following:

$ rpm --checksig tcpdump-4.5.1-3.el7.x86_64.rpm
tcpdump-4.5.1-3.el7.x86_64.rpm: rsa sha1 (md5) pgp md5 OK

If no key in the RPM database matches the package signature you will get a message similar to the following:

$ rpm --checksig tcpdump-4.5.1-3.el7.x86_64.rpm
tcpdump-4.5.1-3.el7.x86_64.rpm: RSA sha1 ((MD5) PGP) md5 NOT OK (MISSING KEYS: (MD5) PGP#f4a80eb5)

Assuming the RPM is not corrupt you can get signature information using the --info query option:

$ rpm -qp --info tcpdump-4.5.1-3.el7.x86_64.rpm
...
Signature   : RSA/SHA256, Wed 25 Nov 2015 15:43:25 GMT, Key ID 24c6a8a7f4a80eb5
Source RPM  : tcpdump-4.5.1-3.el7.src.rpm
Build Date  : Fri 20 Nov 2015 08:36:02 GMT
Build Host  : worker1.bsys.centos.org
...

If you only want to verify the contents of the package against the checksum, you can use the --nosignature option:

$ rpm --checksig --nosignature tcpdump-4.5.1-3.el7.x86_64.rpm
tcpdump-4.5.1-3.el7.x86_64.rpm: sha1 md5 OK

If the file is corrupt or truncated verifying against the checksum should fail:

$ rpm --checksig --nosignature tcpdump-4.5.1-3.el7.x86_64.rpm
tcpdump-4.5.1-3.el7.x86_64.rpm: sha1 MD5 NOT OK

It is also possible to use both the --nosignature and --nodigest options at the same time:

$ rpm --checksig --nosignature --nodigest tcpdump-4.5.1-3.el7.x86_64.rpm
tcpdump-4.5.1-3.el7.x86_64.rpm: OK

Although this initial seems pointless, it does actually check the file header looks like an RPM:

$ rpm --checksig --nosignature --nodigest /dev/null
error: /dev/null: not an rpm package