:toc: left
:source-highlighter: pygments
:doctype: book
:idprefix:
:docinfo:

# PivApplet

## About

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.

## Current status

What works:

 * OpenSC, MacOS (`piv.tokend` for login), Windows PIV
 * RSA-1024 and -2048 key generation on card, signing
 * EC P-256 key generation
 * EC Diffie-Hellman on P-256
 * ECDSA (only when using JavaCard 3.0.4+ builds)
    - JC 2.2.2 builds have a hash-on-card workaround (see below), but this is
      not standard PIV
 * PINs and change of PIN, PUK reset
 * Most https://developers.yubico.com/PIV/Introduction/Yubico_extensions.html[
   YubiKeyPIV-compatible extensions] are implemented and working:
    - PIN policy
    - Version indicator (we pretend to be a YK5)
    - Fetch serial number (randomly generated)
    - Set management key
    - Import asymmetric key
    - Reset after PIN+PUK blocked
    - Change PIN/PUK retry limits
    - Attestation using slot F9

What doesn't work:

 * YubicoPIV touch policy
   - No standard JavaCard APIs for accessing GPIO or sensors so this is
     probably not happening.

## Installing

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:

-----
$ gp -install PivApplet.cap
CAP loaded
-----

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.

$ yubico-piv-tool -r Alcor -a import-certificate -s 9e < cert-9e.pem
Successfully imported a new 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`:

-----
CHUID:	301900000000000000000000000000000000000000000000000000341047132924dfd1f7581290d383781dc81a350832303530303130313e00fe00
CCC:	f015a000000116ff02b8907468b1e6d143231c5c7c452df10121f20121f300f400f50110f600f700fa00fb00fc00fd00fe00
Slot 9e:
	Algorithm:	RSA2048
	Subject DN:	CN=test
	Issuer DN:	CN=test
	Fingerprint:	acbc68b8ec8a25432a296801e4deb375a14b4d78f35016f8729a7c481040eb9a
	Not Before:	Jun 19 05:33:06 2017 GMT
	Not After:	Jun 19 05:33:06 2018 GMT
PIN tries left:	5
-----

## Default admin key & PINs

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.)

## ECDSA hash-on-card extension

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.

## Building the project

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
...

$ export JC_HOME=/path/to/jckit-2.2.2
$ ant
-----

The capfile will be output in the `./bin` directory, along with the `.class`
files (which can be used with jCardSim).

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_RSA`        | Enable RSA support
|`PIV_SUPPORT_EC`         | Enable ECDSA and ECDH support
|`PIV_USE_EC_PRECOMPHASH` | Use JC3.0.4+ API to allow standardised PIV ECDSA (rather than the hash-on-card extension, which will be disabled)
|`PIV_STRICT_CONTACTLESS` | Block most slots and keys from use over contactless (strictly conform to the PIV spec)
|`YKPIV_ATTESTATION`      | Enable YubicoPIV-style attestation slot and command
|`APPLET_USE_RESET_MEM`   | Use `CLEAR_ON_RESET` memory for Cipher and Signature intermediate data. This is needed for cards with low amounts of transient `CLEAR_ON_DESELECT` memory (e.g. J3H145, JC30M48CR)
|`APPLET_EXTLEN`          | Support for extended APDUs. Some cards have bugs that make this feature malfunction (e.g. ACOSJ)
|===

## Simulation

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:

 1. Install `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.
 2. Clone my fork of `jCardSim` (https://github.com/arekinath/jcardsim)
    and build it (using `mvn initialize && mvn clean install`)
 3. From the `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] ...
```
