diff mbox series

[meta-oe,v1] signing.bbclass: add certificate ca-chain handling

Message ID 20241101120514.185668-1-johannes.schneider@leica-geosystems.com
State New
Headers show
Series [meta-oe,v1] signing.bbclass: add certificate ca-chain handling | expand

Commit Message

Johannes Schneider Nov. 1, 2024, 12:05 p.m. UTC
Add handling of ca-chains which can consist of more than one
certificate in a .pem file, which need to be split off, processed and
stored separately in the softhsm - as the tool-chain
signing.bbclass::signing_import_cert* -> softhsm -> 'extract-cert'
only supports one-per-file, due to using/expecting "plain" x509
in-/output.

The added signing_import_cert_chain_from_pem function takes a <role>
basename, and iterates through the input .pem file, creating numbered
<role>_1, _2, ... roles as needed.

Afterwards the certificates can be used or extracted one-by-one from
the softhsm, using the numbered roles; the only precondition - or
limitation - is that the PKI structure has to be known beforhand;
e.g. how many certificates are between leaf and root.

Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
---
 meta-oe/classes/signing.bbclass | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

Comments

Jan Lübbe Nov. 26, 2024, 3:49 p.m. UTC | #1
Hi Johannes,

On Fri, 2024-11-01 at 13:05 +0100, Johannes Schneider via lists.openembedded.org wrote:
> Add handling of ca-chains which can consist of more than one
> certificate in a .pem file, which need to be split off, processed and
> stored separately in the softhsm - as the tool-chain
> signing.bbclass::signing_import_cert* -> softhsm -> 'extract-cert'
> only supports one-per-file, due to using/expecting "plain" x509
> in-/output.
> 
> The added signing_import_cert_chain_from_pem function takes a <role>
> basename, and iterates through the input .pem file, creating numbered
> <role>_1, _2, ... roles as needed.

Why do you want to import certificates without corresponding private
keys into the singing mechanism?

They can't be used for signing, so don't really fit the role concept as
I intended it. This mismatch then leads to workarounds such as the _N
suffix and the problem below.

> Afterwards the certificates can be used or extracted one-by-one from
> the softhsm, using the numbered roles; the only precondition - or
> limitation - is that the PKI structure has to be known beforhand;
> e.g. how many certificates are between leaf and root.

The point of the signing.bbclass is to abstract over different HSMs and
allow flexible key selection at the .conf level.

With this approach, any recipe using this set of generated
(certificate-only) roles now needs to know how long the chain is and
the PKIs with different layouts are no longer possible.


Could you describe you use-case in detail? I think we should try to
find a design which avoids using roles for certificate chains, while
also allowing different PKI layouts between SoftHSM and actual HSMs.

> Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
> ---
>  meta-oe/classes/signing.bbclass | 30 ++++++++++++++++++++++++++++++
>  1 file changed, 30 insertions(+)
> 
> diff --git a/meta-oe/classes/signing.bbclass b/meta-oe/classes/signing.bbclass
> index 3e662ff73..8af7bbf8e 100644
> --- a/meta-oe/classes/signing.bbclass
> +++ b/meta-oe/classes/signing.bbclass
> @@ -134,6 +134,36 @@ signing_import_cert_from_der() {
>      signing_pkcs11_tool --type cert --write-object "${der}" --label "${role}"
>  }
>  
> +# signing_import_cert_chain_from_pem <role> <pem>
> +#
> +
> +# Import a certificate *chain* from a PEM file to a role.
> +# (e.g. multiple ones concatenated in one file)
> +#
> +# Due to limitations in the toolchain:
> +#   signing class -> softhsm -> 'extract-cert'
> +# the input certificate is split into a sequentially numbered list of roles,
> +# starting at <role>_1
> +#
> +# (The limitations are the conversion step from x509 to a plain .der, and
> +# extract-cert expecting a x509 and then producing only plain .der again)
> +signing_import_cert_chain_from_pem() {
> +    local role="${1}"
> +    local pem="${2}"
> +    local i=1
> +
> +    cat "${pem}" | \
> +        while openssl x509 -inform pem -outform der -out ${B}/temp_${i}.der; do
> +            signing_import_define_role "${role}_${i}"

Calling signing_import_define_role() from an import function breaks the
separation and ordering we currently use (first
signing_import_define_role(), then import certs/keys) and is surprising
compared to the existing signing_import_define_role()

Regards
Jan

> +            signing_pkcs11_tool --type cert \
> +                                --write-object  ${B}/temp_${i}.der \
> +                                --label "${role}_${i}"
> +            rm ${B}/temp_${i}.der
> +            echo "imported ${pem} under role: ${role}_${i}"
> +            i=$(awk "BEGIN {print $i+1}")
> +        done
> +}
> +
>  # signing_import_cert_from_pem <role> <pem>
>  #
>  # Import a certificate from PEM file to a role. To be used
>
Johannes Schneider Nov. 27, 2024, 3:39 a.m. UTC | #2
Hoi,

>
> On Fri, 2024-11-01 at 13:05 +0100, Johannes Schneider via lists.openembedded.org wrote:
> > Add handling of ca-chains which can consist of more than one
> > certificate in a .pem file, which need to be split off, processed and
> > stored separately in the softhsm - as the tool-chain
> > signing.bbclass::signing_import_cert* -> softhsm -> 'extract-cert'
> > only supports one-per-file, due to using/expecting "plain" x509
> > in-/output.
> >
> > The added signing_import_cert_chain_from_pem function takes a <role>
> > basename, and iterates through the input .pem file, creating numbered
> > <role>_1, _2, ... roles as needed.
> 
> Why do you want to import certificates without corresponding private
> keys into the singing mechanism?
> 
> They can't be used for signing, so don't really fit the role concept as
> I intended it. This mismatch then leads to workarounds such as the _N
> suffix and the problem below.
>

During a (yocto) build, there can be places where the key is used to sign
something, and other places where the corresponding certificate is inserted/used
to verify the former - so there are two different "consumers" of one role; e.g.
the kernel fitimage is signed with A.key, and at bootloader build time A.cert is
inserted into the bootloader so that it can later verify what it is supposed to load.

Now the bootloader<>kernel uscase is simple, because there is no verification of
the certificate; e.g. there is no need to provide the complete chain.

But other usecases sign $something during buildtime, and at runtime this
artifact would then be verified against a certificate, which in turn would need
to be verified against it's CA-chain.

By keeping the leaf (key+cert) plus all links (just the cert) up the certificate
chain, the softhsm can be used as "single source of keymaterial". Otherwise
there could be a rift between taking the keys from the softhsm during buildtime,
but takeing the certificate (-chains) from $elsewhere.

>
> > Afterwards the certificates can be used or extracted one-by-one from
> > the softhsm, using the numbered roles; the only precondition - or
> > limitation - is that the PKI structure has to be known beforhand;
> > e.g. how many certificates are between leaf and root.
> 
> The point of the signing.bbclass is to abstract over different HSMs and
> allow flexible key selection at the .conf level.
> 
> With this approach, any recipe using this set of generated
> (certificate-only) roles now needs to know how long the chain is and
> the PKIs with different layouts are no longer possible.
> 

That is the one caveat - the layout has to be the same and known beforehand;
should we add more logic=complexity into the class to detect and handle this
(... i guess: no ;-)?

>
> Could you describe you use-case in detail? I think we should try to
> find a design which avoids using roles for certificate chains, while
> also allowing different PKI layouts between SoftHSM and actual HSMs.
>

We have a build setup that, depending on a build-configuration switch, uses
either development-keymaterial, or production-keymaterial. So the (soft)HSM
encapsuled by the signing.bbclass and it's roles becomes the common interface
through which all keymaterial is requested for artifact-signing purposes, or
certificate (chains) are gathered to populate e.g. the rootfs with, for use
during system runtime.

The intended usecase of the signing.bbclass is certainly to swith around the
actual underlying HSMs (e.g. have a softhsm with devkeys, but a "real" HSM for
the production usecase) - but due to limitations with our CI infrastructure...
we only have a secured channel to fetch the productive keymaterial, but still
need/intend to use the softhsm to "secure" the keymaterial on the CI during
build-time.

> > Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
> > ---
> >  meta-oe/classes/signing.bbclass | 30 ++++++++++++++++++++++++++++++
> >  1 file changed, 30 insertions(+)
> >
> > diff --git a/meta-oe/classes/signing.bbclass b/meta-oe/classes/signing.bbclass
> > index 3e662ff73..8af7bbf8e 100644
> > --- a/meta-oe/classes/signing.bbclass
> > +++ b/meta-oe/classes/signing.bbclass
> > @@ -134,6 +134,36 @@ signing_import_cert_from_der() {
> >      signing_pkcs11_tool --type cert --write-object "${der}" --label "${role}"
> >  }
> >
> > +# signing_import_cert_chain_from_pem <role> <pem>
> > +#
> > +
> > +# Import a certificate *chain* from a PEM file to a role.
> > +# (e.g. multiple ones concatenated in one file)
> > +#
> > +# Due to limitations in the toolchain:
> > +#   signing class -> softhsm -> 'extract-cert'
> > +# the input certificate is split into a sequentially numbered list of roles,
> > +# starting at <role>_1
> > +#
> > +# (The limitations are the conversion step from x509 to a plain .der, and
> > +# extract-cert expecting a x509 and then producing only plain .der again)
> > +signing_import_cert_chain_from_pem() {
> > +    local role="${1}"
> > +    local pem="${2}"
> > +    local i=1
> > +
> > +    cat "${pem}" | \
> > +        while openssl x509 -inform pem -outform der -out ${B}/temp_${i}.der; do
> > +            signing_import_define_role "${role}_${i}"
> 
> Calling signing_import_define_role() from an import function breaks the
> separation and ordering we currently use (first
> signing_import_define_role(), then import certs/keys) and is surprising
> compared to the existing signing_import_define_role()
>

That is true... got any advice/ideas on how to handle this?

This way the PKI layout = the certificate chain lenght, has not to be known at
the time of the import, but only when using the roles. A separate
signing_import_define_N_roles(N) to have them prepared "blindly" ahead of time?

>
> Regards
> Jan
> 
> > +            signing_pkcs11_tool --type cert \
> > +                                --write-object  ${B}/temp_${i}.der \
> > +                                --label "${role}_${i}"
> > +            rm ${B}/temp_${i}.der
> > +            echo "imported ${pem} under role: ${role}_${i}"
> > +            i=$(awk "BEGIN {print $i+1}")
> > +        done
> > +}
> > +
> >  # signing_import_cert_from_pem <role> <pem>
> >  #
> >  # Import a certificate from PEM file to a role. To be used
> >
> 

gruß
Johannes
Jan Lübbe Nov. 28, 2024, 10:48 a.m. UTC | #3
Hi,

On Wed, 2024-11-27 at 03:39 +0000, Johannes Schneider via lists.openembedded.org wrote:
> > On Fri, 2024-11-01 at 13:05 +0100, Johannes Schneider via lists.openembedded.org wrote:
> > > Add handling of ca-chains which can consist of more than one
> > > certificate in a .pem file, which need to be split off, processed and
> > > stored separately in the softhsm - as the tool-chain
> > > signing.bbclass::signing_import_cert* -> softhsm -> 'extract-cert'
> > > only supports one-per-file, due to using/expecting "plain" x509
> > > in-/output.
> > > 
> > > The added signing_import_cert_chain_from_pem function takes a <role>
> > > basename, and iterates through the input .pem file, creating numbered
> > > <role>_1, _2, ... roles as needed.
> > 
> > Why do you want to import certificates without corresponding private
> > keys into the singing mechanism?
> > 
> > They can't be used for signing, so don't really fit the role concept as
> > I intended it. This mismatch then leads to workarounds such as the _N
> > suffix and the problem below.
> 
> During a (yocto) build, there can be places where the key is used to sign
> something, and other places where the corresponding certificate is inserted/used
> to verify the former - so there are two different "consumers" of one role; e.g.
> the kernel fitimage is signed with A.key, and at bootloader build time A.cert is
> inserted into the bootloader so that it can later verify what it is supposed to load.

Yes, this split between signed artifact component (e.g. fitimage) and
authenticating component (e.g. bootloader) was the main motivation for
the concept of roles: both users of a role can be switched from one
(development) key to a different one at a single place
(local/auto.conf).

> Now the bootloader<>kernel uscase is simple, because there is no verification of
> the certificate; e.g. there is no need to provide the complete chain.

(for fit images, the certificate isn't even available during
authentication, only the public key)

> But other usecases sign $something during buildtime, and at runtime this
> artifact would then be verified against a certificate, which in turn would need
> to be verified against it's CA-chain.

Beyond the simple case of self-signed certificates, I find it clearer
to talk about leaf, intermediate and root certificates. Depending on
context, "chain" can mean leaf+intermediates, intermediates only, or
even leaf+intermediates+root.

On the target, only the root certificates need to be stored in a
trusted location. The intermediate certificates are usually provided
with the signature to let the authenticating component to build a path
to a trusted root.

The leaf certificate is special in this case mainly because it's the
only certificate for which we have the corresponding private key.

> By keeping the leaf (key+cert) plus all links (just the cert) up the certificate
> chain, the softhsm can be used as "single source of keymaterial". Otherwise
> there could be a rift between taking the keys from the softhsm during buildtime,
> but takeing the certificate (-chains) from $elsewhere.

If we say signing.bbclass instead of SoftHSM, I'd agree.

SoftHSM doesn't actually provide more security for private keys than
simple .pem files. So why add all this complexity instead of using
simple OE variable pointing to a private key file? Because it allows us
to:
- support hardware tokens
- decouple key configuration from key usage
  => simplifies reuse
  => consistent configuration for many recipes
- use same code-paths for development and release
  => better testing

(for more background info, see my OE WS 2023 talk
https://pretalx.com/openembedded-workshop-2023/talk/3C8MFF/)


I definitely agree that signing.bbclass should also have a way to get
the intermediate and root certs for any given key-pair via the role.
Introducing additional certificate-only roles for this causes new
problems, though (see below).

> > > Afterwards the certificates can be used or extracted one-by-one from
> > > the softhsm, using the numbered roles; the only precondition - or
> > > limitation - is that the PKI structure has to be known beforhand;
> > > e.g. how many certificates are between leaf and root.
> > 
> > The point of the signing.bbclass is to abstract over different HSMs and
> > allow flexible key selection at the .conf level.
> > 
> > With this approach, any recipe using this set of generated
> > (certificate-only) roles now needs to know how long the chain is and
> > the PKIs with different layouts are no longer possible.
> 
> That is the one caveat - the layout has to be the same and known beforehand;
> should we add more logic=complexity into the class to detect and handle this
> (... i guess: no ;-)?

The information has to be stored somewhere, but ideally only in one
place. It can't be the recipes, because we always have (at least) two
of them (signing and authenticating components). Also, hard-coding
details of the layout in recipes reduces reusability across layers or
projects.

I'm not willing to give up on the goal of proper decoupling. As the
inherent complexity has to live somewhere, I'd prefer the class, so a
single implementation can be reused and the recipes are less complex.


One aspect to consider is that there is not a one-to-one relationship
between the leaf certificate and the set of CA certificates
(intermediates+root): CAs are used to support multiple interchangeable
leaf certificates under a single trusted root. Without the need of
multiple leaf certs, you could just use a self-signed cert and avoid
all this complexity. :)

For example, one build configuration could have different key-pairs
(roles) for different components, e.g. kernel modules and an IPE
policies. Their certificates may still be signed by the same CA, as
they are authenticated by the same component (the kernel).


So, handling (intermediate and root) CA certificates separately from
the roles would allow us to refer to them indirectly.

> > Could you describe you use-case in detail? I think we should try to
> > find a design which avoids using roles for certificate chains, while
> > also allowing different PKI layouts between SoftHSM and actual HSMs.
> 
> We have a build setup that, depending on a build-configuration switch, uses
> either development-keymaterial, or production-keymaterial. So the (soft)HSM
> encapsuled by the signing.bbclass and it's roles becomes the common interface
> through which all keymaterial is requested for artifact-signing purposes, or
> certificate (chains) are gathered to populate e.g. the rootfs with, for use
> during system runtime.

Yes.

> The intended usecase of the signing.bbclass is certainly to swith around the
> actual underlying HSMs (e.g. have a softhsm with devkeys, but a "real" HSM for
> the production usecase) - but due to limitations with our CI infrastructure...
> we only have a secured channel to fetch the productive keymaterial, but still
> need/intend to use the softhsm to "secure" the keymaterial on the CI during
> build-time.

Using SoftHSM doesn't provide any security. If you're OK with that, it
still is useful as part of the abstraction, so that keys can be
switched without touching recipes.


Note that certificates are not secret, so storing them in a HSM is
primarily useful because that avoids the need of a different storage
location. Alternatively, all certs could be stored as files in the
layer and found by recursively walking the X509v3 Authority Key
Identifiers (but that would likely need a small tool to be called from
the class). 

> > > Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
> > > ---
> > >  meta-oe/classes/signing.bbclass | 30 ++++++++++++++++++++++++++++++
> > >  1 file changed, 30 insertions(+)
> > > 
> > > diff --git a/meta-oe/classes/signing.bbclass b/meta-oe/classes/signing.bbclass
> > > index 3e662ff73..8af7bbf8e 100644
> > > --- a/meta-oe/classes/signing.bbclass
> > > +++ b/meta-oe/classes/signing.bbclass
> > > @@ -134,6 +134,36 @@ signing_import_cert_from_der() {
> > >      signing_pkcs11_tool --type cert --write-object "${der}" --label "${role}"
> > >  }
> > > 
> > > +# signing_import_cert_chain_from_pem <role> <pem>
> > > +#
> > > +
> > > +# Import a certificate *chain* from a PEM file to a role.
> > > +# (e.g. multiple ones concatenated in one file)
> > > +#
> > > +# Due to limitations in the toolchain:
> > > +#   signing class -> softhsm -> 'extract-cert'
> > > +# the input certificate is split into a sequentially numbered list of roles,
> > > +# starting at <role>_1
> > > +#
> > > +# (The limitations are the conversion step from x509 to a plain .der, and
> > > +# extract-cert expecting a x509 and then producing only plain .der again)
> > > +signing_import_cert_chain_from_pem() {
> > > +    local role="${1}"
> > > +    local pem="${2}"
> > > +    local i=1
> > > +
> > > +    cat "${pem}" | \
> > > +        while openssl x509 -inform pem -outform der -out ${B}/temp_${i}.der; do
> > > +            signing_import_define_role "${role}_${i}"
> > 
> > Calling signing_import_define_role() from an import function breaks the
> > separation and ordering we currently use (first
> > signing_import_define_role(), then import certs/keys) and is surprising
> > compared to the existing signing_import_define_role()
> 
> That is true... got any advice/ideas on how to handle this?
> 
> This way the PKI layout = the certificate chain lenght, has not to be known at
> the time of the import, but only when using the roles. A separate
> signing_import_define_N_roles(N) to have them prepared "blindly" ahead of time?

If we separate certificates from roles, we'd have something like this
in the provider (where development keys are imported):
  signing_import_prepare

  signing_import_root_cert kernel ".../kernel-ca.crt"

  signing_import_define_role kernel_modules
  signing_import_cert_from_pem kernel_modules "${S}/kmod-development.crt"
  signing_import_key_from_pem kernel_modules "${S}/kmod-development.key"
  signing_import_set_ca kernel_modules kernel

  signing_import_define_role kernel_ipe
  signing_import_cert_from_pem kernel_ipe "${S}/ipe-development.crt"
  signing_import_key_from_pem kernel_ipe "${S}/ipe-development.key"
  signing_import_set_ca kernel_ipe kernel

  signing_import_finish

In the recipes using these roles, you'd need to refer to the CA. For
the kernel (authenticating component):
  signing_prepare
  cp "$(signing_get_root_cert kernel)" "${B}/kernel_ca.pem"

When signing the modules, you'd use:
  signing_prepare
  signing_use_role kernel_modules
  ... use $PKCS11_URI


For cases wth intermediate certificates, we could chain them to their
respective CA:
  signing_import_prepare

  signing_import_root_cert kernel ".../kernel-ca.crt"
  signing_import_intermediate_cert kernel_2024 kernel ".../kernel-kmods.crt"

  signing_import_define_role kernel_modules
  signing_import_cert_from_pem kernel_modules "${S}/kmod-development.crt"
  signing_import_key_from_pem kernel_modules "${S}/kmod-development.key"
  signing_import_set_ca kernel_modules kernel_2024

  signing_import_define_role kernel_ipe
  signing_import_cert_from_pem kernel_ipe "${S}/ipe-development.crt"
  signing_import_key_from_pem kernel_ipe "${S}/ipe-development.key"
  signing_import_set_ca kernel_ipe kernel_2024

  signing_import_finish

To support this, only the user (e.g. kernel modules, IPE policy) would
need to conditionally include the intermediate certificates:

  signing_prepare
  signing_use_role kernel_modules
  SIGN_CMD="... --key=PKCS11_URI ..."
  if signing_has_intermediate_certs kernel_modules; then
    SIGN_CMD="$SIGN_CMD --intermediate=$(signing_get_intermediate_certs kernel_modules)"
  fi

This recipe-level-code would support all cases:
- self-signed cert for simple cases
- leaf + root cert (no intermediates)
- leaf + intermediate + root cert (intermediates include with the signature)


For release builds, in addition to overriding the roles, you'd need to
override the certificates as well in you .conf:
  SIGNING_PKCS11_URI[kernel_modules] = "pkcs11:serial=DENK0200554;object=kmod&pin-value=123456"
  SIGNING_CA_CERT[kernel_modules] = "file:///kod-ca.cert"
  SIGNING_PKCS11_URI[kernel_ipe] = "pkcs11:serial=DENK0200554;object=ipe&pin-value=123456"
  SIGNING_CA_CERT[kernel_ipe] = "pkcs11:serial=DENK0200554;object=ipe-ca"
  SIGNING_CA_CERT[kernel] = "pkcs11:serial=DENK0200554;object=kernel-ca"
So, you could have the certificates as files or in the HSM.

If you'd need to define a different hierarchy, you'd add:
  SIGNING_CA[kernel_modules] = "product_a_kmods_2024"
  SIGNING_CA_CERT[kernel_modules] = "..."
  SIGNING_CA[kernel_ipe] = "product_a_ipe_2024"
  SIGNING_CA_CERT[kernel_ipe] = "..."
  SIGNING_CA[product_a_kmods_2024] = "product_a_2024"
  SIGNING_CA_CERT[product_a_kmods_2024] = "..."
  SIGNING_CA[product_a_ipe_2024] = "product_a_2024"
  SIGNING_CA_CERT[product_a_ipe_2024] = "..."
  SIGNING_CA[product_a_2024] = "release-ca"
  SIGNING_CA_CERT[product_a_2024] = "..."
  SIGNING_CA_CERT[release-ca] = "..."
And the actual recipes wouldn't need to change, preserving the
decoupling between recipes and PKI configuration.

I think that should offer enough flexibility for your and other
scenarios.

Regards
Jan
Johannes Schneider Nov. 30, 2024, 12:08 p.m. UTC | #4
Hoi,

>
> On Wed, 2024-11-27 at 03:39 +0000, Johannes Schneider via lists.openembedded.org wrote:
> > > On Fri, 2024-11-01 at 13:05 +0100, Johannes Schneider via lists.openembedded.org wrote:
> > > > Add handling of ca-chains which can consist of more than one
> > > > certificate in a .pem file, which need to be split off, processed and
> > > > stored separately in the softhsm - as the tool-chain
> > > > signing.bbclass::signing_import_cert* -> softhsm -> 'extract-cert'
> > > > only supports one-per-file, due to using/expecting "plain" x509
> > > > in-/output.
> > > >
> > > > The added signing_import_cert_chain_from_pem function takes a <role>
> > > > basename, and iterates through the input .pem file, creating numbered
> > > > <role>_1, _2, ... roles as needed.
> > >
> > > Why do you want to import certificates without corresponding private
> > > keys into the singing mechanism?
> > >
> > > They can't be used for signing, so don't really fit the role concept as
> > > I intended it. This mismatch then leads to workarounds such as the _N
> > > suffix and the problem below.
> >
> > During a (yocto) build, there can be places where the key is used to sign
> > something, and other places where the corresponding certificate is inserted/used
> > to verify the former - so there are two different "consumers" of one role; e.g.
> > the kernel fitimage is signed with A.key, and at bootloader build time A.cert is
> > inserted into the bootloader so that it can later verify what it is supposed to load.
>
> Yes, this split between signed artifact component (e.g. fitimage) and
> authenticating component (e.g. bootloader) was the main motivation for
> the concept of roles: both users of a role can be switched from one
> (development) key to a different one at a single place
> (local/auto.conf).
>
> > Now the bootloader<>kernel uscase is simple, because there is no verification of
> > the certificate; e.g. there is no need to provide the complete chain.
>
> (for fit images, the certificate isn't even available during
> authentication, only the public key)
>
> > But other usecases sign $something during buildtime, and at runtime this
> > artifact would then be verified against a certificate, which in turn would need
> > to be verified against it's CA-chain.
>
> Beyond the simple case of self-signed certificates, I find it clearer
> to talk about leaf, intermediate and root certificates. Depending on
> context, "chain" can mean leaf+intermediates, intermediates only, or
> even leaf+intermediates+root.
>
> On the target, only the root certificates need to be stored in a
> trusted location. The intermediate certificates are usually provided
> with the signature to let the authenticating component to build a path
> to a trusted root.

Or stored also in a trusted location?
-> openssl -CApath /usr/share/ca-certificate/youre-company/
(which openssl/libopenssl actually picks up automatically, if the certificates
there have hash-symlinks; and that's what we're using ATM)

>
> The leaf certificate is special in this case mainly because it's the
> only certificate for which we have the corresponding private key.
>
> > By keeping the leaf (key+cert) plus all links (just the cert) up the certificate
> > chain, the softhsm can be used as "single source of keymaterial". Otherwise
> > there could be a rift between taking the keys from the softhsm during buildtime,
> > but takeing the certificate (-chains) from $elsewhere.
>
> If we say signing.bbclass instead of SoftHSM, I'd agree.
>

(thumbsup) i was more refering to the "thing storing the keymaterial"
the softhsm just happens to be the default backend of the .bbclass

>
> SoftHSM doesn't actually provide more security for private keys than
> simple .pem files. So why add all this complexity instead of using
> simple OE variable pointing to a private key file? Because it allows us
> to:
> - support hardware tokens
> - decouple key configuration from key usage
>   => simplifies reuse
>   => consistent configuration for many recipes
> - use same code-paths for development and release
>   => better testing
>
> (for more background info, see my OE WS 2023 talk
> https://pretalx.com/openembedded-workshop-2023/talk/3C8MFF/)
>
>
> I definitely agree that signing.bbclass should also have a way to get
> the intermediate and root certs for any given key-pair via the role.
> Introducing additional certificate-only roles for this causes new
> problems, though (see below).

what new problems?
it's already being done for e.g. HABv4, where there are roles
that do import a (leaf) certificat, but don't have a key to import

>
> > > > Afterwards the certificates can be used or extracted one-by-one from
> > > > the softhsm, using the numbered roles; the only precondition - or
> > > > limitation - is that the PKI structure has to be known beforhand;
> > > > e.g. how many certificates are between leaf and root.
> > >
> > > The point of the signing.bbclass is to abstract over different HSMs and
> > > allow flexible key selection at the .conf level.
> > >
> > > With this approach, any recipe using this set of generated
> > > (certificate-only) roles now needs to know how long the chain is and
> > > the PKIs with different layouts are no longer possible.
> >
> > That is the one caveat - the layout has to be the same and known beforehand;
> > should we add more logic=complexity into the class to detect and handle this
> > (... i guess: no ;-)?
>
> The information has to be stored somewhere, but ideally only in one
> place. It can't be the recipes, because we always have (at least) two
> of them (signing and authenticating components). Also, hard-coding
> details of the layout in recipes reduces reusability across layers or
> projects.
>
> I'm not willing to give up on the goal of proper decoupling. As the
> inherent complexity has to live somewhere, I'd prefer the class, so a
> single implementation can be reused and the recipes are less complex.
>
>
> One aspect to consider is that there is not a one-to-one relationship
> between the leaf certificate and the set of CA certificates

Depends on which way you look, there is a 1:1 from leaf->intermediary,
and then intermediary/-ies->root
But you're right, it's not necessarily 1:1 in the other direction.

> (intermediates+root): CAs are used to support multiple interchangeable
> leaf certificates under a single trusted root. Without the need of
> multiple leaf certs, you could just use a self-signed cert and avoid
> all this complexity. :)

:-D - or just do away with the code-signing complexity alltogether, and allow
people to run OSS-software of their choosing on the hardware they paid money for
;-)

>
> For example, one build configuration could have different key-pairs
> (roles) for different components, e.g. kernel modules and an IPE
> policies. Their certificates may still be signed by the same CA, as
> they are authenticated by the same component (the kernel).
>
>
> So, handling (intermediate and root) CA certificates separately from
> the roles would allow us to refer to them indirectly.
>
> > > Could you describe you use-case in detail? I think we should try to
> > > find a design which avoids using roles for certificate chains, while
> > > also allowing different PKI layouts between SoftHSM and actual HSMs.
> >
> > We have a build setup that, depending on a build-configuration switch, uses
> > either development-keymaterial, or production-keymaterial. So the (soft)HSM
> > encapsuled by the signing.bbclass and it's roles becomes the common interface
> > through which all keymaterial is requested for artifact-signing purposes, or
> > certificate (chains) are gathered to populate e.g. the rootfs with, for use
> > during system runtime.
>
> Yes.
>
> > The intended usecase of the signing.bbclass is certainly to swith around the
> > actual underlying HSMs (e.g. have a softhsm with devkeys, but a "real" HSM for
> > the production usecase) - but due to limitations with our CI infrastructure...
> > we only have a secured channel to fetch the productive keymaterial, but still
> > need/intend to use the softhsm to "secure" the keymaterial on the CI during
> > build-time.
>
> Using SoftHSM doesn't provide any security. If you're OK with that, it
> still is useful as part of the abstraction, so that keys can be
> switched without touching recipes.
>
>
> Note that certificates are not secret, so storing them in a HSM is
> primarily useful because that avoids the need of a different storage
> location. Alternatively, all certs could be stored as files in the
> layer and found by recursively walking the X509v3 Authority Key
> Identifiers (but that would likely need a small tool to be called from
> the class).
>
> > > > Signed-off-by: Johannes Schneider <johannes.schneider@leica-geosystems.com>
> > > > ---
> > > >  meta-oe/classes/signing.bbclass | 30 ++++++++++++++++++++++++++++++
> > > >  1 file changed, 30 insertions(+)
> > > >
> > > > diff --git a/meta-oe/classes/signing.bbclass b/meta-oe/classes/signing.bbclass
> > > > index 3e662ff73..8af7bbf8e 100644
> > > > --- a/meta-oe/classes/signing.bbclass
> > > > +++ b/meta-oe/classes/signing.bbclass
> > > > @@ -134,6 +134,36 @@ signing_import_cert_from_der() {
> > > >      signing_pkcs11_tool --type cert --write-object "${der}" --label "${role}"
> > > >  }
> > > >
> > > > +# signing_import_cert_chain_from_pem <role> <pem>
> > > > +#
> > > > +
> > > > +# Import a certificate *chain* from a PEM file to a role.
> > > > +# (e.g. multiple ones concatenated in one file)
> > > > +#
> > > > +# Due to limitations in the toolchain:
> > > > +#   signing class -> softhsm -> 'extract-cert'
> > > > +# the input certificate is split into a sequentially numbered list of roles,
> > > > +# starting at <role>_1
> > > > +#
> > > > +# (The limitations are the conversion step from x509 to a plain .der, and
> > > > +# extract-cert expecting a x509 and then producing only plain .der again)
> > > > +signing_import_cert_chain_from_pem() {
> > > > +    local role="${1}"
> > > > +    local pem="${2}"
> > > > +    local i=1
> > > > +
> > > > +    cat "${pem}" | \
> > > > +        while openssl x509 -inform pem -outform der -out ${B}/temp_${i}.der; do
> > > > +            signing_import_define_role "${role}_${i}"
> > >
> > > Calling signing_import_define_role() from an import function breaks the
> > > separation and ordering we currently use (first
> > > signing_import_define_role(), then import certs/keys) and is surprising
> > > compared to the existing signing_import_define_role()
> >
> > That is true... got any advice/ideas on how to handle this?
> >
> > This way the PKI layout = the certificate chain lenght, has not to be known at
> > the time of the import, but only when using the roles. A separate
> > signing_import_define_N_roles(N) to have them prepared "blindly" ahead of time?
>
> If we separate certificates from roles, we'd have something like this
> in the provider (where development keys are imported):
>   signing_import_prepare
>
>   signing_import_root_cert kernel ".../kernel-ca.crt"
>
>   signing_import_define_role kernel_modules
>   signing_import_cert_from_pem kernel_modules "${S}/kmod-development.crt"
>   signing_import_key_from_pem kernel_modules "${S}/kmod-development.key"
>   signing_import_set_ca kernel_modules kernel
>

That is an idea! adding a variable to to the SIGNING_ENV_FILE that allows to
preserves the relation between certificates. Having a "signing_import_set_ca" is
probably enough: leafs would point to their intermediary, and that to it's root;
having an signing_import_root_cert is IMO not needed, as it's the same as a
signing_import_cert_from_pem.

>   signing_import_define_role kernel_ipe
>   signing_import_cert_from_pem kernel_ipe "${S}/ipe-development.crt"
>   signing_import_key_from_pem kernel_ipe "${S}/ipe-development.key"
>   signing_import_set_ca kernel_ipe kernel
>
>   signing_import_finish
>
> In the recipes using these roles, you'd need to refer to the CA. For
> the kernel (authenticating component):
>   signing_prepare
>   cp "$(signing_get_root_cert kernel)" "${B}/kernel_ca.pem"
>

if it would be just a simple 'cp'... it's always a "get the pkcs11 uri" + extract-cert :-/
It would be neat if the signing.bbclass would support getting to the (public) certificate
out of a role directly.

>
> When signing the modules, you'd use:
>   signing_prepare
>   signing_use_role kernel_modules
>   ... use $PKCS11_URI
>
>
> For cases wth intermediate certificates, we could chain them to their
> respective CA:
>   signing_import_prepare
>
>   signing_import_root_cert kernel ".../kernel-ca.crt"
>   signing_import_intermediate_cert kernel_2024 kernel ".../kernel-kmods.crt"
>

and why not:
signing_import_define_role kernel
signing_import_cert_from_pem kernel ".../kernel-ca.crt"
signing_import_define_role kernel_2024
signing_import_cert_from_pem kernel_2024 ".../kernel-kmods.crt"
signing_import_set_ca kernel_2024 kernel

Or did you mean to abstract those steps away in the functions
signing_import_root_cert +  signing_import_intermediate_cert
... seems redudndant to the import_cert_from_pem

>
>   signing_import_define_role kernel_modules
>   signing_import_cert_from_pem kernel_modules "${S}/kmod-development.crt"
>   signing_import_key_from_pem kernel_modules "${S}/kmod-development.key"
>   signing_import_set_ca kernel_modules kernel_2024
>
>   signing_import_define_role kernel_ipe
>   signing_import_cert_from_pem kernel_ipe "${S}/ipe-development.crt"
>   signing_import_key_from_pem kernel_ipe "${S}/ipe-development.key"
>   signing_import_set_ca kernel_ipe kernel_2024
>
>   signing_import_finish
>
> To support this, only the user (e.g. kernel modules, IPE policy) would
> need to conditionally include the intermediate certificates:
>
>   signing_prepare
>   signing_use_role kernel_modules
>   SIGN_CMD="... --key=PKCS11_URI ..."
>   if signing_has_intermediate_certs kernel_modules; then
>     SIGN_CMD="$SIGN_CMD --intermediate=$(signing_get_intermediate_certs kernel_modules)"
>   fi
>
> This recipe-level-code would support all cases:
> - self-signed cert for simple cases
> - leaf + root cert (no intermediates)
> - leaf + intermediate + root cert (intermediates include with the signature)
>
>
> For release builds, in addition to overriding the roles, you'd need to
> override the certificates as well in you .conf:
>   SIGNING_PKCS11_URI[kernel_modules] = "pkcs11:serial=DENK0200554;object=kmod&pin-value=123456"
>   SIGNING_CA_CERT[kernel_modules] = "file:///kod-ca.cert"
>   SIGNING_PKCS11_URI[kernel_ipe] = "pkcs11:serial=DENK0200554;object=ipe&pin-value=123456"
>   SIGNING_CA_CERT[kernel_ipe] = "pkcs11:serial=DENK0200554;object=ipe-ca"
>   SIGNING_CA_CERT[kernel] = "pkcs11:serial=DENK0200554;object=kernel-ca"
> So, you could have the certificates as files or in the HSM.
>
> If you'd need to define a different hierarchy, you'd add:
>   SIGNING_CA[kernel_modules] = "product_a_kmods_2024"
>   SIGNING_CA_CERT[kernel_modules] = "..."
>   SIGNING_CA[kernel_ipe] = "product_a_ipe_2024"
>   SIGNING_CA_CERT[kernel_ipe] = "..."
>   SIGNING_CA[product_a_kmods_2024] = "product_a_2024"
>   SIGNING_CA_CERT[product_a_kmods_2024] = "..."
>   SIGNING_CA[product_a_ipe_2024] = "product_a_2024"
>   SIGNING_CA_CERT[product_a_ipe_2024] = "..."
>   SIGNING_CA[product_a_2024] = "release-ca"
>   SIGNING_CA_CERT[product_a_2024] = "..."
>   SIGNING_CA_CERT[release-ca] = "..."
> And the actual recipes wouldn't need to change, preserving the
> decoupling between recipes and PKI configuration.
>
> I think that should offer enough flexibility for your and other
> scenarios.
>
> Regards
> Jan

seems like this patch was already accepted in <master>  :-)
i'll put togheter another one to add the 'signing_import_set_ca', since it
would supplement everything nicely.


gruß
Johannes
diff mbox series

Patch

diff --git a/meta-oe/classes/signing.bbclass b/meta-oe/classes/signing.bbclass
index 3e662ff73..8af7bbf8e 100644
--- a/meta-oe/classes/signing.bbclass
+++ b/meta-oe/classes/signing.bbclass
@@ -134,6 +134,36 @@  signing_import_cert_from_der() {
     signing_pkcs11_tool --type cert --write-object "${der}" --label "${role}"
 }
 
+# signing_import_cert_chain_from_pem <role> <pem>
+#
+
+# Import a certificate *chain* from a PEM file to a role.
+# (e.g. multiple ones concatenated in one file)
+#
+# Due to limitations in the toolchain:
+#   signing class -> softhsm -> 'extract-cert'
+# the input certificate is split into a sequentially numbered list of roles,
+# starting at <role>_1
+#
+# (The limitations are the conversion step from x509 to a plain .der, and
+# extract-cert expecting a x509 and then producing only plain .der again)
+signing_import_cert_chain_from_pem() {
+    local role="${1}"
+    local pem="${2}"
+    local i=1
+
+    cat "${pem}" | \
+        while openssl x509 -inform pem -outform der -out ${B}/temp_${i}.der; do
+            signing_import_define_role "${role}_${i}"
+            signing_pkcs11_tool --type cert \
+                                --write-object  ${B}/temp_${i}.der \
+                                --label "${role}_${i}"
+            rm ${B}/temp_${i}.der
+            echo "imported ${pem} under role: ${role}_${i}"
+            i=$(awk "BEGIN {print $i+1}")
+        done
+}
+
 # signing_import_cert_from_pem <role> <pem>
 #
 # Import a certificate from PEM file to a role. To be used