sshd: Support multiple host keys

The option services.openssh.hostKeys now allows specifying multiple
host keys.  The default value enables both a DSA and ECDSA key.
(Clients by default will use the ECDSA key, unless known_hosts already
has a DSA key for that host.)  To use only an ECDSA key, you can say:

  services.openssh.hostKeys =
    [ { path = "/etc/ssh/ssh_host_ecdsa_key";
        type = "ecdsa";
        bits = 521;
      }
    ];
This commit is contained in:
Eelco Dolstra 2013-08-24 01:01:10 +02:00
parent c9b9f7ee1d
commit 9771f0c96c

View File

@ -15,29 +15,6 @@ let
v == "forced-commands-only" || v == "forced-commands-only" ||
v == "no"; v == "no";
hostKeyTypeNames = {
dsa1024 = "dsa"; # DSA has a key size limitation due to standards
rsa3072 = "rsa";
ecdsa521 = "ecdsa";
};
hostKeyTypeBits = {
dsa1024 = 1024; # =80 bits of security
rsa3072 = 3072; # =128 bits of security
ecdsa521 = 521; # =256 bits of security
};
# equivalent to 112 bit of security strength. Anything below this is very unsafe.
hostKeyTypeSafeBits = {
dsa1024 = 2048;
rsa3072 = 2048;
ecdsa521 = 255;
};
hktn = attrByPath [cfg.hostKeyType] (throw "unknown host key type `${cfg.hostKeyType}'") hostKeyTypeNames;
hktb = attrByPath [cfg.hostKeyType] (throw "unknown host key type `${cfg.hostKeyType}'") hostKeyTypeBits;
hktsb = attrByPath [cfg.hostKeyType] (throw "unknown host key type `${cfg.hostKeyType}'") hostKeyTypeSafeBits;
knownHosts = map (h: getAttr h cfg.knownHosts) (attrNames cfg.knownHosts); knownHosts = map (h: getAttr h cfg.knownHosts) (attrNames cfg.knownHosts);
knownHostsFile = pkgs.writeText "ssh_known_hosts" ( knownHostsFile = pkgs.writeText "ssh_known_hosts" (
@ -176,22 +153,23 @@ in
''; '';
}; };
hostKeyType = mkOption { hostKeys = mkOption {
default = "dsa1024"; default =
[ { path = "/etc/ssh/ssh_host_dsa_key";
type = "dsa";
bits = 1024;
}
{ path = "/etc/ssh/ssh_host_ecdsa_key";
type = "ecdsa";
bits = 521;
}
];
description = '' description = ''
Type of host key to generate (dsa1024/rsa3072/ecdsa521), if NixOS can automatically generate SSH host keys. This option
the file specified by <literal>hostKeyPath</literal> does not specifies the path, type and size of each key. See
exist when the service starts. <citerefentry><refentrytitle>ssh-keygen</refentrytitle>
''; <manvolnum>1</manvolnum></citerefentry> for supported types
}; and sizes.
hostKeyPath = mkOption {
default = "/etc/ssh/ssh_host_${hktn}_key";
description = ''
Path to the server's private key. If there is no key file
on this path, it will be generated when the service is
started for the first time. Otherwise, the ssh daemon will
use the specified key directly in-place.
''; '';
}; };
@ -252,7 +230,7 @@ in
###### implementation ###### implementation
config = mkIf config.services.openssh.enable { config = mkIf cfg.enable {
users.extraUsers = singleton users.extraUsers = singleton
{ name = "sshd"; { name = "sshd";
@ -286,21 +264,16 @@ in
'' ''
mkdir -m 0755 -p /etc/ssh mkdir -m 0755 -p /etc/ssh
if ! test -f ${cfg.hostKeyPath}; then ${flip concatMapStrings cfg.hostKeys (k: ''
ssh-keygen -t ${hktn} -b ${toString hktb} -f ${cfg.hostKeyPath} -N "" if ! [ -f "${k.path}" ]; then
fi ssh-keygen -t "${k.type}" -b "${toString k.bits}" -f "${k.path}" -N ""
fi
result=$(ssh-keygen -lf ${cfg.hostKeyPath}|awk '{ print ($1>=${toString hktsb}?1:0)}') '')}
if [ "$result" -ne "1" ]; then
ERROR="SECURITY ALERT: SSH Host Key is too weak. Generate a strong key NOW."
echo "$ERROR"
echo "$ERROR" > /dev/console
fi
''; '';
serviceConfig = serviceConfig =
{ ExecStart = { ExecStart =
"${pkgs.openssh}/sbin/sshd -h ${cfg.hostKeyPath} " + "${pkgs.openssh}/sbin/sshd " +
"-f ${pkgs.writeText "sshd_config" cfg.extraConfig}"; "-f ${pkgs.writeText "sshd_config" cfg.extraConfig}";
Restart = "always"; Restart = "always";
Type = "forking"; Type = "forking";
@ -351,6 +324,10 @@ in
PrintMotd no # handled by pam_motd PrintMotd no # handled by pam_motd
AuthorizedKeysFile ${toString cfg.authorizedKeysFiles} AuthorizedKeysFile ${toString cfg.authorizedKeysFiles}
${flip concatMapStrings cfg.hostKeys (k: ''
HostKey ${k.path}
'')}
''; '';
assertions = [{ assertion = if cfg.forwardX11 then cfgc.setXAuthLocation else true; assertions = [{ assertion = if cfg.forwardX11 then cfgc.setXAuthLocation else true;