QCecuring - Enterprise Security Solutions

Code Signing in Linux: Complete Guide to Signing Packages, Binaries, and Containers

Code Signing 26 May, 2026 · 08 Mins read

Learn how to sign Linux artifacts including RPM/DEB packages, kernel modules, container images, Git commits, and AppImages. Covers GPG, cosign, Sigstore, and CI/CD integration.


Code signing on Linux is fragmented across multiple ecosystems — each package format, container runtime, and distribution channel has its own signing mechanism. Unlike Windows (Authenticode) or macOS (codesign), there’s no single unified signing framework for Linux. This means developers and DevOps engineers need to understand GPG signing for packages, cosign for containers, kernel module signing for drivers, and emerging standards like Sigstore for keyless verification.

This guide covers every major Linux signing workflow, from traditional GPG-based package signing through modern keyless approaches, with practical commands and CI/CD integration examples.

Why Sign Linux Artifacts?

Code signing establishes two critical properties:

  1. Authenticity: The artifact was produced by the claimed publisher
  2. Integrity: The artifact hasn’t been modified since signing

Without signing, users and systems cannot distinguish legitimate software from tampered or malicious versions. Supply chain attacks like the SolarWinds compromise and the xz-utils backdoor (CVE-2024-3094) demonstrate why verification of software provenance is essential.

What Should Be Signed?

Artifact TypeSigning MethodVerification Point
RPM packagesGPG (rpmsign)Package manager (dnf/yum)
DEB packagesGPG (dpkg-sig, debsign)Package manager (apt)
Container imagescosign, Notary v2Container runtime/admission controller
Git commits/tagsGPG or SSHGit hosting platform
Kernel modulesX.509 (sign-file)Kernel module loader
AppImagesGPGAppImage runtime
FlatpaksGPG (OSTree)Flatpak runtime
Generic binariesGPG detached signaturesManual verification

GPG Signing Fundamentals

GPG (GNU Privacy Guard) is the foundation of most Linux signing workflows. Before diving into specific artifact types, let’s establish the GPG basics.

Generating a Signing Key

# Generate a new GPG key for code signing
gpg --full-generate-key

# Recommended settings:
#   Key type: RSA (sign only) or ed25519
#   Key size: 4096 bits (RSA) or default (ed25519)
#   Expiration: 2-3 years (rotate regularly)
#   Name: Your Name (Code Signing)
#   Email: signing@yourorg.com

# For automated environments, use batch mode:
cat > key-params.txt << 'PARAMS'
%no-protection
Key-Type: eddsa
Key-Curve: ed25519
Key-Usage: sign
Name-Real: Acme Corp
Name-Email: security@acme.com
Expire-Date: 2y
%commit
PARAMS

gpg --batch --gen-key key-params.txt
rm key-params.txt

Key Management Best Practices

# Export public key for distribution
gpg --armor --export security@acme.com > acme-signing-key.asc

# Backup private key securely (offline storage)
gpg --armor --export-secret-keys security@acme.com > private-key-backup.asc

# Store in a hardware token (YubiKey)
gpg --edit-key security@acme.com
# > keytocard

# List signing keys
gpg --list-secret-keys --keyid-format=long

RPM Package Signing

RPM uses GPG signatures embedded directly in the package file.

Setting Up RPM Signing

# Configure RPM macros for signing
cat >> ~/.rpmmacros << 'EOF'
%_signature gpg
%_gpg_name security@acme.com
%__gpg /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}
EOF

Signing RPM Packages

# Sign a single package
rpmsign --addsign mypackage-1.0.0-1.x86_64.rpm

# Sign multiple packages
rpmsign --addsign *.rpm

# Verify signature
rpm --checksig mypackage-1.0.0-1.x86_64.rpm
# mypackage-1.0.0-1.x86_64.rpm: digests signatures OK

# Import public key on client systems
rpm --import https://packages.acme.com/RPM-GPG-KEY-acme

# Verify with verbose output
rpm -Kv mypackage-1.0.0-1.x86_64.rpm

Repository Signing

# Sign repository metadata (repomd.xml)
gpg --detach-sign --armor repodata/repomd.xml

# Configure yum/dnf to require signatures
cat > /etc/yum.repos.d/acme.repo << 'EOF'
[acme]
name=Acme Packages
baseurl=https://packages.acme.com/el9/
enabled=1
gpgcheck=1
gpgkey=https://packages.acme.com/RPM-GPG-KEY-acme
repo_gpgcheck=1
EOF

DEB Package Signing

Debian packages use a different signing approach with multiple tools available.

Using dpkg-sig

# Sign a .deb package
dpkg-sig -k security@acme.com --sign builder mypackage_1.0.0_amd64.deb

# Verify signature
dpkg-sig --verify mypackage_1.0.0_amd64.deb
# Processing mypackage_1.0.0_amd64.deb...
# GOODSIG _gpgbuilder A1B2C3D4E5F6G7H8

Using debsign (for source packages)

# Sign .dsc and .changes files
debsign -k security@acme.com mypackage_1.0.0_amd64.changes

# This signs both the .dsc (source description) and .changes file

APT Repository Signing

# Generate a repository signing key
gpg --full-generate-key  # Use RSA 4096 for repository signing

# Export for the repository
gpg --armor --export security@acme.com > /var/www/repo/KEY.gpg

# Sign Release file
gpg --default-key security@acme.com \
    --detach-sign --armor \
    --output Release.gpg Release

# Create InRelease (clearsigned, preferred)
gpg --default-key security@acme.com \
    --clearsign \
    --output InRelease Release
# Client-side: Add repository key
curl -fsSL https://packages.acme.com/KEY.gpg | \
  gpg --dearmor -o /usr/share/keyrings/acme-archive-keyring.gpg

# Configure apt source with signed-by
cat > /etc/apt/sources.list.d/acme.list << 'EOF'
deb [signed-by=/usr/share/keyrings/acme-archive-keyring.gpg] https://packages.acme.com/debian stable main
EOF

Kernel Module Signing

Linux kernel module signing ensures only authorized modules can be loaded, critical for Secure Boot and system integrity.

How Kernel Module Signing Works

The kernel uses X.509 certificates (not GPG) for module signing. During kernel build, a signing key is generated and embedded in the kernel. Only modules signed with this key (or keys in the system keyring) can be loaded.

# Check if module signing is enforced
cat /proc/sys/kernel/modules_disabled  # 1 = no new modules
cat /proc/keys | grep -i "asymmetric"  # Shows trusted keys

# Check a module's signature
modinfo my_module.ko | grep sig
# sig_id:     PKCS#7
# signer:     Build time autogenerated kernel key
# sig_key:    AB:CD:EF:...
# sig_hashalgo: sha256

Signing Custom Kernel Modules

# Generate a signing key pair
openssl req -new -x509 -newkey rsa:4096 \
  -keyout signing_key.pem \
  -outform DER -out signing_key.x509 \
  -nodes -days 365 \
  -subj "/CN=Module Signing Key/"

# Sign the module
/usr/src/kernels/$(uname -r)/scripts/sign-file \
  sha256 \
  signing_key.pem \
  signing_key.x509 \
  my_module.ko

# Verify the signature
modinfo my_module.ko | grep -A2 "sig"

# Enroll the key for Secure Boot (requires MOK)
mokutil --import signing_key.x509
# Reboot and confirm enrollment in MOK Manager

DKMS with Module Signing

# Configure DKMS to sign modules automatically
cat > /etc/dkms/framework.conf.d/signing.conf << 'EOF'
sign_tool="/usr/lib/linux-kbuild-$(uname -r | cut -d. -f1-2)/scripts/sign-file"
mok_signing_key="/var/lib/dkms/mok.key"
mok_certificate="/var/lib/dkms/mok.pub"
EOF

# Generate MOK key for DKMS
openssl req -new -x509 -newkey rsa:4096 \
  -keyout /var/lib/dkms/mok.key \
  -outform DER -out /var/lib/dkms/mok.pub \
  -nodes -days 730 \
  -subj "/CN=DKMS Module Signing/"

mokutil --import /var/lib/dkms/mok.pub

Container Image Signing with Cosign

Cosign (part of the Sigstore project) is the modern standard for container image signing.

Key-Based Signing

# Generate a cosign key pair
cosign generate-key-pair
# Creates cosign.key (private) and cosign.pub (public)

# Sign a container image
cosign sign --key cosign.key registry.acme.com/app:v1.2.3

# Verify the signature
cosign verify --key cosign.pub registry.acme.com/app:v1.2.3

# Sign with annotations (metadata)
cosign sign --key cosign.key \
  -a "commit=$(git rev-parse HEAD)" \
  -a "pipeline=https://ci.acme.com/builds/123" \
  -a "builder=github-actions" \
  registry.acme.com/app:v1.2.3

Keyless Signing with Sigstore

Keyless signing uses short-lived certificates from Sigstore’s Fulcio CA, tied to an OIDC identity:

# Keyless signing (uses OIDC identity - GitHub, Google, Microsoft)
cosign sign registry.acme.com/app:v1.2.3
# Opens browser for OIDC authentication
# Certificate issued by Fulcio, logged in Rekor transparency log

# Verify keyless signature
cosign verify \
  --certificate-identity=ci@acme.com \
  --certificate-oidc-issuer=https://accounts.google.com \
  registry.acme.com/app:v1.2.3

Kubernetes Admission Control

# Kyverno policy requiring signed images
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signatures
spec:
  validationFailureAction: Enforce
  rules:
    - name: verify-cosign-signature
      match:
        any:
          - resources:
              kinds:
                - Pod
      verifyImages:
        - imageReferences:
            - "registry.acme.com/*"
          attestors:
            - entries:
                - keyless:
                    subject: "ci@acme.com"
                    issuer: "https://accounts.google.com"
                    rekor:
                      url: https://rekor.sigstore.dev

Notary v2 (ORAS Signatures)

Notary v2 provides OCI-native signatures stored alongside images in registries:

# Sign with notation (Notary v2 CLI)
notation sign registry.acme.com/app:v1.2.3 \
  --key "signing-key" \
  --signature-format cose

# Verify
notation verify registry.acme.com/app:v1.2.3

# List signatures
notation list registry.acme.com/app:v1.2.3

Git Commit and Tag Signing

GPG Signing

# Configure Git to use GPG signing
git config --global user.signingkey A1B2C3D4E5F6G7H8
git config --global commit.gpgsign true
git config --global tag.gpgsign true

# Sign a commit
git commit -S -m "feat: add authentication module"

# Sign a tag
git tag -s v1.2.3 -m "Release v1.2.3"

# Verify signatures
git log --show-signature -1
git tag -v v1.2.3

SSH Signing (Git 2.34+)

# Configure Git to use SSH signing
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true

# Create allowed signers file
echo "user@acme.com $(cat ~/.ssh/id_ed25519.pub)" > ~/.config/git/allowed_signers
git config --global gpg.ssh.allowedSignersFile ~/.config/git/allowed_signers

# Sign and verify
git commit -S -m "feat: signed with SSH key"
git log --show-signature -1

Gitsign (Sigstore for Git)

# Install gitsign
go install github.com/sigstore/gitsign@latest

# Configure Git to use gitsign
git config --global commit.gpgsign true
git config --global tag.gpgsign true
git config --global gpg.x509.program gitsign
git config --global gpg.format x509

# Commits are now signed with Sigstore (keyless, OIDC-based)
git commit -m "feat: keyless signed commit"

AppImage and Flatpak Signing

AppImage Signing

# Sign an AppImage with GPG
gpg --detach-sign --armor MyApp-x86_64.AppImage

# Embed signature in AppImage (using appimagetool)
appimagetool --sign --sign-key security@acme.com AppDir/ MyApp-x86_64.AppImage

# Verify
gpg --verify MyApp-x86_64.AppImage.asc MyApp-x86_64.AppImage

Flatpak Signing

Flatpak uses OSTree’s GPG signing for repositories:

# Generate a key for Flatpak repository
gpg --homedir=/var/lib/flatpak-repo-gpg --gen-key

# Initialize a signed Flatpak repository
ostree init --repo=repo --mode=archive-z2
flatpak build-sign repo --gpg-sign=KEYID --gpg-homedir=/var/lib/flatpak-repo-gpg

# Export and sign
flatpak build-export --gpg-sign=KEYID repo app-build/

# Users add the repository with the GPG key
flatpak remote-add --gpg-import=acme-flatpak.gpg acme-repo https://flatpak.acme.com/repo

Sigstore for Linux: The Modern Approach

Sigstore provides a complete signing ecosystem that eliminates long-lived key management:

Components

ComponentPurpose
CosignContainer image signing
FulcioShort-lived certificate authority (OIDC-based)
RekorTransparency log (immutable record of signatures)
GitsignGit commit signing
Policy ControllerKubernetes admission control

How Keyless Signing Works

1. Developer authenticates via OIDC (GitHub, Google, etc.)
2. Fulcio issues a short-lived certificate (10 minutes)
   binding the OIDC identity to a signing key
3. Artifact is signed with the ephemeral key
4. Signature + certificate are recorded in Rekor transparency log
5. Ephemeral key is discarded
6. Verification checks Rekor log + certificate chain

Benefits Over Traditional GPG

AspectGPG SigningSigstore Keyless
Key managementManual, error-proneNo long-lived keys
Key rotationComplex, breaks verificationAutomatic (per-signing)
RevocationCRL/keyserver updatesNot needed (short-lived)
Identity bindingEmail in key UIDOIDC identity (verified)
TransparencyNonePublic log (Rekor)
Offline verificationYesRequires Rekor access

Setting Up a Signing Infrastructure

Centralized Signing Service

For organizations managing multiple projects, a centralized signing service provides consistency and key protection:

# Architecture overview
signing-service:
  components:
    - name: "Key Storage"
      options:
        - "HashiCorp Vault (Transit engine)"
        - "AWS KMS / GCP Cloud KMS"
        - "Hardware Security Module (HSM)"
    
    - name: "Signing API"
      description: "Internal service that accepts signing requests"
      auth: "mTLS + RBAC"
    
    - name: "Policy Engine"
      description: "Validates what can be signed and by whom"
      rules:
        - "Only CI/CD pipelines can sign release artifacts"
        - "Human approval required for production releases"
        - "All signing events logged to audit trail"

Vault Transit Engine for Signing

# Enable Vault Transit for signing operations
vault secrets enable transit

# Create a signing key
vault write transit/keys/code-signing \
  type=ecdsa-p256 \
  exportable=false

# Sign data (base64-encoded)
DIGEST=$(sha256sum artifact.tar.gz | awk '{print $1}' | xxd -r -p | base64)
vault write transit/sign/code-signing \
  input="$DIGEST" \
  hash_algorithm=sha2-256 \
  signature_algorithm=pkcs1v15

# Verify
vault write transit/verify/code-signing \
  input="$DIGEST" \
  signature="vault:v1:..."

CI/CD Integration

GitHub Actions

name: Build and Sign
on:
  push:
    tags: ['v*']

permissions:
  contents: read
  packages: write
  id-token: write  # Required for keyless signing

jobs:
  build-sign:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Install cosign
        uses: sigstore/cosign-installer@v3
      
      - name: Login to registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      
      - name: Build and push image
        id: build
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }}
      
      - name: Sign image (keyless)
        run: |
          cosign sign --yes \
            -a "repo=${{ github.repository }}" \
            -a "sha=${{ github.sha }}" \
            -a "ref=${{ github.ref }}" \
            ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}
      
      - name: Sign RPM packages
        run: |
          echo "${{ secrets.GPG_PRIVATE_KEY }}" | gpg --import
          rpmsign --addsign dist/*.rpm
      
      - name: Generate SBOM and sign
        run: |
          syft ghcr.io/${{ github.repository }}:${{ github.ref_name }} -o spdx-json > sbom.spdx.json
          cosign attest --yes --predicate sbom.spdx.json \
            --type spdxjson \
            ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}

GitLab CI

sign-artifacts:
  stage: sign
  image: alpine:latest
  variables:
    COSIGN_YES: "true"
  id_tokens:
    SIGSTORE_ID_TOKEN:
      aud: sigstore
  before_script:
    - apk add --no-cache cosign gpg rpm-sign
  script:
    # Sign container image
    - cosign sign
        --identity-token=$SIGSTORE_ID_TOKEN
        ${CI_REGISTRY_IMAGE}:${CI_COMMIT_TAG}
    
    # Sign RPM packages
    - echo "${GPG_PRIVATE_KEY}" | gpg --import
    - rpmsign --addsign dist/*.rpm
    
    # Upload signed artifacts
    - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN"
        --upload-file dist/*.rpm
        "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/release/${CI_COMMIT_TAG}/"'
  rules:
    - if: $CI_COMMIT_TAG

Verification Workflows

Automated Verification in Deployment

#!/bin/bash
# verify-artifacts.sh - Run before deployment

set -euo pipefail

REGISTRY="registry.acme.com"
IMAGE="${REGISTRY}/app:${VERSION}"
SIGNER="ci@acme.com"
ISSUER="https://accounts.google.com"

echo "Verifying container image signature..."
cosign verify \
  --certificate-identity="${SIGNER}" \
  --certificate-oidc-issuer="${ISSUER}" \
  "${IMAGE}" || { echo "FAILED: Image signature invalid"; exit 1; }

echo "Verifying SBOM attestation..."
cosign verify-attestation \
  --certificate-identity="${SIGNER}" \
  --certificate-oidc-issuer="${ISSUER}" \
  --type spdxjson \
  "${IMAGE}" || { echo "FAILED: SBOM attestation invalid"; exit 1; }

echo "All verifications passed."

RPM Verification on Install

# Ensure gpgcheck is enforced system-wide
sed -i 's/gpgcheck=0/gpgcheck=1/' /etc/yum.conf

# Verify a package before manual install
rpm -K package.rpm
# package.rpm: digests signatures OK

# Check which key signed a package
rpm -qi package.rpm | grep "Signature"

Key Management for Signing Keys

Key Hierarchy

┌─────────────────────────────────────────┐
│         Root Signing Key                 │
│  (Offline, HSM, used to sign sub-keys)  │
└─────────────────┬───────────────────────┘

    ┌─────────────┼─────────────┐
    ▼             ▼             ▼
┌────────┐  ┌────────┐  ┌────────────┐
│ Release │  │  CI/CD │  │ Repository │
│ Signing │  │ Signing│  │  Signing   │
│  Key    │  │  Key   │  │   Key      │
└────────┘  └────────┘  └────────────┘

Key Rotation Strategy

# Generate new signing key
gpg --full-generate-key

# Cross-sign with old key (establishes trust chain)
gpg --default-key OLD_KEY_ID --sign-key NEW_KEY_ID

# Update repository metadata with new key
rpm --import new-signing-key.asc

# Transition period: sign with both keys
rpmsign --addsign --key-id=NEW_KEY_ID *.rpm

# After transition: revoke old key
gpg --gen-revoke OLD_KEY_ID > revocation.asc
gpg --import revocation.asc

Protecting Signing Keys

Protection LevelMethodUse Case
BasicPassphrase-protected GPG keyIndividual developer
StandardKey in CI/CD secrets storeAutomated pipelines
HighHardware token (YubiKey)Release signing
EnterpriseHSM (PKCS#11) or Cloud KMSOrganization-wide

For enterprise environments, platforms like QCecuring provide centralized signing key management with HSM-backed key storage, policy enforcement, and complete audit trails — ensuring signing keys are never exposed to individual systems or developers.

Key Takeaways

  • Linux code signing is fragmented — each artifact type (RPM, DEB, container, kernel module) has its own signing mechanism. Plan for all of them.
  • Sigstore/cosign is the future for container images — keyless signing eliminates key management complexity while providing stronger identity binding through OIDC.
  • GPG remains essential for traditional package signing (RPM, DEB, APT repositories). Invest in proper key management and rotation procedures.
  • Kernel module signing is mandatory for Secure Boot environments — use MOK (Machine Owner Key) enrollment for custom modules.
  • CI/CD integration is critical — signing should happen automatically in your pipeline, not manually by developers. Use ephemeral keys or centralized signing services.
  • Verification must be enforced — signing is only valuable if consumers verify signatures. Configure package managers to require signatures and use Kubernetes admission controllers for containers.
  • Protect your signing keys — a compromised signing key undermines all trust. Use hardware tokens, HSMs, or cloud KMS for key storage. Never store signing keys in source control.
  • Plan for key rotation — keys expire and algorithms weaken. Design your signing infrastructure to support key rotation without breaking verification of previously signed artifacts.

Code Signing Maturity Assessment

Evaluate your software supply chain signing practices and identify gaps in your artifact verification workflow.

Get Assessment

Related Insights

Cryptography Fundamentals

Hash Functions Explained: SHA-256, SHA-3, MD5, BLAKE3 and Beyond

Complete guide to cryptographic hash functions covering SHA-256, SHA-3, MD5, BLAKE3, HMAC, and password hashing. Learn properties, security analysis, and how to choose the right hash function.

By Shivam sharma

26 May, 2026 · 07 Mins read

Cryptography FundamentalsDeveloper Security

PKI & Certificate Management

HashiCorp Vault PKI Engine: Complete Setup and Production Guide

Master HashiCorp Vault's PKI secrets engine for automated certificate management. Covers CA setup, short-lived certificates, cert-manager integration, and production deployment.

By Shivam sharma

26 May, 2026 · 06 Mins read

PKI & Certificate ManagementDevOps & AutomationTools & Platforms

Cryptography Fundamentals

AES-256 Encryption Explained: How It Works, Modes, and Implementation

Deep dive into AES-256 encryption covering the algorithm internals, modes of operation (GCM, CBC, CTR), implementation in OpenSSL, Python, Java, and Go, plus key management best practices.

By Shivam sharma

25 May, 2026 · 08 Mins read

Cryptography FundamentalsDeveloper SecurityEncryption

Ready to Secure Your Enterprise?

Experience how our cryptographic solutions simplify, centralize, and automate identity management for your entire organization.

Stay ahead on cryptography & PKI

Get monthly insights on certificate management, post-quantum readiness, and enterprise security. No spam.

We respect your privacy. Unsubscribe anytime.