On continue dans la série systemd.
Hier c'était la Saint Patrick . En cette occasion, la Guinness coule à flots et ça m'a donné la force de plonger dans le code de systemd (ou plutôt l'inconscience !).
Malgré ce que dit le site du projet , si l'on souhaite s'aventurer dans les méandres du code il n'existe pas de doc très claire et le code est relativement peu documenté. C'est vraiment le système D !
J'ai récemment troqué mon disque mécanique pour un disque SSD dans mon ordinateur portable. Pour sécuriser mes données sur mon disque à plateaux, j'utilisais un conteneur encfs . Le dévérouillage de mon conteneur était pluggué à mon authentification via pam_mount . Je m'étais basé sur ce guide . Malheureusement, encfs étant basé sur FUSE les performances s'en ressentent. Sur un disque mécanique ce n'est pas bien grave puisque j'arrivais à des débits de 60Mo/s en écriture, mais sur un SSD c'est dommage.
Je suis donc passé sur dm-crypt qui agit directement dans le kernel Linux. Et pour le coup les perfs sont au rendez-vous puisque j'atteints désormais des débit de l'ordre de 600Mo/s ! Le problème, c'est que le dévérouillage du disque se fait durant la phase de boot. Du coup je dois entrer mon mot de passe plusieurs fois et ceux qui me connaissent savent à quel point j'ai la flemme !
Sur debian, cryptsetup implémente une option sympa dans le crypttab nommée keyscript. Cette option permet d'appeler un script qui sera appelé pour dévérouiller le device.
Malheureusement, systemd a décidé de ne pas l'implémenter pour de sombres raisons comme on peut le voir ici ou bien là . Un patch a pourtant été proposé , mais il ne convient pas à l'upstream.
J'ai donc décidé d'implémenter un patch également pour ajouter cette fois une option nommée keyhandler (puisque visiblement Lennart Poettering est allergique aux scripts...).
J'ai préparé deux patchs : un pour debian Wheezy, et un pour debian Jessie
Le patch pour Jessie ressemble à ça :
diff --git a/systemd-204.orig/src/cryptsetup/cryptsetup.c b/systemd-204/src/cryptsetup/cryptsetup.c
index 2f7a68a..61d3ef8 100644
--- a/systemd-204.orig/src/cryptsetup/cryptsetup.c
+++ b/systemd-204/src/cryptsetup/cryptsetup.c
@@ -40,6 +40,7 @@ static unsigned opt_key_size = 0;
static unsigned opt_keyfile_size = 0;
static unsigned opt_keyfile_offset = 0;
static char *opt_hash = NULL;
+static char *opt_keyhandler = NULL;
static unsigned opt_tries = 0;
static bool opt_readonly = false;
static bool opt_verify = false;
@@ -106,6 +107,23 @@ static int parse_one_option(const char *option) {
free(opt_hash);
opt_hash = t;
+ } else if (startswith(option, "keyhandler=")) {
+ char *t;
+
+ if (!option[11] || !path_is_absolute(option+11)) {
+ log_error("keyhandler= path is invalid, ignoring.");
+ free(opt_keyhandler);
+ opt_keyhandler = NULL;
+ return 0;
+ }
+
+ t = strdup(option+11);
+ if (!t)
+ return -ENOMEM;
+
+ free(opt_keyhandler);
+ opt_keyhandler = t;
+
} else if (startswith(option, "tries=")) {
if (safe_atou(option+6, &opt_tries) < 0) {
@@ -292,22 +310,24 @@ int main(int argc, char *argv[]) {
goto finish;
}
- if (argc >= 5 &&
- argv[4][0] &&
- !streq(argv[4], "-") &&
- !streq(argv[4], "none")) {
+ if (argc >= 6 && argv[5][0] && !streq(argv[5], "-")) {
+ if (parse_options(argv[5]) < 0)
+ goto finish;
+ }
- if (!path_is_absolute(argv[4]))
+ if (argc >= 5) {
+ if (opt_keyhandler)
+ key_file = argv[4]; /* used as arg to keyscript */
+ else if (argv[4][0] &&
+ !streq(argv[4], "-") &&
+ !streq(argv[4], "none"))
+ /* do nothing */;
+ else if (!path_is_absolute(argv[4]))
log_error("Password file path %s is not absolute. Ignoring.", argv[4]);
else
key_file = argv[4];
}
- if (argc >= 6 && argv[5][0] && !streq(argv[5], "-")) {
- if (parse_options(argv[5]) < 0)
- goto finish;
- }
-
/* A delicious drop of snake oil */
mlockall(MCL_FUTURE);
@@ -390,6 +410,13 @@ int main(int argc, char *argv[]) {
strv_free(passwords);
passwords = NULL;
+ /* We only launch the handler once */
+ if (opt_keyhandler && try == 0) {
+ passwords = strv_new(opt_keyhandler, key_file, NULL);
+ /* We unset the key_file as it is only an argument to our handler and it has been copied above */
+ key_file = NULL;
+ }
+
if (!key_file) {
char *text, **p;
@@ -497,9 +524,12 @@ int main(int argc, char *argv[]) {
crypt_get_volume_key_size(cd)*8,
argv[3]);
- if (key_file) {
+ if (key_file || (opt_keyhandler && try == 0)) {
struct stat st;
+ if (opt_keyhandler)
+ key_file = passwords[0];
+
/* Ideally we'd do this on the open
* fd, but since this is just a
* warning it's OK to do this in two
@@ -512,9 +542,18 @@ int main(int argc, char *argv[]) {
opt_keyfile_offset, flags);
if (k < 0) {
log_error("Failed to activate with key file '%s': %s", key_file, strerror(-k));
+ /* Remove the temporary file created by our agent as it contains our key! */
+ if (opt_keyhandler)
+ unlink(key_file);
key_file = NULL;
continue;
}
+
+ /* Remove the temporary file created by our agent as it contains our key! */
+ if (opt_keyhandler) {
+ unlink(key_file);
+ key_file = NULL;
+ }
} else {
char **p;
diff --git a/systemd-204.orig/src/shared/ask-password-api.c b/systemd-204/src/shared/ask-password-api.c
index 4557155..36af254 100644
--- a/systemd-204.orig/src/shared/ask-password-api.c
+++ b/systemd-204/src/shared/ask-password-api.c
@@ -372,6 +372,17 @@ int ask_password_agent(
if (icon)
fprintf(f, "Icon=%s\n", icon);
+ /* We assume the keyhandler path and options are provided */
+ if (strv_length(*_passphrases) == 2) {
+ char **p = *_passphrases;
+ fprintf(f, "KeyHandler=%s\n"
+ "KeyOpts=%s\n",
+ p[0],
+ p[1]);
+ strv_free(*_passphrases);
+ *_passphrases = NULL;
+ }
+
fflush(f);
if (ferror(f)) {
diff --git a/systemd-204.orig/src/tty-ask-password-agent/tty-ask-password-agent.c b/systemd-204/src/tty-ask-password-agent/tty-ask-password-agent.c
index f463662..a8e399a 100644
--- a/systemd-204.orig/src/tty-ask-password-agent/tty-ask-password-agent.c
+++ b/systemd-204/src/tty-ask-password-agent/tty-ask-password-agent.c
@@ -246,7 +246,7 @@ finish:
}
static int parse_password(const char *filename, char **wall) {
- char *socket_name = NULL, *message = NULL, *packet = NULL;
+ char *socket_name = NULL, *message = NULL, *packet = NULL, *keyhandler = NULL, *keyopts = NULL;
uint64_t not_after = 0;
unsigned pid = 0;
int socket_fd = -1;
@@ -258,6 +258,8 @@ static int parse_password(const char *filename, char **wall) {
{ "Ask", "Message", config_parse_string, 0, &message },
{ "Ask", "PID", config_parse_unsigned, 0, &pid },
{ "Ask", "AcceptCached", config_parse_bool, 0, &accept_cached },
+ { "Ask", "KeyHandler", config_parse_string, 0, &keyhandler },
+ { "Ask", "KeyOpts", config_parse_string, 0, &keyopts },
{ NULL, NULL, NULL, 0, NULL }
};
@@ -338,7 +340,59 @@ static int parse_password(const char *filename, char **wall) {
goto finish;
}
- if (arg_plymouth) {
+ if (keyhandler) {
+ char temp[] = "/run/systemd/ask-password/tmp.XXXXXX";
+ int fd = -1;
+ pid_t pid;
+
+ fd = mkostemp(temp, O_WRONLY|O_CLOEXEC);
+ if (fd < 0) {
+ log_error("Failed to create password file: %m");
+ r = -errno;
+ goto finish;
+ }
+
+ fchmod(fd, 0644);
+
+ pid = fork();
+
+ if (pid < 0) {
+ log_error("Failed to fork: %m");
+ r = -errno;
+ close_nointr_nofail(fd);
+ goto finish;
+ } else if (pid > 0) {
+ close_nointr_nofail(fd);
+ waitpid(pid, NULL, 0);
+
+ /* send back the temporary filename that now contains our key */
+ packet_length = 2 + strlen(temp);
+
+ if (!(packet = new(char, packet_length)))
+ r = -ENOMEM;
+ else {
+ char *d;
+ packet[0] = '+';
+ d = packet+1;
+ d = stpcpy(d, temp) + 1;
+ }
+ } else if (pid == 0) {
+ /* launch the keyhandler program and redirect its output to our temporary file */
+ const char *args[3];
+
+ if (dup2(fd, STDOUT_FILENO) < 0)
+ _exit(EXIT_FAILURE);
+
+ close(fd);
+
+ args[0] = keyhandler;
+ args[1] = keyopts;
+ args[2] = NULL;
+
+ execv(args[0], (char **)args);
+ _exit(EXIT_FAILURE);
+ }
+ } else if (arg_plymouth) {
_cleanup_strv_free_ char **passwords = NULL;
if ((r = ask_password_plymouth(message, not_after, filename, accept_cached, &passwords)) >= 0) {
Je vais maintenant vous montrer comment l'appliquer.
On commence par se monter un petit environnement de build et on récupère les sources :
% cd ~
% aptitude install devscripts gcc debhelper fakeroot
% apt-get source systemd
% apt-get build-dep systemd
On suppose que le patch se trouve dans /root et qu'il se nomme keyhandler.patch.
On l'applique, puis on va regénérer les .deb :
% cd systemd-204
% patch -p2 </root/keyhandler.patch
% cp -a systemd-204 systemd_204* /home/builder/
% chown -R builder: /home/builder/*
% su - builder
$ cd systemd-204
$ debuild -i -us -uc -b
Si tout se passe bien, on doit récupérer dans le home de notre user builder les fichiers suivants :
$ cd ~
$ ls *.deb
gir1.2-gudev-1.0_204-7_amd64.deb libpam-systemd_204-7_amd64.deb libsystemd-id128-0_204-7_amd64.deb libsystemd-journal-dev_204-7_amd64.deb libudev1_204-7_amd64.deb systemd_204-7_amd64.deb
libgudev-1.0-0_204-7_amd64.deb libsystemd-daemon0_204-7_amd64.deb libsystemd-id128-dev_204-7_amd64.deb libsystemd-login0_204-7_amd64.deb libudev-dev_204-7_amd64.deb systemd-sysv_204-7_amd64.deb
libgudev-1.0-dev_204-7_amd64.deb libsystemd-daemon-dev_204-7_amd64.deb libsystemd-journal0_204-7_amd64.deb libsystemd-login-dev_204-7_amd64.deb python-systemd_204-7_amd64.deb udev_204-7_amd64.deb
Il ne reste plus qu'à réinstaller les paquets dont on a besoin (pour ma part les suivants) :
% dpkg -i libpam-systemd_204-7_amd64.deb libsystemd-daemon0_204-7_amd64.deb libsystemd-journal0_204-7_amd64.deb libsystemd-login0_204-7_amd64.deb systemd_204-7_amd64.deb
Voilà, maintenant on peut écrire notre handler.
Par exemple ça :
#!/bin/bash
# check and modprobe the USB driver if not already loaded
cat /proc/modules | grep usb_storage >/dev/null 2>&1
USBLOAD=$?
if [ $USBLOAD -gt 0 ]; then
modprobe usb_storage >/dev/null 2>&1
fi
# give the system time to settle and open the USB device
udevadm settle
# check for the specifc /dev/usbkey device created by udev using /etc/udev/rules.d/99-unlock-luks.rules
if [ -b /dev/usbkey ]; then
# if device exists then output the keyfile from the usb key (hidden key is 2048 bytes long starting at 2048 bytes)
dd if=/dev/usbkey bs=512 skip=4 count=4
fi
exit 0
À quoi ça sert ?
Éh bien, je commence par vérifier la présence d'un device nommé /dev/usbkey. Ce device est créé à partir d'une règle udev que je vais vous indiquer ci-dessous. Si la clé USB correspondant à la règle est branchée, je vais lire quelques octets qui correspondent en fait à ma clé de déchiffrement de ma partition. J'envoie ensuite sur ma sortie standard cette clé qui sera passée directement à systemd-cryptsetup pour tenter de déchiffrer ma partition.
Si la clé USB n'est pas présente ou si la clé de déchiffrement ne correspond pas, le patch est implémenté de façon à avoir un fallback avec un déchiffrement par mot de passe (comme par défaut).
Voilà, comme ça je peux booter mon ordi sans qu'il me demande le mot de passe de déchiffrement juste en branchant une clé USB particulière ! Mais les possibilités sont infinies. On pourrait par exemple développer un handler de reconnaissance faciale, ou vocale, ou même d'empreinte digitale !
Maintenant la règle udev. On commence pour récupérer un ensemble d'informations permettant d'identifier notre clé.
% udevadm info -a -p $(udevadm info -q path -n /dev/sdx)
Puis on crée une règle udev associée dans le fichier /etc/udev/rules.d/99-unlock-luks.rules :
SUBSYSTEMS=="usb", DRIVERS=="usb", ATTRS{manufacturer}=="XYZ Corporation", ATTRS{product}=="Flash Thingy", ATTRS{serial}=="0123456789abc", SYMLINK+="usbkey%n"
Le patch est compatible avec le fonctionnement introduit par debian. La seule différence c'est que j'ai renommé l'option keyscript en keyhandler. Mais le troisième argument de notre fichier crypttab sera passé en paramètre à notre handler.
Notes
J'ai proposé ce patch à systemd ici , mais pour l'instant je n'ai pas de retours... J'ai également envoyé mes patchs au mainteneur du paquet systemd pour debian.
Conclusion
Encore une bidouille de plus pour refaire fonctionner quelque chose qui fonctionnait bien avant systemd... Linux est un système de bidouilleurs, je vous l'accorde, mais systemd semble particulièrement "traumatisé" par les scripts, et pour bidouiller systemd, il faut savoir faire du C ! Le code n'est pas documenté (ou très peu), et quoi qu'on en dise, c'est clairement une usine à gaz. Un truc tentaculaire qui veut tout faire, mais qui fait quasi tout mal (ça marchote, mais je ne suis clairement pas convaincu !).