From 94a36f74d0d78bcf7392157bea7bda12f941fdb1 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Wed, 27 May 2026 03:19:26 +0200 Subject: [PATCH] nixos/dhparams: remove This module was deprecated in 26.05 and is being removed in 26.11. Generating your own dhparams has been obsoleted by RFC 7919 (2016). DHE itself has been obsoleted by ECHDE (RFC8422, 2018) and Hybrid PQ (draft-ietf-tls-ecdhe-mlkem, 2026) key exchanges. TLS 1.3 (RFC8446, 2018) stopped defining any DHE cipher suites and lists this as a major difference from TLS 1.2. --- nixos/modules/module-list.nix | 1 - nixos/modules/rename.nix | 3 + nixos/modules/security/dhparams.nix | 223 ---------------------------- nixos/tests/all-tests.nix | 1 - nixos/tests/dhparams.nix | 143 ------------------ 5 files changed, 3 insertions(+), 368 deletions(-) delete mode 100644 nixos/modules/security/dhparams.nix delete mode 100644 nixos/tests/dhparams.nix diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index c57b627e875c..2778c6f295ce 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -394,7 +394,6 @@ ./security/ca.nix ./security/chromium-suid-sandbox.nix ./security/default.nix - ./security/dhparams.nix ./security/doas.nix ./security/duosec.nix ./security/google_oslogin.nix diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix index 4bca9ffcaaa0..bacb7275cea0 100644 --- a/nixos/modules/rename.nix +++ b/nixos/modules/rename.nix @@ -125,6 +125,9 @@ in (mkRemovedOptionModule [ "programs" "yabar" ] "programs.yabar has been removed from NixOS. This is because the yabar repository has been archived upstream." ) + (mkRemovedOptionModule [ "security" "dhparams" ] '' + The security.dhparams module has been removed as RFC 7919 has shown that generating your own params is problematic. + '') (mkRemovedOptionModule [ "security" "hideProcessInformation" ] '' The hidepid module was removed, since the underlying machinery is broken when using cgroups-v2. diff --git a/nixos/modules/security/dhparams.nix b/nixos/modules/security/dhparams.nix deleted file mode 100644 index a18af9f85ae7..000000000000 --- a/nixos/modules/security/dhparams.nix +++ /dev/null @@ -1,223 +0,0 @@ -{ - config, - lib, - options, - pkgs, - ... -}: - -let - inherit (lib) literalExpression mkOption types; - cfg = config.security.dhparams; - opt = options.security.dhparams; - - bitType = types.addCheck types.int (b: b >= 16) // { - name = "bits"; - description = "integer of at least 16 bits"; - }; - - paramsSubmodule = - { name, config, ... }: - { - options.bits = mkOption { - type = bitType; - default = cfg.defaultBitSize; - defaultText = literalExpression "config.${opt.defaultBitSize}"; - description = '' - The bit size for the prime that is used during a Diffie-Hellman - key exchange. - ''; - }; - - options.path = mkOption { - type = types.path; - readOnly = true; - description = '' - The resulting path of the generated Diffie-Hellman parameters - file for other services to reference. This could be either a - store path or a file inside the directory specified by - {option}`security.dhparams.path`. - ''; - }; - - config.path = - let - generated = pkgs.runCommand "dhparams-${name}.pem" { - nativeBuildInputs = [ pkgs.openssl ]; - } "openssl dhparam -out \"$out\" ${toString config.bits}"; - in - if cfg.stateful then "${cfg.path}/${name}.pem" else generated; - }; - -in -{ - options = { - security.dhparams = { - enable = mkOption { - type = types.bool; - default = false; - description = '' - Whether to generate new DH params and clean up old DH params. - ''; - }; - - params = mkOption { - type = - with types; - let - coerce = bits: { inherit bits; }; - in - attrsOf (coercedTo int coerce (submodule paramsSubmodule)); - default = { }; - example = lib.literalExpression "{ nginx.bits = 3072; }"; - description = '' - Diffie-Hellman parameters to generate. - - The value is the size (in bits) of the DH params to generate. The - generated DH params path can be found in - `config.security.dhparams.params.«name».path`. - - ::: {.note} - The name of the DH params is taken as being the name of - the service it serves and the params will be generated before the - said service is started. - ::: - - ::: {.warning} - If you are removing all dhparams from this list, you - have to leave {option}`security.dhparams.enable` for at - least one activation in order to have them be cleaned up. This also - means if you rollback to a version without any dhparams the - existing ones won't be cleaned up. Of course this only applies if - {option}`security.dhparams.stateful` is - `true`. - ::: - - ::: {.note} - **For module implementers:** It's recommended - to not set a specific bit size here, so that users can easily - override this by setting - {option}`security.dhparams.defaultBitSize`. - ::: - ''; - }; - - stateful = mkOption { - type = types.bool; - default = true; - description = '' - Whether generation of Diffie-Hellman parameters should be stateful or - not. If this is enabled, PEM-encoded files for Diffie-Hellman - parameters are placed in the directory specified by - {option}`security.dhparams.path`. Otherwise the files are - created within the Nix store. - - ::: {.note} - If this is `false` the resulting store - path will be non-deterministic and will be rebuilt every time the - `openssl` package changes. - ::: - ''; - }; - - defaultBitSize = mkOption { - type = bitType; - default = 2048; - description = '' - This allows to override the default bit size for all of the - Diffie-Hellman parameters set in - {option}`security.dhparams.params`. - ''; - }; - - path = mkOption { - type = types.str; - default = "/var/lib/dhparams"; - description = '' - Path to the directory in which Diffie-Hellman parameters will be - stored. This only is relevant if - {option}`security.dhparams.stateful` is - `true`. - ''; - }; - }; - }; - - config = lib.mkMerge [ - (lib.mkIf cfg.enable { - warnings = [ - '' - The `security.dhparams` module is deprecated and scheduled for removal in NixOS 26.11. - Generating your own params has been shown to be problematic in RFC 7919 (2016). - - Remove any uses of DHE and migrate to ECDHE (RFC 8422, 2018) and - Hybrid PQ (draft-ietf-tls-ecdhe-mlkem, 2026) key exchange algorithms. - '' - ]; - }) - (lib.mkIf (cfg.enable && cfg.stateful) { - systemd.services = { - dhparams-init = { - description = "Clean Up Old Diffie-Hellman Parameters"; - - # Clean up even when no DH params is set - wantedBy = [ "multi-user.target" ]; - - serviceConfig.RemainAfterExit = true; - serviceConfig.Type = "oneshot"; - - script = '' - if [ ! -d ${cfg.path} ]; then - mkdir -p ${cfg.path} - fi - - # Remove old dhparams - for file in ${cfg.path}/*; do - if [ ! -f "$file" ]; then - continue - fi - ${lib.concatStrings ( - lib.mapAttrsToList ( - name: - { bits, path, ... }: - '' - if [ "$file" = ${lib.escapeShellArg path} ] && \ - ${pkgs.openssl}/bin/openssl dhparam -in "$file" -text \ - | head -n 1 | grep "(${toString bits} bit)" > /dev/null; then - continue - fi - '' - ) cfg.params - )} - rm "$file" - done - - # TODO: Ideally this would be removing the *former* cfg.path, though - # this does not seem really important as changes to it are quite - # unlikely - rmdir --ignore-fail-on-non-empty ${cfg.path} - ''; - }; - } - // lib.mapAttrs' ( - name: - { bits, path, ... }: - lib.nameValuePair "dhparams-gen-${name}" { - description = "Generate Diffie-Hellman Parameters for ${name}"; - after = [ "dhparams-init.service" ]; - before = [ "${name}.service" ]; - requiredBy = [ "${name}.service" ]; - wantedBy = [ "multi-user.target" ]; - unitConfig.ConditionPathExists = "!${path}"; - serviceConfig.Type = "oneshot"; - script = '' - mkdir -p ${lib.escapeShellArg cfg.path} - ${pkgs.openssl}/bin/openssl dhparam -out ${lib.escapeShellArg path} \ - ${toString bits} - ''; - } - ) cfg.params; - }) - ]; - -} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index f9ecf72845ed..edd511c2bbd3 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -455,7 +455,6 @@ in dependency-track = runTest ./dependency-track.nix; devpi-server = runTest ./devpi-server.nix; dex-oidc = runTest ./dex-oidc.nix; - dhparams = runTest ./dhparams.nix; dictd = runTest ./dictd.nix; disable-installer-tools = runTest ./disable-installer-tools.nix; discourse = runTest { diff --git a/nixos/tests/dhparams.nix b/nixos/tests/dhparams.nix deleted file mode 100644 index 89df5b1a0154..000000000000 --- a/nixos/tests/dhparams.nix +++ /dev/null @@ -1,143 +0,0 @@ -{ - name = "dhparams"; - - nodes.machine = - { pkgs, ... }: - { - security.dhparams.enable = true; - environment.systemPackages = [ pkgs.openssl ]; - - specialisation = { - gen1.configuration = - { config, ... }: - { - security.dhparams.params = { - # Use low values here because we don't want the test to run for ages. - foo.bits = 1024; - # Also use the old format to make sure the type is coerced in the right - # way. - bar = 1025; - }; - - systemd.services.foo = { - description = "Check systemd Ordering"; - wantedBy = [ "multi-user.target" ]; - before = [ "shutdown.target" ]; - conflicts = [ "shutdown.target" ]; - unitConfig = { - # This is to make sure that the dhparams generation of foo occurs - # before this service so we need this service to start as early as - # possible to provoke a race condition. - DefaultDependencies = false; - - # We check later whether the service has been started or not. - ConditionPathExists = config.security.dhparams.params.foo.path; - }; - serviceConfig.Type = "oneshot"; - serviceConfig.RemainAfterExit = true; - # The reason we only provide an ExecStop here is to ensure that we don't - # accidentally trigger an error because a file system is not yet ready - # during very early startup (we might not even have the Nix store - # available, for example if future changes in NixOS use systemd mount - # units to do early file system initialisation). - serviceConfig.ExecStop = "${pkgs.coreutils}/bin/true"; - }; - }; - gen2.configuration = { - security.dhparams.params.foo.bits = 1026; - }; - gen3.configuration = { }; - gen4.configuration = { - security.dhparams.stateful = false; - security.dhparams.params.foo2.bits = 1027; - security.dhparams.params.bar2.bits = 1028; - }; - gen5.configuration = { - security.dhparams.defaultBitSize = 1029; - security.dhparams.params.foo3 = { }; - security.dhparams.params.bar3 = { }; - }; - }; - }; - - testScript = - { nodes, ... }: - let - getParamPath = - gen: name: - let - node = "gen${toString gen}"; - in - nodes.machine.config.specialisation.${node}.configuration.security.dhparams.params.${name}.path; - - switchToGeneration = - gen: - let - switchCmd = "${nodes.machine.config.system.build.toplevel}/specialisation/gen${toString gen}/bin/switch-to-configuration test"; - in - '' - with machine.nested("switch to generation ${toString gen}"): - machine.succeed("${switchCmd}") - ''; - - in - '' - import re - - - def assert_param_bits(path, bits): - with machine.nested(f"check bit size of {path}"): - output = machine.succeed(f"openssl dhparam -in {path} -text") - pattern = re.compile(r"^\s*DH Parameters:\s+\((\d+)\s+bit\)\s*$", re.M) - match = pattern.match(output) - if match is None: - raise Exception("bla") - if match[1] != str(bits): - raise Exception(f"bit size should be {bits} but it is {match[1]} instead.") - - machine.wait_for_unit("multi-user.target") - ${switchToGeneration 1} - - with subtest("verify startup order"): - machine.succeed("systemctl is-active foo.service") - - with subtest("check bit sizes of dhparam files"): - assert_param_bits("${getParamPath 1 "foo"}", 1024) - assert_param_bits("${getParamPath 1 "bar"}", 1025) - - ${switchToGeneration 2} - - with subtest("check whether bit size has changed"): - assert_param_bits("${getParamPath 2 "foo"}", 1026) - - with subtest("ensure that dhparams file for 'bar' was deleted"): - machine.fail("test -e ${getParamPath 1 "bar"}") - - ${switchToGeneration 3} - - with subtest("ensure that 'security.dhparams.path' has been deleted"): - machine.fail("test -e ${nodes.machine.config.specialisation.gen3.configuration.security.dhparams.path}") - - ${switchToGeneration 4} - - with subtest("check bit sizes dhparam files"): - assert_param_bits( - "${getParamPath 4 "foo2"}", 1027 - ) - assert_param_bits( - "${getParamPath 4 "bar2"}", 1028 - ) - - with subtest("check whether dhparam files are in the Nix store"): - machine.succeed( - "expr match ${getParamPath 4 "foo2"} ${builtins.storeDir}", - "expr match ${getParamPath 4 "bar2"} ${builtins.storeDir}", - ) - - ${switchToGeneration 5} - - with subtest("check whether defaultBitSize works as intended"): - assert_param_bits("${getParamPath 5 "foo3"}", 1029) - assert_param_bits("${getParamPath 5 "bar3"}", 1029) - ''; -}