:toc: left :source-highlighter: pygments :doctype: book :idprefix: :docinfo:
This is an attempt at making a PIV (NIST SP 800-73-4) compatible JavaCard applet. Target is JavaCard 2.2.2+, with 1-3k of transient memory.
What works:
piv.tokend
for login), Windows PIVWhat doesn't work:
The pre-built .cap
files for each release can be found on the
https://github.com/arekinath/pivapplet/releases[project release page].
You can use the
https://github.com/martinpaljak/GlobalPlatformPro[Global Platform] command-line
tool (gp
) to upload the applet to your JavaCard:
Now you have a PIV card ready to initialise. It's easiest to do the initialisation with the https://developers.yubico.com/yubico-piv-tool/[`yubico-piv-tool`]:
$ yubico-piv-tool -r '' -a list-readers Alcor Micro AU9560 00 00 Yubico Yubikey 4 OTP+U2F+CCID 01 00
$ yubico-piv-tool -r Alcor -a generate -s 9e > pubkey-9e.pem Successfully generated a new private key.
$ yubico-piv-tool -r Alcor -a selfsign-certificate -s 9e \ -S '/CN=test' < pubkey-9e.pem > cert-9e.pem Successfully generated a new self signed certificate.
Now your PIV token is set up with a self-signed Card Authentication (9e
)
key. You can generate keys and certificates in the other slots in a similar
fashion (remember that most other slots default to requiring a PIN entry,
which you have to do with -a verify-pin -a selfsign-certificate ...
when
using yubico-piv-tool
).
Sample output of yubico-piv-tool -a status
:
Default PIN:: 123456
Default PUK:: 12345678
Default card administration (9B
) key:: 01 02 03 04 05 06 07 08 01 02 03 04 05 06 07 08 ...
(This is the default used by Yubikeys, so that the yubico-piv-tool
will
work with PivApplet.)
Note that if 3DES admin key support is disabled, the default card administration key will be AES-128 instead (with the first 16 bytes of the default 3DES key as its value).
When built for JavaCard 2.2.2, this applet supports an extension for doing ECDSA with hash-on-card, which client software will have to specifically add support for if it wants to use ECDSA signing. As of writing, the only PIV client with support for this extension known to the authors is https://github.com/arekinath/pivy[`pivy`].
Unfortunately, regular PIV ECDSA signing is not possible with the JavaCard standard crypto functions in JC2.2.2 (it is possible in JC3.0.4 and later, and builds of this applet for 3.0.4 use the standardised method), which only support a single operation that combines hashing and signing. The current PIV standard demands that the data to be signed is hashed by the host, and then the hash is sent to the card.
In addition to the algorithm ID 0x11
for ECCP256
, we introduce two new IDs,
ECCP256-SHA1
(0xF0
) and ECCP256-SHA256
(0xF1
). For key generation the
client should continue to use ECCP256
(as well as for ECDH), but for signing
one of the two new algorithm IDs must be used (ECCP256
will be rejected).
These two new algorithms are "hash-on-card" algorithms, where the "challenge" tag sent by the host to the card should include the full data to be signed without any hashing applied. The card will hash the data and return the signature in the same was as a normal EC signature.
For example, to sign the payload "foo\n"
with the Card Authentication (9e)
key, with SHA-256, the host could send the following APDU:
00 87 F1 9E 0A 7C 08 82 00 81 04 66 6F 6F 0A
This extension, naturally, will not work with existing PIV host software that is not aware of it. It is supported as a workaround for users who are ok with customising their host software who really want to use ECDSA.
Support for these new algorithms is advertised in the 0xAC
(supported
algorithms) tag in the response to INS_SELECT
. Client software may detect
it there to decide whether to attempt use hash-on-card or not.
We use https://github.com/martinpaljak/ant-javacard[ant-javacard] for builds.
$ git clone https://github.com/arekinath/PivApplet ...
$ cd PivApplet $ git submodule init && git submodule update ...
The capfile will be output in the ./bin
directory, along with the .class
files (which can be used with jCardSim).
You can also download pre-built capfiles from the https://github.com/arekinath/PivApplet/releases[releases page] here on GitHub.
The applet can be configured to suit different cards or needs by adjusting
the feature flags in build.xml
before running ant
.
Currently available feature flags:
|===
|PIV_SUPPORT_3DES
| D
| Enable support for 3DES admin keys
|PIV_SUPPORT_AES
| a
| Enable support for AES admin keys
|PIV_SUPPORT_RSA
| R
| Enable RSA support
|PIV_SUPPORT_EC
| E
| Enable ECDSA and ECDH support
|PIV_SUPPORT_ECCP384
| e
| Enable P-384 support with ECDSA/ECDH
|PIV_USE_EC_PRECOMPHASH
| P
| Use JC3.0.4+ API to allow standardised PIV ECDSA (rather than the hash-on-card extension, which will be disabled)
|PIV_STRICT_CONTACTLESS
| S
| Block most slots and keys from use over contactless (strictly conform to the PIV spec)
|YKPIV_ATTESTATION
| A
| Enable YubicoPIV-style attestation slot and command
|APPLET_EXTLEN
| x
| Support for extended APDUs. Some cards have bugs that make this feature malfunction (e.g. ACOSJ)
|APPLET_LOW_TRANSIENT
| L
| Reduce required transient memory for the applet by shrinking buffers. Reduces maximum certificate size and may impact performance. Cannot be used with YKPIV_ATTESTATION
.
|===
Tested card configurations:
|===
|NXP J3H145 | JC3.0.4 | REePSAx
, REePSAxaD
|NXP J3D081 | JC2.2.2 | RESAx
|NXP J2A040 | JC2.2.2 | RESAx
|JC30M48CR | JC3.0.4 | ESPxL
, RSxL
|ACOSJ 40k D1 | JC3.0.4 | REePSA
|G&D StarSign CUT | JC3.0.4 | REePSAx
, REePSAxaD
|===
As of v0.8.0, the builds on the releases page are labelled with these same abbreviations.
Simulator testing for this project has so far been done on Linux, using jCardSim (both with and without a Virtual Smartcard Reader).
The easiest way to do it on Linux is with a virtual reader:
vsmartcard
(see
http://frankmorgner.github.io/vsmartcard/virtualsmartcard/README.html[here],
but it might also be in your distro's package manager). Once it's installed
(and PCSCd restarted) your list of smartcard readers on the system (try
opensc-tool -l
or yubico-piv-tool -a list-readers
) should include a
bunch of Virtual PCD
entries.jCardSim
(https://github.com/arekinath/jcardsim)
and build it (using mvn initialize && mvn clean install
)pivapplet
directory (after running ant
to build), run:
java -noverify -cp bin/:../jcardsim/target/jcardsim-3.0.5-SNAPSHOT.jar com.licel.jcardsim.remote.VSmartCard test/jcardsim.cfg
Now you should see a card appear in the first of the Virtual PCD
readers. To
start the PivApplet up, send it a command like this:
$ opensc-tool -r 'Virtual PCD 00 00' -s '80 b8 00 00 12 0b a0 00 00 03 08 00 00 10 00 01 00 05 00 00 02 0F 0F 7f'
Then you should see the simulated PivApplet come to life! The forked jCardSim currently spits out debug output on the console including full APDUs sent and received, and stack traces of exceptions (very useful!).
You can also use the simulator with jdb
, the Java debugger:
$ jdb -noverify -classpath bin/:../jcardsim/target/jcardsim-3.0.5-SNAPSHOT.jar com.licel.jcardsim.remote.VSmartCard test/jcardsim.cfg
Initializing jdb ...
> stop at net.cooperi.pivapplet.PivApplet:1769
Deferring breakpoint net.cooperi.pivapplet.PivApplet:1769.
It will be set after the class is loaded.
> run
run com.licel.jcardsim.remote.VSmartCard test/jcardsim.cfg
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
>
VM Started:
== APDU
0000: 00 A4 04 00 09 A0 00 00
0008: 03 08 00 00 10 00 00
javacard.framework.ISOException
at javacard.framework.ISOException.throwIt(Unknown Source)
at net.cooperi.pivapplet.PivApplet.sendOutgoing(PivApplet.java:470)
at net.cooperi.pivapplet.PivApplet.sendSelectResponse(PivApplet.java:435)
at net.cooperi.pivapplet.PivApplet.process(PivApplet.java:284)
at com.licel.jcardsim.base.SimulatorRuntime.transmitCommand(SimulatorRuntime.java:303)
at com.licel.jcardsim.base.Simulator.transmitCommand(Simulator.java:263)
at com.licel.jcardsim.base.CardManager.dispatchApduImpl(CardManager.java:66)
at com.licel.jcardsim.base.CardManager.dispatchApdu(CardManager.java:36)
at com.licel.jcardsim.remote.VSmartCard$IOThread.run(VSmartCard.java:151)
== Reply APDU
0000: 61 3D 4F 0B A0 00 00 03
0008: 08 00 00 10 00 01 00 79
0010: 0D 4F 0B A0 00 00 03 08
0018: 00 00 10 00 01 00 50 09
0020: 50 69 76 41 70 70 6C 65
0028: 74 AC 14 80 01 03 80 01
0030: 06 80 01 07 80 01 11 80
0038: 01 F0 80 01 F1 06 00 90
0040: 00
== APDU
0000: 00 CB 3F FF 03 5C 01 7E
0008: 08
Breakpoint hit: "thread=Thread-0", net.cooperi.pivapplet.PivApplet.processGetData(), line=1,769 bci=70
Thread-0[1] wherei
[1] net.cooperi.pivapplet.PivApplet.processGetData (PivApplet.java:1,769), pc = 70
[2] net.cooperi.pivapplet.PivApplet.process (PivApplet.java:290), pc = 146
[3] com.licel.jcardsim.base.SimulatorRuntime.transmitCommand (SimulatorRuntime.java:303), pc = 223
[4] com.licel.jcardsim.base.Simulator.transmitCommand (Simulator.java:263), pc = 12
[5] com.licel.jcardsim.base.CardManager.dispatchApduImpl (CardManager.java:66), pc = 102
[6] com.licel.jcardsim.base.CardManager.dispatchApdu (CardManager.java:36), pc = 5
[7] com.licel.jcardsim.remote.VSmartCard$IOThread.run (VSmartCard.java:151), pc = 109
Thread-0[1] dump buffer
buffer = {
0, -53, 63, -1, 3, 92, 1, 126, 8
}
Thread-0[1] dump tlv.s
tlv.s = {
0, 0, 3, 3
}
Thread-0[1] dump incoming.state
incoming.state = {
0, 63, 63, 0, 63, 63, 0, 0
}
Thread-0[1] ...