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 v2] Document shared state signing
Date: Mon, 27 Apr 2026 16:43:38 +0200 [thread overview]
Message-ID: <DI40MO4BDL1R.306ON3EYMK35F@bootlin.com> (raw)
In-Reply-To: <a4723996-6d07-41b1-a40b-35e12e48807f@cherry.de>
Hi,
On Fri Apr 24, 2026 at 5:18 PM CEST, Quentin Schulz via lists.yoctoproject.org wrote:
> Hi Antonin,
>
> On 4/21/26 9:19 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 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 | 50 +++++
>> documentation/security-manual/index.rst | 1 +
>> documentation/security-manual/sstate-signing.rst | 272 +++++++++++++++++++++++
>> 4 files changed, 329 insertions(+)
>>
>> diff --git a/documentation/overview-manual/concepts.rst b/documentation/overview-manual/concepts.rst
>> index ab723d7c3..f0b336226 100644
>> --- a/documentation/overview-manual/concepts.rst
>> +++ b/documentation/overview-manual/concepts.rst
>> @@ -1240,6 +1240,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
>> + <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 317b75913..90d7fc924 100644
>> --- a/documentation/ref-manual/variables.rst
>> +++ b/documentation/ref-manual/variables.rst
>> @@ -10097,6 +10097,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).
>> +
>> + 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.
>> +
>> :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>`
>> @@ -10117,6 +10136,37 @@ 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:
>> +
>> + .. parsed-literal::
>> +
>
> You could use
>
> .. code-block:: console
>
> $ gpg --list-keys
> pub....
>
> to avoid having to use a literal include.
This use of parsed-literal was intended, as it shows the last 16 characters in
bold. I'll add " (in bold text below)" before the colon.
>> + 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 identier. In the above
>
> s/identier/identifier/
>
>> + example: ``5B97632FA7F4E942``.
>> +
>> + 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..54ae92d50
>> --- /dev/null
>> +++ b/documentation/security-manual/sstate-signing.rst
>> @@ -0,0 +1,272 @@
>> +.. 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.
>
> Double tick around gpg maybe?
>
>> +Install it as follows:
>> +
>> +.. code-block:: console
>> +
>> + $ sudo apt install gpg
>> +
>
> apt-get
>
> :D
>
>> +Verify that your installation is successful by showing the version of GPG:
>> +
>> +.. code-block:: console
>> +
>> + $ gpg --version
>> +
>
> What exactly does this depend on? Only the gpg binary or also libraries?
> It'd be nice to know because maybe other distros have different packages
> and need more/less. It seems that Debian's package installs the binary
> and depends on libgpg-error0 library.
>
> Can be figured out later but it'd be nice.
This: https://pkgs.org/search/?q=%2Fusr%2Fbin%2Fgpg
tells me that you probably only need to install a single package to get the
``gpg`` command-line tool. However I'm not sure if there are other requirements,
but I don't think this document should go in length about it
>> +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.
>> +
>> +We need a pair of public and private keys for two independent tasks:
>> +
>> +- Signing our sstate artifacts that the :term:`OpenEmbedded Build System`
>> + generates with our **private key**.
>> +
>> +- Verifying our sstate artifacts with our **public key** when re-using them.
>> +
>
> You alternate between "we/us/our" and "you" and the use of the
> imperative mood ("Do this") throughout the document, it's a bit
> confusing. I think we've mostly use the second person and imperative
> mood so far, so maybe stick to that?
>
> You also alternate between "sstate" and "shared state".
I've harmonized the doc in v3, thanks
>> +.. 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
>> +
>> +This will guide you through the steps of creating the key pair.
>> +
>
> s/This/It/
>
> I initially was being confused why there was no step after "This will
> guide you" that would actually guide me through the steps :)
>
>> +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 our build.
>> +
>> +Configuring Our Build System To Sign Sstate Artifacts
>> +=====================================================
>> +
>> +Shared State Location
>> +---------------------
>> +
>> +Our build system needs to be configured to sign new sstate 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 our build system has the shared
>> +state directory location (:term:`SSTATE_DIR`) defined as follows::
>> +
>> + SSTATE_DIR = "${TOPDIR}/sstate-cache"
>> +
>> +Assuming this directory and our temporary directory (:term:`TMPDIR`) are empty,
>> +as an example throughout this document, let's run the ``create_recipe_spdx``
>
> What part of the sentence does "as an example throughout this document"
> apply to? The wording is awkward.
>
>> +task of the ``gettext-minimal-native`` recipe:
>> +
>> +.. code-block:: console
>> +
>> + $ bitbake gettext-minimal-native -c create_recipe_spdx
>> +
>> +After execution, our 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::
>> +
>> + The `siginfo` file is not related to GPG signing.
>> +
>
> But the first file also not since we haven't enabled signing yet, what
> did you want to say here?
Avoid confusion as "siginfo" could make someone think of "signatures" and hence
signing/gpg. Is adding the note more confusing?
>
>> +Enabling Shared State Signing
>> +-----------------------------
>> +
>> +Create a new :term:`configuration file` on your host **in a safe location** and
>
> What makes a location safe?
> I would suggest to put it somewhere in $HOME/.config/ maybe?
Maybe, but I don't think that makes it safer?
> Since it contains a secret, we should probably also recommend to do
>
> chmod o-rwx <file>
Yes, I'll add that.
> no (and possibly chown $USER:$USER <file> ?)
This should be the default when creating the file but I can add that.
>> +add the two following statements::
>> +
>> + SSTATE_VERIFY_SIG = "1"
>> + SSTATE_SIG_KEY = "80613045069236143C2EE1A3C8855DA51F042B42"
>> + SSTATE_SIG_PASSPHRASE = "3lvJGHo8HZIcaufWFRezFIYRFDMSDmmkxOiwQ67PeM8IZre90I"
>> +
>
> Those (the key and the passphrase) don't match anything in this file,
> it'd be best to reuse what you've highlighted in the previous section.
>
> For the passphrase, maybe mention to "for example use that passphrase"
> when generating the key (and adding a big fat warning to not actually
> use that passphrase), so we know this here refers to that there. Just a
> suggestion, maybe it's easier to not give an example than having to
> remind people not to reuse it.
>
>> +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``.
>> +
>> +These statements 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 our 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.
>> +
>
> Am I the only bothered this is an option? Is there no way to use a
> keyring or the gpg-agent instead?
I don't think so, no.
>> +Let's test our configuration:
>> +
>> +#. Continuing with our ``gettext-minimal-native`` example, let's first clean the
>> + existing shared state artifacts:
>> +
>> + .. code-block:: console
>> +
>> + $ bitbake gettext-minimal-native -c cleansstate
>> +
>
> Why do we need to clean the cache (I'm assuming because you want to make
> sure the artifacts you sign are the one you just generated and not
> malicious ones, but I think it'd be worth explaining this is not a
> limitation but a design decision (IFF I really guessed that right)).
I'm giving an example here, and want to make sure my share state artifacts are
re-generated.
>> +#. Run the ``create_recipe_spdx`` task for ``gettext-minimal-native``, but this
>> + time pass our 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
>> +
>> +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 our artifact was successfully
>> +signed, and the signature is stored in a separate ``.sig`` file.
>> +
>> +Verifying Signed Shared State Artifacts
>> +=======================================
>> +
>> +Now that we have set up our build to sign shared state artifacts, let's see how
>> +we 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 spread on multiple hosts.
>> +
>
> s/spread on/between/
>
>> +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 we can accept shared state artifacts.
>> +
>> + 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``:
>> +
>> + .. 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]
>> +
>
> See comment in the first occurrence of this parsed-literal.
>
>> +Let's verify that signature verification works:
>> +
>> +#. First, remove temporary outputs (:term:`TMPDIR`) from the previous builds, to
>> + make the :term:`OpenEmbedded Build System` use the shared state:
>
> make <OE> rebuild everything using the shared state
>
> ?
>
>> +
>> + .. code-block:: console
>> +
>> + $ rm -r $BUILDDIR/tmp/
>> +
>
> You never tell the user what this variable should be set to. If they
> forget, they'll wreak havoc on their host system deleting /tmp/ recursively.
I'll remove BUILDDIR
>> +#. Then, run the task again:
>> +
>> + .. code-block:: console
>> +
>> + $ bitbake gettext-minimal-native -c create_recipe_spdx
>> +
>> +#. To make sure the shared state artifact was successfully used, look for the
>> + :ref:`setscene <overview-manual/concepts:setscene tasks and shared state>` task
>> + for ``create_recipe_spdx`` in the latest log file from :term:`BitBake`:
>> +
>> + .. code-block:: console
>> +
>> + $ grep create_recipe_spdx_setscene tmp/log/cooker/<machine>/console-latest.log
>> + NOTE: Running setscene task 1 of 1 (../layers/openembedded-core/meta/recipes-core/gettext/gettext-minimal-native_1.0.bb:do_create_recipe_spdx_setscene)
>> + NOTE: recipe gettext-minimal-native-1.0-r0: task do_create_recipe_spdx_setscene: Started
>> + NOTE: recipe gettext-minimal-native-1.0-r0: task do_create_recipe_spdx_setscene: Succeeded
>> +
>> + Our shared state was successfully verified and used!
>
> Is there any indication in the logs that verification was actually
> performed?
No, I didn't find any in the code, or with any verbosity level
>> +
>> +.. note::
>> +
>> + To make sure shared state verification is working, you can set a "fake"
>> + public key identifier in :term:`SSTATE_VALID_SIGS`::
>> +
>> + SSTATE_VALID_SIGS = "CAFECAFECAFECAFE"
>> +
>> + Remove the temporary outputs again:
>> +
>> + .. code-block:: console
>> +
>> + $ rm -r $BUILDDIR/tmp/
>> +
>> + Now, try executing the task:
>> +
>> + .. code-block:: console
>> +
>> + $ bitbake gettext-minimal-native -c create_recipe_spdx
>> +
>> + You should have warnings from :term:`BitBake`:
>> +
>
> Is this output in the console or in the log file you mentioned earlier?
In the console. I'll mention it
>> + .. code-block:: text
>> +
>> + WARNING: gettext-minimal-native-1.0-r0 do_create_recipe_spdx_setscene: No accepted signatures found. Good signatures found: 5B97632FA7F4E942.
>> + WARNING: gettext-minimal-native-1.0-r0 do_create_recipe_spdx_setscene: Cannot verify signature on sstate package ../build/sstate-cache/universal/a3/6e/sstate:gettext-minimal-native:x86_64-linux:1.0:r0:x86_64:14:a36ef66df6b8c0cb5a849bc70a99dcfd61e4bacd11cefe6bbaf4978b2b3b617a_create_recipe_spdx.tar.zst, skipping acceleration...
>> + WARNING: gettext-minimal-native-1.0-r0 do_create_recipe_spdx_setscene: No sstate archive obtainable, will run full task instead.
>> + WARNING: Logfile for failed setscene task is ../build/tmp/work/x86_64-linux/gettext-minimal-native/1.0/temp/log.do_create_recipe_spdx_setscene.6994
>> + WARNING: Setscene task (../layers/openembedded-core/meta/recipes-core/gettext/gettext-minimal-native_1.0.bb:do_create_recipe_spdx_setscene) failed with exit code '1' - real task will be run instead
>> +
>> + As you can see, this does not prevent :term:`BitBake` from continuing, but
>> + the real task is executed instead of re-using the shared state.
>>
Thanks!
Antonin
next prev parent reply other threads:[~2026-04-27 14:43 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-21 7:19 [PATCH v2] Document shared state signing Antonin Godard
2026-04-24 15:18 ` [docs] " Quentin Schulz
2026-04-27 14:43 ` Antonin Godard [this message]
2026-04-27 15:08 ` Quentin Schulz
2026-04-27 15:33 ` Antonin Godard
2026-04-27 16:44 ` Quentin Schulz
2026-04-28 8:27 ` Antonin Godard
2026-05-07 9:11 ` Quentin Schulz
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=DI40MO4BDL1R.306ON3EYMK35F@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