On Mon, Aug 09, 2021 at 10:06:08AM +0200, Roland Hieber wrote:
> Signed-off-by: Roland Hieber <r...@pengutronix.de>
> ---
> PATCH v2:
>  - cs_check_whitelisted: make "needle"  local variable (feedback by
>    Michael Olbrich)
>  - cs_check_whitelisted: error out with ERROR_KEY_NOT_WHITELISTED also
>    if whitelist does not exist yet (Michael Olbrich)
>  - rename cs_get_uri to cs_get_uri_unchecked and let cs_get_uri wrap it
>    instead of setting cs_no_whitelist_check=1 (Michael Olbrich)
>  - docs: simplify introductory example (Michael Olbrich)
>  - docs: add short paragraph on how to determine fingerprints of certs
> 
> PATCH v1: https://lore.ptxdist.org/ptxdist/
> ---
>  doc/dev_code_signing.rst                  | 80 +++++++++++++++++++++++
>  platforms/code-signing.in                 | 22 +++++++
>  rules/pre/030-code-signing-consumers.make |  6 ++
>  scripts/lib/ptxd_lib_code_signing.sh      | 71 ++++++++++++++++++--
>  4 files changed, 172 insertions(+), 7 deletions(-)
> 
> diff --git a/doc/dev_code_signing.rst b/doc/dev_code_signing.rst
> index 413f694980eb..28ebe055346e 100644
> --- a/doc/dev_code_signing.rst
> +++ b/doc/dev_code_signing.rst
> @@ -172,3 +172,83 @@ also via an environment variable.
>    (``=``, not ``:=``).
>    Otherwise the variable is expanded before a code signing provider can 
> perform
>    its setup.
> +
> +Key Whitelisting
> +~~~~~~~~~~~~~~~~
> +
> +In some use cases, it may be feasible to do additional checks to make sure 
> that
> +a package uses the correct key material.
> +For example, suppose you have a "release" code signing provider which you use
> +to sign bootloaders for production,
> +and a "development" code signing provider which you use to sign bootloaders
> +with an extended feature set, e.g. to allow booting arbitrary kernels and
> +userlands for debugging purposes.
> +Your production boards are locked down in hardware so the ROM code only
> +executes bootloaders signed with the "release" key.
> +Now you don't want any bootloader with debugging features to be signed with a
> +release key, otherwise someone could boot them on a locked-down production
> +device, and use the additional debugging features to get extended access to 
> the
> +production device.
> +In this case, key whitelisting can help to prevent signing bootloader 
> packages
> +with the wrong key.
> +
> +When the ``CODE_SIGNING_REQUIRE_WHITELIST`` kconfig symbol is enabled,
> +the consumer functions :ref:`ptx/cs-get-ca` and :ref:`ptx/cs-get-uri`
> +look up the triplet of package name, role name, and the pubkey's SHA256
> +fingerprint in the whitelist whose file name is determined by the
> +``CODE_SIGNING_WHITELIST_FILENAME`` kconfig symbol (the default path is
> +``configs/platform-<name>/code-signing-whitelist``).
> +If a key or a CA is not whitelisted for the package in which it is to be 
> used,
> +the functions will exit with an error message on the terminal::
> +
> +   $ ptxdist -v print KERNEL_MAKE_OPT
> +   ptxdist: error: SPKI whitelist record 'kernel kernel-modules
> +   69C9BBB8BB4DFAE74AB21D06DFB5F2C67066373AE545453276847340822CDF04' not 
> found in
> +   distrokit/configs/platform-v7a/code-signing-whitelist
> +
> +   …/ptxdist/rules/kernel.make:196: *** cs-get-uri: whitelist check failed, 
> see errors above.  Stop.
> +
> +
> +If ``CODE_SIGNING_REQUIRE_WHITELIST`` is disabled (the default),
> +all keys and CAs are provided to all packages without further checks.
> +
> +The format of the code signing whitelist consists of one triplet per line, in
> +which the elements of the triplet are separated by whitespace.
> +If a CA is to be checked, the role name is prefixed with a literal ``ca:``,
> +and the fingerprint refers to the public key of the certificate.
> +All other unmatched lines in the file are ignored, but we suggest to use 
> ``#``
> +to start a line comment so as not to add a whitelist record accidentally.
> +
> +For example, here is a whitelist for use with the *devel* code provider which
> +allows all provided keys to be used by their respective consumers::
> +
> +   # format: package-name role-name sha256-pubkey-fingerprint
> +   kernel      kernel-modules   
> 69C9BBB8BB4DFAE74AB21D06DFB5F2C67066373AE545453276847340822CDF04
> +   image-rauc  update           
> 0034F8FE5ADC3B0DFE642407275D144DE2398C68CC9A86DD6703D7151116B44E
> +   image-rauc  ca:update        
> 0034F8FE5ADC3B0DFE642407275D144DE2398C68CC9A86DD6703D7151116B44E
> +   rauc        ca:update        
> 0034F8FE5ADC3B0DFE642407275D144DE2398C68CC9A86DD6703D7151116B44E
> +
> +.. note:: The match is case-sensitive, and the fingerprints are expected
> +   in uppercase.
> +
> +   If a CA consists of more than one certificate, all of their fingerprints
> +   must be whitelisted.
> +
> +You can determine the key fingerprints by copying it from the error message,
> +or with the `spki-hash`__ tool from the ``host-extract-cert`` package,
> +or with openssl::
> +
> +   $ openssl pkey -in keyfile.pem -pubout -outform der \
> +     | openssl sha256 \
> +     | tr 'a-z' 'A-Z'
> +   (STDIN)= 69C9BBB8BB4DFAE74AB21D06DFB5F2C67066373AE545453276847340822CDF04
> +
> +or, in the case of certificates::
> +
> +   $ openssl x509 -noout -in cert.pem -pubkey \
> +     | openssl pkey -pubin -inform pem -pubout -outform der \
> +     | openssl sha256 \
> +     | tr 'a-z' 'A-Z'
> +   (STDIN)= 0034F8FE5ADC3B0DFE642407275D144DE2398C68CC9A86DD6703D7151116B44E
> +
> +__ https://git.pengutronix.de/cgit/extract-cert/tree/spki-hash.c
> diff --git a/platforms/code-signing.in b/platforms/code-signing.in
> index 81f9ef6f3c9e..92f555c7e965 100644
> --- a/platforms/code-signing.in
> +++ b/platforms/code-signing.in
> @@ -20,4 +20,26 @@ source "generated/code_signing_provider.in"
>  
>  endchoice
>  
> +config CODE_SIGNING_REQUIRE_WHITELISTED_KEYS
> +     bool
> +     prompt "require whitelisted keys"
> +     help
> +       Every time a key from the code provider is used, check if the consumer
> +       is allowed to use it.
> +
> +       Code signing consumers can depend on this option if they want to force
> +       the key whitelist check.
> +
> +if CODE_SIGNING_REQUIRE_WHITELISTED_KEYS
> +
> +config CODE_SIGNING_WHITELIST_FILENAME
> +     string
> +     prompt "whitelist file name"
> +     default "code-signing-whitelist"
> +     help
> +       This file name is used for the key whitelist. The path is relative to
> +       PTXDIST_PLATFORMCONFIGDIR (e.g. configs/platform-nnn/).

I think, we can just hard-code this name. No need for a config option.
Or is there a use-case for this?

> +
> +endif # CODE_SIGNING_REQUIRE_WHITELISTED_KEYS
> +
>  endif
> diff --git a/rules/pre/030-code-signing-consumers.make 
> b/rules/pre/030-code-signing-consumers.make
> index 909e8ebd6936..bca05854372d 100644
> --- a/rules/pre/030-code-signing-consumers.make
> +++ b/rules/pre/030-code-signing-consumers.make
> @@ -28,6 +28,9 @@ $(strip \
>               $(call ptx/cs-consumer-env, $(1))\
>                       cs_get_uri '$(strip $(2))'\
>       )\
> +     $(if $(filter-out 0,$(.SHELLSTATUS)),\
> +             $(call ptx/error, cs-get-uri: whitelist check failed – see 
> errors above)\
> +     )\

You cannot use ptx/error here. That macro can only be used for stuff that
is evaluated before the first target is executed. Otherwise the error
message will get lost.
You need to use $(error ...) here. Probably with the same $(shell :) hack
that is used in kernel.make.

>  )
>  endef
>  
> @@ -40,5 +43,8 @@ $(strip \
>               $(call ptx/cs-consumer-env, $(1))\
>                       cs_get_ca '$(strip $(2))'\
>       )\
> +     $(if $(filter-out 0,$(.SHELLSTATUS)),\
> +             $(call ptx/error, cs-get-ca: whitelist check failed – see 
> errors above)\
> +     )\

same here.

>  )
>  endef
> diff --git a/scripts/lib/ptxd_lib_code_signing.sh 
> b/scripts/lib/ptxd_lib_code_signing.sh
> index 24730d3cf742..c855821bd039 100644
> --- a/scripts/lib/ptxd_lib_code_signing.sh
> +++ b/scripts/lib/ptxd_lib_code_signing.sh
> @@ -1,6 +1,7 @@
>  #!/bin/bash
>  #
>  # Copyright (C) 2019 Sascha Hauer <s.ha...@pengutronix.de>
> +# Copyright (C) 2020 Jan Luebbe <j.lue...@pengutronix.de>
>  # Copyright (C) 2021 Roland Hieber, Pengutronix <r...@pengutronix.de>
>  #
>  # For further information about the PTXdist project and license conditions
> @@ -70,6 +71,7 @@ cs_init_variables() {
>      sysroot="$(ptxd_get_ptxconf PTXCONF_SYSROOT_HOST)"
>      keyprovider="$(ptxd_get_ptxconf PTXCONF_CODE_SIGNING_PROVIDER)"
>      keydir="${sysroot}/var/lib/keys/${keyprovider}"
> +    whitelist="$(ptxd_get_ptxconf PTXCONF_CODE_SIGNING_WHITELIST_FILENAME || 
> true)"
>  }
>  export -f cs_init_variables
>  
> @@ -157,6 +159,42 @@ cs_group_get_roles() {
>  }
>  export -f cs_group_get_roles
>  
> +#
> +# cs_check_whitelisted <role> <uri/pem or fingerprint prefixed with 
> "sha256:">
> +#
> +# Checks if the SPKI (Subject Public Key Info) Hash is in the whitelist
> +#
> +cs_check_whitelisted() {
> +    local role="${1:-ERROR_ROLE_IS_EMPTY}"
> +    local src="${2}"
> +    cs_init_variables
> +
> +    if [ "$(ptxd_get_ptxconf PTXCONF_CODE_SIGNING_REQUIRE_WHITELISTED_KEYS)" 
> != "y" ]; then
> +     return
> +    fi
> +
> +    if ! ptxd_in_path PTXDIST_PATH_PLATFORMCONFIGDIR "${whitelist}"; then
> +     echo ERROR_KEY_NOT_WHITELISTED
> +     ptxd_bailout "${pkg_name}: SPKI hash whitelist check for ${role} 
> (${src}) is required, but configs/<platform>/${whitelist} is missing."
> +    fi
> +    local whitelist_path="${ptxd_reply}"
> +
> +    local hash
> +    if [ "${src#sha256:}" != "${src}" ]; then
> +     hash="${src#sha256:}"
> +    else
> +     hash=$(ptxd_exec_silent_stderr spki-hash "${src}")
> +    fi
> +    hash=${hash:-ERROR_HASH_IS_EMPTY}
> +    local needle="${pkg_name}\s\+${role}\s\+${hash}"
> +
> +    if ! grep -q --line-regexp "${needle}" "${whitelist_path}"; then
> +     echo ERROR_KEY_NOT_WHITELISTED
> +     ptxd_bailout "SPKI whitelist record '${pkg_name} ${role} ${hash}' not 
> found in $(ptxd_print_path "${whitelist_path}")"
> +    fi
> +}
> +export -f cs_check_whitelisted
> +
>  #
>  # cs_set_uri <role> <uri>
>  #
> @@ -171,12 +209,8 @@ cs_set_uri() {
>  }
>  export -f cs_set_uri
>  
> -#
> -# cs_get_uri <role>
> -#
> -# Get the uri from a role
> -#
> -cs_get_uri() {
> +# internal helper for cs_get_uri
> +cs_get_uri_unchecked() {
>      if [ -z "${pkg_name}" ]; then
>           echo ERROR_UNSUPPORTED_CS_API_CALL
>           ptxd_bailout '$(shell cs_get_uri, <role>) is no longer supported in 
> make files.' \
> @@ -198,8 +232,24 @@ cs_get_uri() {
>           return
>       fi
>      fi
> +
>      cat "${keydir}/${role}/uri"
>  }
> +export -f cs_get_uri_unchecked
> +
> +#
> +# cs_get_uri <role>
> +#
> +# Get the uri from a role
> +#
> +cs_get_uri() {
> +    local role="${1}"
> +    local uri=$(cs_get_uri_unchecked "$1")
> +
> +    if cs_check_whitelisted "${role}" "${uri}"; then
> +     echo "${uri}"
> +    fi
> +}
>  export -f cs_get_uri
>  
>  #
> @@ -321,6 +371,9 @@ cs_get_ca() {
>      fi
>  
>      if [ -e "${ca}" ]; then
> +     while read fp; do
> +         cs_check_whitelisted "ca:${role}" "sha256:${fp}"
> +     done < "${keydir}/${role}/ca.fingerprints"
>       echo "${ca}"
>      fi
>  }
> @@ -339,6 +392,9 @@ cs_append_ca_from_pem() {
>      cat "${pem}" >> "${keydir}/${role}/ca.pem"
>      # add new line in case ${pem} does not end with an EOL
>      echo >> "${keydir}/${role}/ca.pem"
> +
> +    openssl x509 -in "${pem}" -inform pem -noout -pubkey | \
> +     spki-hash /dev/stdin >> "${keydir}/${role}/ca.fingerprints"

This should happen first, before the extending ca.pem and make sure to
abort on error. Use check_pipe_status to catch errors from openssl.

>  }
>  export -f cs_append_ca_from_pem
>  
> @@ -370,7 +426,8 @@ cs_append_ca_from_uri() {
>      cs_init_variables
>  
>      if [ -z "${uri}" ]; then
> -     uri=$(cs_get_uri "${role}")
> +     # cs_append_ca_from_der will check this later

I don't think this comment is correct. There is no checking while adding to
the CA, right?

Michael

> +     uri=$(cs_get_uri_unchecked "${role}")
>      fi
>  
>      ptxd_exec extract-cert "${uri}" "${tmpdir}/ca.der" &&
> -- 
> 2.30.2
> 
> 
> _______________________________________________
> ptxdist mailing list
> ptxdist@pengutronix.de
> To unsubscribe, send a mail with subject "unsubscribe" to 
> ptxdist-requ...@pengutronix.de

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

_______________________________________________
ptxdist mailing list
ptxdist@pengutronix.de
To unsubscribe, send a mail with subject "unsubscribe" to 
ptxdist-requ...@pengutronix.de

Reply via email to