Skip to content

Commit

Permalink
remove javacardx.framework.tlv reimplementation
Browse files Browse the repository at this point in the history
Remove the javacardx.framework.tlv reimplementation and move most
functions into our own NeoBERParser. The main reason for this is that
some APDUs of the OpenPGP specification cannot be parsed with the
official implementation. Therefore, if a card will implement the
javacardx.framework.tlv and we do not use our own, things will go
sideways. Our own NeoBERParser will process the TLVs in a more
relaxed way.

On a sidenote, I also bricked one JCOP4 card with a library
reimplementation, where you now cannot remove the applet nor the
library anymore.

Signed-off-by: Michael Walle <[email protected]>
  • Loading branch information
mwalle committed Mar 17, 2024
1 parent a3ebeb9 commit b396a0e
Show file tree
Hide file tree
Showing 17 changed files with 140 additions and 543 deletions.
10 changes: 1 addition & 9 deletions build.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="all" name="NeoPGP Applet">
<project basedir="." default="applet" name="NeoPGP Applet">

<mkdir dir="lib" />
<get src="https://github.com/martinpaljak/ant-javacard/releases/latest/download/ant-javacard.jar" dest="lib" skipexisting="true" />
Expand All @@ -15,14 +15,6 @@

<taskdef name="javacard" classname="pro.javacard.ant.JavaCard" classpath="lib/ant-javacard.jar" />

<target name="all" depends="applet,javacardx-framework-tlv" />

<target name="javacardx-framework-tlv">
<javacard>
<cap output="javacardx.framework.tlv.cap" targetsdk="3.0.4" sources="${main.src.dir}" includes="javacardx/framework/tlv/*.java" package="javacardx.framework.tlv" aid="a000000062020803" version="1.0" exportmap="true" />
</javacard>
</target>

<target name="applet">
<javacard>
<cap output="NeoPGPApplet.cap" targetsdk="3.0.4" sources="${main.src.dir}" includes="cc/walle/neopgp/*.java" classes="${ant.classes.dir}" aid="d27600012401" version="1.0">
Expand Down
121 changes: 121 additions & 0 deletions src/cc/walle/neopgp/NeoBERParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package cc.walle.neopgp;

import javacard.framework.Util;

/*
* This is bascially a reimplementation of javacardx.framework.tlv.
*
* Unfortunately, the OpenPGP specification has some APDUs that look
* like BER encoded, but aren't. For example, the original implementation
* will throw a TLVException(MALFORMED_TLV) when parsing the APDU to
* import keys (instruction DBh).
*
* This implementation doesn't verify the TLVs nor will it enforce that
* a tag will match the constructed bit (20h).
*/

public class NeoBERParser {
private NeoBERParser() {}

public static byte size(byte[] berTagArray, short bOff) throws TLVException {
return tagNumber(berTagArray, bOff) < 31 ? (byte)1 : (byte)2;
}

public static short tagNumber(byte[] berTagArray, short bOff) throws TLVException {
short number;

number = (short)(berTagArray[bOff] & 0x1f);
if (number < 31)
return number;

number = (short)(berTagArray[(short)(bOff + 1)] & 0xff);
if (number < 31)
TLVException.throwIt(TLVException.MALFORMED_TAG);

/* for simplicity this only supports two byte tags */
if (number >= 128)
TLVException.throwIt(TLVException.ILLEGAL_SIZE);

return number;
}

public static boolean isConstructed(byte[] berTagArray, short bOff) {
return (berTagArray[bOff] & 0x20) != 0;
}

public static byte tagClass(byte[] berTagArray, short bOff) {
return (byte)((berTagArray[bOff] >> 6) & 0x03);
}

public static short getLengthOffset(byte[] berTLVArray, short bOff) {
return (short)(bOff + size(berTLVArray, bOff));
}

public static short getValueOffset(byte[] berTLVArray, short bOff) {
bOff = getLengthOffset(berTLVArray, bOff);

if ((berTLVArray[bOff] & (byte)0x80) == (byte)0x80) {
byte tmp = (byte)(berTLVArray[bOff] & (byte)0x7f);
if (tmp > 2)
TLVException.throwIt(TLVException.TLV_SIZE_GREATER_THAN_32767);

bOff += tmp;
}

return (short)(bOff + (short)1);
}

public static short getLength(byte[] berTLVArray, short bOff) {
bOff = getLengthOffset(berTLVArray, bOff);

if ((berTLVArray[bOff] & 0x80) == 0)
return berTLVArray[bOff];

switch ((berTLVArray[bOff] & 0x7f)) {
case 1:
return (short)(berTLVArray[(short)(bOff + 1)] & 0xff);
case 2:
return Util.getShort(berTLVArray, (short)(bOff + 1));
default:
TLVException.throwIt(TLVException.ILLEGAL_SIZE);
return 0;
}
}

public static short getTag(byte[] berTLVArray, short bTLVOff, byte[] berTagArray, short bTagOff) {
short size = size(berTLVArray, bTLVOff);
Util.arrayCopyNonAtomic(berTLVArray, bTLVOff, berTagArray, bTagOff, size);
return size;
}

public static short find(byte[] berTLVArray, short bTLVOff, byte[] berTagArray, short bTagOff) {
short startOffset = getValueOffset(berTLVArray, bTLVOff);
return findCommon(berTLVArray, bTLVOff, startOffset, berTagArray, bTagOff);
}

public static short findNext(byte[] berTLVArray, short bTLVOff, short startOffset, byte[] berTagArray, short bTagOff) {
startOffset = next(berTLVArray, startOffset);
return findCommon(berTLVArray, bTLVOff, startOffset, berTagArray, bTagOff);
}

private static short next(byte[] berTLVArray, short startOffset) {
return (short)(getValueOffset(berTLVArray, startOffset) + getLength(berTLVArray, startOffset));
}

private static short findCommon(byte[] berTLVArray, short bTLVOff, short startOffset, byte[] berTagArray, short bTagOff) {
short valueLength = getLength(berTLVArray, bTLVOff);
short valueOffset = getValueOffset(berTLVArray, bTLVOff);

while (startOffset < (short)(valueOffset + valueLength)) {
if (tagNumber(berTagArray, bTagOff) == tagNumber(berTLVArray, startOffset) &&
isConstructed(berTagArray, bTagOff) == isConstructed(berTLVArray, startOffset) &&
tagClass(berTagArray, bTagOff) == tagClass(berTLVArray, startOffset))
return startOffset;

startOffset = next(berTLVArray, startOffset);
}

return -1;
}
}
1 change: 0 additions & 1 deletion src/cc/walle/neopgp/NeoKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ public void clear() {
timestamp.clear();
}


public void importKey(byte[] buf, short off, short len) {
try {
doImportKey(buf, off, len);
Expand Down
7 changes: 3 additions & 4 deletions src/cc/walle/neopgp/NeoPGPApplet.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import javacard.framework.JCSystem;
import javacard.framework.Util;
import javacardx.apdu.ExtendedLength;
import javacardx.framework.tlv.ConstructedBERTLV;

public class NeoPGPApplet extends Applet implements ExtendedLength {
public static final short BUFFER_SIZE_MIN_LENGTH = (short)0x200;
Expand Down Expand Up @@ -1119,7 +1118,7 @@ private void processImportKey(APDU apdu) throws ISOException {
if (buf[(short)0] != (byte)TAG_EXTENDED_HEADER_LIST)
ISOException.throwIt(ISO7816.SW_WRONG_DATA);

off = ConstructedBERTLV.find(buf, (short)0, BER_TAG_SIGNATURE_KEY, (short)0);
off = NeoBERParser.find(buf, (short)0, BER_TAG_SIGNATURE_KEY, (short)0);
if (off > (short)0) {
JCSystem.beginTransaction();
zeroByteArray(digitalSignatureCounter);
Expand All @@ -1128,15 +1127,15 @@ private void processImportKey(APDU apdu) throws ISOException {
return;
}

off = ConstructedBERTLV.find(buf, (short)0, BER_TAG_DECRYPTION_KEY, (short)0);
off = NeoBERParser.find(buf, (short)0, BER_TAG_DECRYPTION_KEY, (short)0);
if (off > (short)0) {
JCSystem.beginTransaction();
decryptionKey.importKey(buf, (short)0, lc);
JCSystem.commitTransaction();
return;
}

off = ConstructedBERTLV.find(buf, (short)0, BER_TAG_AUTHENTICATION_KEY, (short)0);
off = NeoBERParser.find(buf, (short)0, BER_TAG_AUTHENTICATION_KEY, (short)0);
if (off > (short)0) {
JCSystem.beginTransaction();
authenticationKey.importKey(buf, (short)0, lc);
Expand Down
42 changes: 13 additions & 29 deletions src/cc/walle/neopgp/NeoRSAKey.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@
import javacard.security.RSAPrivateCrtKey;
import javacard.security.RSAPublicKey;
import javacardx.crypto.Cipher;
import javacardx.framework.tlv.BERTLV;
import javacardx.framework.tlv.ConstructedBERTLV;
import javacardx.framework.tlv.PrimitiveBERTLV;

public class NeoRSAKey extends NeoKey {
public static final byte IMPORT_FORMAT_CRT_W_MODULUS = 0x03;
Expand Down Expand Up @@ -128,54 +125,41 @@ public void doImportKey(byte[] buf, short off, short len) {
short exponentLength = 0, modulusLength = 0, pLength = 0;
short qLength = 0, pqLength = 0, dp1Length = 0, dq1Length = 0;

/*
* Oh the horror. The contents of the 7F48 tag looks like BER
* encoded but it's missing the actual content. That is then
* part of the 5F48 tag. Even worse, the 7F48 tag refers to a
* constructed one, but because the value is missing, it's not
* and we cannot use the javacardx.framework.tlv utils. Or can
* we...
*/
templateTLV = ConstructedBERTLV.find(buf, off, NeoKey.BER_TAG_PRIVATE_KEY_TEMPLATE, (short)0);
templateTLV = NeoBERParser.find(buf, off, NeoKey.BER_TAG_PRIVATE_KEY_TEMPLATE, (short)0);
if (templateTLV < (short)0)
ISOException.throwIt(ISO7816.SW_WRONG_DATA);

dataTLV = ConstructedBERTLV.findNext(buf, off, templateTLV, NeoKey.BER_TAG_PRIVATE_KEY_DATA, (short)0);
dataTLV = NeoBERParser.findNext(buf, off, templateTLV, NeoKey.BER_TAG_PRIVATE_KEY_DATA, (short)0);
if (dataTLV < (short)0)
ISOException.throwIt(ISO7816.SW_WRONG_DATA);

/*
* Here be dragons. We clear the constructed bit so we can
* actually use PrimitiveBERTLV.getValueOffset().
*/
templateTLVLength = BERTLV.getLength(buf, templateTLV);
buf[templateTLV] &= (byte)~0x20;
templateTLVOffset = PrimitiveBERTLV.getValueOffset(buf, templateTLV);
templateTLVLength = NeoBERParser.getLength(buf, templateTLV);
templateTLVOffset = NeoBERParser.getValueOffset(buf, templateTLV);

for (off = templateTLVOffset;
off < (short)(templateTLVOffset + templateTLVLength);
off = PrimitiveBERTLV.getValueOffset(buf, off)) {
off = NeoBERParser.getValueOffset(buf, off)) {
switch (buf[off]) {
case TAG_PUBLIC_KEY_EXPONENT:
exponentLength = BERTLV.getLength(buf, off);
exponentLength = NeoBERParser.getLength(buf, off);
break;
case TAG_PRIVATE_KEY_P:
pLength = BERTLV.getLength(buf, off);
pLength = NeoBERParser.getLength(buf, off);
break;
case TAG_PRIVATE_KEY_Q:
qLength = BERTLV.getLength(buf, off);
qLength = NeoBERParser.getLength(buf, off);
break;
case TAG_PRIVATE_KEY_PQ:
pqLength = BERTLV.getLength(buf, off);
pqLength = NeoBERParser.getLength(buf, off);
break;
case TAG_PRIVATE_KEY_DP1:
dp1Length = BERTLV.getLength(buf, off);
dp1Length = NeoBERParser.getLength(buf, off);
break;
case TAG_PRIVATE_KEY_DQ1:
dq1Length = BERTLV.getLength(buf, off);
dq1Length = NeoBERParser.getLength(buf, off);
break;
case TAG_PUBLIC_KEY_MODULUS:
modulusLength = BERTLV.getLength(buf, off);
modulusLength = NeoBERParser.getLength(buf, off);
break;
}
}
Expand All @@ -186,7 +170,7 @@ public void doImportKey(byte[] buf, short off, short len) {
dq1Length == (short)0)
ISOException.throwIt(ISO7816.SW_WRONG_DATA);

off = PrimitiveBERTLV.getValueOffset(buf, dataTLV);
off = NeoBERParser.getValueOffset(buf, dataTLV);

rsaPublicKey.setExponent(buf, off, exponentLength);
off += exponentLength;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-3.0-or-later
package javacardx.framework.tlv;
package cc.walle.neopgp;

import javacard.framework.CardRuntimeException;

Expand All @@ -18,7 +18,7 @@ public class TLVException extends CardRuntimeException {

private static TLVException theTLVException;

public TLVException(short reason) {
TLVException(short reason) {
super(reason);
}

Expand Down
76 changes: 0 additions & 76 deletions src/javacardx/framework/tlv/BERTLV.java

This file was deleted.

Loading

0 comments on commit b396a0e

Please sign in to comment.