.. _signing-service:

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
<https://wiki.debian.org/SecureBoot/Discussion>`_ for an outline of the
current implementation in Debian.

Requirements
------------

We must support Debian's system of `template packages
<https://wiki.debian.org/SecureBoot/Discussion#Template_organization>`_ 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 <https://salsa.debian.org/ftp-team/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 using ``dput``.  It has
  policy configuration for which packages should be signed using which keys.

* `lp-signing <https://git.launchpad.net/lp-signing/tree/>`_ 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
<https://security-tracker.debian.org/tracker/source-package/dpkg>`_ all by
itself from 2010 to 2022, even ``dpkg -x`` has had problems in the past
(e.g. `CVE-2015-0860
<https://security-tracker.debian.org/tracker/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 :ref:`workflow <explanation-workflows>`, although an optimization is
possible here: the :ref:`Sbuild task <task-sbuild>` can do the initial
extraction step itself, saving a considerable amount of overhead.

We define three new tasks for this (:ref:`GenerateKey <task-generate-key>`,
:ref:`Sign <task-sign>`, and :ref:`AssembleSignedSource
<task-assemble-signed-source>`) and three new artifact categories to mediate
these tasks (:ref:`artifact-signing-key`, :ref:`artifact-signing-input`, and
:ref:`artifact-signing-output`).

Signing key management
----------------------

debusine will need a new ``debian:suite-signing-keys`` collection category,
whose per-item data would include a mapping from package names to signing
key fingerprint and an optional default signing key fingerprint.

``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.)

.. _signing-app:

Backend overview
----------------

We will write a new ``debusine.signing`` application.  This will be 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 will use 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 will have a worker, reusing most of the existing
``debusine.worker`` code.  It will send metadata to the server indicating
that it has a new :ref:`worker type <explanation-workers>` (``Signing``),
and it will require HTTPS connectivity to the server.  ``debusine-admin
list_workers`` will gain an extra field for the worker type, and
``debusine-admin manage_worker`` will require a special option to manage
signing workers, to avoid enabling them by accident.

There will be 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 them
* ``fingerprint``: the key fingerprint; keys are unique by purpose and
  fingerprint
* ``private_key``: a :ref:`protected representation <key-protection>` of the
  private key
* ``public_key``: the public key, as binary data
* ``created_at``, ``updated_at``: timestamps for creation and update

.. _key-protection:

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
<https://pynacl.readthedocs.io/en/latest/public/#nacl-public-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.
