diff --git a/docs/kb/edk2-capsule-updates.md b/docs/kb/edk2-capsule-updates.md index bf21c92629..c6f09952c6 100644 --- a/docs/kb/edk2-capsule-updates.md +++ b/docs/kb/edk2-capsule-updates.md @@ -90,7 +90,10 @@ Signing takes three keys (root, its subkey and subkey's subkey for signing): This structure of signing keys is presupposed by `GenerateCapsule` and `Pkcs7Sign` but it seems to be a design decision of the tooling rather than a strict requirement on the length of a signing key chain by UEFI or cryptographic -implementation in EDK2. +implementation in EDK2. In particular, a chain of length 2 (without a subkey) +can be used by specifying any certificate in place of the subkey (root, signing +certificate or literally any other; it won't be used by the code but still needs +to be a valid certificate so the tools don't complain). ### Optional payload metadata @@ -186,12 +189,15 @@ EDK2 payload: cd payloads/external/edk2/workspace/Dasharo/ BaseTools/BinWrappers/PosixLike/GenerateCapsule --encode \ --capflag PersistAcrossReset \ - --capflag InitiateReset \ --json-file dasharo.json \ --output dasharo.cap ``` -Values from multiple `--capflag` options are combined together. +In case more than one flag needs to be specified, values from multiple +`--capflag` options are combined together. There is no `InitiateReset` because +Linux rejects capsules with this flag and requires a manual soft reset for +persistent capsules (`CapsuleApp.efi` does reset for persistent capsules by +default, so no difference in behaviour for it). ## Capsule introspection @@ -325,6 +331,333 @@ interferes). [wiki-pkc]: https://en.wikipedia.org/wiki/Public-key_cryptography +## Generating signing keys with OpenSSL + +The process involves creation of 3 certificates, a local certificate +authority (CA) in a directory and 14 files. This warrants some overview of +what's going to be done. + +Keys making up the chain will assume the following roles: + +- _root_ — self-signed certificate acting as the basis of a CA +- _sub_ — second-level CA signed by _root_ key +- _sign_ — a signing key (not a CA) that is signed by _sub_ key + +A digital signature algorithm (DSA) consists of a private key (PK) algorithm and +a digest algorithm. Keys in a chain don't have to use the same DSA. Also, most +DSAs allow use of an arbitrary digest (not Ed25519 or Ed448 which prescribe the +use of SHA-512 and SHAKE-256 respectively). + +Keys exist independently of the chains in which they appear. When stored, +private keys are often encrypted. + +### Key generation by example + +It will be easier to go into finer details while looking at the actual commands. +The entire process can be broken into several stages: + +1. Creation of 3 certificates +2. Construction of a CA +3. Preparation of _root_ certificate for inclusion into EDK +4. Preparation of _sign_ certificate for signing + +#### Make certificates + +Using 4096-bit RSA keys as an example: + +```bash +openssl genrsa -aes256 -out root.p8e 4096 +openssl genrsa -aes256 -out sub.p8e 4096 +openssl genrsa -aes256 -out sign.p8e 4096 +``` + +Each of them is encrypted with AES-256. The password is queried interactively +for creation and each time the certificate is used to sign something. Drop +`-aes256` to not use encryption (for a test or if you consider access to the +files a complete compromise of the security). + +`.p8e` extension is for PKCS #8 format carrying an encrypted private key. + +#### Make a CA + +By default, directory for a CA is called `demoCA`, although it can be different +depending on the OS. It can be looked up in `/etc/ssl/openssl.cnf` as +`dir = ...` in `[ CA_default ]` section (and that section is in turn referenced +by `default_ca` in `[ ca ]` section). The directory needs to be set up prior +to using `openssl ca`: + +```bash +mkdir -p demoCA/newcerts +touch demoCA/index.txt +echo 01 > demoCA/serial +``` + +Initialize it with self-signed root certificate (will ask for a password +and certificate fields; country, state and organization fields must match _root_ +certificate, common name must be a unique non-empty value): + +```bash +openssl req -new -x509 -days 3650 -key root.p8e -out root.pub.pem +``` + +Create certificate signing requests (CSRs) (don't bother with +entering a challenge for CSRs, you won't be asked for it): + +```bash +openssl req -new -key sub.p8e -out sub.csr +openssl req -new -key sign.p8e -out sign.csr +``` + +Perform the signing (there will be password and confirmation prompts): + +```bash +openssl ca -extensions v3_ca \ + -in sub.csr \ + -days 3650 \ + -cert root.pub.pem \ + -keyfile root.p8e \ + -notext \ + -out sub.pub.pem +openssl ca -in sign.csr \ + -days 3650 \ + -cert sub.pub.pem \ + -keyfile sub.p8e \ + -notext \ + -out sign.crt +``` + +The `-days 3650` is something to be adjusted and is provided as an example that +certificate properties are set in a different command for _root_ compared to +other certificates. + +`-notext` avoids dumping certificate details in text form to the output file +thus making all certificates look consistent. The details are easy to obtain +by running `openssl x509 -in {cert-file} -text -noout` when needed. + +`*.csr` files aren't necessary after successful signing and can be removed. + +`.pub.pem` and `.crt` files contain essentially the same X.509 certificates, but +the former is used for CAs. There is little consistency or sense in extension +for these types of files in general, so don't read too much meaning into them. + +#### Prepare _root_ for EDK build system + +EDK gets _root_ certificate(s) in a PCD. The PCD name differ and support one +or many certificates, in this case it's +`gFmpDevicePkgTokenSpaceGuid.PcdFmpDevicePkcs7CertBufferXdr` which expects one +or more certificates in DER (binary) form combined via XDR (simple format where +big-endian 32-bit length is followed by that number of bytes). + +EDK provides `BinToPcd.py` that can generate a file for inclusion via `!include` +in some DSC-file of an EDK package. + +```bash +openssl x509 -in root.pub.pem -out root.cer -outform DER +python payloads/external/edk2/workspace/Dasharo/BaseTools/Scripts/BinToPcd.py \ + -p gFmpDevicePkgTokenSpaceGuid.PcdFmpDevicePkcs7CertBufferXdr \ + -i root.cer \ + -x \ + -o CapsuleRootKey.inc +``` + +`root.cer` can be removed afterward. + +#### Prepare _sign_ certificate + +`GenerateCapsule` and EDK will only ever need public parts of _root_ and _sub_ +certificates, but _sign_ certificate will have to be provided in combination +with the corresponding private key. This is achieved by packing the two parts +via PKCS #12 which is an archive file format. + +`openssl pkcs12` either creates a PKCS #12 file or converts it, thus requiring +two invocations for obtaining the result in PEM format. + +First, create binary PKCS #12 (certificate and corresponding private key): + +```bash +openssl pkcs12 -export -inkey sign.p8e -in sign.crt -out sign.pfx +``` + +Add `-passout pass:` to perform export (creation, that is) without encryption. + +Now convert binary PKCS #12 into PEM form: + +```bash +openssl pkcs12 -in sign.pfx -out sign.p12 +``` + +`-passin pass:` can be added if `sign.pfx` was created without a password to +skip the prompt. + +`-noenc` (`-nodec` is deprecated in OpenSSL v3) can be added to avoid +encrypting `sign.p12`. Without this option, there will be a password prompt +during the conversion and whenever a capsule is signed. + +`sign.pfx` can now be removed. + +### Supported algorithms and digests + +EDK has 2 libraries implementing `AuthenticateFmpImage()` which is responsible +for verifying an FMP image: + +- `SecurityPkg/Library/FmpAuthenticationLibRsa2048Sha256` +- `SecurityPkg/Library/FmpAuthenticationLibPkcs7` + +The first one supports only one kind of a key as indicated by its name. +Nowadays the second library is much more likely to be used. PKCS #7 is a +generic container for signature/signed data and the set of permitted algorithms +and digests is defined by the implementation handling it. There are two +options in Dasharo EDK based on top of `edk2-stable202402` upstream release: + +- OpenSSL v3.0.9 +- MbedTLS v3.3.0 + +However, because OpenSSL was never designed for embedded environments, it became +prohibitively large in v3 which introduced providers feature. This is why +Dasharo uses MbedTLS and it will be the focus of the below discussion. + +Because MbedTLS deals with certificates prepared by OpenSSL tools, both +constrain the set of what can be used. + +#### Build-time configuration + +What's actually supported by MbedTLS depends on a specific build and which +version of an EDK wrapper is being used. + +The library is configured via a set of defines in +`CryptoPkg/Library/MbedTlsLib/Include/mbedtls/mbedtls_config.h`. One of the +most important ones is: + +```c +#define MBEDTLS_MPI_MAX_SIZE 1024 +``` + +It specifies the maximum number of bytes available for multiple precision +integers and puts a limit on private key algorithms. `1024` is the default +value that prevents the use of RSA keys longer than 8192 bits. Elliptic +curve (EC) keys are much shorter in size, so the default doesn't affect them in +any way. + +Another important set of defines is the list of enabled curves: + +```c +/* Short Weierstrass curves (supporting ECP, ECDH, ECDSA) */ +// #define MBEDTLS_ECP_DP_SECP192R1_ENABLED +// #define MBEDTLS_ECP_DP_SECP224R1_ENABLED +#define MBEDTLS_ECP_DP_SECP256R1_ENABLED +#define MBEDTLS_ECP_DP_SECP384R1_ENABLED +#define MBEDTLS_ECP_DP_SECP521R1_ENABLED +// #define MBEDTLS_ECP_DP_SECP192K1_ENABLED +// #define MBEDTLS_ECP_DP_SECP224K1_ENABLED +// #define MBEDTLS_ECP_DP_SECP256K1_ENABLED +// #define MBEDTLS_ECP_DP_BP256R1_ENABLED +// #define MBEDTLS_ECP_DP_BP384R1_ENABLED +// #define MBEDTLS_ECP_DP_BP512R1_ENABLED +/* Montgomery curves (supporting ECP) */ +#define MBEDTLS_ECP_DP_CURVE25519_ENABLED +#define MBEDTLS_ECP_DP_CURVE448_ENABLED +``` + +Mind that using EC requires the use of `MbedTlsLibFull` instead of +`MbedTlsLib`, that's really the difference between the two wrappers. + +This define is supposed to enable RSASSA-PSS (see below though): + +```c +#define MBEDTLS_X509_RSASSA_PSS_SUPPORT +``` + +#### Digest types + +MbedTLS has the list of supported digests in `include/mbedtls/md.h`: + +```c +typedef enum { + MBEDTLS_MD_MD5, /**< The MD5 message digest. */ + MBEDTLS_MD_SHA1, /**< The SHA-1 message digest. */ + MBEDTLS_MD_SHA224, /**< The SHA-224 message digest. */ + MBEDTLS_MD_SHA256, /**< The SHA-256 message digest. */ + MBEDTLS_MD_SHA384, /**< The SHA-384 message digest. */ + MBEDTLS_MD_SHA512, /**< The SHA-512 message digest. */ + MBEDTLS_MD_RIPEMD160, /**< The RIPEMD-160 message digest. */ +}; +``` + +OpenSSL supports all of them (see `openssl list -digest-algorithms`, but not all +listed there work with X.509 certificates, not clear why). + +#### Private key algorithms + +These are listed in `include/mbedtls/pk.h` of MbedTLS: + +```c +typedef enum { + MBEDTLS_PK_NONE=0, + MBEDTLS_PK_RSA, + MBEDTLS_PK_ECKEY, + MBEDTLS_PK_ECKEY_DH, + MBEDTLS_PK_ECDSA, + MBEDTLS_PK_RSA_ALT, + MBEDTLS_PK_RSASSA_PSS, + MBEDTLS_PK_OPAQUE, +} mbedtls_pk_type_t; +``` + +OpenSSL supports the following ones (see `openssl list -signature-algorithms` +but only those which were accepted by OpenSSL in the commands above are listed +here): + +- `rsa` +- `rsa-pss` +- `ed25519` +- `ed448` +- `ec` + +`rsa` and `ec` are the only ones that actually worked. While Ed25519 and Ed448 +curves are there, that is far from a complete implementation of a corresponding +DSAs. + +`rsa-pss` is also explicitly enabled, but the following error reported at +run-time: + +```c +/** Key algorithm is unsupported (only RSA and EC are supported). */ +#define MBEDTLS_ERR_PK_UNKNOWN_PK_ALG -0x3C80 +``` + +Suggesting that it's not actually supported even when enabled. The situation +might be similar to the elliptic curves above. + +#### What's actually supported and how to use it + +Valid digest names for OpenSSL: + +- `md5` +- `ripemd160` +- `sha1` +- `sha224` +- `sha256` +- `sha384` +- `sha512` + +How a digest is to be specified depends on a particular subcommand: + +- `openssl ca` takes `-md {digest-name}` +- `openssl req` takes `-{digest-name}` (confusingly documented as `-digest`) + +`openssl genpkey` accepts `-algorithm {alg}` parameter and `rsa` or `ec` works. + +RSA key length's lower bound is 512 in OpenSSL and upper bound is 8192 in +MbedTLS by default. Key size can be configured with +`-pkeyopt rsa_keygen_bits:numbits` if `openssl genpkey` is used. + +Given that MbedTLS has only 3 standard curves enabled, there are only 3 +possibilities for an EC algorithm (use of `MbedTlsLibFull` is required): + +- `-pkeyopt ec_paramgen_curve:P-256` +- `-pkeyopt ec_paramgen_curve:P-384` +- `-pkeyopt ec_paramgen_curve:P-521` + ## `capsule.sh` script ### Building a capsule @@ -362,12 +695,17 @@ suitable set of keys which can be done like this: ./capsule.sh keygen my-test-keys ``` -At the end the script prints a command to use the keys: +At the end the script prints commands to use the keys (wrapped manually here): ```bash -./capsule.sh make -t my-test-keys/root.pub.pem \ - -o my-test-keys/sub.pub.pem \ - -s my-test-keys/sign.p12 +Installing root certificate (before build): + cp my-test-keys/CapsuleRootKey.inc \ + payloads/external/edk2/workspace/Dasharo/DasharoPayloadPkg/ + +Signing a capsule (after build): + ./capsule.sh make -t my-test-keys/root.pub.pem \ + -o my-test-keys/sub.pub.pem \ + -s my-test-keys/sign.p12 ``` !!! warning @@ -384,7 +722,7 @@ the EDK2-way of doing things via `SignedCapsulePkg` ([PDF][edk2-capsules]) but it appears to be less flexible due to relying on the build system of EDK2 which is quite rigid for a large firmware variance that can be found in Dasharo. -Because `GenerateCapsules` is in Python and at least part of the functionality +Because `GenerateCapsule` is in Python and at least part of the functionality is abstracted in form of modules, it's also possible to build custom tools on top of that.