]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
mkosi: add command genkey 633/head
authorJoerg Behrmann <behrmann@physik.fu-berlin.de>
Mon, 18 Jan 2021 18:27:16 +0000 (19:27 +0100)
committerJoerg Behrmann <behrmann@physik.fu-berlin.de>
Sun, 24 Jan 2021 11:31:22 +0000 (12:31 +0100)
.gitignore
NEWS.md
mkosi.md
mkosi/__init__.py
tests/test_config_parser.py

index 970c32fd4855fbf984cd865b660799506a5c6c3b..cf062673318373644efa4cf1c5f83c6c7677eea4 100644 (file)
@@ -16,4 +16,6 @@
 /mkosi.nspawn
 /mkosi.rootpw
 /mkosi.default
+/mkosi.secure-boot.key
+/mkosi.secure-boot.crt
 __pycache__
diff --git a/NEWS.md b/NEWS.md
index c73ea6381b2407cf5235140e09e4122137dd7ad1..79012dc1f705e40ea7c26db4c9c49102dda7b86b 100644 (file)
--- a/NEWS.md
+++ b/NEWS.md
 - `--source-file-transfer` and `--source-file-transfer-final` now accept the
   empty string as an argument which can be used to override a previously
   set value.
+- Add a new command `genkey` to mkosi that can be used to generate secure boot
+  keys for usage with mkosi's `--secure-boot` options. The number of days the
+  keys should remain valid can be specified via `--secure-boot-valid-days=` and
+  their CN via `--secure-boot-common-name=`
 
 ## v9
 
index de1ef349eb541fd809c250342456cea9afc7d28e..14ef7bff8f233c778ac947c820cdb1aa3dfd15a3 100644 (file)
--- a/mkosi.md
+++ b/mkosi.md
@@ -296,6 +296,17 @@ details see the table below.
 : Path to the X.509 file containing the certificate for the signed
   UEFI kernel image, if `--secure-boot` is used.
 
+`--secure-boot-common-name=`
+
+: Common name to be used when generating SecureBoot keys via mkosi's `genkey`
+  command. Defaults to `mkosi of %u`, where `%u` expands to the username of the
+  user invoking mkosi.
+
+`--secure-boot-valid-days=`
+
+: Number of days that the keys should remain valid when generating SecureBoot
+  keys via mkosi's `genkey` command. Defaults to two years (730 days).
+
 `--read-only`
 
 : Make root file system read-only. Only applies to `gpt_ext4`,
@@ -807,6 +818,8 @@ which settings file options.
 | `--secure-boot`                   | `[Output]`              | `SecureBoot=`                 |
 | `--secure-boot-key=`              | `[Output]`              | `SecureBootKey=`              |
 | `--secure-boot-certificate=`      | `[Output]`              | `SecureBootCertificate=`      |
+| `--secure-boot-valid-days=`       | `[Output]`              | `SecureBootValidDays=`        |
+| `--secure-boot-common-name=`      | `[Output]`              | `SecureBootCommonName=`       |
 | `--read-only`                     | `[Output]`              | `ReadOnly=`                   |
 | `--encrypt=`                      | `[Output]`              | `Encrypt=`                    |
 | `--verity=`                       | `[Output]`              | `Verity=`                     |
index e582415a0c6dcdbc4ef907b2154c79190a8837fb..e72e4b2a6c8d4c11048a9d4620eab1f66bfe6afe 100644 (file)
@@ -8,6 +8,7 @@ import copy
 import crypt
 import ctypes
 import ctypes.util
+import datetime
 import enum
 import errno
 import fcntl
@@ -76,7 +77,7 @@ else:
 MKOSI_COMMANDS_CMDLINE = ("build", "shell", "boot", "qemu", "ssh")
 MKOSI_COMMANDS_NEED_BUILD = ("shell", "boot", "qemu")
 MKOSI_COMMANDS_SUDO = ("build", "clean", "shell", "boot", "qemu")
-MKOSI_COMMANDS = ("build", "clean", "help", "summary") + MKOSI_COMMANDS_CMDLINE
+MKOSI_COMMANDS = ("build", "clean", "help", "summary", "genkey") + MKOSI_COMMANDS_CMDLINE
 
 DRACUT_SYSTEMD_EXTRAS = [
     "/usr/lib/systemd/systemd-veritysetup",
@@ -4258,6 +4259,9 @@ class ArgumentParserMkosi(argparse.ArgumentParser):
                 continue
             # replace arguments referencing files with the file content
             try:
+                # This used to use configparser.ConfigParser before, but
+                # ConfigParser's interpolation clashes with systemd style
+                # specifier, e.g. %u for user, since both use % as a sigil.
                 config = configparser.RawConfigParser(delimiters="=")
                 config.optionxform = str  # type: ignore
                 with open(arg_string[1:]) as args_file:
@@ -4367,6 +4371,18 @@ def create_parser() -> ArgumentParserMkosi:
     )
     group.add_argument("--secure-boot-key", help="UEFI SecureBoot private key in PEM format", metavar="PATH")
     group.add_argument("--secure-boot-certificate", help="UEFI SecureBoot certificate in X509 format", metavar="PATH")
+    group.add_argument(
+        "--secure-boot-valid-days",
+        help="Number of days UEFI SecureBoot keys should be valid when generating keys",
+        metavar="DAYS",
+        default="730",
+    )
+    group.add_argument(
+        "--secure-boot-common-name",
+        help="Template for the UEFI SecureBoot CN when generating keys",
+        metavar="CN",
+        default="mkosi of %u",
+    )
     group.add_argument(
         "--read-only",
         action=BooleanAction,
@@ -6334,6 +6350,57 @@ def run_ssh(args: CommandLineArguments) -> None:
         )
 
 
+def generate_secure_boot_key(args: CommandLineArguments) -> NoReturn:
+    """Generate secure boot keys using openssl"""
+    args.secure_boot_key = args.secure_boot_key or "./mkosi.secure-boot.key"
+    args.secure_boot_certificate = args.secure_boot_certificate or "./mkosi.secure-boot.crt"
+
+    keylength = 2048
+    expiration_date = datetime.date.today() + datetime.timedelta(int(args.secure_boot_valid_days))
+    cn = expand_specifier(args.secure_boot_common_name)
+
+    for f in (args.secure_boot_key, args.secure_boot_certificate):
+        if os.path.exists(f) and not args.force:
+            die(
+                dedent(
+                    f"""\
+                    {f} already exists.
+                    If you are sure you want to generate new secure boot keys
+                    remove {args.secure_boot_key} and {args.secure_boot_certificate} first.
+                    """
+                )
+            )
+
+    MkosiPrinter.print_step(f"Generating secure boot keys rsa:{keylength} for CN `{cn}`.")
+    MkosiPrinter.info(
+        dedent(
+            f"""
+            The keys will expire in {args.secure_boot_valid_days} days ({expiration_date:%A %d. %B %Y}).
+            Remember to roll them over to new ones before then.
+            """
+        )
+    )
+
+    cmd = [
+        "openssl",
+        "req",
+        "-new",
+        "-x509",
+        "-newkey",
+        f"rsa:{keylength}",
+        "-keyout",
+        args.secure_boot_key,
+        "-out",
+        args.secure_boot_certificate,
+        "-days",
+        str(args.secure_boot_valid_days),
+        "-subj",
+        f"/CN={cn}/",
+    ]
+
+    os.execvp(cmd[0], cmd)
+
+
 def expand_paths(paths: List[str]) -> List[str]:
     if not paths:
         return []
@@ -6370,6 +6437,12 @@ def prepend_to_environ_path(paths: List[str]) -> None:
         os.environ["PATH"] = new_path + ":" + original_path
 
 
+def expand_specifier(s: str) -> str:
+    user = os.getenv("SUDO_USER") or os.getenv("USER")
+    assert user is not None
+    return s.replace("%u", user)
+
+
 def needs_build(args: CommandLineArguments) -> bool:
     return args.verb == "build" or (not os.path.exists(args.output) and args.verb in MKOSI_COMMANDS_NEED_BUILD)
 
@@ -6379,6 +6452,9 @@ def run_verb(args: CommandLineArguments) -> None:
 
     prepend_to_environ_path(args.extra_search_paths)
 
+    if args.verb == "genkey":
+        generate_secure_boot_key(args)
+
     if args.verb in MKOSI_COMMANDS_SUDO:
         check_root()
         unlink_output(args)
index f85f141f09ad8205287c1c1b7445bb0cb8a6ac6b..a4150390511191b2c8c9677b0fd5cbb194c4e215 100644 (file)
@@ -96,6 +96,8 @@ class MkosiConfig(object):
             "secure_boot": False,
             "secure_boot_certificate": None,
             "secure_boot_key": None,
+            "secure_boot_common_name": "mkosi of %u",
+            "secure_boot_valid_days": "730",
             "sign": False,
             "skeleton_trees": [],
             "source_file_transfer": None,
@@ -210,6 +212,10 @@ class MkosiConfig(object):
                 self.reference_config[job_name]["secure_boot_key"] = mk_config_output["SecureBootKey"]
             if "SecureBootCertificate" in mk_config_output:
                 self.reference_config[job_name]["secure_boot_certificate"] = mk_config_output["SecureBootCertificate"]
+            if "SecureBootCommonName" in mk_config_output:
+                self.reference_config[job_name]["secure_boot_common_name"] = mk_config_output["SecureBootCommonName"]
+            if "SecureBootValidDays" in mk_config_output:
+                self.reference_config[job_name]["secure_boot_valid_days"] = mk_config_output["SecureBootValidDays"]
             if "ReadOnly" in mk_config_output:
                 self.reference_config[job_name]["read_only"] = mk_config_output["ReadOnly"]
             if "Encrypt" in mk_config_output:
@@ -490,6 +496,8 @@ class MkosiConfigManyParams(MkosiConfigOne):
                 "SecureBoot": False,
                 "SecureBootKey": "/foo.pem",
                 "SecureBootCertificate": "bar.crt",
+                "SecureBootCommonName": "mkosi for %u",
+                "SecureBootValidDays": "730",
                 "ReadOnly": False,
                 "Encrypt": "all",
                 "Verity": False,