Signing service
Some packages need to be signed for the benefit of UEFI Secure Boot and other similar firmware arrangements that ensure the authenticity of code executed by the firmware. See SecureBoot/Discussion for an outline of the current implementation in Debian.
Requirements
We must support Debian’s system of template packages that specify what is to be signed and provide a structure for uploading the results back to the archive. (It is not necessary for the package manipulation itself to be done on the same system that has access to the necessary private keys.)
It must be possible to restrict the use of signing keys to particular archives, suites, and/or source packages.
For parity with Debian’s existing system, the signing service must support at least UEFI and kmod signatures. It should be straightforward to extend it with additional signing methods: in particular, GPG.
It must be possible to sign using a YubiKey. It may be acceptable to support private key material stored on the signing system (this is likely to be convenient for development, and may be reasonable for lower-security keys), though if so they must at least be encrypted at rest.
All requests to the signing service must be encrypted and authenticated.
The signing service must retain an audit log of all signatures, and must provide a way for debusine to retrieve and present information from that log.
Prior art
code-signing is Debian’s current implementation. Most of it is in a single
secure-boot-code-sign.py
script, with a small attached database. It operates on a polling model: every so often it polls a configured URL for each archive that contains a GPG-signed list of requests for it. It downloads and unpacks template binary packages from an archive, downloads and unpacks binary packages containing objects to sign, constructs a new source package, and submits it back to the archive usingdput
. It has policy configuration for which packages should be signed using which keys.lp-signing is used in Ubuntu. It is structured as a web service with an attached database, taking NaCl-boxed HTTP requests to generate keys, sign data, or inject keys. It does not interact with source or binary packages directly; that is left to the client of that service. The policy for which packages should be signed using which keys is also largely left to the client, although only authorized clients may use any given key. (Ubuntu uses a different and incompatible mechanism for indicating that packages should be signed.)
code-signing
’s polling interface would be cumbersome to use in the
context of a debusine task, and lp-signing
’s interface is largely
friendlier here, although it does make the mistake of using synchronous HTTP
requests; signing can be quite slow and so timeouts are difficult to manage.
code-signing
unpacks template binary packages and binary packages
containing to-be-signed objects using dpkg -x
, and builds source
packages containing signed output using dpkg-source -b
. By contrast,
the design of lp-signing
is careful to avoid needing to manipulate
packages itself, which seems important for a security-critical service:
while dpkg-source -x
is probably the weakest point in dpkg
’s
security, having had at least 11 CVEs all by
itself from 2010 to 2022, even dpkg -x
has had problems in the past
(e.g. CVE-2015-0860) and running
dpkg-source -b
on an untrusted directory tree also seems unwise.
lp-signing
uses JSON as its message serialization format. This is
simple, but inefficient since the messages often contain large binary blobs
to be signed (e.g. kernel images), which thus have to be base64-encoded. A
binary serialization format would be better here.
Signing workflows
Handling Debian’s template package system requires three basic steps: extracting input binary packages, signing files, and building the output source package. To avoid risk from vulnerabilities such as those listed above, unpacking binary packages and building source packages must always be run on an external worker within some kind of container. The simplest approach would be to have a task for each step and link them together using a workflow, although an optimization is possible here: the Sbuild task can do the initial extraction step itself, saving a considerable amount of overhead.
We define three new tasks for this (GenerateKey, Sign, and AssembleSignedSource) and three new artifact categories to mediate these tasks (Category debusine:signing-key, Category debusine:signing-input, and Category debusine:signing-output).
Signing key management
We define a debian:suite-signing-keys collection category, allowing signing keys to be associated with suites and optionally with specific source package names in that suite.
debian:suite
collections gain a reference to a
debian:suite-signing-keys
collection suitable for that suite.
There will be API and UI actions to generate new signing keys for a package in a suite. It is not possible to set the signing key for a package to a particular known fingerprint via the API or the UI; doing that requires administrative access to the database.
(We might consider having additional controls on the signing service for the use of particular high-value keys. However, in general, debusine can build things for any key controlled by its signing service; the protections that the signing service provides are (a) the inability of debusine to extract plaintext of private keys, and (b) an audit log that a compromised debusine server cannot tamper with.)
Backend overview
There is a debusine.signing
application, which is a separate Django
application under the debusine
project with its database models routed
to a separate database, such that it can run on a separate system from the
debusine server and workers. It uses Django primarily for its database
facilities: there will initially be no need for it to have its own server
component, although it may eventually be useful to add one for things like
reading audit logs.
This application has a worker, reusing most of the existing
debusine.worker
code. It sends metadata to the server indicating that
it has a new worker type (Signing
), and it
requires HTTPS connectivity to the server. debusine-admin list_workers
gains an extra field for the worker type, and debusine-admin
manage_worker
requires a special option to manage signing workers, to
avoid enabling them by accident.
There is a new Signing
task type; tasks of this type are scheduled
similarly to worker tasks, and are required to use the public API to
interact with artifacts in the same way that worker tasks do, but they only
execute on signing workers. Signing workers do not take tasks of any other
type.
Todo
Specify how to configure keys to be used with a YubiKey.
Each successful generate
and sign
operation adds a row to an
append-only audit log table.
Database models
Each key has a row with the following fields:
purpose
: the purpose of this key (e.g.uefi
for UEFI Secure Boot) different key purposes typically require different tools to generate them or sign data using themfingerprint
: the key fingerprint; keys are unique by purpose and fingerprintprivate_key
: a protected representation of the private keypublic_key
: the public key, as binary datacreated_at
,updated_at
: timestamps for creation and update
Key protection
Private key material is never stored in the clear. If it is stored directly in the database, it is encrypted at rest using a NaCl SealedBox, with a configured key. The encrypted form includes both the public key and the ciphertext to allow for key rotation.
Alternatively, private keys may be stored as a handle referring to an attached hardware security module (HSM). The details required are implementation-dependent; for example, they may include a PKCS#11 URI.
These details are stored as a JSON object using a discriminated union to allow for easy extension.
HSM key availability
If keys are stored in a hardware security module such as a YubiKey, then
they may not be available to all signing workers. A worker can add the list
of such keys it supports to its dynamic metadata, and then the
can_run_on
method of the relevant tasks can check that metadata to avoid
dispatching requests to workers that do not have access to the relevant
keys.
Todo
Add more precise details of how this is recorded.