Index
Le 5 ventôse an CCXXX

Lecteur de cartes

Cette semaine j'ai eu envie de permettre à mon enfant de choisir lui-même la musique qu'il écoute. Il n'est pas encore assez adroit pour utiliser un lecteur de CD ou même de cassette (et pas assez patient pour écouter tout un album de toute façon) et je ne vais pas lui donner une tablette ou un portable avec YouTube, soyons sérieux. À son âge, l'intersection entre ce qu'il aime bien et ce qu'on veut bien le laisser écouter/regarder représente une poignée de chansons, et trois séries de dessins animés.

J'ai d'abord pensé à un système à base de NFC et un vieux téléphone portable inutilisé, mais déjà en tant qu'adulte je trouve l'utilisation du NFC assez aléatoire, il faut souvent déplacer la carte un peu partout au dos du portable jusqu'à ce qu'elle soit détectée, et il n'aura jamais la patience de faire ça.

Mais j'ai aussi un lecteur de carte à puce (que j'utilise pour m'authentifier sur les sites du gouvernement avec ma carte d'identité belge, entre autres) et quelques cartes qui traînent : de vieilles cartes bancaires, vieilles cartes SIM, et même ma carte vitale qui n'est plus valide. Du coup j'ai rapidement bricolé un script pour scanner les cartes et effectuer des actions suivant la carte insérée. Ça ne demande pas trop de dextérité, et puis le fonctionnement est simple et facile à comprendre.

Je me suis d'abord basé sur l'ATR des cartes, ce qui est facile à récupérer avec pcsc_scan des pcsc-tools et qui marchait pas mal initialement, mais apparemment toutes les cartes belges Bancontact ont le même ATR, impossible de les différencier là-dessus donc. Récupérer le numéro de carte est possible mais relativement compliqué puisqu'il faut communiquer avec les applications présentes sur la puce, et je devrais donc supporter Mastercard, VISA, et Maestro ou Bancontact pour récupérer les infos, et utiliser des méthodes encore différentes pour communiquer avec les cartes SIM et avec la carte vitale.

Donc après avoir un peu bricolé, j'ai fini par simplement identifier les cartes d'après la liste des applications disponibles et autres détails de configuration avec un outil qui permet de lister toutes les applications d'une carte : EMV-CAP. Pour identifier une carte comme un sagouin, je fais un bon gros md5 de la sortie de cet outil, et ça marche.

Finalement, j'ai un script PHP qui lit les messages de pcsc_scan pour savoir quand une carte est insérée ou retirée, et appelle EMV-CAP pour l'identifier, puis exécute des commandes prédéfinies. Le script tourne sur une autre machine que celle qui joue de la musique ou des vidéos, donc il faut ruser un peu et utiliser ssh pour lancer les vidéos/musiques avec mplayer et écrire "q" sur stdin pour fermer mplayer lorsque la carte est retirée :

<?php

$pcsc_scan = "pcsc_scan -n -q";

$actions = [
    '56e8bacf36cf3d520b67ce03c9f69745' => [
        'command' => "ssh 10.0.1.11 mplayer -really-quiet '/srv/audio/K3 - Kusjessoldaten.mp3'",
        'stop_command' => function($action) { fwrite($action['pipes'][0], "q"); },
    ],
    '7300eff887fca1bb4c9b6189508379ed' => [
        'command' => "ssh 10.0.1.11 mplayer -really-quiet '/srv/audio/Petit Escargot.mp3'",
        'stop_command' => function($action) { fwrite($action['pipes'][0], "q"); },
    ],
    '9c34c6da31f5a7bb2ca961a31e34a8cd' => [
        'command' => "ssh 10.0.1.11 mplayer -really-quiet -fs -shuffle /srv/video/series/Simon/*.mp4",
        'stop_command' => function($action) { fwrite($action['pipes'][0], "q"); },
    ],
    'fbb6d4104eda5066498d7ce16e974066' => [
        'command' => "ssh 10.0.1.11 mplayer -really-quiet -fs -shuffle /srv/video/series/Tik Tak/*.mp4",
        'stop_command' => function($action) { fwrite($action['pipes'][0], "q"); },
    ],
];

function card_id() {
    return trim(`EMV-CAP -v -L -d 2>/dev/null | md5sum - | cut -d' ' -f1`);
}

$current_action = null;

$proc_watch = proc_open($pcsc_scan, [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']], $pipes, "/tmp", []);
if (is_resource($proc_watch)) {
    while (($line = stream_get_line($pipes[1], 2048, "\n"))!== false) {
        if (strpos($line, 'ATR:')!== false) {
            $atr = str_replace(" ", "", substr($line, 4));
            $card_id = card_id();

            echo "Card ATR: {$atr} / id: {$card_id}\n";

            if (isset($actions[$card_id])) {
                if (isset($actions[$card_id]['command'])) {
                    $proc_command = proc_open($actions[$card_id]['command'], [
                        ['pipe', 'r'],
                        ['pipe', 'w'],
                        ['pipe', 'w']
                    ], $pipes2);
                }

                $current_action = $actions[$card_id];
                $current_action['command'] = $proc_command;
                $current_action['pipes'] = $pipes2;
            }
        } else if (strpos($line, 'removed')!== false) {
            if (isset($current_action['command'])) {
                echo "Card removed\n";
                if (isset($current_action['stop_command'])) {
                    $current_action['stop_command']($current_action);
                    proc_terminate($current_action['command']);
                } else {
                    proc_terminate($current_action['command']);
                }
            }
        }
    }
}

Je précise que je me dégage de toute responsabilité quant au bon fonctionnement du script, et que comme je l'ai modifié pour enlever des informations plus ou moins privées et corriger du code dont j'avais honte, ce que j'ai posté ici pourrait même ne pas être syntaxiquement correct, mais c'est plus pour donner une idée qu'autre chose.

@contact