Skip to content

Commit

Permalink
Bump YubicoPIV version to 5.0.0, implement a few more extensions
Browse files Browse the repository at this point in the history
New exts: INS_GET_SERIAL, INS_RESET and INS_SET_PIN_RETRIES

Fixes #14, #15
  • Loading branch information
Alex Wilson committed Jan 6, 2019
1 parent be4942b commit 118c585
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 26 deletions.
24 changes: 16 additions & 8 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,27 @@ What works:
* EC P-256 key generation (but no ECDSA)
* EC Diffie-Hellman on P-256
* PINs and change of PIN, PUK reset
* Some https://developers.yubico.com/PIV/Introduction/Yubico_extensions.html[
* 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 YK4)
- 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:

* ECDSA (probably not fixable without JavaCard 3.0.5 or proprietary APIs)
- There is partial support for it in the code, but it will not work without
a client that is specifically aware of the double-hashing issue.
* Yubikey extensions (TODO):
- Reset after PUK blocked
* PIV-compatible ECDSA (probably not fixable without JavaCard 3.0.5 or
proprietary APIs)
- There is support for it in the code, but it will not work without
a client that is specifically aware of the double-hashing issue (uses
a proprietary algorithm ID).
* YubicoPIV touch policy
- No standard JavaCard APIs for accessing GPIO or sensors so this is
probably not happening.

## Installing

Expand Down Expand Up @@ -104,7 +110,9 @@ work with PivApplet.)

The 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 with this applet.
signing with this applet. As of writing, the only PIV client with support
for this extension known to the authors is
https://github.com/arekinath/piv-agent[`piv-agent`].

Unfortunately, regular PIV ECDSA signing is not possible with the JavaCard
standard crypto functions, which only support a single operation that combines
Expand Down
169 changes: 151 additions & 18 deletions src/net/cooperi/pivapplet/PivApplet.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,10 @@ public class PivApplet extends Applet implements ExtendedLength
};

private static final byte[] YKPIV_VERSION = {
(byte)4, (byte)0, (byte)0
(byte)5, (byte)0, (byte)0
};

/* Standard PIV commands we support. */
private static final byte INS_VERIFY = (byte)0x20;
private static final byte INS_CHANGE_PIN = (byte)0x24;
private static final byte INS_RESET_PIN = (byte)0x2C;
Expand All @@ -86,11 +87,16 @@ public class PivApplet extends Applet implements ExtendedLength
private static final byte INS_GEN_ASYM = (byte)0x47;
private static final byte INS_GET_RESPONSE = (byte)0xC0;

/* YubicoPIV extensions we support. */
private static final byte INS_SET_MGMT = (byte)0xff;
private static final byte INS_IMPORT_ASYM = (byte)0xfe;
private static final byte INS_GET_VER = (byte)0xfd;
private static final byte INS_RESET = (byte)0xfb;
private static final byte INS_SET_PIN_RETRIES = (byte)0xfa;
private static final byte INS_ATTEST = (byte)0xf9;
private static final byte INS_GET_SERIAL = (byte)0xf8;

/* Our own private extensions. */
private static final byte INS_SG_DEBUG = (byte)0xe0;

/* ASSERT: tag.end() was called but tag has bytes left. */
Expand All @@ -116,6 +122,7 @@ public class PivApplet extends Applet implements ExtendedLength

private byte[] guid = null;
private byte[] cardId = null;
private byte[] serial = null;
private byte[] fascn = null;
private byte[] expiry = null;

Expand Down Expand Up @@ -274,6 +281,10 @@ public class PivApplet extends Applet implements ExtendedLength
randData.generateData(cardId, (short)CARD_ID_FIXED.length,
(short)(21 - (short)CARD_ID_FIXED.length));

serial = new byte[4];
randData.generateData(serial, (short)0, (short)4);
serial[0] |= (byte)0x80;

certSerial = new byte[16];
fascn = new byte[25];
expiry = new byte[] { '2', '0', '5', '0', '0', '1', '0', '1' };
Expand Down Expand Up @@ -402,6 +413,12 @@ public class PivApplet extends Applet implements ExtendedLength
case INS_RESET_PIN:
processResetPin(apdu);
break;
case INS_SET_PIN_RETRIES:
processSetPinRetries(apdu);
break;
case INS_RESET:
processReset(apdu);
break;
case INS_GET_VER:
processGetVersion(apdu);
break;
Expand All @@ -420,6 +437,9 @@ public class PivApplet extends Applet implements ExtendedLength
case INS_ATTEST:
processAttest(apdu);
break;
case INS_GET_SERIAL:
processGetSerial(apdu);
break;
default:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
Expand Down Expand Up @@ -472,6 +492,24 @@ public class PivApplet extends Applet implements ExtendedLength
apdu.sendBytes((short)0, len);
}

private void
processGetSerial(APDU apdu)
{
short len = 0;
final short le;
final byte[] buffer = apdu.getBuffer();

le = apdu.setOutgoing();
buffer[len++] = serial[0];
buffer[len++] = serial[1];
buffer[len++] = serial[2];
buffer[len++] = serial[3];

len = le > 0 ? (le > len ? len : le) : len;
apdu.setOutgoingLength(len);
apdu.sendBytes((short)0, len);
}

private void
processAttest(APDU apdu)
{
Expand Down Expand Up @@ -1601,7 +1639,7 @@ public class PivApplet extends Applet implements ExtendedLength
{
final byte[] buffer = apdu.getBuffer();
short lc, pinOff, idx;
OwnerPIN pin;
final OwnerPIN pin;

if (buffer[ISO7816.OFFSET_P1] != (byte)0x00 &&
buffer[ISO7816.OFFSET_P1] != (byte)0xFF) {
Expand Down Expand Up @@ -1674,7 +1712,7 @@ public class PivApplet extends Applet implements ExtendedLength
{
final byte[] buffer = apdu.getBuffer();
short lc, oldPinOff, newPinOff, idx;
OwnerPIN pin;
final OwnerPIN pin;

if (buffer[ISO7816.OFFSET_P1] != (byte)0x00) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
Expand Down Expand Up @@ -1736,7 +1774,7 @@ public class PivApplet extends Applet implements ExtendedLength
{
final byte[] buffer = apdu.getBuffer();
short lc, pukOff, newPinOff, idx;
OwnerPIN pin;
final OwnerPIN pin;

if (buffer[ISO7816.OFFSET_P1] != (byte)0x00) {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
Expand Down Expand Up @@ -1767,7 +1805,7 @@ public class PivApplet extends Applet implements ExtendedLength
if (!pukPin.isValidated() &&
!pukPin.check(buffer, pukOff, (byte)8)) {
ISOException.throwIt((short)(
(short)0x63C0 | pin.getTriesRemaining()));
(short)0x63C0 | pukPin.getTriesRemaining()));
return;
}

Expand All @@ -1791,6 +1829,91 @@ public class PivApplet extends Applet implements ExtendedLength
pin.resetAndUnblock();
}

private void
processSetPinRetries(APDU apdu)
{
final byte[] buffer = apdu.getBuffer();
final byte pinTries = buffer[ISO7816.OFFSET_P1];
final byte pukTries = buffer[ISO7816.OFFSET_P2];

if (!slots[SLOT_9B].flags[PivSlot.F_UNLOCKED]) {
ISOException.throwIt(
ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
return;
}

if (!pivPin.isValidated()) {
ISOException.throwIt(
ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
return;
}

pivPin = new OwnerPIN(pinTries, (byte)8);
pivPin.update(DEFAULT_PIN, (short)0, (byte)8);
pukPin = new OwnerPIN(pukTries, (byte)8);
pukPin.update(DEFAULT_PUK, (short)0, (byte)8);
}

private void
processReset(APDU apdu)
{
byte idx;

if (pivPin.getTriesRemaining() > (byte)0 ||
pukPin.getTriesRemaining() > (byte)0) {
ISOException.throwIt(
ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
return;
}

for (idx = (byte)0; idx < MAX_SLOTS; ++idx) {
final PivSlot slot = slots[idx];
if (slot == null)
continue;
if (slot.asym != null)
slot.asym.getPrivate().clearKey();
slot.asymAlg = (byte)-1;
slot.imported = false;
if (slot.cert != null) {
slot.cert.len = (short)0;
}
}

for (idx = (byte)0; idx < TAG_MAX; ++idx) {
final File file = files[idx];
if (file == null)
continue;
file.len = (short)0;
}

final DESKey dk = (DESKey)slots[SLOT_9B].sym;
dk.setKey(DEFAULT_ADMIN_KEY, (short)0);

pivPin = new OwnerPIN((byte)5, (byte)8);
pivPin.update(DEFAULT_PIN, (short)0, (byte)8);
pukPin = new OwnerPIN((byte)3, (byte)8);
pukPin.update(DEFAULT_PUK, (short)0, (byte)8);

randData.generateData(guid, (short)0, (short)16);

randData.generateData(cardId, (short)CARD_ID_FIXED.length,
(short)(21 - (short)CARD_ID_FIXED.length));

randData.generateData(serial, (short)0, (short)4);
serial[0] |= (byte)0x80;

initCARDCAP();
initCHUID();
initKEYHIST();
initAttestation();

try {
JCSystem.requestObjectDeletion();
} catch (Exception e) {
incoming.gcBlewUp = true;
}
}

private void
processPutData(APDU apdu)
{
Expand Down Expand Up @@ -2071,10 +2194,13 @@ public class PivApplet extends Applet implements ExtendedLength

final short len = outgoing.available();

files[TAG_CARDCAP] = new File();
files[TAG_CARDCAP].len = len;
files[TAG_CARDCAP].data = new byte[len];
outgoing.read(files[TAG_CARDCAP].data, (short)0, len);
if (files[TAG_CARDCAP] == null)
files[TAG_CARDCAP] = new File();
final File f = files[TAG_CARDCAP];
f.len = len;
if (f.data == null || f.data.length < len)
f.data = new byte[len];
outgoing.read(f.data, (short)0, len);
}

private void
Expand Down Expand Up @@ -2109,10 +2235,13 @@ public class PivApplet extends Applet implements ExtendedLength

final short len = outgoing.available();

files[TAG_CHUID] = new File();
files[TAG_CHUID].len = len;
files[TAG_CHUID].data = new byte[len];
outgoing.read(files[TAG_CHUID].data, (short)0, len);
if (files[TAG_CHUID] == null)
files[TAG_CHUID] = new File();
final File f = files[TAG_CHUID];
f.len = len;
if (f.data == null || f.data.length < len)
f.data = new byte[len];
outgoing.read(f.data, (short)0, len);
}

private void
Expand All @@ -2136,10 +2265,13 @@ public class PivApplet extends Applet implements ExtendedLength

final short len = outgoing.available();

files[TAG_KEYHIST] = new File();
files[TAG_KEYHIST].len = len;
files[TAG_KEYHIST].data = new byte[len];
outgoing.read(files[TAG_KEYHIST].data, (short)0, len);
if (files[TAG_KEYHIST] == null)
files[TAG_KEYHIST] = new File();
final File f = files[TAG_KEYHIST];
f.len = len;
if (f.data == null || f.data.length < len)
f.data = new byte[len];
outgoing.read(f.data, (short)0, len);
}

private void
Expand Down Expand Up @@ -2180,7 +2312,8 @@ public class PivApplet extends Applet implements ExtendedLength
final short len = outgoing.available();
final File file = atslot.cert;

file.data = new byte[len];
if (file.data == null || file.data.length < len)
file.data = new byte[len];
file.len = outgoing.read(file.data, (short)0, len);

outgoing.reset();
Expand Down

0 comments on commit 118c585

Please sign in to comment.