Security-relevant changes:
* No (salted) passphrase hash send to the yubikey, only hash of the salt (as it was in the original implementation).
* Derive $k_luks with PBKDF2 from the yubikey $response (as the PBKDF2 salt) and the passphrase $k_user
(as the PBKDF2 password), so that if two-factor authentication is enabled
(a) a USB-MITM attack on the yubikey itself is not enough to break the system
(b) the potentially low-entropy $k_user is better protected against brute-force attacks
* Instead of using uuidgen, gather the salt (previously random uuid / uuid_r) directly from /dev/random.
* Length of the new salt in byte added as the parameter "saltLength", defaults to 16 byte.
Note: Length of the challenge is 64 byte, so saltLength > 64 may have no benefit over saltLengh = 64.
* Length of $k_luks derived with PBKDF2 in byte added as the parameter "keyLength", defaults to 64 byte.
Example: For a luks device with a 512-bit key, keyLength should be 64.
* Increase of the PBKDF2 iteration count per successful authentication added as the
parameter "iterationStep", defaults to 0.
Other changes:
* Add optional grace period before trying to find the yubikey, defaults to 2 seconds.
Full overview of the yubikey authentication process:
(1) Read $salt and $iterations from unencrypted device (UD).
(2) Calculate the $challenge from the $salt with a hash function.
Chosen instantiation: SHA-512($salt).
(3) Challenge the yubikey with the $challenge and receive the $response.
(4) Repeat three times:
(a) Prompt for the passphrase $k_user.
(b) Derive the key $k_luks for the luks device with a key derivation function from $k_user and $response.
Chosen instantiation: PBKDF2(HMAC-SHA-512, $k_user, $response, $iterations, keyLength).
(c) Try to open the luks device with $k_luks and escape loop (4) only on success.
(5) Proceed only if luks device was opened successfully, fail otherwise.
(6) Gather $new_salt from a cryptographically secure pseudorandom number generator
Chosen instantiation: /dev/random
(7) Calculate the $new_challenge from the $new_salt with the same hash function as (2).
(8) Challenge the yubikey with the $new_challenge and receive the $new_response.
(9) Derive the new key $new_k_luks for the luks device in the same manner as in (4) (b),
but with more iterations as given by iterationStep.
(10) Try to change the luks device's key $k_luks to $new_k_luks.
(11) If (10) was successful, write the $new_salt and the $new_iterations to the UD.
Note: $new_iterations = $iterations + iterationStep
Known (software) attack vectors:
* A MITM attack on the keyboard can recover $k_user. This, combined with a USB-MITM
attack on the yubikey for the $response (1) or the $new_response (2) will result in
(1) $k_luks being recovered,
(2) $new_k_luks being recovered.
* Any attacker with access to the RAM state of stage-1 at mid- or post-authentication
can recover $k_user, $k_luks, and $new_k_luks
* If an attacker has recovered $response or $new_response, he can perform a brute-force
attack on $k_user with it without the Yubikey needing to be present (using cryptsetup's
"luksOpen --verify-passphrase" oracle. He could even make a copy of the luks device's
luks header and run the brute-force attack without further access to the system.
* A USB-MITM attack on the yubikey will allow an attacker to attempt to brute-force
the yubikey's internal key ("shared secret") without it needing to be present anymore.
Credits:
* Florian Klien,
for the original concept and the reference implementation over at
https://github.com/flowolf/initramfs_ykfde
* Anthony Thysse,
for the reference implementation of accessing OpenSSL's PBKDF2 over at
http://www.ict.griffith.edu.au/anthony/software/pbkdf2.c
Rationale:
* The main reason for choosing to implement the PBA in accordance
with the Yubico documentation was to prevent a MITM-USB-attack
successfully recovering the new LUKS key.
* However, a MITM-USB-attacker can read user id and password when
they were entered for PBA, which allows him to recover the new
challenge after the PBA is complete, with which he can challenge
the Yubikey, decrypt the new AES blob and recover the LUKS key.
* Additionally, since the Yubikey shared secret is stored in the
same AES blob, after such an attack not only is the LUKS device
compromised, the Yubikey is as well, since the shared secret
has also been recovered by the attacker.
* Furthermore, with this method an attacker could also bruteforce
the AES blob, if he has access to the unencrypted device, which
would again compromise the Yubikey, should he be successful.
* Finally, with this method, once the LUKS key has been recovered
once, the encryption is permanently broken, while with the previous
system, the LUKS key itself it changed at every successful boot,
so recovering it once will not necessarily result in a permanent
breakage and will also not compromise the Yubikey itself (since
its secret is never stored anywhere but on the Yubikey itself).
Summary:
The current implementation opens up up vulnerability to brute-forcing
the AES blob, while retaining the current MITM-USB attack, additionally
making the consequences of this attack permanent and extending it to
the Yubikey itself.
switch-to-configuration.pl is currently hard-coded to assume that if a
unit is in the "auto-restart" state that something has gone wrong, but
this is not strictly true. For example, I run offlineimap as a oneshot
service restarting itself every minute (on success). NixOS currently
thinks that offlineimap has failed to start as it enters the
auto-restart state, because it doesn't consider why the unit failed.
This commit changes switch-to-configuration.pl to inspect the full
status of a unit in auto-restart state, and now only considers it failed
if the ExecMainStatus is non-zero.
Currently switch-to-configuration.pl uses system() calls to interact
with DBus. This can be error prone, especially when we are parsing
output that could change. In this commit, almost all calls to the
systemctl binary have been replaced with equivalent operations via DBus.
This is achieved by having multiple lines per storage file, one for each user (if the feature is enabled); each of these
lines has the same format as would be the case for the userless authentication, except that they are prepended with a
SHA-512 of the user's id.
'YubiKey Integration for Full Disk Encryption Pre-Boot Authentication (Copyright) Yubico, 2011 Version: 1.1'.
Used binaries:
* uuidgen - for generation of random sequence numbers
* ykchalresp - for challenging a Yubikey
* ykinfo - to check if a Yubikey is plugged in at boot (fallback to passphrase authentication otherwise)
* openssl - for calculation of SHA-1, HMAC-SHA-1, as well as AES-256-CTR (de/en)cryption
Main differences to the specification mentioned above:
* No user management (yet), only one password+yubikey per LUKS device
* SHA-512 instead of CRC-16 for checksum
Main differences to the previous implementation:
* Instead of changing the key slot of the LUKS device each boot,
the actual key for the LUKS device will be encrypted itself
* Since the response for the new challenge is now calculated
locally with openssl, the MITM-USB-attack with which previously
an attacker could obtain the new response (that was used as the new
encryption key for the LUKS device) by listening to the
Yubikey has ideally become useless (as long as uuidgen can
successfuly generate new random sequence numbers).
Remarks:
* This is not downwards compatible to the previous implementation
This will allow overriding package-provided units, or overriding only a
specific instance of a unit template.
Signed-off-by: Shea Levy <shea@shealevy.com>
This required some changes to systemd unit handling:
* Add an option to specify that a unit is just a symlink
* Allow specified units to overwrite systemd-provided ones
* Have gettys.target require autovt@1.service instead of getty@1.service
Signed-off-by: Shea Levy <shea@shealevy.com>
Some programs (notably the Java Runtime Environment) expect to be able
to extract the name of the time zone from the target of the
/etc/localtime symlink. That doesn't work if /etc/localtime is a
symlink to /etc/static/localtime. So make it a direct symlink.
You can now say:
systemd.containers.foo.config =
{ services.openssh.enable = true;
services.openssh.ports = [ 2022 ];
users.extraUsers.root.openssh.authorizedKeys.keys = [ "ssh-dss ..." ];
};
which defines a NixOS instance with the given configuration running
inside a lightweight container.
You can also manage the configuration of the container independently
from the host:
systemd.containers.foo.path = "/nix/var/nix/profiles/containers/foo";
where "path" is a NixOS system profile. It can be created/updated by
doing:
$ nix-env --set -p /nix/var/nix/profiles/containers/foo \
-f '<nixos>' -A system -I nixos-config=foo.nix
The container configuration (foo.nix) should define
boot.isContainer = true;
to optimise away the building of a kernel and initrd. This is done
automatically when using the "config" route.
On the host, a lightweight container appears as the service
"container-<name>.service". The container is like a regular NixOS
(virtual) machine, except that it doesn't have its own kernel. It has
its own root file system (by default /var/lib/containers/<name>), but
shares the Nix store of the host (as a read-only bind mount). It also
has access to the network devices of the host.
Currently, if the configuration of the container changes, running
"nixos-rebuild switch" on the host will cause the container to be
rebooted. In the future we may want to send some message to the
container so that it can activate the new container configuration
without rebooting.
Containers are not perfectly isolated yet. In particular, the host's
/sys/fs/cgroup is mounted (writable!) in the guest.
The major changes are:
* The evaluation is now driven by the declared options. In
particular, this fixes the long-standing problem with lack of
laziness of disabled option definitions. Thus, a configuration like
config = mkIf false {
environment.systemPackages = throw "bla";
};
will now evaluate without throwing an error. This also improves
performance since we're not evaluating unused option definitions.
* The implementation of properties is greatly simplified.
* There is a new type constructor "submodule" that replaces
"optionSet". Unlike "optionSet", "submodule" gets its option
declarations as an argument, making it more like "listOf" and other
type constructors. A typical use is:
foo = mkOption {
type = type.attrsOf (type.submodule (
{ config, ... }:
{ bar = mkOption { ... };
xyzzy = mkOption { ... };
}));
};
Existing uses of "optionSet" are automatically mapped to
"submodule".
* Modules are now checked for unsupported attributes: you get an error
if a module contains an attribute other than "config", "options" or
"imports".
* The new implementation is faster and uses much less memory.