JavaCard: Secure Channel
Java Cards sind programmierbare Smart Cards.
In diesem Seminar ging es darum einen sicheren Kanal zwischen einer solchen Java Card und einem Kartenlesegrät herzustellen, sodass eine PIN sicher kontaktlos übertragen werden kann.
Dazu haben wir uns zuerst mit der Programmierung von Java Card Applets im Allgemeinen beschäftigt.
Dann haben wir in einer Recherche die sicheren Übertagungsprotokolle SCP (von GlobalPlatform) und Secure Messaging (von OpenPGP card und NIST PIV) gefunden.
Ein PIV SM Client existiert bereits in OpenSC und wir haben versucht PIV SM in das existierende PivApplet zu integrieren.
Kompilieren und Installieren von Applets
Die Werkzeuge zum Bauen eines Java-Card-Applets werden von Oracle zur Verfügung gestellt: Java Card Downloads.
Informationen zum Installieren der Werkzeuge sind hier zu finden: Java Card 3.2 Documentation. Obwohl unsere Java Cards die Version 3.0.5 haben, sollte dennoch immer das aktuellste Java Card Development Kit verwendet werden.
Zunächst wird der Quellcode eines Java-Card-Applets in Form einer .java-Datei benötigt. Dieses wird mit dem Java-Compiler javac
in eine .class-Datei konvertiert:
javac -cp /path/to/tools/lib/api_classic-3.0.5 -g -source 7 -target 7 -d bin/ src/package/Class.java
Diese .class-Datei kann mit dem converter.sh
Skript aus den Java Card Development Kit Tools in eine .cap-Datei konvertiert werden:
/path/to/tools/bin/converter.sh -applet 0x01:0x02:0x03:0x04:0x05:0x00 package.Class -classdir bin/ -d deliverables/ -target 3.0.5 package 0x01:0x02:0x03:0x04:0x05:0x01 1.0
Installation der Treiber für den Kartenleser
sudo pacman -S pcsclite ccid
sudo systemctl start pcscd.service
sudo systemctl enable pcscd.service
Der Treiber für Kartenleser von REINER SCT kann aus dem AUR installiert werden:
git clone https://aur.archlinux.org/pcsc-cyberjack.git
cd pcsc-cyberjack
makepkg
sudo pacman -U pcsc-cyberjack-3.99.5_SP16-1-x86_64.pkg.tar.zst
Prüfen ob der Kartenleser und die Karte erkannt werden:
sudo pacman -S pcsc-tools
pcsc_scan
GlobalPlatformPro
GlobalPlatformPro is ein Werkzeug zur Verwaltung von Applets und Packages auf einer Java Card. Zum Beispiel können damit die kompilierten .cap-Dateien installiert werden:
java -jar gp.jar --install Applet.cap
Um ein bereits existierendes Applet zu überschreiben muss zusätzlich noch --force
angegeben werden.
Mit --delete
können Applets und Packages wieder gelöscht werden.
Automatisierung des Buildprozesses
Es gibt verschiedene Möglichkeiten den Build-Prozess zu automatisieren, zum Beispiel:
- Verwendung des Java Card Eclipse Plugins von Oracle
- Makefile
- ant-javacard
- Gradle
Eclipse Plugin
Oracle stellt ein Java Card Eclipse Plugin zur Verfügung, welches unter dem folgenden Link heruntergeladen werden kann: Java Card Downloads.
Das Plugin ist Teil des Java Card Development Kits, welches wiederum aus den Java Card Development Kit Tools, Simulator, und Eclipse-Plugin besteht. Um das Eclipse-Plugin verwenden zu können, müssen sowohl die Tools, als auch der Simulator installiert sein.
Das Eclipse-Plugin ermöglicht eine einfache Erstellung von Java-Card-Applets in Eclipse, das automatische Bauen einer .cap-Datei, sowie die Verwendung des Java-Debuggers mit dem Simulator. Der Simulator ist wie eine virtuelle Java Card, auf der eigene Applets installiert und ausgeführt werden können.
Installation des Simulators
Obwohl die Installation der Tools, des Simulators, und des Eclipse-Plugins in der Java Card 3.2 Documentation ausführlich beschrieben sind, sollen hier die wesentlichen Punkte der Installation nocheinmal aufgeführt werden.
Die Tools und der Simulator müssen zunächst entpackt werden.
Schreibe nach /etc/reader.conf.d/jcsdk_config
:
# Configuration to interact with the Oracle PCSC Reader for Linux
FRIENDLYNAME "Oracle JCSDK PCSC Reader Demo 1"
DEVICENAME 127.0.0.1:9025
LIBPATH /home/user/its-workshop/simulator/drivers/IFDHandler/libjcsdkifdh.so
Starte den pcscd-Dienst neu:
sudo systemctl restart pcscd.service
Prüfe ob der virtuelle Kartenleser erkannt wird:
pcsc_scan
Es müsste ein Kartenleser mir dem Namen Oracle JCSDK PCSC Reader Demo 1
zu sehen sein.
Der Simulator unterstützt in der von uns getesteten Version 3.2 vom Februar 2024 den GlobalPlatform-Standard. Daher müssen vor der ersten Verwendung des Simulators die GlobalPlatform-Schlüssel, sowie eine PIN gesetzt werden. Dafür befindet sich in dem Verzeichnis, in das der Simulator entpackt wurde, ein Konfigurationswerkzeug. Abstrakt muss folgender Befehl ausgeführt werden:
java -jar Configurator.jar -binary <simulator-binary> -SCP-keyset <k_enc> <k_mac> <k_dek> -global-pin <pin> <tries_count>
Konkret haben wir unseren Simlator (das Binary mit dem Namen jcsl
) folgendermaßen konfiguriert:
java -jar /home/user/its-workshop/simulator/tools/Configurator.jar -binary /home/user/its-workshop/simulator/runtime/bin/jcsl -SCP-keyset 1111111111111111111111111111111111111111111111111111111111111111 2222222222222222222222222222222222222222222222222222222222222222 3333333333333333333333333333333333333333333333333333333333333333 -global-pin 01020304050f 03
Der Simulator ist ein 32-Bit-Executable, also müssen wir noch glibc und openssl in der 32-Bit Version installieren. Unter Arch Linux muss dafür das multilib-repo aktiviert werden (siehe Arch Wiki), und die Pakete lib32-glibc
sowie lib32-openssl
installiert werden.
Installation des Eclipse-Plugins
Die Installation des Eclipse-Plugins ist im Developer Kit Simulator User Guide zu finden. In den darauffolgenden Abschnitten wird darüberhinaus noch die Konfiguration der Sample Platform und des Sample Device beschrieben. Das beinhaltet die Bearbeitung der Datei simulator/samples/client.config.properties
.
Verwendung des Eclipse-Plugins
Im Developer Kit Simulator User Guide ist auch die Verwendung des Plugins beschrieben. In Eclipse kann nach erfolgreicher Installation ein neues Java-Card-Projekt angelegt werden. Dabei wird ein kleines Applet erstellt, welches auch direkt kompiliert wird.
Die Kompiltation schlug allerdings in Eclipse 09-2024 mit dem Java Card Development Kit 3.2 vom Februar 2024 fehl:
Compiling for Java version '1.7' is no longer supported. Minimal supported version is '1.8'
Die target-Version des .class-Files darf maximal 7 sein, d.h. Java 1.7. Class-Dateien höhrerer Versionen werden vom Java Card Converter nicht akzeptiert. Jedoch verweigert Eclipse die Erstellung von Class-Dateien der Version 1.7, da diese veraltet sind.
Mittlerweile existiert eine neue Version des Java Card Development Kits 3.2 vom 6. Oktober 2024. Wir haben nicht geprüft, ob dieser Fehler mit dieser Version immernoch auftritt.
Verwendung der Smartcardio-Bibliothek in Eclipse
Smartcardio ist der Name des Teils der Java-Standardbibliothek, mit dem aus einem Java-Programm heraus mit Java-Card-Applets kommuniziert werden kann. Konkret stellt diese Bibliothek Methoden zur Verfügung, mit denen Kommando-APDUs an ein Java-Card-Applet gesendet, und Antwort-APDUs ausgewertet werden können.
Da Smartcardio Teil der Standardbibliothek ist, kann sie einfach importiert werden; es ist nicht nötig einen Classpath beim Kompilieren anzugeben. In Eclipse jedoch wird diese Bibliothek nicht direkt erkannt. Sie muss erst als Modul-Abhängigkeit angegeben werden.
Dafür muss in der Datei src/module-info.java
die Zeile
requires java.smartcardio;
hinzugefügt werden. Sollte die Datei nicht existieren, kann sie mit folgendem Inhalt neu angelegt werden:
module <module_name> {
requires java.smartcardio;
}
Alternativ kann auch die grafische Benutzeroberfläche von Eclipse verwendet werden, sofern die module-info.java
bereits existiert: Rechtsklick auf das Projekt im Datei-Baum, build path, configure build path, module dependencies, add system module, dann wähle java.smartcardio.
VS Code Plugin
Alternativ zu Eclipse kann VS Code für syntax highlighting und auto completions verwendet werden. Dazu müssen zumindest diese beiden Plugins installiert werden:
redhat.java
(“Language Support for Java(TM) by Red Hat”)vscjava.vscode-java-dependency
(“Project Manager for Java”)
Außerdem wird eine lokale Kopie des Java Card SDK benötigt. Das kann zum Beispiel von https://github.com/martinpaljak/oracle_javacard_sdks heruntergeladen oder als git submodule eingebunden werden.
Das Projekt lässt sich durch die Datei .vsode/setting.json
konfiguren. (Datei im lokalen Projekt erstellen falls nicht vorhanden und (optional) im globalen ~/.gitignore
ignorieren.)
{
"java.project.sourcePaths": [
"src"
],
"java.project.referencedLibraries": [
"PATH_TO_SDKS/jc320v24.0_kit/lib/*.jar"
],
"java.import.maven.enabled": false
}
Die Pfade zu src
und dem SDK können entsprechend angepasst werden. Falls in einem Unterordner zusätzlich noch Maven-Projekte liegen, dann muss java.import.maven.enabled
auf false
gesetzt werden, da diese sonst über die java.project.*
-Konfiguration bevorzugt werden.
Makefile
Da das Eclipse-Plugin nicht wie gewünscht funktioniert hat, haben wir die Werkzeuge direkt von der Kommandozeile verwendet. Um aus dem Quellcode direkt eine .cap-Datei zu bauen, haben wir ein Makefile geschrieben:
package := secureChannelTest
class := SecureChannelTest
package_aid := 0x01:0x02:0x03:0x04:0x05:0x24
applet_aid := 0x01:0x02:0x03:0x04:0x05:0x25
javacard_api := /home/user/its-workshop/tools/lib/api_classic-3.0.5.jar
globalplatform_api := /home/user/its-workshop/gpapi-globalplatform.jar
converter := /home/user/its-workshop/tools/bin/converter.sh
javacard_version := 3.0.5
sourcedir := src
builddir := bin
outputdir := deliverables
exportdir := exports
sourcefile := ${sourcedir}/${package}/${class}.java
classfile := ${builddir}/${package}/${class}.class
capfile := ${outputdir}/${package}/javacard/${package}.cap
exportfile := ${exportdir}/org/globalplatform/javacard/globalplatform.exp
all: applet
applet: ${capfile}
clean:
rm -rf ${builddir}
rm -rf ${outputdir}
${builddir}:
mkdir ${builddir}
${classfile}: ${sourcefile} ${builddir} ${javacard_api} ${globalplatform_api}
javac \
-cp ${javacard_api}:${globalplatform_api} \
-d ${builddir} \
-g \
-source 7 \
-target 7 \
${sourcefile}
${outputdir}:
mkdir ${outputdir}
${capfile}: ${classfile} ${exportfile} ${converter}
${converter} \
-applet ${applet_aid} ${package}.${class} \
-classdir ${builddir} \
-d ${outputdir} \
-exportpath ${exportdir} \
-target ${javacard_version} \
${package} ${package_aid} 1.0
Dieses Makefile baut das Secure Channel Beispiel. Die Pfade zu den Java-Card-Tools müssen angepasst werden. Außerdem werden die .jar-Datei und die .exp-Datei der GlobalPlatform API benötigt. Für unsere Java Cards benötigen wir die GlobalPlatform API in der Version 1.6 (mehr dazu hier im Abschnitt 6.1) und das Exportfile in der Version 2.3, zu finden im Verzeichnis exports23.
Sollte das Makefile verwendet werden um ein Applet zu bauen welches die GlobalPlatform API nicht verwendet, können die entsprechenden Variablen, die GlobalPlatform API aus dem Classpath des javac-Befehels, und die -exportpath
-Option des converter-Befehls entfernt werden.
ant-javacard
ant-javacard ist ein Plugin für Apache Ant (Ant ist wie Makefiles für Java), das von den meisten open source applets zum Kompilieren per Komandozeile verwentet wird. Mit ant-javacard ist das Setup einfacher und weniger Fehleranfälling als mit einfachen Makefiles.
Gradle
Gradle ist ein Buildsystem, das direkt mit Ant und IDEs (z.B. VS Code) integrierbar ist und deshalb haben wir versucht den Workflow durch den Einsatz von Gradle zu vereinfachen. Es hat sich aber herausgestellt, dass die Konfiguration von Gradle wesentlich umfangreicher als von ant-javacard kombiniert mit dem VS Code setting.json
ist. Im Gegensatz zum KISS-Prinzip implementiert Gradle viele für unsere Zwecke unnötige Features und erschwert somit für uns die Entwicklung.
Insgesamt würden wir aktuell vom Einsatz von Gradle zur Entwicklung von (einfachen) Java-Card-Applets abraten.
GlobalPlatform – Secure Channel Protocol
GlobalPlatform standardisiert die Installation und Deinstallation von Applets. Zu diesem Zweck wurde das Secure Channel Protokoll entwickelt.
Es existiert eine Implementation, welche sowohl die Client- als auch die Card-Application beinhaltet, und eine gegenseitige Authentisierung vornimmt, wie sie in SCP03, Abschnitt 5.2 beschrieben ist.
Das Java-Card-Applet verwendet die Security Domain der Java Card beim Aufbau des Secure Channels. Das bedeutet, dass das Secure Channel Protokoll auf der Karte nicht selber implementiert werden muss, sondern die Implementation der Karte verwendet werden kann.
Die Client-Seite ist eine eigene Implementation von SCP03, die jedoch nur die gegenseitige Authentifikation beinhaltet. Es können noch keine APDUs sicher übermittelt werden.
GlobalPlatform C-Bibliothek
Die C-Bibliothek GlobalPlatform ist eine quelloffene Implementation des GlobalPlatform Standards. Wir haben versucht einen Client zu schreiben, der diese Bibliothek verwendet. Die API-Dokumentation befindet sich unter: GlobalPlatform API. Das Projekt beinhaltet die GPShell, welche selber die C-Bibliothek verwendet, und daher Beispielcode zur Verwendung der Bibliothek enthält.
Wir haben ein Programm gp-client.c
erstellt, welches zunächst nur eine Verbindung zum Kartenleser herstellen sollte. Leider gab es bereits beim Auslesen der Anzahl angeschlossener Kartenleser Probleme, in diesem Fall einen Segmentation Fault:
#include <globalplatform/globalplatform.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static OPGP_CARD_CONTEXT cardContext;
int main()
{
OPGP_enable_trace_mode(OPGP_TRACE_MODE_ENABLE, stderr);
// Create the error status structure.
OPGP_ERROR_STATUS status;
OPGP_ERROR_CREATE_NO_ERROR(status);
_tcsncpy(cardContext.libraryName, _T("gppcscconnectionplugin"), _tcslen(_T("gppcscconnectionplugin"))+1);
_tcsncpy(cardContext.libraryVersion, _T("1"), _tcslen(_T("1"))+1);
// Establish context.
status = OPGP_establish_context(&cardContext);
if (OPGP_ERROR_CHECK(status)) {
_tprintf(_T("establish_context failed with error 0x%08X (%s)\n"),
(unsigned int)status.errorCode, status.errorMessage);
return EXIT_FAILURE;
}
// Release context.
status = OPGP_release_context(&cardContext);
if (OPGP_ERROR_CHECK(status)) {
_tprintf(_T("release_context failed with error 0x%08X (%s)\n"),
(unsigned int)status.errorCode, status.errorMessage);
return EXIT_FAILURE;
}
// Get the number of card readers.
DWORD len = 0;
status = OPGP_list_readers(cardContext, NULL, &len);
if (OPGP_ERROR_CHECK(status)) {
_tprintf(_T("list_readers failed with error 0x%08X (%s)\n"),
(unsigned int)status.errorCode, status.errorMessage);
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
Das Makefile dazu:
source_file := gp-client.c
target_file := gp-client
library_path := /home/user/its-workshop/globalplatform/lib
run: ${target_file}
LD_LIBRARY_PATH=${library_path} ./${target_file}
${target_file}: ${source_file} ./Makefile
cc \
-Wall \
-I /home/user/its-workshop/globalplatform/include \
-I /usr/include/PCSC \
-L ${library_path} \
${source_file} \
-l globalplatform \
-o ${target_file}
Secure Messaging
Secure Messaging ist eine end-to-end encryption (+MAC) zwischen dem Terminal (Kartenleser) und der Karte und wird bei kontaktlosen Datenübertragungen eingesetzt. Bei kontaktbehafteten Anwendungen ist kein SM vorgesehen (zumindest bei PIV und openPGP card).
Der Vorteil von SM gegenüber SCP ist, dass nicht die statischen GlobalPlatform-Schlüssel bekannt sein müssen, um einen sicheren Kanal aufzubauen. Dadurch ist der Aufbau eines sicheren Kanals auch für Anwenungen möglich, die nicht zur (De-)Installation der Applets berechtigt sind.
Es gibt zwei verbreitete Standards, die Secure Messaging einsetzen:
- openPGP card
- PIV (NIST SP 800-73-5)
- Part 1: Allgemeines, z.B. welche Daten auf der Karte existieren und welches Autorisierunglevel für den Zugriff benötigt wird
- Part 2: Protokoll Details, z.B. wie die Schlüsselaushandlung und die Verschlüsselung funktioniert
- Part 3 enthält eine Spezifikation einer API zwischen einer off-card Middleware und einem Anwendungsprogramm – unsere Middleware (OpenSC) implementiert diese API nicht
Der wesentliche Unterschied zwischen dem SM von openPGP card und PIV ist, dass openPGP card statische AES-Schlüssel für Verschlüsselung und MACing verwendet und PIV ephemäre Sitzungsschlüssel aus einem durch Eliptic-Curve-Diffie-Hellman erzeugtem geteiltem Gehemnis ableitet.
In NIST SP 800-73-5 Part 2, Abschnitt 4 wird das PIV SM ausführlich beschrieben.
Wenn ein Applet SM unterstützt, dann muss das Applet dies in der Anwort auf das SELECT
-APDU durch die unterstützten Ciphersuites CS2
und/oder CS7
melden.
OpenSC
OpenSC ist eine open source middleware (library + CLI tools) für smart cards und implementiert unter anderem auch PIV einschließlich SM auf der Client-Seite.
Die Implementation von SM ist in card-piv.c zu finden. Die Funktion, in der die client-seitige Schlüsselübereinkunft implementiert ist, heißt piv_sm_open
.
kompilieren
OpenSC zu compilieren ist leider wegen der Benutzung des antiken GNU-Buildsystems nicht trivial. Standardmäßig versucht dieses Buildsystem immer alles global zu installieren, was für “printf-Debugging” eher ungeeignet ist. Um das zu unterbinden, muss für alles was normalerweise global installiert wird ein lokaler Pfad angegeben werden.
Außerdem ist SM standardmäßig durch den C-Präprozessor statisch deaktiviert und muss zur Kompilezeit mit --enable-piv-sm
aktiviert werden.
Das Build-Script muss vom Wurzelverzeichnis des Repos ausgeführt werden.
prefix="path/to/prefix" # e.g. prefix="$(realpath "$(dirname "$0")")/dist"
autoreconf --verbose --install
./configure --prefix="$prefix" --sysconfdir="$prefix/etc" --with-completiondir="$prefix/etc/bash_completion.d" --enable-piv-sm
make -j "$(nproc)"
make install
Um den Integrationstest p11test
(zum testen der PIV Karte) mit zu kompilieren muss außerdem vor dem Kompilieren das package cmocka
installiert werden, ansonsten wird das GNU-Buildsystem hilfreicherweise den Test einfach ohne sämtliche Warnungen ignoreren und nicht mit kompilieren.
sudo pacman -S cmocka
bash completions fix
Die bash completions von OpenSC funktionieren nicht mehr mit der aktuellen Version von bash-completions
, da die Funktion _split_longopt
entfernt wurde. Mit diesem Patch haben wir die bash completions gefixed. (Die gepatchte Datei gibt aus autogeneriert zu sein, aber wir haben nicht herausgefunden wie die Datei generiert wird.)
Patch für OpenSC bash completions |
---|
diff --git a/doc/tools/completion-template b/doc/tools/completion-template
index 895a3a9ceb52970e5ff7dd4515e473b649945090..85f80fac6d4576f40b2529209d4700f76d4c3003 100644
--- a/doc/tools/completion-template
+++ b/doc/tools/completion-template
@@ -3,10 +3,9 @@ _FUNCTION_NAME()
{
COMPREPLY=()
local cur prev split=false
+ _comp_initialize -s
_get_comp_words_by_ref -n : cur prev
- _split_longopt && split=true
-
opts="ALLOPTS"
if [ ${COMP_CWORD} -eq 1 ]; then
@@ -37,7 +36,7 @@ _FUNCTION_NAME()
;;
esac
- $split && return 0
+ $was_split && return 0
if [[ "$cur" == -* ]]; then
_longopt $1
|
benutzen
Wenn OpenSC nicht global installiert ist, dann muss PATH
und LD_LIBRARY_PATH
entsprechent gesetzt werden. Wir haben dazu eine bashrc
-Datei geschrieben, die diese Umgebungsvariablen setzt und zusätzlich noch die bash completions einbindet.
Eine Test-Umgebung kann dann mit bash --rcfile "path/to/bashrc"
gestartet werden.
#!/usr/bin/env bash
[[ -f ~/.bashrc ]] && source ~/.bashrc # include user's bashrc
prefix="path/to/prefix" # e.g. prefix="$(realpath "$(dirname "${BASH_SOURCE[0]}")")/dist"
export PATH="$prefix/bin:$PATH"
export LD_LIBRARY_PATH="$prefix/lib:$LD_LIBRARY_PATH"
for file in "$prefix/etc/bash_completion.d/"*; do
[[ -f "$file" ]] && source "$file"
done
OpenSC beinhaltet ein CLI-Tool piv-tool
was angeblich zur Verwaltung von Schlüsseln und Zertifikaten auf der Karte verwentet werden kann, allerdings hat das Tool für uns nicht funktioniert und das CI testing von OpenSC benutzt selber das yubico-piv-tool
statt ihrem eigenem Tool (???), deswegen haben wir das dann auch benutzt.
Nach der initializierung kann mit dem CLI-Tool pkcs11-tool
auf die PIV-Funktionalität zugegriffen werden oder der Integrationstest in src/tests/p11test
verwendet werden, um das Applet vollständig zu testen.
Standardmäßig verwendet OpenSC kein SM, auch wenn es mit --penable-piv-sm
kompiliert wurde. Es muss zusätzlich noch die Umgebungsvariable PIV_USE_SM=always
gesetzt werden.
Außerdem wird PIV_USE_SM=always
aktuell ignoriert, wenn die Karte als Antwort auf das SELECT
-APDU meldet, dass sie kein SM unterstützt. Das erscheint für uns wie ein möglicher Downgrade-Angriff auf OpenSC.
yubico-piv-tool
YubiKeys implementieren unter anderem auch PIV aber ohne SM. Das yubico-piv-tool kann verwendet werden, um die PIV-Schlüssel auf der Karte generieren zu lassen und um die X.509 Zertifikate der Schlüssel auf die Karte zu laden:
# Schlüsselpaar erstellen und öffentlichen Schlüssel ausgeben
yubico-piv-tool --verbose --reader '' --pin 123456 --slot 9e --action generate --algorithm RSA2048 | tee 9e.pub
# Zertifikat importieren (nach Signieren durch eigene CA)
yubico-piv-tool --verbose --reader '' --pin 123456 --slot 9e --action import-certificate < 9e.cert
Durch einen einfachen Patch kann dieses Tool auch einen SM-Schlüssel generieren:
Patch zum erstellen von SM-Schlüsseln mit yubico-piv-tool
|
---|
diff --git a/common/util.c b/common/util.c
index f0a9d5c9da9b4837a3bf3df5e0c5c8c2223938c8..6d9dbdd5d42431d0b7af460732cf2ea8e3576c6e 100644
--- a/common/util.c
+++ b/common/util.c
@@ -374,6 +374,9 @@ int get_slot_hex(enum enum_slot slot_enum) {
case slot_arg_f9:
slot = 0xf9;
break;
+ case slot_arg_04:
+ slot = 0x04;
+ break;
case slot__NULL:
default:
slot = -1;
diff --git a/tool/cmdline.ggo b/tool/cmdline.ggo
index 7fe347f597214099f3cfd0e31696e9f8bd9053d9..dbc10ceea9415a099de42393fab4cfe21b69f129 100644
--- a/tool/cmdline.ggo
+++ b/tool/cmdline.ggo
@@ -37,14 +37,16 @@ option "action" a "Action to take" values="version","generate","set-mgm-key",
text "
Multiple actions may be given at once and will be executed in order
for example --action=verify-pin --action=request-certificate\n"
-option "slot" s "What key slot to operate on" values="9a","9c","9d","9e","82","83","84","85","86","87","88","89","8a","8b","8c","8d","8e","8f","90","91","92","93","94","95","f9" enum optional
+option "slot" s "What key slot to operate on" values="9a","9c","9d","9e","82","83","84","85","86","87","88","89","8a","8b","8c","8d","8e","8f","90","91","92","93","94","95","f9","04" enum optional
text "
9a is for PIV Authentication
9c is for Digital Signature (PIN always checked)
9d is for Key Management
9e is for Card Authentication (PIN never checked)
82-95 is for Retired Key Management
- f9 is for Attestation\n"
+ f9 is for Attestation
+ 04 is for Secure Messaging
+ "
option "to-slot" - "What slot to move an existing key to" values="9a","9c","9d","9e","82","83","84","85","86","87","88","89","8a","8b","8c","8d","8e","8f","90","91","92","93","94","95","f9" enum optional
text "
9a is for PIV Authentication
@@ -52,7 +54,8 @@ text "
9d is for Key Management
9e is for Card Authentication (PIN never checked)
82-95 is for Retired Key Management
- f9 is for Attestation\n"
+ f9 is for Attestation
+ "
option "algorithm" A "What algorithm to use" values="RSA1024","RSA2048","RSA3072", "RSA4096", "ECCP256","ECCP384", "ED25519", "X25519" enum optional default="RSA2048"
option "hash" H "Hash to use for signatures" values="SHA1","SHA256","SHA384","SHA512" enum optional default="SHA256"
option "new-key" n "New management key to use for action set-mgm-key, if omitted key will be asked for" string optional
|
Generieren des SM-Schlüssels nach Patch (muss ECCP384 oder ECCP256 sein!):
yubico-piv-tool --verbose --reader '' --pin 123456 --slot 04 --action generate --algorithm ECCP384
PivApplet
Das PivApplet ist eine Open-Source-Implementation von PIV auf der Kartenseite, unterstützt aber aktuell noch kein SM.
kompilieren & installieren
Das PivApplet wird mit ant-javacard gebaut:
ant dist
Dabei wird die .cap-Datei in bin/PivApplet.cap
erstellt und kann dann wie alle Applets mit GlobalPlatformPro installiert werden:
java -jar path/to/gp.jar --install bin/PivApplet.cap --force
In dem repo existiert auch ein Skript build.rb
das verschiede Varianten (Preprozessor defines) hintereinander kompiliert – dieses Skript wird nicht benötigt und wurde von uns nicht verwendet.
Migration zu JavaCard 3.0.5
Als ersten Schritt haben wir das PivApplet zu der Java-Card-Version unserer physischen Java Cards (3.0.5) und dem neusten SDK (24.0) geupdated.
Dazu musste als erstes ant-javacard, das als git submodule in das Repo eingebunden ist, auf die neueste Version gebracht werden.
git submouldes crash course |
---|
Um ein Submodul lokal zu updaten muss in das Verzeichnis des Submoduls gewechselt werden, dort die cd ext/ant
git fetch
git switch --detach origin/master
cd ../..
git add ext/ant
git commit -m 'update ant-javacard submodule'
Der resultierende Commit sollte dann etwa so aussehen (siehe diff --git a/ext/ant b/ext/ant
index 2733c30..e2e294b 160000
--- a/ext/ant
+++ b/ext/ant
@@ -1 +1 @@
-Subproject commit 2733c303704ae1356991285456d0d1a06b3dcb24
+Subproject commit e2e294b4444d6b25d77fd0162d32071b8650c69c
Dieser Commit kann dann mit git submodule update --init --recursive
Fall irgendwas schief geht, dann hilft häufig das Zurücksetzen der Submodule: git submodule deinit --all --force
|
Anschließend haben wir im build.xml
die entsprechenden Versionsnummern eingetragen und in PivApplet.java
veraltete (deprecated) Methoden ersetzt:
Patch für Versionsupdate |
---|
diff --git a/build.xml b/build.xml
index 5417422598ccf67a9561ef05b8abb9745fbbeef7..7fd393a3ef9eef5903836f86bdf2535462dbb23b 100644
--- a/build.xml
+++ b/build.xml
@@ -72,8 +72,8 @@
<ant dir="ext/ant"/>
<!-- Create the distribution directory -->
<taskdef name="javacard" classname="pro.javacard.ant.JavaCard" classpath="ext/ant/ant-javacard.jar"/>
- <javacard>
- <cap aid="a0:00:00:03:08:00:00:10" output="bin/PivApplet.cap" sources="src-gen" classes="bin" version="1.0">
+ <javacard jckit="ext/ant/sdks/jc320v24.0_kit">
+ <cap aid="a0:00:00:03:08:00:00:10" output="bin/PivApplet.cap" sources="src-gen" classes="bin" version="1.0" targetsdk="3.0.5">
<applet class="net.cooperi.pivapplet.PivApplet" aid="a0:00:00:03:08:00:00:10:00:01:00"/>
</cap>
</javacard>
diff --git a/src/net/cooperi/pivapplet/PivApplet.java b/src/net/cooperi/pivapplet/PivApplet.java
index 9abbfeb453e060ba08386f4354121086f138ecec..8b9c6bf5591f03682a55a0d0283ff370d1b61aa9 100644
--- a/src/net/cooperi/pivapplet/PivApplet.java
+++ b/src/net/cooperi/pivapplet/PivApplet.java
@@ -271,7 +271,7 @@ public class PivApplet extends Applet
protected
PivApplet()
{
- randData = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
+ randData = RandomData.getInstance(RandomData.ALG_KEYGENERATION);
//#if PIV_SUPPORT_3DES
tripleDes = Cipher.getInstance(Cipher.ALG_DES_CBC_NOPAD, false);
//#endif
@@ -376,15 +376,15 @@ public class PivApplet extends Applet
JCSystem.CLEAR_ON_DESELECT);
guid = new byte[16];
- randData.generateData(guid, (short)0, (short)16);
+ randData.nextBytes(guid, (short)0, (short)16);
cardId = new byte[21];
Util.arrayCopy(CARD_ID_FIXED, (short)0, cardId, (short)0,
(short)CARD_ID_FIXED.length);
- randData.generateData(cardId, (short)CARD_ID_FIXED.length,
+ randData.nextBytes(cardId, (short)CARD_ID_FIXED.length,
(short)(21 - (short)CARD_ID_FIXED.length));
serial = new byte[4];
- randData.generateData(serial, (short)0, (short)4);
+ randData.nextBytes(serial, (short)0, (short)4);
serial[0] |= (byte)0x80;
certSerial = new byte[16];
@@ -1903,7 +1903,7 @@ public class PivApplet extends Applet
outgoingLe = apdu.setOutgoing();
wtlv.useApdu((short)0, outgoingLe);
- randData.generateData(challenge, (short)0, len);
+ randData.nextBytes(challenge, (short)0, len);
chalValid[0] = true;
/*for (byte i = 0; i < (byte)len; ++i)
challenge[i] = (byte)(i + 1);*/
@@ -1926,7 +1926,7 @@ public class PivApplet extends Applet
outgoingLe = apdu.setOutgoing();
wtlv.useApdu((short)0, outgoingLe);
- randData.generateData(challenge, (short)0, len);
+ randData.nextBytes(challenge, (short)0, len);
chalValid[0] = true;
/*for (byte i = 0; i < (byte)len; ++i)
challenge[i] = (byte)(i + 1);*/
@@ -2682,9 +2682,9 @@ public class PivApplet extends Applet
pukPin.update(DEFAULT_PUK, (short)0, (byte)8);
pukPinIsDefault = true;
- randData.generateData(guid, (short)0, (short)16);
+ randData.nextBytes(guid, (short)0, (short)16);
- randData.generateData(cardId, (short)CARD_ID_FIXED.length,
+ randData.nextBytes(cardId, (short)CARD_ID_FIXED.length,
(short)(21 - (short)CARD_ID_FIXED.length));
/*
@@ -3539,7 +3539,7 @@ public class PivApplet extends Applet
return;
}
- randData.generateData(certSerial, (short)0,
+ randData.nextBytes(certSerial, (short)0,
(short)certSerial.length);
certSerial[0] = (byte)(certSerial[0] & (byte)0x7F);
|
Dieses geupdatete Applet konnte dann erfolgreich auf unseren Java Cards installiert und getestet werden.
Generation des SM-Schlüsselpaars
Wir haben das Applet gepatcht, um die Generation eines SM-Schlüsselpaars zu erlauben und an die Client-Anwendung zu melden, dass SM unterstützt ist.
Dazu haben wir im Applet den entsprechenden key slot (04
) für das SM-Schlüsselpaar angelegt und ermöglichen für diesen slot eine Schlüsselgeneration per GENERATE ASYMMETRIC KEY
-APDU mit den erlaubten Schlüsseltypen (NIST P-256 oder P-384). Wenn sich in dem SM key slot ein Schlüsselpaar befindet, dann wird als Antwort auf das SELECT
-APDU die entsprechende cipher suite als unterstüzt gemeldet (CS2
für P-256 / CS7
für P-384).
Patch zum generieren von SM-Schlüsselpaaren im Applet |
---|
diff --git a/src/net/cooperi/pivapplet/PivApplet.java b/src/net/cooperi/pivapplet/PivApplet.java
index 8b9c6bf5591f03682a55a0d0283ff370d1b61aa9..4a6d4aff832d941b36c90fb092a82021dbf48311 100644
--- a/src/net/cooperi/pivapplet/PivApplet.java
+++ b/src/net/cooperi/pivapplet/PivApplet.java
@@ -196,7 +196,7 @@ public class PivApplet extends Applet
private KeyAgreement ecdh = null;
private KeyAgreement ecdhSha = null;
- private static final byte MAX_SLOTS = (byte)17;
+ private static final byte MAX_SLOTS = (byte)18;
private static final byte SLOT_9A = (byte)0;
private static final byte SLOT_9B = (byte)1;
@@ -206,6 +206,7 @@ public class PivApplet extends Applet
private static final byte SLOT_82 = (byte)5;
private static final byte SLOT_8C = (byte)15;
private static final byte SLOT_F9 = (byte)16;
+ private static final byte SLOT_04 = (byte)17; // Secure messaging (SM) key.
private PivSlot[] slots = null;
private byte retiredKeys = 0;
@@ -223,6 +224,10 @@ public class PivApplet extends Applet
private static final byte PIV_ALG_ECCP256 = (byte)0x11;
private static final byte PIV_ALG_ECCP384 = (byte)0x14;
+ // Cipher suites for secure messaging from NIST SP 800-73-5 part 2.
+ private static final byte PIV_ALG_CS2 = (byte)0x27;
+ private static final byte PIV_ALG_CS7 = (byte)0x2E;
+
private static final byte GA_TAG_WITNESS = (byte)0x80;
private static final byte GA_TAG_CHALLENGE = (byte)0x81;
private static final byte GA_TAG_RESPONSE = (byte)0x82;
@@ -400,6 +405,8 @@ public class PivApplet extends Applet
slots[SLOT_F9] = new PivSlot((byte)0xF9);
//#endif
+ slots[SLOT_04] = new PivSlot((byte)0x04);
+
files = new File[TAG_MAX + 1];
ykFiles = new File[YK_TAG_MAX + 1];
@@ -941,6 +948,17 @@ public class PivApplet extends Applet
}
//#endif
+ if (slots[SLOT_04] != null) {
+ switch (slots[SLOT_04].asymAlg) {
+ case PIV_ALG_ECCP256:
+ pushAlgorithm(PIV_ALG_CS2);
+ break;
+ case PIV_ALG_ECCP384:
+ pushAlgorithm(PIV_ALG_CS7);
+ break;
+ }
+ }
+
wtlv.pop();
wtlv.end();
@@ -1062,6 +1080,8 @@ public class PivApplet extends Applet
slot = slots[idx];
} else if (key == (byte)0xF9) {
slot = slots[SLOT_F9];
+ } else if (key == (byte)0x04) {
+ slot = slots[SLOT_04];
} else {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
return;
@@ -1172,6 +1192,12 @@ public class PivApplet extends Applet
return;
}
+ // The SM key must be an ECC key.
+ if (key == (byte)0x04 && alg != PIV_ALG_ECCP256 && alg != PIV_ALG_ECCP384) {
+ ISOException.throwIt(ISO7816.SW_WRONG_DATA);
+ return;
+ }
+
//#if PIV_SUPPORT_EC
final ECPrivateKey ecPriv;
final ECPublicKey ecPub;
|
Integrationstest
Testen mit virtueller Java Card
OpenSC verwendet das PivApplet um die PIV-Implementation zu testen. Dafür gibt es einen GitHub Workflow, der eine Testumgebung aufsetzt in der OpenSC gebaut, und das PivApplet auf einer virtuellen Java Card installiert wird. Da wir das PivApplet erweitern wollten, haben wir versucht diese virtuelle Testumgebung einzurichten.
Als erstes muss OpenSC gebaut werden. Dafür muss das Programm cmocka
installiert werden.
sudo pacman -S cmocka git clone https://github.com/OpenSC/OpenSC cd OpenSC ./bootstrap ./configure --enable-piv-sm make -j 4 make install
Als nächstes wird das Programm vsmartcard gebaut. Dafür wird das Programm help2man
benötigt.
sudo pacman -S help2man git clone https://github.com/frankmorgner/vsmartcard.git cd vsmartcard/virtualsmartcard autoreconf -vis ./configure make -j 4 sudo make install
Dann kompilieren wir das Programm jcardsim. Dafür installieren wir zunächst Java Version 8 und aktivieren es. Außerdem benötigen wir ant.
sudo pacman -S jdk8-openjdk ant sudo archlinux-java set java-8-openjdk git clone https://github.com/Jakuje/jcardsim.git cd jcardsim sudo pacman -S maven mvn initialize mvn clean install
Jetzt kann das PivApplet gebaut werden (siehe hier). Danach muss pcscd gestartet werden und die virtuelle Java Card kann verwendet werden:
sudo systemctl start pcscd.socket sudo systemctl start pcscd.service java -noverify -cp PivApplet/bin/:jcardsim/target/jcardsim-3.0.5-SNAPSHOT.jar com.licel.jcardsim.remote.VSmartCard PivApplet/test/jcardsim.cfg >/dev/null &
Dies führte bei uns jedoch zu folgendem Fehler:
Exception in thread "main" java.net.ConnectException: Connection refused (Connection refused)
Mit dem durch vsmartcard
installierten Programm vpcd-config
kann die Konfiguration eingesehen werden. Die Konfiguration befindet sich in PivApplet/test/jcardsim.cfg
. Dort ist als Host localhost
und der Port 35963
angegeben. Mit ss -tunlp
konnte kein Programm identifiziert werden, welches diesen Port verwendet. Wir haben an dieser Stelle nicht weiter investigiert, und stattdessen unsere physischen Java Cards verwendet.
Initialisierung der Schlüssel
Bevor das PivApplet getestet werden kann, müssen PIV-Schlüssel initialisiert und auf die Karte geschrieben werden. Dabei gehen wir so vor, wie OpenSC beim Testen von PIV.
Zunächst brauchen wir den Namen unseres Kartenlesers.
opensc-tool -l
Den Namen des Kartenlesers, der die Karte mit dem PivApplet enthält, wird in eine Variable geschrieben:
export GP_READER="REINER SCT cyberJack RFID standard (0954493871) 00 00"
Außerdem verwenden wir die Standard PIN:
export PIN="123456"
Dann können wir mit dem yubico-piv-tool die PIV-Schlüssel generieren, signieren, und das Zerifikat auf die Karte laden:
yubico-piv-tool -v 9999 -r "$GP_READER" -P "$PIN" -s 9e -a generate -A RSA2048 | tee 9e.pub
yubico-piv-tool -v 9999 -r "$GP_READER" -P "$PIN" -s 9e -S'/CN=barCard/OU=test/O=example.com/' -averify-pin -aselfsign < 9e.pub | tee 9e.cert
yubico-piv-tool -v 9999 -r "$GP_READER" -P "$PIN" -s 9e -aimport-certificate < 9e.cert
yubico-piv-tool -v 9999 -r "$GP_READER" -P "$PIN" -s 9a -a generate -A RSA2048 | tee 9a.pub
yubico-piv-tool -v 9999 -r "$GP_READER" -P "$PIN" -s 9a -S'/CN=bar/OU=test/O=example.com/' -averify-pin -aselfsign < 9a.pub | tee 9a.cert
yubico-piv-tool -v 9999 -r "$GP_READER" -P "$PIN" -s 9a -aimport-certificate < 9a.cert
yubico-piv-tool -v 9999 -r "$GP_READER" -P "$PIN" -s 9c -a generate -A ECCP256 | tee 9c.pub
yubico-piv-tool -v 9999 -r "$GP_READER" -P "$PIN" -s 9c -S'/CN=bar/OU=test/O=example.com/' -averify-pin -aselfsign < 9c.pub | tee 9c.cert
yubico-piv-tool -v 9999 -r "$GP_READER" -P "$PIN" -s 9c -aimport-certificate < 9c.cert
yubico-piv-tool -v 9999 -r "$GP_READER" -P "$PIN" -s 9d -a generate -A ECCP256 | tee 9d.pub
yubico-piv-tool -v 9999 -r "$GP_READER" -P "$PIN" -s 9d -S'/CN=bar/OU=test/O=example.com/' -averify-pin -aselfsign < 9d.pub | tee 9d.cert
yubico-piv-tool -v 9999 -r "$GP_READER" -P "$PIN" -s 9d -aimport-certificate < 9d.cert
Testen des PIV-Applets
cd OpenSC/src/tests/p11test
./p11test -v -p "$PIN" -o piv.json
diff -u3 piv{_ref,}.json
Ausblick
Die folgenden Schritte werden noch benötigt, um den Schlüsselaustausch im PivApplet zu implementieren.
Card Verifiable Certificate
Das PIV Secure Messaging Key Establishment ist in NIST SP 800-73-5, Teil 2, Abschnitt 4.1 beschrieben. Der erste Schritt der PIV Card Application ist die Berechnung des Hashes über dem Secure Messaging Card Verifiable Certificate.
Mit dem Werkzeug cvc-create aus OpenPACE lassen sich CVCs erstellen. Wir haben dieses Tool jedoch nicht getestet.
Der Aufbau eines CVC ist in NIST SP 800-73-5, Teil 2, Abschnitt 4.1.5 beschrieben.
Unklar ist noch, wie ein CVC auf die Karte geladen werden kann. Für die X.509 Zertifikate der PIV-Schlüssel sind in NIST SP 800-73-5, Teil 1 Object Identifier spezifiziert. Diese können mit der PUT DATA
Instruktion verwendet werden, um Daten auf die Karte zu laden. Die PUT DATA
Instruktion ist in NIST SP 800-73-5, Teil 2 standardisiert und wird vom PivApplet implementiert. Es existiert jedoch kein Object Identifier für das CVC des PIV Secure Messaging Schlüssels.
Eine Möglichkeit das CVC in das Applet zu laden besteht darin, eine eigene Instruktion dafür zu implementieren. Jedoch müsste dann auch die Client-Seite, also OpenSC oder yubico-piv-tool, diese Instruktion verwenden, um das CVC zu installieren.
Key-derivation function
Ein weiteres Problem bei der Implementation der Schlüsselübereinkunft auf der Java Card ist die verwendete Schlüsselableitungsfunktion (Schritt C7 in NIST SP 800-73-5, Teil 2, Abschnitt 4.1).
Sie ist spezifiziert in NIST SP 800-73-5, Teil 2, Abschnitt 4.1.6; in SP800-56A, Abschnitt 5.8.1; sowie in NIST SP 800-56C.
Wir haben keine existierende Java Card Implementation dieser Schlüsselableitungsfunktion finden können. Diese muss also voraussichtlich selber implementiert werden.
Gemeinsames Geheimnis
Zur Berechnung des gemeinsamen Geheimnisses (Schritt C5 in NIST SP 800-73-5, Teil 2, Abschnitt 4.1), kann (voraussichtlich) die Java Card API verwendet werden.
Dazu wird ein Objekt der Klasse KeyAgreement verwendet. Dieses wird mit der Methode init
und dem Algorithmus ALG_EC_SVDP_DHC_PLAIN
initialisiert. Mit der Methode generateSecret
kann das gemeinsame Geheimnis berechnet werden. Dies konnten wir jedoch nicht testen.
Implementation im PivApplet
Die Schlüsselübereinkunft im PivApplet kann implementiert werden in src/net/cooperi/pivapplet/PivApplet.java
. In der Methode processGeneralAuth
wird zunächst der Keyslot ausgewählt. Dann wird die neu erstellte Methode processGenAuthSm
gerufen.
Der erste Schritt besteht darin, die Daten auszulesen und zugreifbar zu machen, die der Client der Karte sendet. Dazu stellt das PivApplet Hilfsfunktionen bereit. Bis hierhin haben wir eine Implementation:
diff --git a/src/net/cooperi/pivapplet/PivApplet.java b/src/net/cooperi/pivapplet/PivApplet.java
index 4a6d4af..ac3d2ee 100644
--- a/src/net/cooperi/pivapplet/PivApplet.java
+++ b/src/net/cooperi/pivapplet/PivApplet.java
@@ -2217,6 +2217,19 @@ public class PivApplet extends Applet
}
//#endif
+ private void
+ processGenAuthSm(final APDU apdu, final PivSlot slot,
+ byte alg, final byte key, final Readable input,
+ final byte wanted, final byte tag)
+ {
+ // Read the data from the client
+ final short payload_len = tlv.tagLength();
+ final short read = tlv.read(tempBuf, payload_len);
+ if (read != payload_len)
+ ISOException.throwIt(ISO7816.SW_UNKNOWN);
+ final byte[] payload = tempBuf.data();
+ }
+
private void
processGeneralAuth(final APDU apdu)
{
@@ -2237,6 +2250,8 @@ public class PivApplet extends Applet
final byte idx = (byte)(SLOT_MIN_HIST +
(byte)(key - MIN_HIST_SLOT));
slot = slots[idx];
+ } else if (key == 0x04 && (alg == PIV_ALG_CS2 || alg == PIV_ALG_CS7)) {
+ slot = slots[SLOT_04];
} else {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
return;
@@ -2371,6 +2386,10 @@ public class PivApplet extends Applet
wanted, tag);
break;
//#endif
+ case PIV_ALG_CS2:
+ case PIV_ALG_CS7:
+ processGenAuthSm(apdu, slot, alg, key, input, wanted, tag);
+ break;
default:
tlv.abort();
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
Danach muss in processGenAuthSm
die eigentliche Schlüsselübereinkunft implementiert werden.