Yocto Project Documentation
 help / color / mirror / Atom feed
From: "Antonin Godard" <antonin.godard@bootlin.com>
To: <quentin.schulz@cherry.de>, <docs@lists.yoctoproject.org>
Cc: "Thomas Petazzoni" <thomas.petazzoni@bootlin.com>
Subject: Re: [docs] [PATCH v3] Document shared state signing
Date: Tue, 26 May 2026 10:16:48 +0200	[thread overview]
Message-ID: <DISGKANP7F02.XXW0GQUSJK9G@bootlin.com> (raw)
In-Reply-To: <e5ac74f3-0b61-4e30-bbf4-0b9aeb725e94@cherry.de>

Hi,

On Thu May 7, 2026 at 11:49 AM CEST, Quentin Schulz via lists.yoctoproject.org wrote:
> Hi Antonin,
>
> On 4/28/26 10:28 AM, Antonin Godard via lists.yoctoproject.org wrote:
>> Document the shared state signing feature. Add a new document in the
>> Security manual, and definitions for the variables involved in this
>> process in the variable glossary.
>> 
>> [YOCTO #15217]
>> 
>> Signed-off-by: Antonin Godard <antonin.godard@bootlin.com>
>> ---
>> Changes in v3:
>> - Rework after Quentin's suggestions (thanks!)
>> - Link to v2: https://patch.msgid.link/20260421-sstate-signing-v2-1-7b572121f2fd@bootlin.com
>> 
>> Changes in v2:
>> - Fix typos reported by Ulrich (thanks!)
>> - Link to v1: https://patch.msgid.link/20260417-sstate-signing-v1-1-5df11613249e@bootlin.com
>> ---
>>   documentation/overview-manual/concepts.rst       |   6 +
>>   documentation/ref-manual/variables.rst           |  57 +++++
>>   documentation/security-manual/index.rst          |   1 +
>>   documentation/security-manual/sstate-signing.rst | 285 +++++++++++++++++++++++
>>   4 files changed, 349 insertions(+)
>> 
>> diff --git a/documentation/overview-manual/concepts.rst b/documentation/overview-manual/concepts.rst
>> index 1faa790f3..3f3093bfc 100644
>> --- a/documentation/overview-manual/concepts.rst
>> +++ b/documentation/overview-manual/concepts.rst
>> @@ -1239,6 +1239,12 @@ variable is the function that determines whether a given dependency
>>   needs to be followed, and whether for any given relationship the
>>   function needs to be passed. The function returns a True or False value.
>>   
>> +.. note::
>> +
>> +   Is is possible to sign these artifacts with :wikipedia:`GPG
>
> typo: s/Is/It/
>
>> +   <GNU_Privacy_Guard>`. See :doc:`/security-manual/sstate-signing` in the Yocto
>> +   Project Security Manual for more information.
>> +
>>   Images
>>   ------
>>   
>> diff --git a/documentation/ref-manual/variables.rst b/documentation/ref-manual/variables.rst
>> index e713204e3..3b3aeed4c 100644
>> --- a/documentation/ref-manual/variables.rst
>> +++ b/documentation/ref-manual/variables.rst
>> @@ -10113,6 +10113,25 @@ system and gives an overview of their function and contents.
>>   
>>         For details on the process, see the :ref:`ref-classes-staging` class.
>>   
>> +   :term:`SSTATE_SIG_KEY`
>> +      When signing :ref:`shared state <overview-manual/concepts:setscene tasks
>> +      and shared state>` artifacts (when :term:`SSTATE_VERIFY_SIG` is set to
>> +      "1"), the :term:`SSTATE_SIG_KEY` variable is the :wikipedia:`GPG
>> +      <GNU_Privacy_Guard>` key identifier used to sign them (with the private
>> +      key).
>> +
>
> thought: I'm not sure we need to specify that the private key is used 
> for signing. That's what signing in asymmetric cryptography is?
>
>> +      See :doc:`/security-manual/sstate-signing` in the Yocto Project Security
>> +      Manual for more information.
>> +
>> +   :term:`SSTATE_SIG_PASSPHRASE`
>> +      When signing :ref:`shared state <overview-manual/concepts:setscene tasks
>> +      and shared state>` artifacts (when :term:`SSTATE_VERIFY_SIG` is set to
>> +      "1"), the :term:`SSTATE_SIG_PASSPHRASE` variable is the passphrase used to
>> +      protect the private key signing the artifacts.
>> +
>> +      See :doc:`/security-manual/sstate-signing` in the Yocto Project Security
>> +      Manual for more information.
>> +
> thought: implement a file-based passphrase?
>
> The detach_sign() function actually supports providing the passphrase 
> either as a string or from a file. The file-based implem seems to be 
> actually used for signing (ipk, rpm, deb) packages, see 
> IPK_GPG_PASSPHRASE_FILE and PACKAGE_FEED_GPG_PASSPHRASE_FILE.
>
> How much better that is in terms of security, unclear, as the file still 
> needs to be readable but I guess at least the passphrase cannot leak 
> when looking at the datastore (e.g. with bitbake -e).

This probably isn't too hard to implement if we have the support for package
signing already, yes.

>>      :term:`SSTATE_SKIP_CREATION`
>>         The :term:`SSTATE_SKIP_CREATION` variable can be used to skip the
>>         creation of :ref:`shared state <overview-manual/concepts:shared state cache>`
>> @@ -10133,6 +10152,44 @@ system and gives an overview of their function and contents.
>>   
>>            SSTATE_SKIP_CREATION = "1"
>>   
>> +   :term:`SSTATE_VALID_SIGS`
>> +      When verifying :ref:`shared state <overview-manual/concepts:setscene tasks
>> +      and shared state>` artifacts (when :term:`SSTATE_VERIFY_SIG` is set to
>> +      "1"), the :term:`SSTATE_VALID_SIGS` variable is a space-separated list of
>> +      :wikipedia:`GPG <GNU_Privacy_Guard>` key identifiers to use to verify their
>> +      signature.
>> +
>> +      It must contain the short form identifier of the key pair. For example,
>> +      when running the ``gpg --list-keys`` command (in bold text below):
>> +
>> +      .. parsed-literal::
>> +
>> +         pub   ed25519 2026-04-17 [SC]
>> +               \4049A47E3AAA99D0250966DC\ **5B97632FA7F4E942**
>> +         uid           [ultimate] Antonin Godard (SState Signing) <antonin.godard\@bootlin.com>
>> +         sub   cv25519 2026-04-17 [E]
>> +
>> +      The short form equals the last 16 characters of the identifier. In the
>> +      above example: ``5B97632FA7F4E942``.
>> +
>> +      .. note::
>> +
>> +         Leaving this variable empty will make the :term:`OpenEmbedded Build
>> +         System` let any key installed on the :term:`Build Host` be used for
>> +         verify the shared state artifacts, as long as its private key
>
> typo: s/verify/verifying/
>
> suggestion (non-blocking): reword
>
> A bit heavy to read, what about:
>
> """
> If this variable is empty (the default), any of the available GPG key 
> can be used by the :term:`OpenEmbedded Build System` to verify the 
> shared state artifacts.
> """
>
> In any case, I would strip the "as long as" part of the sentence, as 
> that's how asymmetric cryptography works, you cannot validate a 
> signature with a public key that isn't the other in the priavte-public 
> key pair, that's the whole point of verifying a signature.
>
>> +         counterpart was used for signing them.
>> +
>> +      See :doc:`/security-manual/sstate-signing` in the Yocto Project Security
>> +      Manual for more information.
>> +
>> +   :term:`SSTATE_VERIFY_SIG`
>> +      The :term:`SSTATE_VERIFY_SIG` variable controls whether to enable or
>> +      disable the :ref:`shared state <overview-manual/concepts:setscene tasks
>> +      and shared state>` artifacts signing feature.
>> +
>> +      See :doc:`/security-manual/sstate-signing` in the Yocto Project Security
>> +      Manual for more information.
>> +
>>      :term:`STAGING_BASE_LIBDIR_NATIVE`
>>         Specifies the path to the ``/lib`` subdirectory of the sysroot
>>         directory for the build host.
>> diff --git a/documentation/security-manual/index.rst b/documentation/security-manual/index.rst
>> index 3453940f5..a767cd9c6 100644
>> --- a/documentation/security-manual/index.rst
>> +++ b/documentation/security-manual/index.rst
>> @@ -14,6 +14,7 @@ Yocto Project Security Manual
>>      securing-images
>>      vulnerabilities
>>      read-only-rootfs
>> +   sstate-signing
>>   
>>   .. include:: /boilerplate.rst
>>   
>> diff --git a/documentation/security-manual/sstate-signing.rst b/documentation/security-manual/sstate-signing.rst
>> new file mode 100644
>> index 000000000..21cdff9fc
>> --- /dev/null
>> +++ b/documentation/security-manual/sstate-signing.rst
>> @@ -0,0 +1,285 @@
>> +.. SPDX-License-Identifier: CC-BY-SA-2.0-UK
>> +
>> +Shared State Signing
>> +********************
>> +
>> +The :term:`OpenEmbedded Build System` build system has a built-in mechanism
>> +allowing to save execution time by re-using pre-built artifacts: the
>> +:ref:`shared state cache (sstate cache) <overview-manual/concepts:shared state
>> +cache>`. These artifacts are stored in a directory (:term:`SSTATE_DIR`) and are
>> +not signed by default.
>> +
>> +This document goes through the steps to enable shared state signing.
>> +This feature is fully dependent on :wikipedia:`GPG <GNU_Privacy_Guard>`, meaning
>> +examples shown in this document will use the ``gpg`` command-line tool.
>> +
>> +Host Requirements
>> +=================
>> +
>> +As :wikipedia:`GPG <GNU_Privacy_Guard>` is not part of the default :ref:`host
>> +requirements <ref-manual/system-requirements:Required Packages for the Build
>> +Host>`, you will need to install it on your host.
>> +
>> +For example, Debian based distributions provide it with the ``gpg`` package name.
>
> typo: s/Debian based/Debian-based/
>
> typo: s/package name/package/
>
> suggestion (non-blocking): s/with/in/
>
>> +Install it as follows:
>> +
>> +.. code-block:: console
>> +
>> +   $ sudo apt-get install gpg
>> +
>> +Verify that your installation is successful by showing the version of GPG:
>> +
>> +.. code-block:: console
>> +
>> +   $ gpg --version
>> +
>> +Generating A Public And Private Key Pair With GPG
>> +=================================================
>> +
>> +.. note::
>> +
>> +   This step is optional if you already have a pair of public and private keys.
>> +
>> +You need a pair of public and private keys for two independent tasks:
>> +
>> +-  Signing the shared state artifacts that the :term:`OpenEmbedded Build System`
>> +   generates with your **private key**.
>> +
>> +-  Verifying the shared state artifacts with your **public key** when re-using them.
>> +
>> +.. note::
>> +
>> +   For more information on public key cryptography, see
>> +   :wikipedia:`Public-key_cryptography`.
>> +
>> +With the ``gpg`` command-line tool, generate a new pair of public and private
>> +keys:
>> +
>> +.. code-block:: console
>> +
>> +   $ gpg --full-generate-key
>> +
>> +It will guide you through the steps of creating the key pair.
>> +
>> +Once done, you should be able to list your new key with the following command:
>> +
>> +.. code-block:: console
>> +
>> +   $ gpg --list-keys
>> +   pub   ed25519 2026-04-17 [SC]
>> +         4049A47E3AAA99D0250966DC5B97632FA7F4E942
>> +   uid           [ultimate] Antonin Godard (SState Signing) <antonin.godard@bootlin.com>
>> +   sub   cv25519 2026-04-17 [E]
>> +
>> +In the above example, take note of the
>> +``4049A47E3AAA99D0250966DC5B97632FA7F4E942`` key identifier. This will be used
>> +to configure the build.
>> +
>
> issue: can you please synchronize this example with the one in 
> SSTATE_VALID_SIGS?
>
>> +Configuring The Build System To Sign Shared State Artifacts
>> +===========================================================
>> +
>> +Shared State Location
>> +---------------------
>> +
>> +The build system needs to be configured to sign new shared state artifacts when
>> +they are generated. The generation of new artifacts is done once a task has
>> +finished being executed.
>> +
>> +For the following sections let's assume that the build system has the shared
>> +state directory location (:term:`SSTATE_DIR`) defined as follows::
>> +
>> +   SSTATE_DIR = "${TOPDIR}/sstate-cache"
>> +
>> +Assuming this directory and the temporary directory (:term:`TMPDIR`) are empty,
>> +let's run the ``create_recipe_spdx`` task of the ``gettext-minimal-native``
>> +recipe:
>> +
>> +.. code-block:: console
>> +
>> +   $ bitbake gettext-minimal-native -c create_recipe_spdx
>> +
>> +Let's take this command as an example throughout this document.
>> +
>> +After execution, the shared state directory should be populated with new files:
>> +
>> +.. code-block:: console
>> +
>> +   $ find $BUILDDIR/sstate-cache/ -name "*gettext-minimal-native*create_recipe_spdx*"
>> +   sstate-cache/universal/a3/6e/sstate:gettext-minimal-native:x86_64-linux:1.0:r0:x86_64:14:a36ef66df6b8c0cb5a849bc70a99dcfd61e4bacd11cefe6bbaf4978b2b3b617a_create_recipe_spdx.tar.zst
>> +   sstate-cache/universal/a3/6e/sstate:gettext-minimal-native:x86_64-linux:1.0:r0:x86_64:14:a36ef66df6b8c0cb5a849bc70a99dcfd61e4bacd11cefe6bbaf4978b2b3b617a_create_recipe_spdx.tar.zst.siginfo
>> +
>> +These are the default shared state artifacts generated by the
>> +:term:`OpenEmbedded Build System`. They are not signed with GPG by default, so
>> +let's see how to add signing of these artifacts.
>> +
>> +.. note::
>> +
>> +   Despite its name, the `siginfo` file is unrelated to GPG signing.
>> +
>> +Enabling Shared State Signing
>> +-----------------------------
>> +
>> +Create a new :term:`configuration file` on your host **in a safe location** and
>> +add the two following statements::
>> +
>> +   SSTATE_VERIFY_SIG = "1"
>> +   SSTATE_SIG_KEY = "4049A47E3AAA99D0250966DC5B97632FA7F4E942"
>> +   SSTATE_SIG_PASSPHRASE = "<your GPG key passphrase>"
>> +
>> +It is advised to put these statements in a separate file as those contain
>> +secrets and should not be shared. For this example, let's assume this file is
>> +``conf/sstate-sig-key.conf``.
>> +
>> +You can make sure this file is only owned by you and not readable by another
>> +user with:
>> +
>> +.. code-block:: console
>> +
>> +   $ chown $USER:$USER conf/sstate-sig-key.conf
>> +   $ chmod o-rwx conf/sstate-sig-key.conf
>> +
>> +The statements in this file define:
>> +
>> +-  :term:`SSTATE_VERIFY_SIG`: setting this variable to "1" enables the shared
>> +   state signing feature.
>> +
>> +-  :term:`SSTATE_SIG_KEY`: the GPG key identifier for signing shared state
>> +   artifacts with the private key.
>> +
>> +   In the example, this corresponds to the identifier printed with the ``gpg
>> +   --list-keys`` command :ref:`above <security-manual/sstate-signing:Generating
>> +   A Public And Private Key Pair With GPG>`.
>> +
>> +-  :term:`SSTATE_SIG_PASSPHRASE`: the passphrase used to protect your private
>> +   key when creating the key, chosen when creating the key pair.
>> +
>
> issue: redundant "when creating the key"
>
> I can suggest removing the first "when creating the key", the end of the 
> sentence is better worded.
>
>> +Let's test the configuration:
>> +
>> +#. Continuing with the ``gettext-minimal-native`` example, let's first clean the
>> +   existing shared state artifacts, to make sure my shared state artifacts for
>> +   my ``do_create_recipe_spdx`` task are re-generated:
>> +
>
> issue: confusing switch to first-person
>
> We've been using the second person (you, let's, ...) until now.
>
>> +   .. code-block:: console
>> +
>> +      $ bitbake gettext-minimal-native -c cleansstate
>> +
>> +#. Run the ``create_recipe_spdx`` task for ``gettext-minimal-native``, but this
>> +   time pass the new ``sstate-sig-key.conf`` file to :term:`BitBake`:
>> +
>> +   .. code-block:: console
>> +
>> +      $ bitbake -R conf/sstate-sig-key.conf gettext-minimal-native -c create_recipe_spdx
>> +
>
> TIL about -R :)
>
> Another option is to make conf/local.conf `require` it?

Sure, but somehow it felt wrong hardcoding this in local.conf since
conf/sstate-sig-key.conf contains a secret. Not a very important distinction though

>> +List the files in the shared state directory again:
>> +
>> +.. code-block:: console
>> +
>> +   $ find sstate-cache/ -name "*gettext-minimal-native*create_recipe_spdx*"
>> +   sstate-cache/universal/a3/6e/sstate:gettext-minimal-native:x86_64-linux:1.0:r0:x86_64:14:a36ef66df6b8c0cb5a849bc70a99dcfd61e4bacd11cefe6bbaf4978b2b3b617a_create_recipe_spdx.tar.zst
>> +   sstate-cache/universal/a3/6e/sstate:gettext-minimal-native:x86_64-linux:1.0:r0:x86_64:14:a36ef66df6b8c0cb5a849bc70a99dcfd61e4bacd11cefe6bbaf4978b2b3b617a_create_recipe_spdx.tar.zst.sig
>> +   sstate-cache/universal/a3/6e/sstate:gettext-minimal-native:x86_64-linux:1.0:r0:x86_64:14:a36ef66df6b8c0cb5a849bc70a99dcfd61e4bacd11cefe6bbaf4978b2b3b617a_create_recipe_spdx.tar.zst.siginfo
>> +
>> +A new ``.sig`` file was created: this means the artifact was successfully
>> +signed, and the signature is stored in a separate ``.sig`` file.
>> +
>> +Verifying Signed Shared State Artifacts
>> +=======================================
>> +
>> +Now that you have set up the build to sign shared state artifacts, let's see how
>> +you can verify them with the public key counterpart of the private key.
>> +
>> +.. note::
>> +
>> +   Signature of shared state and its verification can happen on two different
>> +   hosts, meaning one host can be in charge of the signature while another only
>> +   verifies the artifacts. This is preferred as the private key should not be
>> +   shared between multiple hosts.
>> +
>> +From a :term:`configuration file` such as the :ref:`site configuration file
>> +<structure-build-conf-site.conf>`, include the following statements::
>> +
>> +   SSTATE_VERIFY_SIG = "1"
>> +   SSTATE_VALID_SIGS = "5B97632FA7F4E942"
>> +
>> +These statements define:
>> +
>> +-  :term:`SSTATE_VERIFY_SIG`: setting this variable to "1" enables the shared
>> +   state signing feature.
>> +
>> +-  :term:`SSTATE_VALID_SIGS`: a space-separated list of key identifiers for
>> +   which shared state artifacts are accepted.
>> +
>> +   This means that shared state will be reused **only if it was signed with the
>> +   private key corresponding to key identifier**.
>> +
>> +   You'll notice the short-form of the key identifier here, which are the last 16
>> +   characters of the long-form key identifier shown with ``gpg --list-keys`` (in
>> +   bold text below):
>> +
>> +   .. parsed-literal::
>> +
>> +      pub   ed25519 2026-04-17 [SC]
>> +            \4049A47E3AAA99D0250966DC\ **5B97632FA7F4E942**
>> +      uid           [ultimate] Antonin Godard (SState Signing) <antonin.godard\@bootlin.com>
>> +      sub   cv25519 2026-04-17 [E]
>> +
>> +Let's verify that signature verification works:
>> +
>> +#. First, remove temporary outputs (:term:`TMPDIR`) from the previous builds, to
>> +   make the :term:`OpenEmbedded Build System` rebuild everything using the
>> +   shared state:
>> +
>> +   .. code-block:: console
>> +
>> +      $ rm -rf tmp/
>> +
>
> question: Will this actually work?
>
> In the signing example, we only triggered a rebuild of 
> gettext-minimal-native's create_recipe_spdx (and earlier) tasks. Every 
> task which isn't from that recipe wasn't rebuilt as we only cleaned the 
> sstate-cache from that recipe, therefore we would only use the shared 
> state cache for the gettext-minimal-native recipe.

I think I see what you mean in that other tasks were not cleaned and so their
shared state weren't signed. Ideally you'd clean the entire content of the
sstate-cache/ directory before starting to sign artifacts. I think it might be
the more sane approach indeed. I'll document to remove the shared-state cache
directory.

Thanks!
Antonin


      reply	other threads:[~2026-05-26  8:17 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-28  8:28 [PATCH v3] Document shared state signing Antonin Godard
2026-05-07  9:49 ` [docs] " Quentin Schulz
2026-05-26  8:16   ` Antonin Godard [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=DISGKANP7F02.XXW0GQUSJK9G@bootlin.com \
    --to=antonin.godard@bootlin.com \
    --cc=docs@lists.yoctoproject.org \
    --cc=quentin.schulz@cherry.de \
    --cc=thomas.petazzoni@bootlin.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox