Skip to content

Commit 5b77bbb

Browse files
committed
wip: move to java 17; generic bitfield enums and schemas.
1 parent 2033369 commit 5b77bbb

File tree

17 files changed

+687
-418
lines changed

17 files changed

+687
-418
lines changed

library/src/main/java/module-info.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@
1111
exports pro.javacard.gp;
1212
exports pro.javacard.gp.emv;
1313
exports pro.javacard.gp.keys;
14+
exports pro.javacard.gp.data;
1415
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package pro.javacard.gp;
2+
3+
import apdu4j.core.HexUtils;
4+
import org.slf4j.Logger;
5+
import org.slf4j.LoggerFactory;
6+
7+
import java.time.DateTimeException;
8+
import java.time.LocalDate;
9+
import java.time.format.DateTimeFormatter;
10+
import java.util.Arrays;
11+
import java.util.LinkedHashMap;
12+
import java.util.Optional;
13+
import java.util.stream.Collectors;
14+
15+
public final class CPLC {
16+
private static final Logger logger = LoggerFactory.getLogger(CPLC.class);
17+
private final LinkedHashMap<Field, byte[]> values = new LinkedHashMap<>();
18+
19+
private CPLC(byte[] data) {
20+
int offset = 0;
21+
for (Field f : Field.values()) {
22+
values.put(f, Arrays.copyOfRange(data, offset, offset + f.len));
23+
offset += f.len;
24+
}
25+
}
26+
27+
public static CPLC fromBytes(byte[] data) throws GPDataException {
28+
if (data == null)
29+
throw new IllegalArgumentException("data is null");
30+
if (data.length < 0x2A)
31+
throw new GPDataException(String.format("Input can't be valid CPLC if length is only %02X!", data.length), data);
32+
// Remove tag, if present
33+
if (data[0] == (byte) 0x9f && data[1] == (byte) 0x7f && data[2] == (byte) 0x2A)
34+
data = Arrays.copyOfRange(data, 3, data.length);
35+
return new CPLC(data);
36+
}
37+
38+
public byte[] get(Field f) {
39+
return values.get(f);
40+
}
41+
42+
public String toString() {
43+
return Arrays.stream(Field.values()).map(i -> i.toString() + "=" + HexUtils.bin2hex(values.get(i))).collect(Collectors.joining(", ", "[CPLC: ", "]"));
44+
}
45+
46+
public String toPrettyString() {
47+
return Arrays.stream(Field.values()).map(i -> i.toString() + "=" + HexUtils.bin2hex(values.get(i)) + (i.toString().endsWith("Date") ? " (" + toDateFailsafe(values.get(i)) + ")" : "")).collect(Collectors.joining("\n ", "CPLC: ", "\n"));
48+
}
49+
50+
public enum Field {
51+
ICFabricator(2),
52+
ICType(2),
53+
OperatingSystemID(2),
54+
OperatingSystemReleaseDate(2),
55+
OperatingSystemReleaseLevel(2),
56+
ICFabricationDate(2),
57+
ICSerialNumber(4),
58+
ICBatchIdentifier(2),
59+
ICModuleFabricator(2),
60+
ICModulePackagingDate(2),
61+
ICCManufacturer(2),
62+
ICEmbeddingDate(2),
63+
ICPrePersonalizer(2),
64+
ICPrePersonalizationEquipmentDate(2),
65+
ICPrePersonalizationEquipmentID(4),
66+
ICPersonalizer(2),
67+
ICPersonalizationDate(2),
68+
ICPersonalizationEquipmentID(4);
69+
70+
private final int len;
71+
72+
Field(int len) {
73+
this.len = len;
74+
}
75+
}
76+
77+
public static Optional<LocalDate> toRelativeDate(byte[] v, LocalDate now) throws GPDataException {
78+
if ((v[0] == 0 && v[1] == 0) || (v[0] == (byte) 0xFF && v[1] == (byte) 0xFF)) {
79+
logger.debug("0x0000/0xFFFF does not represent a valid date");
80+
return Optional.empty();
81+
}
82+
String sv = HexUtils.bin2hex(v);
83+
try {
84+
int y = Integer.parseInt(sv.substring(0, 1));
85+
int d = Integer.parseInt(sv.substring(1, 4));
86+
int base = 2020;
87+
if (y >= now.getYear() % 10 && d > now.getDayOfYear())
88+
base = 2010;
89+
LocalDate ld = LocalDate.ofYearDay(base + y, d);
90+
return Optional.of(ld);
91+
} catch (NumberFormatException | DateTimeException e) {
92+
logger.warn("Invalid CPLC date: " + sv);
93+
return Optional.empty();
94+
}
95+
}
96+
97+
public static String toDateFailsafe(byte[] v) {
98+
return toRelativeDate(v, LocalDate.now()).map(e -> e.format(DateTimeFormatter.ISO_LOCAL_DATE)).orElse("invalid date format");
99+
}
100+
101+
public static byte[] today() {
102+
return dateToBytes(LocalDate.now());
103+
}
104+
105+
public static byte[] dateToBytes(LocalDate d) {
106+
return HexUtils.hex2bin(String.format("%d%03d", d.getYear() - 2020, d.getDayOfYear()));
107+
}
108+
}

library/src/main/java/pro/javacard/gp/GPCommands.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,24 +65,24 @@ public static void listRegistry(GPRegistry reg, PrintStream out, boolean verbose
6565
String tab = " ";
6666
for (GPRegistryEntry e : reg) {
6767
AID aid = e.getAID();
68-
out.print(e.getType().toShortString() + ": " + HexUtils.bin2hex(aid.getBytes()) + " (" + e.getLifeCycleString() + ")");
69-
if (e.getType() != GPRegistryEntry.Kind.IssuerSecurityDomain && verbose) {
70-
out.println(" (" + WellKnownAID.getName(aid).orElse(GPUtils.byteArrayToReadableString(aid.getBytes())) + ")");
68+
out.print(e.getType() + ": " + HexUtils.bin2hex(aid.getBytes()) + " (" + e.getLifeCycleString() + ")");
69+
if (e.getType() != GPRegistryEntry.Kind.ISD && verbose) {
70+
out.println(" (" + WellKnownAID.getName(aid).orElse(GPUtils.bin2readable(aid.getBytes())) + ")");
7171
} else {
7272
out.println();
7373
}
7474

7575
if (e.getDomain().isPresent()) {
7676
out.println(tab + "Parent: " + e.getDomain().get());
7777
}
78-
if (e.getType() == GPRegistryEntry.Kind.ExecutableLoadFile) {
78+
if (e.getType() == GPRegistryEntry.Kind.PKG) {
7979
if (e.getVersion() != null) {
8080
out.println(tab + "Version: " + e.getVersionString());
8181
}
8282
for (AID a : e.getModules()) {
8383
out.print(tab + "Applet: " + HexUtils.bin2hex(a.getBytes()));
8484
if (verbose) {
85-
out.println(" (" + WellKnownAID.getName(a).orElse(GPUtils.byteArrayToReadableString(a.getBytes())) + ")");
85+
out.println(" (" + WellKnownAID.getName(a).orElse(GPUtils.bin2readable(a.getBytes())) + ")");
8686
} else {
8787
out.println();
8888
}

library/src/main/java/pro/javacard/gp/GPData.java

Lines changed: 23 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,15 @@
3030
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
3131
import org.slf4j.Logger;
3232
import org.slf4j.LoggerFactory;
33+
import pro.javacard.gp.data.BitField;
3334

3435
import java.io.IOException;
3536
import java.math.BigInteger;
36-
import java.time.DateTimeException;
37-
import java.time.LocalDate;
38-
import java.time.format.DateTimeFormatter;
3937
import java.util.*;
4038
import java.util.stream.Collectors;
4139

4240
import static pro.javacard.gp.GPSession.CLA_GP;
41+
import static pro.javacard.gp.data.BitField.byte_mask;
4342

4443
// Various constants from GP specification and other sources
4544
// Methods to pretty-print those structures and constants.
@@ -49,13 +48,6 @@ public final class GPData {
4948
private GPData() {
5049
}
5150

52-
// SD states
53-
public static final byte readyStatus = 0x1;
54-
public static final byte initializedStatus = 0x7;
55-
public static final byte securedStatus = 0xF;
56-
public static final byte lockedStatus = 0x7F;
57-
public static final byte terminatedStatus = (byte) 0xFF;
58-
5951
// Default ISD AID-s
6052
static final byte[] defaultISDBytes = HexUtils.hex2bin("A000000151000000");
6153
static final Map<Integer, String> sw = new HashMap<>();
@@ -190,38 +182,34 @@ public String toString() {
190182
}
191183

192184
// GPC 2.3.1 Table H-9 and Table H-10
193-
enum SIGNATURE {
185+
public enum SIGNATURE implements BitField<SIGNATURE> {
194186
// First byte
195-
RSA1024_SHA1(0x01, 0),
196-
RSAPSS_SHA256(0x02, 0),
197-
DES_MAC(0x04, 0),
198-
CMAC_AES128(0x08, 0),
199-
CMAC_AES192(0x10, 0),
200-
CMAC_AES256(0x20, 0),
201-
ECCP256_SHA256(0x40, 0),
202-
ECCP384_SHA384(0x80, 0),
187+
RSA1024_SHA1(byte_mask(0, 0x01)),
188+
RSAPSS_SHA256(byte_mask(0, 0x02)),
189+
DES_MAC(byte_mask(0, 0x04)),
190+
CMAC_AES128(byte_mask(0, 0x08)),
191+
CMAC_AES192(byte_mask(0, 0x10)),
192+
CMAC_AES256(byte_mask(0, 0x20)),
193+
ECCP256_SHA256(byte_mask(0, 0x40)),
194+
ECCP384_SHA384(byte_mask(0, 0x80)),
203195
// Second byte
204-
ECCP512_SHA512(0x01, 1),
205-
ECCP521_SHA512(0x02, 1);
196+
ECCP512_SHA512(byte_mask(1, 0x01)),
197+
ECCP521_SHA512(byte_mask(1, 0x02)),
198+
RFU(new Def.RFU(byte_mask(1, 0xFC)));
206199

207-
int value;
208-
int pos;
200+
private final BitField.Def def;
209201

210-
SIGNATURE(int byteValue, int pos) {
211-
this.value = byteValue;
212-
this.pos = pos;
202+
SIGNATURE(BitField.Def def) {
203+
this.def = def;
204+
}
205+
206+
@Override
207+
public Def def() {
208+
return def;
213209
}
214210

215211
public static Set<SIGNATURE> byValue(byte[] v) {
216-
LinkedHashSet<SIGNATURE> r = new LinkedHashSet<>();
217-
for (int i = 0; i < v.length; i++) {
218-
final int p = i;
219-
Arrays.stream(values()).filter(e -> e.pos == p).forEach(e -> {
220-
if (e.value == (e.value & v[p]))
221-
r.add(e);
222-
});
223-
}
224-
return r;
212+
return BitField.parse(SIGNATURE.class, v);
225213
}
226214
}
227215

@@ -427,98 +415,4 @@ public static byte[] getData(APDUBIBO channel, int p1, int p2, String name, bool
427415
}
428416
}
429417

430-
public static final class CPLC {
431-
432-
private final LinkedHashMap<Field, byte[]> values = new LinkedHashMap<>();
433-
434-
private CPLC(byte[] data) {
435-
int offset = 0;
436-
for (Field f : Field.values()) {
437-
values.put(f, Arrays.copyOfRange(data, offset, offset + f.len));
438-
offset += f.len;
439-
}
440-
}
441-
442-
public static CPLC fromBytes(byte[] data) throws GPDataException {
443-
if (data == null)
444-
throw new IllegalArgumentException("data is null");
445-
if (data.length < 0x2A)
446-
throw new GPDataException(String.format("Input can't be valid CPLC if length is only %02X!", data.length), data);
447-
// Remove tag, if present
448-
if (data[0] == (byte) 0x9f && data[1] == (byte) 0x7f && data[2] == (byte) 0x2A)
449-
data = Arrays.copyOfRange(data, 3, data.length);
450-
return new CPLC(data);
451-
}
452-
453-
public byte[] get(Field f) {
454-
return values.get(f);
455-
}
456-
457-
public String toString() {
458-
return Arrays.stream(Field.values()).map(i -> i.toString() + "=" + HexUtils.bin2hex(values.get(i))).collect(Collectors.joining(", ", "[CPLC: ", "]"));
459-
}
460-
461-
public String toPrettyString() {
462-
return Arrays.stream(Field.values()).map(i -> i.toString() + "=" + HexUtils.bin2hex(values.get(i)) + (i.toString().endsWith("Date") ? " (" + toDateFailsafe(values.get(i)) + ")" : "")).collect(Collectors.joining("\n ", "CPLC: ", "\n"));
463-
}
464-
465-
public enum Field {
466-
ICFabricator(2),
467-
ICType(2),
468-
OperatingSystemID(2),
469-
OperatingSystemReleaseDate(2),
470-
OperatingSystemReleaseLevel(2),
471-
ICFabricationDate(2),
472-
ICSerialNumber(4),
473-
ICBatchIdentifier(2),
474-
ICModuleFabricator(2),
475-
ICModulePackagingDate(2),
476-
ICCManufacturer(2),
477-
ICEmbeddingDate(2),
478-
ICPrePersonalizer(2),
479-
ICPrePersonalizationEquipmentDate(2),
480-
ICPrePersonalizationEquipmentID(4),
481-
ICPersonalizer(2),
482-
ICPersonalizationDate(2),
483-
ICPersonalizationEquipmentID(4);
484-
485-
private final int len;
486-
487-
Field(int len) {
488-
this.len = len;
489-
}
490-
}
491-
492-
public static Optional<LocalDate> toRelativeDate(byte[] v, LocalDate now) throws GPDataException {
493-
if ((v[0] == 0 && v[1] == 0) || (v[0] == (byte) 0xFF && v[1] == (byte) 0xFF)) {
494-
logger.debug("0x0000/0xFFFF does not represent a valid date");
495-
return Optional.empty();
496-
}
497-
String sv = HexUtils.bin2hex(v);
498-
try {
499-
int y = Integer.parseInt(sv.substring(0, 1));
500-
int d = Integer.parseInt(sv.substring(1, 4));
501-
int base = 2020;
502-
if (y >= now.getYear() % 10 && d > now.getDayOfYear())
503-
base = 2010;
504-
LocalDate ld = LocalDate.ofYearDay(base + y, d);
505-
return Optional.of(ld);
506-
} catch (NumberFormatException | DateTimeException e) {
507-
logger.warn("Invalid CPLC date: " + sv);
508-
return Optional.empty();
509-
}
510-
}
511-
512-
public static String toDateFailsafe(byte[] v) {
513-
return toRelativeDate(v, LocalDate.now()).map(e -> e.format(DateTimeFormatter.ISO_LOCAL_DATE)).orElse("invalid date format");
514-
}
515-
516-
public static byte[] today() {
517-
return dateToBytes(LocalDate.now());
518-
}
519-
520-
public static byte[] dateToBytes(LocalDate d) {
521-
return HexUtils.hex2bin(String.format("%d%03d", d.getYear() - 2020, d.getDayOfYear()));
522-
}
523-
}
524418
}

0 commit comments

Comments
 (0)