Explorar o código

swupdate-common: add support for multiple CMS signers

So far SWUPDATE_CMS_CERT and SWUPDATE_CMS_KEY accepted only a single
certificate and private key. Allow both variables to hold a space
delimited list paired by position and emit one "-signer"/"-inkey"
pair per entry on the openssl command line.

This enables hybrid signing e.g. a classical RSA or ECDSA signature combined
with a post-quantum ML-DSA signature in the same CMS structure. Note
that SWUpdate's CMS_verify() treats the signers as an AND: every listed
signer's certificate must be present in the verification trust store on
the target.

A single certificate/key pair produces exactly the same openssl command
as before, so existing recipes are unaffected.

Signed-off-by: Ayoub Zaki <ayoub.zaki@embetrix.com>
Ayoub Zaki hai 1 semana
pai
achega
c8ed5a3523
Modificáronse 2 ficheiros con 25 adicións e 12 borrados
  1. 11 3
      README
  2. 14 9
      classes-recipe/swupdate-common.bbclass

+ 11 - 3
README

@@ -58,9 +58,15 @@ There are 3 signing mechanisms supported by meta-swupdate at the moment:
 
   * Set variable: `SWUPDATE_SIGNING = "CMS"`
 
-  * Set `SWUPDATE_CMS_CERT` to the full path of certificate file
+  * Set `SWUPDATE_CMS_CERT` to the full path of certificate file. A space
+    delimited list of certificates may be given to produce a CMS with multiple
+    signers (e.g. hybrid classic + post-quantum signing). SWUpdate verifies the
+    signers as an AND, so every certificate must be present in the target trust
+    store.
 
-  * Set `SWUPDATE_CMS_KEY ` to the full path of private key file
+  * Set `SWUPDATE_CMS_KEY ` to the full path of private key file. When several
+    certificates are listed in `SWUPDATE_CMS_CERT`, list the matching private
+    keys here in the same order; the two lists must have the same length.
 
   * (Optional) Set `SWUPDATE_CMS_EXTRA_CERTS` to a space delimited list of intermediate certificate files
 
@@ -68,7 +74,9 @@ There are 3 signing mechanisms supported by meta-swupdate at the moment:
     `openssl cms -sign` via `-md` (e.g. `sha256`, `sha512`). When unset, openssl
     picks the signing key's default digest. This is required for signing keys
     that have no default digest such as ML-DSA where openssl otherwise fails
-    with "no default digest".
+    with "no default digest". `openssl cms` uses a single digest for all
+    signers, so with multiple certificates choose one strong enough for the
+    strongest key (e.g. `sha512` when any of ML-DSA-65/87 is used).
 
 3. Custom signing tool:
 

+ 14 - 9
classes-recipe/swupdate-common.bbclass

@@ -206,19 +206,24 @@ def prepare_sw_description(d):
                 bb.fatal("SWUPDATE_PRIVATE_KEY %s doesn't exist" % (privkey))
             signcmd = ["openssl", "dgst", "-sha256", "-sign", privkey] + get_pwd_file_args(d, 'SWUPDATE_PASSWORD_FILE') + ["-out", sw_desc_sig, sw_desc]
         elif signing == "CMS":
-            cms_cert = d.getVar('SWUPDATE_CMS_CERT')
-            if not cms_cert:
+            cms_certs = (d.getVar('SWUPDATE_CMS_CERT') or "").split()
+            if not cms_certs:
                 bb.fatal("SWUPDATE_CMS_CERT is not set")
-            if not os.path.exists(cms_cert):
-                bb.fatal("SWUPDATE_CMS_CERT %s doesn't exist" % (cms_cert))
-            cms_key = d.getVar('SWUPDATE_CMS_KEY')
-            if not cms_key:
+            cms_keys = (d.getVar('SWUPDATE_CMS_KEY') or "").split()
+            if not cms_keys:
                 bb.fatal("SWUPDATE_CMS_KEY isn't set")
-            if not os.path.exists(cms_key):
-                bb.fatal("SWUPDATE_CMS_KEY %s doesn't exist" % (cms_key))
+            if len(cms_certs) != len(cms_keys):
+                bb.fatal("SWUPDATE_CMS_CERT and SWUPDATE_CMS_KEY must list the same number of entries (got %d certs, %d keys)" % (len(cms_certs), len(cms_keys)))
+            signer_args = []
+            for cms_cert, cms_key in zip(cms_certs, cms_keys):
+                if not os.path.exists(cms_cert):
+                    bb.fatal("SWUPDATE_CMS_CERT %s doesn't exist" % (cms_cert))
+                if not os.path.exists(cms_key):
+                    bb.fatal("SWUPDATE_CMS_KEY %s doesn't exist" % (cms_key))
+                signer_args += ["-signer", cms_cert, "-inkey", cms_key]
             cms_md = d.getVar('SWUPDATE_CMS_MD')
             md_args = ["-md", cms_md] if cms_md else []
-            signcmd = ["openssl", "cms", "-sign", "-in", sw_desc, "-out", sw_desc_sig, "-signer", cms_cert, "-inkey", cms_key] + \
+            signcmd = ["openssl", "cms", "-sign", "-in", sw_desc, "-out", sw_desc_sig] + signer_args + \
                         ["-outform", "DER", "-nosmimecap", "-binary"] + md_args + \
                         get_pwd_file_args(d, 'SWUPDATE_PASSWORD_FILE') + \
                         get_certfile_args(d)