Commit a02a2c95 authored by Jason Plum's avatar Jason Plum
Browse files

Add Debian (.deb) package signing (implementing debsig)



Add Debian package signing, via methodology describe in `debsigs` documentation

Addition of a `sign_deb_file` function to `Packager::DEB`, after `create_deb_file`. The essential concepts of what is required to sign a `.deb` with with a `type: origin` signature is delineated per the link to `debsigs` above. There is no current functionailty built into `dpkg` scripting akin to `rpm --addsign`. Since the `.deb` file format is simple, we extract the contents of the archvice (`ar x`), sign the concatenated (specifically ordered) contents, and then append the created signature to the archive (`ar rc debfile _gpgorigin`).

These steps could have been accomplished in pure Ruby with the addition of several modules (GPGME, libarchive) except for two concerns: age & maintenance, `fakeroot` requirements.

Tests have been added to attempt to cover the behavior correctly.

- `gpg` : This is already an existing requirement of `Packager::RPM` due to the use of `rpmsign`
- `ar` : Most systems that attempt to build Debian packages will have the `ar` command, and it has been confirmed that MacOS also has this utility.
- `fakeroot` : This is inline with `ar`, however it should be noted that not all systems had this program, and as such it has been added to the Omnibus cookbook.

Adding `ar` and `fakeroot` to the required tools compiled by Omnibus for ensuring this presence was done by @kwilczynski in #217

There is *no need* to add `debsigs`/`debsig-verify` as a requirement, as we are implmenting the login in Ruby and `Shellout`.

Care was taken to ensure compatibility with distribution provided binary versions of `gpg2` or `gpg` for LTS versions of distributions supported by GitLab. This list can be seen at . The code is written to prefer `gpg2` if present.

GitLab experienced issues in regards to `gpg --import`, and eventually settled on `gpg --batch --no-tty --allow-secret-key-import --import` as a part of our CI job. So long as the key is present for the GPG calls (via `--homedir #{ENV['HOME']}/.gnupg`) this should not be an issue for any non-automated use. Note that this behavior is the same with RPM signing behaviors. As the GPG key import process is not a part of Omnibus itself, this should be of little concern to this code changes in this MR.

Relates to
Relates to

Submitted with the approval of GitLab, and with great thanks for the project!
Signed-off-by: Jason Plum's avatarJason Plum <>
parent 52393d7c
......@@ -39,6 +39,7 @@ package :deb do
license 'Apache 2.0'
priority 'extra'
section 'databases'
signing_passphrase 'acbd1234'
......@@ -46,6 +47,7 @@ Some DSL methods available include:
| DSL Method | Description |
| :------------------: | --------------------------------------------|
| `signing_passphrase` | The passphrase to sign the RPM with |
| `vendor` | The name of the package producer |
| `license` | The default license for the package |
| `priority` | The priority for the package |
......@@ -56,4 +58,4 @@ If you are unfamiliar with any of these terms, you should just accept the defaul
For more information, please see the [`Packager::DEB` documentation](
### Notes on DEB-signing
At this time, signing Debian packages is not supported.
To sign an DEB, you will need a GPG keypair. You can [create your own signing key]( or [import an existing one]( Omnibus will automatically call `gpg` with arguments that assume the real name associated to the GPG key is the same as the name of the project maintainer as specified in your Omnibus config.
......@@ -60,12 +60,37 @@ module Omnibus
# Create the deb
# Sign the deb
# @!group DSL methods
# --------------------------------------------------
# Set or return the signing passphrase. If this value is provided,
# Omnibus will attempt to sign the DEB.
# @example
# signing_passphrase "foo"
# @param [String] val
# the passphrase to use when signing the DEB
# @return [String]
# the DEB-signing passphrase
def signing_passphrase(val = NULL)
if null?(val)
@signing_passphrase = val
expose :signing_passphrase
# Set or return the vendor who made this package.
......@@ -389,6 +414,62 @@ module Omnibus
# Sign the +.deb+ file with gpg. This has to be done as separate steps
# from creating the +.deb+ file. See +debsigs+ source for behavior
# replicated here. +
# @return [void]
def sign_deb_file
if !signing_passphrase { "Signing not enabled for .deb file" }
end { "Signing enabled for .deb file" }
# Check our dependencies and determine command for GnuPG. +Omnibus.which+ returns the path, or nil.
gpg = nil
if Omnibus.which("gpg2")
gpg = "gpg2"
elsif Omnibus.which("gpg")
gpg = "gpg"
if gpg && Omnibus.which("ar")
# Create a directory that will be cleaned when we leave the block
Dir.mktmpdir do |tmp_dir|
Dir.chdir(tmp_dir) do
# Extract the deb file contents
shellout!("ar x #{Config.package_dir}/#{package_name}")
# Concatenate contents, in order per +debsigs+ documentation.
shellout!("cat debian-binary control.tar.* data.tar.* > complete")
# Create signature (as +root+)
gpg_command = "#{gpg} --armor --sign --detach-sign"
gpg_command << " --local-user '#{project.maintainer}'"
gpg_command << " --homedir #{ENV['HOME']}/.gnupg" # TODO: Make this configurable
## pass the +signing_passphrase+ via +STDIN+
gpg_command << " --batch --no-tty"
## Check `gpg` for the compatibility/need of pinentry-mode
# - We're calling gpg with the +--pinentry-mode+ argument, and +STDIN+ of +/dev/null+
# - This _will_ fail with exit code 2 no matter what. We want to check the +STDERR+
# for the error message about the parameter. If it is _not present_ in the
# output, then we _do_ want to add it. (If +grep -q+ is +1+, add parameter)
if shellout("#{gpg} --pinentry-mode loopback </dev/null 2>&1 | grep -q pinentry-mode").exitstatus == 1
gpg_command << " --pinentry-mode loopback"
gpg_command << " --passphrase-fd 0"
gpg_command << " -o _gpgorigin complete"
shellout!("fakeroot #{gpg_command}", input: signing_passphrase)
# Append +_gpgorigin+ to the +.deb+ file (as +root+)
shellout!("fakeroot ar rc #{Config.package_dir}/#{package_name} _gpgorigin")
else { "Signing not possible. Ensure that GnuPG and GNU AR are available" }
# The size of this Debian package. This is dynamically calculated.
......@@ -337,6 +337,46 @@ module Omnibus
describe "#sign_deb_file", :not_supported_on_windows do
context "when DEB signing is not enabled" do
before do
it "logs a message" do
output = capture_logging { subject.sign_deb_file }
expect(output).to include("Signing not enabled for .deb file")
context "when DEB signing is enabled" do
before do
allow(subject).to receive(:shellout!)
allow(subject).to receive(:package_name).and_return("safe")
it "logs a message" do
output = capture_logging { subject.sign_deb_file }
expect(output).to include("Signing enabled for .deb file")
it "finds gpg and ar commands" do
output = capture_logging { subject.sign_deb_file }
expect(output).not_to include("Signing not possible.")
it "runs correct commands" do
expect(subject).to receive(:shellout!)
.at_least(:once).with(/ar x/)
.at_least(:once).with(/cat debian-binary control\.tar/)
.at_least(:once).with(/fakeroot gpg/)
.at_least(:once).with(/fakeroot ar rc/)
describe "#package_size" do
before do
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment