Sign Debian Packages

You can sign Debian packages (.deb) using 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"

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

Confirm

O

PIN prompt

Your Signum password

Expected output:

pub   rsa2048 2026-05-21 [SCE]
      BB07CE2D9778F6CED7D0F05A46C163CE62A3B77B
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-21 [SCE]
      BB07CE2D9778F6CED7D0F05A46C163CE62A3B77B
      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
  1. Make GPG_TTY permanent across sessions:

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

Step 6 - Sign a DEB Package

  1. Sign the package, which creates a detached .asc signature file alongside the .deb:

gpg --armor --detach-sign /path/to/yourpackage.deb

Expected output:

yourpackage.deb
yourpackage.deb.asc  ← detached GPG signature
  1. Verify the signature locally:

gpg --verify /path/to/yourpackage.deb.asc /path/to/yourpackage.deb

Expected output:

gpg: Signature made Thu 21 May 2026
gpg:                using RSA key BB07CE2D9778F6CED7D0F05A46C163CE62A3B77B
gpg: Good signature from "Your Name <your@email.com>" [ultimate]

Export Public Key for Distribution

For recipients to validate your signature, you need to share your public key:

gpg --armor --export <your_email> > /tmp/signum-pubkey.asc

Recipients then import it and verify:

gpg --import signum-pubkey.asc
gpg --verify yourpackage.deb.asc yourpackage.deb

Troubleshooting

gpg --card-status Returns "No card" or Hangs

  • Confirm gnupg-pkcs11-scd is the patched 0.11.0 build:

    /usr/local/bin/gnupg-pkcs11-scd --version
    
  • Check the scdaemon-program path in gpg-agent.conf points to the correct binary.

  • Kill all GPG daemons and retry:

    gpgconf --kill all
    gpg --card-status
    
  • Review the agent log for errors:

    cat /tmp/gpg-agent.log
    

KEY-FRIENDLY Hash Not Appearing in SCD GETATTR KEYPAIRINFO

  • Confirm the Signum Agent is authenticated: signum-util test

  • Confirm the PKCS#11 library is accessible:

    pkcs11-tool --module /usr/lib/libsignumpkcs11.so --list-objects
    
  • If the token has mixed RSA + ECC certificates, confirm you applied the patch to the gnupg-pkcs11-scd build, or the loop stops before reaching RSA certificates.

gpg --full-generate-key Fails at Keygrip Prompt

  • The keygrip entered must match exactly the KEY-FRIENDLY hash.

  • Make sure you ran gpg --card-status after updating gnupg-pkcs11-scd.conf
    and that the Signature key field is populated (not [none]).

sec (Without >) After Key Generation

  • This means the private key was stored locally on disk, not linked to the HSM.

  • Delete the key (gpg --delete-secret-and-public-key <FINGERPRINT>) and repeat
    Step 5, confirming you selected key type 13 (Existing key) and entered the
    correct keygrip.

Signature Verification Fails on Recipient Machine

  • The recipient must have imported your public key before running gpg --verify.

  • Confirm the .asc file and the .deb file are both present and unmodified.

  • Ensure the recipient's GPG version supports the key algorithm used.