Sign RPM Files

You can sign RPM files with a virtual GPG key and the Signum Linux Agent.

What is a Virtual GPG Key?

GPG uses its own key format (OpenPGP), while Signum stores keys as X.509 certificates inside an HSM. These are two completely separate systems. GPG cannot natively talk to an HSM or use an X.509 certificate directly for signing.

A virtual GPG key is the bridge between them. Instead of generating a new key pair locally, you create a GPG key entry that points to an existing key already living in Signum. Concretely:

  • The public key and metadata (name, email, expiry) are stored locally in ~/.gnupg

  • The private key entry is just a stub file that says "this key lives on smart card with serial X"

  • When GPG needs to sign something, it follows that stub → calls the smart card daemon → calls the PKCS#11 library → reaches Signum → the HSM performs the signing operation

The private key never leaves the Signum HSM at any point.

You can tell the difference in gpg --list-secret-keys:

sec   rsa3072  ← private key stored on disk (no HSM)
sec>  rsa2048  ← private key is in the HSM — the > means "stub only"

The link between the GPG virtual key and the Signum key is the keygrip which is the GnuPG internal identifier computed from the raw RSA key material (modulus + exponent). Since both sides are working with the same underlying RSA key pair, they produce the same keygrip, which is how GPG identifies the virtual key as the key in Signum.

The GPG Key ID and the Signum certificate thumbprint are different identifiers computed in different ways from the same key material. There is no automatic correlation between them.

Prerequisites


Step 1 - Install Dependencies

  1. Install the packages:

Bash
sudo apt update
sudo apt install -y \
  gnupg2 \
  pinentry-curses \
  opensc \
  build-essential \
  bzip2 \
  wget \
  devscripts \
  libgpg-error-dev \
  libgcrypt20-dev \
  libassuan-dev \
  libpkcs11-helper1-dev \
  pkg-config
  1. Build gnupg-pkcs11-scd 0.11.0 from source with a one-line patch in cmd_keyinfo:

The version of gnupg-pkcs11-scd available in Debian apt repositories is 0.10.0 and has a known bug. Version 0.11.0 partially fixes this but still requires a one-line patch in cmd_keyinfo.

cd /tmp
wget https://github.com/alonbl/gnupg-pkcs11-scd/releases/download/gnupg-pkcs11-scd-0.11.0/gnupg-pkcs11-scd-0.11.0.tar.bz2
tar -xjf gnupg-pkcs11-scd-0.11.0.tar.bz2
cd gnupg-pkcs11-scd-0.11.0
# Apply the patch
python3 << 'EOF'
with open('gnupg-pkcs11-scd/command.c', 'r') as f:
    content = f.read()
old = '\t\tif (error != GPG_ERR_NO_ERROR) {\n\t\t\tgoto cleanup;\n\t\t}\n\t}\n\n\terror = found ? GPG_ERR_NO_ERROR : GPG_ERR_NOT_FOUND;'
new = '\t\tif (error == GPG_ERR_WRONG_PUBKEY_ALGO) {\n\t\t\terror = GPG_ERR_NO_ERROR;\n\t\t}\n\t\tif (error != GPG_ERR_NO_ERROR) {\n\t\t\tgoto cleanup;\n\t\t}\n\t}\n\n\terror = found ? GPG_ERR_NO_ERROR : GPG_ERR_NOT_FOUND;'
if old in content:
    content = content.replace(old, new)
    with open('gnupg-pkcs11-scd/command.c', 'w') as f:
        f.write(content)
    print("Patch applied!")
else:
    print("Pattern not found!")
EOF
# Build and install
./configure && make && sudo make install
# Verify
/usr/local/bin/gnupg-pkcs11-scd --version

Expected output:

gnupg-pkcs11-scd 0.11.0

Step 2 - Verify Signum Agent and PKCS#11 Library

  1. Verify the Agent is authenticated and certificates are visible:

signum-util test
signum-util listcertificates
  1. Verify the PKCS#11 library is present and keys are accessible:

ls -la /usr/lib/libsignumpkcs11.so
pkcs11-tool --module /usr/lib/libsignumpkcs11.so --list-objects

Step 3 - Create GPG Configuration Files

  1. Create the GPG configuration files:

mkdir -p ~/.gnupg
chmod 700 ~/.gnupg
cat > ~/.gnupg/gpg-agent.conf << 'EOF'
verbose
debug-all
log-file /tmp/gpg-agent.log
scdaemon-program /usr/local/bin/gnupg-pkcs11-scd
pinentry-program /usr/bin/pinentry-curses
allow-loopback-pinentry
EOF
cat > ~/.gnupg/gnupg-pkcs11-scd.conf << 'EOF'
providers signum
provider-signum-library /usr/lib/libsignumpkcs11.so
EOF
chmod 600 ~/.gnupg/gpg-agent.conf
chmod 600 ~/.gnupg/gnupg-pkcs11-scd.conf
  1. Set GPG_TTY and start the smart card daemon:

GPG_TTY must always be set before signing.

export GPG_TTY=$(tty)
gpgconf --kill all
gpg --card-status

Expected result is the card detected with Application type: OpenPGP.

  1. Get the KEY-FRIENDLY hash for your certificate from Signum:

gpg-connect-agent << 'EOF'
SCD LEARN --force
SCD GETATTR KEYPAIRINFO
EOF

Note the KEY-FRIENDLY hash for the certificate you want to use:

S KEY-FRIENDLY 2588F2F67BBC82E872B15287621964636FB074D9 /CN=SignumCertificate on Signum for Linux

Save this hash for the following steps.


Step 4 - Map the Signing Key

Update gnupg-pkcs11-scd.conf with the KEY-FRIENDLY hash:

  1. Replace <KEY-FRIENDLY-HASH> with your actual hash value:

cat > ~/.gnupg/gnupg-pkcs11-scd.conf << 'EOF'
providers signum
provider-signum-library /usr/lib/libsignumpkcs11.so
openpgp-sign <KEY-FRIENDLY-HASH>
EOF
  1. Restart the daemon to apply the change:

gpgconf --kill all
gpg --card-status
  1. Confirm the Signature key field is now populated. It should show the keygrip instead of [none]:

Signature key ....: 2588 F2F6 7BBC 82E8 72B1  5287 6219 6463 6FB0 74D9

Step 5 - Create the Virtual GPG Key

  1. Generate the virtual GPG key:

gpg --expert --full-generate-key
  1. When prompted, provide the following values:

Prompt

Value

Key type

13 (Existing key)

Keygrip

<KEY-FRIENDLY-HASH>

Capabilities

Q (keep defaults)

Expiry

0y

Real name

Your full name

Email address

Your email address

Confirm

O

PIN prompt

Your Signum password

Expected output:

pub   rsa2048 2026-05-05 [SCE]
      EF9365EAB0975DE4ED1D241B431698166BB91279
uid                      Your Name <your@email.com>
  1. Verify the key is linked to the Signum card:

gpg --list-secret-keys --with-keygrip

Confirm sec> (with >) appears, meaning the private key is in the Signum HSM, not on disk:

sec>  rsa2048 2026-05-05 [SCE]
      EF9365EAB0975DE4ED1D241B431698166BB91279
      Keygrip = 2588F2F67BBC82E872B15287621964636FB074D9
      Card serial no. = 3131 DC6624E9
uid           [ultimate] Your Name <your@email.com>
  1. Set this key as the GPG default. Replace <FINGERPRINT> with the full fingerprint
    from the output above:

echo "default-key <FINGERPRINT>" >> ~/.gnupg/gpg.conf
echo "default-key EF9365EAB0975DE4ED1D241B431698166BB91279" >> ~/.gnupg/gpg.conf
  1. Make GPG_TTY permanent across sessions:

echo 'export GPG_TTY=$(tty)' >> ~/.bashrc
source ~/.bashrc

Step 6 - Configure RPM Macros

If your Linux distribution does not natively use RPM, you must first create a directory:

sudo mkdir /etc/rpm

Create the macro file, replacing Your name and <your_email> with your actual values:

cat > ~/.rpmmacros << 'EOF'
%_signature gpg
%_gpg_path /home/<username>/.gnupg
%_gpg_name Your name <your_email>
%_gpgbin /usr/bin/gpg2
%__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}
%_dbpath /var/lib/rpm
EOF
echo "%_dbpath /var/lib/rpm" | sudo tee /etc/rpm/macros.db

The %_gpg_name value must match the UID of your GPG key exactly, including
the email address in single angle brackets. Use gpg --list-keys to confirm the
exact UID string.


Step 7 - Import the Public Key into the RPM Keyring

Export your public key and import it into the RPM database so rpm --checksig can
verify signatures against it:

gpg --armor --export <your_email> > /tmp/signum-pubkey.asc
sudo rpm --import /tmp/signum-pubkey.asc --dbpath /var/lib/rpm

Verify the key was imported:

rpm -q gpg-pubkey --dbpath /var/lib/rpm \
  --qf '%{name}-%{version}-%{release} --> %{summary}\n'

Expected output:

gpg-pubkey-6bb91279-69f99e75 --> Your Name <your@email.com> public key

Step 8 - Sign an RPM

  1. Sign the .rpm:

rpmsign --addsign /path/to/yourpackage.rpm
  1. Verify the RPM signature:

rpm --checksig --verbose /path/to/yourpackage.rpm
rpm --checksig /path/to/yourpackage.rpm

Expected output:

Header V4 RSA/SHA256 Signature, key ID 6bb91279: OK
Header SHA256 digest: OK
Payload SHA256 digest: OK
digests signatures OK

Troubleshooting

rpmsign Fails with "key not found" or "secret key not available"

  • Confirm %_gpg_name in ~/.rpmmacros matches the GPG key UID exactly:
    gpg --list-keys

  • The UID format must be: Your Name <your@email.com> (single angle brackets).
    Double brackets or extra spaces will cause the lookup to fail.

  • Confirm GPG_TTY is exported in the current shell before running rpmsign.

rpm --checksig Shows "NOKEY" or Signature Verification Fails

  • Confirm the public key was imported into the RPM keyring:

    rpm -q gpg-pubkey --dbpath /var/lib/rpm
    
  • If the key is missing, re-run the import:

    sudo rpm --import /tmp/signum-pubkey.asc --dbpath /var/lib/rpm
    
  • Check that the key ID in the checksig output matches the version field of the imported key (such as 6bb91279).

rpmsign Prompts for a Passphrase Interactively and Hangs in CI

  • Confirm allow-loopback-pinentry is set in gpg-agent.conf.

  • Confirm --batch is present in the %__gpg_sign_cmd macro in ~/.rpmmacros.

  • In a CI environment, ensure GPG_TTY is set and a pinentry program is available:

    export GPG_TTY=$(tty)
    

RPM Was Signed but Signature Appears as "gpg" Not "RSA/SHA256"

  • The --force-v3-sigs flag in %__gpg_sign_cmd produces v3 OpenPGP signatures,
    which older RPM versions require. If your RPM toolchain supports v4 signatures,
    remove --force-v3-sigs for stronger compatibility with modern verification tools.