summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRobert Marko2026-10-12 17:21:27 +0000
committerRobert Marko2026-10-14 07:39:33 +0000
commit9a54bf699efeb2ebf7be47065db7ebb03067c9fa (patch)
treebf00fdb0d5767a4405df161b3a29ad4173753463
parent965842e14bd8112a8880e65381e11a46a15f24cc (diff)
boot: arm-trusted-firmware-microchipsw: stabilize cert metadata
Use SOURCE_DATE_EPOCH for X.509 validity and hash the unsigned certificate fields to derive stable serial numbers. The hash includes payload extensions, so changed firmware gets a new serial. Retain the existing behavior when the variable is unset and reject invalid values. ECDSA signatures remain nondeterministic. Signed-off-by: Robert Marko <robert.marko@sartura.hr>
-rw-r--r--package/boot/arm-trusted-firmware-microchipsw/Makefile2
-rw-r--r--package/boot/arm-trusted-firmware-microchipsw/patches/0006-cert_create-honour-SOURCE_DATE_EPOCH.patch173
-rw-r--r--package/boot/arm-trusted-firmware-microchipsw/src/keys/README5
3 files changed, 177 insertions, 3 deletions
diff --git a/package/boot/arm-trusted-firmware-microchipsw/Makefile b/package/boot/arm-trusted-firmware-microchipsw/Makefile
index 992d796a11..9af16aba87 100644
--- a/package/boot/arm-trusted-firmware-microchipsw/Makefile
+++ b/package/boot/arm-trusted-firmware-microchipsw/Makefile
@@ -1,7 +1,7 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=arm-trusted-firmware-microchipsw
-PKG_RELEASE:=3
+PKG_RELEASE:=4
PKG_SOURCE_PROTO:=git
PKG_SOURCE_URL=https://github.com/microchip-ung/arm-trusted-firmware.git
diff --git a/package/boot/arm-trusted-firmware-microchipsw/patches/0006-cert_create-honour-SOURCE_DATE_EPOCH.patch b/package/boot/arm-trusted-firmware-microchipsw/patches/0006-cert_create-honour-SOURCE_DATE_EPOCH.patch
new file mode 100644
index 0000000000..17131c4e6a
--- /dev/null
+++ b/package/boot/arm-trusted-firmware-microchipsw/patches/0006-cert_create-honour-SOURCE_DATE_EPOCH.patch
@@ -0,0 +1,173 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Robert Marko <robert.marko@sartura.hr>
+Date: Mon, 12 Oct 2026 19:21:27 +0200
+Subject: [PATCH] cert_create: honour SOURCE_DATE_EPOCH for metadata
+
+Use SOURCE_DATE_EPOCH for certificate validity and derive serial numbers
+from stable certificate fields. Changes to authenticated payload hashes
+then change the serial, while repeated builds keep it stable.
+
+Keep the existing behaviour when SOURCE_DATE_EPOCH is not set and reject
+malformed values.
+
+Signed-off-by: Robert Marko <robert.marko@sartura.hr>
+---
+--- a/tools/cert_create/src/cert.c
++++ b/tools/cert_create/src/cert.c
+@@ -4,9 +4,11 @@
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
++#include <errno.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
++#include <time.h>
+
+ #include <openssl/conf.h>
+ #include <openssl/err.h>
+@@ -66,6 +68,84 @@ error:
+
+ return ret;
+ }
++
++/* Return 0 when unset, 1 for a valid epoch, and -1 for an invalid value. */
++static int source_date_epoch(time_t *epoch)
++{
++ const char *value = getenv("SOURCE_DATE_EPOCH");
++ char *end;
++ long long seconds;
++
++ if (value == NULL) {
++ return 0;
++ }
++ errno = 0;
++ seconds = strtoll(value, &end, 10);
++ if (value == end || *end != '\0' || errno != 0 || seconds < 0 ||
++ (long long)(time_t)seconds != seconds) {
++ ERROR("Invalid SOURCE_DATE_EPOCH: %s\n", value);
++ return -1;
++ }
++ *epoch = (time_t)seconds;
++ return 1;
++}
++
++/* Feed one DER-encoded field into the serial digest and release its buffer. */
++static int digest_der(EVP_MD_CTX *ctx, int len, unsigned char **der)
++{
++ int ok = len > 0 && *der != NULL && EVP_DigestUpdate(ctx, *der, len);
++
++ OPENSSL_free(*der);
++ *der = NULL;
++ return ok;
++}
++
++/* Hash the unsigned certificate fields to derive a positive serial. */
++static int deterministic_serial(X509 *x, EVP_PKEY *pkey)
++{
++ EVP_MD_CTX *ctx = NULL;
++ unsigned char digest[EVP_MAX_MD_SIZE];
++ unsigned char *der = NULL;
++ unsigned int digest_len;
++ BIGNUM *bn = NULL;
++ ASN1_INTEGER *serial = NULL;
++ int ok = 0;
++
++ ctx = EVP_MD_CTX_create();
++ if (ctx == NULL || !EVP_DigestInit_ex(ctx, EVP_sha256(), NULL)) {
++ goto out;
++ }
++ if (!digest_der(ctx, i2d_X509_NAME(X509_get_issuer_name(x), &der), &der) ||
++ !digest_der(ctx, i2d_X509_NAME(X509_get_subject_name(x), &der), &der) ||
++ !digest_der(ctx, i2d_ASN1_TIME(X509_get_notBefore(x), &der), &der) ||
++ !digest_der(ctx, i2d_ASN1_TIME(X509_get_notAfter(x), &der), &der) ||
++ !digest_der(ctx, i2d_PUBKEY(pkey, &der), &der) ||
++ !digest_der(ctx, i2d_X509_EXTENSIONS(
++ (X509_EXTENSIONS *)X509_get0_extensions(x), &der), &der) ||
++ !EVP_DigestFinal_ex(ctx, digest, &digest_len) ||
++ digest_len < 20) {
++ goto out;
++ }
++
++ /* RFC 5280 requires a positive serial of at most 20 octets. */
++ digest[0] = (digest[0] & 0x7f) | 0x01;
++ bn = BN_bin2bn(digest, 20, NULL);
++ if (bn == NULL) {
++ goto out;
++ }
++ serial = BN_to_ASN1_INTEGER(bn, NULL);
++ if (serial == NULL || !X509_set_serialNumber(x, serial)) {
++ goto out;
++ }
++ ok = 1;
++out:
++ OPENSSL_free(der);
++ ASN1_INTEGER_free(serial);
++ BN_free(bn);
++ EVP_MD_CTX_destroy(ctx);
++ return ok;
++}
++
+ const EVP_MD *get_digest(int alg)
+ {
+ switch (alg) {
+@@ -215,7 +295,8 @@ int cert_new(
+ X509_NAME *name;
+ ASN1_INTEGER *sno;
+ int i, num, rc = 0;
+- int fb, attempt;
++ int fb, attempt, epoch_set;
++ time_t epoch;
+
+ /* Create the certificate structure */
+ x = X509_new();
+@@ -242,14 +323,19 @@ int cert_new(
+ /* x509.v3 */
+ X509_set_version(x, 2);
+
+- /* Random serial number */
+- sno = ASN1_INTEGER_new();
+- rand_serial(NULL, sno);
+- X509_set_serialNumber(x, sno);
+- ASN1_INTEGER_free(sno);
+-
+- X509_gmtime_adj(X509_get_notBefore(x), 0);
+- X509_gmtime_adj(X509_get_notAfter(x), (long)60*60*24*days);
++ epoch_set = source_date_epoch(&epoch);
++ if (epoch_set < 0) {
++ goto END;
++ }
++ if (epoch_set) {
++ if (ASN1_TIME_adj(X509_get_notBefore(x), epoch, 0, 0) == NULL ||
++ ASN1_TIME_adj(X509_get_notAfter(x), epoch, days, 0) == NULL) {
++ goto END;
++ }
++ } else {
++ X509_gmtime_adj(X509_get_notBefore(x), 0);
++ X509_gmtime_adj(X509_get_notAfter(x), (long)60*60*24*days);
++ }
+ X509_set_pubkey(x, pkey);
+
+ /* Subject name */
+@@ -283,6 +369,21 @@ int cert_new(
+ }
+ }
+
++ if (epoch_set) {
++ if (!deterministic_serial(x, pkey)) {
++ ERROR("Failed to set deterministic serial for %s\n", cert->cn);
++ goto END;
++ }
++ } else {
++ sno = ASN1_INTEGER_new();
++ if (sno == NULL || !rand_serial(NULL, sno) ||
++ !X509_set_serialNumber(x, sno)) {
++ ASN1_INTEGER_free(sno);
++ goto END;
++ }
++ ASN1_INTEGER_free(sno);
++ }
++
+ /*
+ * Sign the certificate with the issuer key. For ECDSA, re-sign until
+ * neither signature scalar (r, s) has a leading zero byte, which the
diff --git a/package/boot/arm-trusted-firmware-microchipsw/src/keys/README b/package/boot/arm-trusted-firmware-microchipsw/src/keys/README
index 7d9607f646..c5ff6b9438 100644
--- a/package/boot/arm-trusted-firmware-microchipsw/src/keys/README
+++ b/package/boot/arm-trusted-firmware-microchipsw/src/keys/README
@@ -10,5 +10,6 @@ For a device provisioned with a private root key, supply a private directory
containing the same filenames via TFA_KEY_DIR. The root key's public X and Y
coordinates must have no leading zero byte because of ERR-LAN969X-001.
-Fixed keys alone do not make the FIP reproducible: cert_create still generates
-random certificate serials and ECDSA signatures and uses the current time.
+The package passes SOURCE_DATE_EPOCH to cert_create for stable certificate
+validity and serials. ECDSA signatures are still random, so the FIP is not
+yet reproducible.