Просмотр исходного кода

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 1 неделя назад
Родитель
Сommit
c8ed5a3523
2 измененных файлов с 25 добавлено и 12 удалено
  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 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
   * (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
     `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
     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
     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:
 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))
                 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]
             signcmd = ["openssl", "dgst", "-sha256", "-sign", privkey] + get_pwd_file_args(d, 'SWUPDATE_PASSWORD_FILE') + ["-out", sw_desc_sig, sw_desc]
         elif signing == "CMS":
         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")
                 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")
                 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')
             cms_md = d.getVar('SWUPDATE_CMS_MD')
             md_args = ["-md", cms_md] if cms_md else []
             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 + \
                         ["-outform", "DER", "-nosmimecap", "-binary"] + md_args + \
                         get_pwd_file_args(d, 'SWUPDATE_PASSWORD_FILE') + \
                         get_pwd_file_args(d, 'SWUPDATE_PASSWORD_FILE') + \
                         get_certfile_args(d)
                         get_certfile_args(d)