├── ext
└── jpp-1.0.3.jar
├── .gitignore
├── .gitmodules
├── test
├── jcardsim.cfg
├── apdu.script
└── simulator-tests
├── .classpath
├── .project
├── src
└── net
│ └── cooperi
│ └── pivapplet
│ ├── File.java
│ ├── Readable.java
│ ├── Buffer.java
│ ├── PivSlot.java
│ ├── APDUStream.java
│ ├── BaseBuffer.java
│ ├── TlvReader.java
│ ├── TransientBuffer.java
│ ├── BufferManager.java
│ ├── TlvWriter.java
│ ├── ECParams.java
│ ├── SGList.java
│ └── PivApplet.java
├── .travis.yml
└── README.adoc
/ext/jpp-1.0.3.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arekinath/PivApplet/HEAD/ext/jpp-1.0.3.jar
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /bin/
2 | /jckit/
3 | /src-gen/
4 | java_card_kit-2_2_2-linux.zip
5 | /test/*.pem
6 | /test/*.ebox
7 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "ext/ant"]
2 | path = ext/ant
3 | url = https://github.com/martinpaljak/ant-javacard.git
4 |
--------------------------------------------------------------------------------
/test/jcardsim.cfg:
--------------------------------------------------------------------------------
1 | com.licel.jcardsim.card.applet.0.AID=A000000308000010000100
2 | com.licel.jcardsim.card.applet.0.Class=net.cooperi.pivapplet.PivApplet
3 | com.licel.jcardsim.card.ATR=3B80800101
4 | com.licel.jcardsim.vsmartcard.host=localhost
5 | com.licel.jcardsim.vsmartcard.port=35963
6 |
--------------------------------------------------------------------------------
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | pivapplet
4 | PIV applet
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.jdt.core.javanature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/net/cooperi/pivapplet/File.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | *
6 | * Copyright (c) 2018, Alex Wilson
7 | */
8 |
9 | package net.cooperi.pivapplet;
10 |
11 | public class File {
12 | public static final byte P_ALWAYS = (byte)0;
13 | public static final byte P_PIN = (byte)1;
14 | public static final byte P_NEVER = (byte)2;
15 |
16 | public byte[] data;
17 | public short len;
18 |
19 | public byte contact = P_ALWAYS;
20 | public byte contactless = P_ALWAYS;
21 | }
22 |
--------------------------------------------------------------------------------
/src/net/cooperi/pivapplet/Readable.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | *
6 | * Copyright (c) 2018, Alex Wilson
7 | */
8 |
9 | package net.cooperi.pivapplet;
10 |
11 | public interface Readable {
12 | public boolean atEnd();
13 | public short available();
14 | public byte readByte();
15 | public short readShort();
16 | public short read(byte[] dest, short offset, short maxLen);
17 | public short read(TransientBuffer into, short maxLen);
18 | public short readPartial(TransientBuffer into, short maxLen);
19 | public void skip(short len);
20 | public void rewind();
21 | }
22 |
--------------------------------------------------------------------------------
/src/net/cooperi/pivapplet/Buffer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | *
6 | * Copyright (c) 2018, Alex Wilson
7 | */
8 |
9 | package net.cooperi.pivapplet;
10 |
11 | public interface Buffer {
12 | public short offset();
13 | public short len();
14 | public byte[] data();
15 |
16 | public short rpos();
17 | public void read(short bytes);
18 | public short wpos();
19 | public void jumpWpos(short newpos);
20 | public void write(short bytes);
21 | public short remaining();
22 | public short available();
23 | public void reset();
24 | public void rewind();
25 |
26 | public void free();
27 | }
28 |
--------------------------------------------------------------------------------
/src/net/cooperi/pivapplet/PivSlot.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | *
6 | * Copyright (c) 2017, Alex Wilson
7 | */
8 |
9 | package net.cooperi.pivapplet;
10 |
11 | import javacard.framework.JCSystem;
12 | import javacard.framework.Util;
13 | import javacard.framework.OwnerPIN;
14 |
15 | import javacard.security.KeyPair;
16 | import javacard.security.SecretKey;
17 |
18 | public class PivSlot {
19 | public static final byte F_UNLOCKED = (byte)0;
20 | public static final byte F_AFTER_VERIFY = (byte)1;
21 | public static final byte MAX_FLAGS = (byte)(F_AFTER_VERIFY + 1);
22 |
23 | public static final byte P_DEFAULT = (byte)0x00;
24 | public static final byte P_NEVER = (byte)0x01;
25 | public static final byte P_ONCE = (byte)0x02;
26 | public static final byte P_ALWAYS = (byte)0x03;
27 |
28 | public boolean imported = false;
29 | public File cert = null;
30 |
31 | public byte pinPolicy = P_ONCE;
32 |
33 | public KeyPair asym = null;
34 | public byte asymAlg = -1;
35 | public SecretKey sym = null;
36 | public byte symAlg = -1;
37 | public byte id = (byte)0;
38 |
39 | public boolean[] flags = null;
40 |
41 | public
42 | PivSlot(byte id)
43 | {
44 | flags = JCSystem.makeTransientBooleanArray((short)MAX_FLAGS,
45 | JCSystem.CLEAR_ON_DESELECT);
46 | this.id = id;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: generic
2 | os: linux
3 | dist: bionic
4 |
5 | addons:
6 | apt:
7 | packages:
8 | - pcscd
9 | - pcsc-tools
10 | - libpcsclite-dev
11 | - docbook-xsl
12 | - xsltproc
13 | - help2man
14 | - ant
15 | - socat
16 | - yubico-piv-tool
17 | - opensc
18 | - gcc
19 | - openjdk-8-jdk
20 |
21 | before_script:
22 | - set -ex;
23 |
24 | sudo update-java-alternatives -s java-1.8.0-openjdk-amd64;
25 | sudo update-alternatives --get-selections | grep ^java;
26 | export PATH="/usr/lib/jvm/java-8-openjdk-amd64/bin/:$PATH";
27 | export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64/;
28 | env | grep -i openjdk;
29 |
30 | git clone https://github.com/frankmorgner/vsmartcard.git;
31 | cd vsmartcard/virtualsmartcard;
32 | autoreconf -vis && ./configure && sudo make install;
33 | cd $TRAVIS_BUILD_DIR;
34 | sudo /etc/init.d/pcscd restart;
35 |
36 | git clone https://github.com/martinpaljak/oracle_javacard_sdks.git;
37 | export JC_SDKS=$PWD/oracle_javacard_sdks;
38 | export JC_HOME=$JC_SDKS/jc305u3_kit;
39 | export JC_CLASSIC_HOME=$JC_HOME;
40 |
41 | git clone https://github.com/arekinath/jcardsim.git;
42 | cd jcardsim;
43 | mvn initialize && mvn clean install -DskipTests;
44 | cd $TRAVIS_BUILD_DIR;
45 |
46 | export JC_HOME=$JC_SDKS/jc304_kit;
47 | ant dist;
48 |
49 | sudo wget https://github.com/arekinath/pivy/releases/download/v0.7.1/pivy_0.7.1-1_amd64_bionic.deb;
50 | sudo dpkg -i pivy_0.7.1-1_amd64_bionic.deb;
51 |
52 | gem install minitest;
53 |
54 | set +ex;
55 |
56 | script:
57 | - set -ex;
58 |
59 | sudo systemctl stop pcscd.service pcscd.socket;
60 | sudo /usr/sbin/pcscd -f &
61 | PCSCD_PID=$!;
62 |
63 | java -noverify -cp bin/:jcardsim/target/jcardsim-3.0.5-SNAPSHOT.jar com.licel.jcardsim.remote.VSmartCard test/jcardsim.cfg >/dev/null &
64 | PID=$!;
65 | sleep 5;
66 | opensc-tool --card-driver default --send-apdu 80b80000120ba000000308000010000100050000020F0F7f;
67 | opensc-tool -n;
68 |
69 | ./test/simulator-tests 'Virtual PCD 00 00';
70 |
71 | kill -9 $PID;
72 |
73 | sudo kill -9 $PCSCD_PID;
74 |
75 | set +ex;
76 |
--------------------------------------------------------------------------------
/src/net/cooperi/pivapplet/APDUStream.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | *
6 | * Copyright (c) 2017, Alex Wilson
7 | */
8 |
9 | package net.cooperi.pivapplet;
10 |
11 | import javacard.framework.APDU;
12 | import javacard.framework.JCSystem;
13 | import javacard.framework.Util;
14 | import javacard.framework.ISO7816;
15 | import javacard.framework.ISOException;
16 |
17 | public class APDUStream implements Readable {
18 | private static final byte ST_BEGIN = 0;
19 | private static final byte ST_RPTR = 1;
20 | private static final byte ST_END = 2;
21 |
22 | private short[] s = null;
23 |
24 | public
25 | APDUStream()
26 | {
27 | s = JCSystem.makeTransientShortArray((short)3,
28 | JCSystem.CLEAR_ON_DESELECT);
29 | }
30 |
31 | public void
32 | reset(final short offset, final short len)
33 | {
34 | s[ST_RPTR] = offset;
35 | s[ST_BEGIN] = offset;
36 | s[ST_END] = (short)(len + offset);
37 | }
38 |
39 | public void
40 | rewind()
41 | {
42 | s[ST_RPTR] = s[ST_BEGIN];
43 | }
44 |
45 | public byte
46 | readByte()
47 | {
48 | final byte[] buf = APDU.getCurrentAPDUBuffer();
49 | if ((short)(s[ST_RPTR] + 1) > s[ST_END]) {
50 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
51 | return ((byte)0);
52 | }
53 | return (buf[s[ST_RPTR]++]);
54 | }
55 |
56 | public short
57 | readShort()
58 | {
59 | final byte[] buf = APDU.getCurrentAPDUBuffer();
60 | if ((short)(s[ST_RPTR] + 2) > s[ST_END]) {
61 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
62 | return ((short)0);
63 | }
64 | final short val = Util.getShort(buf, s[ST_RPTR]);
65 | s[ST_RPTR] += 2;
66 | return (val);
67 | }
68 |
69 | public boolean
70 | atEnd()
71 | {
72 | return (s[ST_RPTR] >= s[ST_END]);
73 | }
74 |
75 | public short
76 | available()
77 | {
78 | return ((short)(s[ST_END] - s[ST_RPTR]));
79 | }
80 |
81 | public short
82 | read(final byte[] dest, final short offset, final short maxLen)
83 | {
84 | final byte[] buf = APDU.getCurrentAPDUBuffer();
85 | final short rem = (short)(s[ST_END] - s[ST_RPTR]);
86 | final short take = (rem < maxLen) ? rem : maxLen;
87 | Util.arrayCopyNonAtomic(buf, s[ST_RPTR], dest, offset, take);
88 | s[ST_RPTR] += take;
89 | return (take);
90 | }
91 |
92 | public short
93 | read(final TransientBuffer into, final short maxLen)
94 | {
95 | final short rem = (short)(s[ST_END] - s[ST_RPTR]);
96 | final short take = (rem < maxLen) ? rem : maxLen;
97 | into.setApdu(s[ST_RPTR], take);
98 | s[ST_RPTR] += take;
99 | return (take);
100 | }
101 |
102 | public short
103 | readPartial(final TransientBuffer into, final short maxLen)
104 | {
105 | return (read(into, maxLen));
106 | }
107 |
108 | public void
109 | skip(final short len)
110 | {
111 | if ((short)(s[ST_RPTR] + len) > s[ST_END]) {
112 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
113 | return;
114 | }
115 | s[ST_RPTR] += len;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/test/apdu.script:
--------------------------------------------------------------------------------
1 | // create piv applet
2 | 0x80 0xb8 0x00 0x00 0x12 0x0b 0xa0 0x00 0x00 0x03 0x08 0x00 0x00 0x10 0x00 0x01 0x00 0x05 0x00 0x00 0x02 0xF 0xF 0x7f;
3 | // select piv applet
4 | 0x00 0xa4 0x04 0x00 0x0b 0xa0 0x00 0x00 0x03 0x08 0x00 0x00 0x10 0x00 0x01 0x00 0x00;
5 | // get discovery object
6 | 0x00 0xcb 0x3f 0xff 0x03 0x5c 0x01 0x7e 0x00;
7 | // get chuid file
8 | 0x00 0xcb 0x3f 0xff 0x05 0x5c 0x03 0x5f 0xc1 0x02 0x08;
9 | 0x00 0xC0 0x00 0x00 0x00 0x00;
10 | // ask for a challenge for admin auth
11 | 0x00 0x87 0x00 0x9b 0x04 0x7c 0x02 0x81 0x00 0x00;
12 | 0x00 0x87 0x00 0x9b 0x0c 0x7c 0x0a 0x82 0x08 119 167 214 188 245 121 98 185 0x00;
13 | // admin auth with witness
14 | 0x00 0x87 0x00 0x9b 0x04 0x7c 0x02 0x80 0x00 0x00;
15 | 0x00 0x87 0x03 0x9b 0x16 0x7c 0x14 0x80 0x08 1 2 3 4 5 6 7 8 0x81 0x08 0x6e 0xe3 0x27 0x1c 0x4b 0x1b 0x64 0xba 0x00;
16 | // generate rsa key
17 | 0x00 0x47 0x00 0x9e 0x05 0xAC 0x03 0x80 0x01 0x07 0x00;
18 | 0x00 0xC0 0x00 0x00 0x00 0x00;
19 | // set cert in 9e
20 | 0x10 0xDB 0x3f 0xff 0xFF 0x5C 0x03 0x5F 0xC1 0x01 0x53 0x82 0x02 0x5F 0x70 0x82 0x02 0x58 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0x01 0x02 0x00;
21 | 0x10 0xDB 0x3f 0xff 0xFF 0x03 0x04 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0x05 0x06 0x00;
22 | 0x00 0xDB 0x3f 0xff 0x6a 0x07 0x08 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0x09 0x0A 0x71 0x01 0x01 0x00;
23 | // read cert in 9e
24 | 0x00 0xCB 0x3F 0xFF 0x05 0x5C 0x03 0x5F 0xC1 0x01 0x00;
25 | 0x00 0xC0 0x00 0x00 0x00 0x00;
26 | 0x00 0xC0 0x00 0x00 0x00 0x00;
27 | // sg debug info
28 | 0x00 0xe0 0x00 0x00 0x00 0x00;
29 |
--------------------------------------------------------------------------------
/src/net/cooperi/pivapplet/BaseBuffer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | *
6 | * Copyright (c) 2018, Alex Wilson
7 | */
8 |
9 | package net.cooperi.pivapplet;
10 |
11 | import javacard.framework.JCSystem;
12 | import javacard.framework.SystemException;
13 |
14 | public class BaseBuffer implements Buffer {
15 | //#if PIV_SUPPORT_RSA
16 | public static final short RAM_ALLOC_SIZE = 512;
17 | public static final short RAM_ALLOC_SIZE_2 = 256;
18 | /*#else
19 | public static final short RAM_ALLOC_SIZE = 256;
20 | public static final short RAM_ALLOC_SIZE_2 = 128;
21 | #endif*/
22 |
23 | /*#if APPLET_LOW_TRANSIENT
24 | public static final short RAM_ALLOC_MAX_INDEX = 2;
25 | public static final short EEPROM_ALLOC_SIZE = 2048;
26 | #else*/
27 | public static final short RAM_ALLOC_MAX_INDEX = 7;
28 | public static final short EEPROM_ALLOC_SIZE = 1024;
29 | //#endif
30 |
31 | private static final short ST_RPOS = 0;
32 | private static final short ST_WPOS = 1;
33 | private static final short ST_AMASK = 2;
34 |
35 | private byte[] data;
36 | public boolean isTransient;
37 | public final short index;
38 | private short[] state;
39 | public final BufferManager manager;
40 | public short offsetStep;
41 |
42 | public
43 | BaseBuffer(BufferManager mgr, short idx)
44 | {
45 | index = idx;
46 | manager = mgr;
47 | data = null;
48 | offsetStep = 0;
49 | state = JCSystem.makeTransientShortArray((short)(ST_AMASK + 1),
50 | JCSystem.CLEAR_ON_DESELECT);
51 | isTransient = false;
52 | }
53 |
54 | public boolean
55 | maskAvailable(final short mask)
56 | {
57 | return ((short)(mask & state[ST_AMASK]) == 0);
58 | }
59 |
60 | public short
61 | maskAnd(final short mask)
62 | {
63 | return ((short)(mask & state[ST_AMASK]));
64 | }
65 |
66 | public boolean
67 | maskFull()
68 | {
69 | return (state[ST_AMASK] == (short)0xffff);
70 | }
71 |
72 | public void
73 | setMask(final short mask)
74 | {
75 | state[ST_AMASK] |= mask;
76 | }
77 |
78 | public void
79 | clearMask(final short mask)
80 | {
81 | final short invMask = (short)~mask;
82 | state[ST_AMASK] &= invMask;
83 | }
84 |
85 | public boolean
86 | setMaskIfAvailable(final short mask)
87 | {
88 | if ((short)(mask & state[ST_AMASK]) == 0) {
89 | state[ST_AMASK] |= mask;
90 | return (true);
91 | }
92 | return (false);
93 | }
94 |
95 | public short
96 | rpos()
97 | {
98 | return (state[ST_RPOS]);
99 | }
100 |
101 | public void
102 | read(short bytes)
103 | {
104 | state[ST_RPOS] += bytes;
105 | }
106 |
107 | public short
108 | wpos()
109 | {
110 | return (state[ST_WPOS]);
111 | }
112 |
113 | public void
114 | write(short bytes)
115 | {
116 | state[ST_WPOS] += bytes;
117 | }
118 |
119 | public void
120 | jumpWpos(short newpos)
121 | {
122 | state[ST_WPOS] = newpos;
123 | }
124 |
125 | public void
126 | reset()
127 | {
128 | state[ST_RPOS] = 0;
129 | state[ST_WPOS] = 0;
130 | }
131 |
132 | public void
133 | rewind()
134 | {
135 | state[ST_RPOS] = 0;
136 | }
137 |
138 | public short
139 | remaining()
140 | {
141 | return ((short)(state[ST_WPOS] - state[ST_RPOS]));
142 | }
143 |
144 | public short
145 | available()
146 | {
147 | return ((short)((short)data.length - state[ST_WPOS]));
148 | }
149 |
150 | public short
151 | offset()
152 | {
153 | return (0);
154 | }
155 |
156 | public short
157 | len()
158 | {
159 | return ((short)data.length);
160 | }
161 |
162 | public byte[]
163 | data()
164 | {
165 | return (data);
166 | }
167 |
168 | public void
169 | free()
170 | {
171 | data = null;
172 | isTransient = false;
173 | offsetStep = 0;
174 | }
175 |
176 | public void
177 | alloc()
178 | {
179 | if (data != null)
180 | return;
181 |
182 | if (index > RAM_ALLOC_MAX_INDEX) {
183 | isTransient = false;
184 | data = new byte[EEPROM_ALLOC_SIZE];
185 | offsetStep = (short)((short)data.length >> 4);
186 | return;
187 | }
188 |
189 | try {
190 | data = JCSystem.makeTransientByteArray(RAM_ALLOC_SIZE,
191 | JCSystem.CLEAR_ON_DESELECT);
192 | isTransient = true;
193 | offsetStep = (short)((short)data.length >> 4);
194 | return;
195 | } catch (SystemException ex) {
196 | if (ex.getReason() != SystemException.NO_TRANSIENT_SPACE) {
197 | throw (ex);
198 | }
199 | }
200 |
201 | try {
202 | data = JCSystem.makeTransientByteArray(RAM_ALLOC_SIZE_2,
203 | JCSystem.CLEAR_ON_DESELECT);
204 | isTransient = true;
205 | offsetStep = (short)((short)data.length >> 4);
206 | return;
207 | } catch (SystemException ex) {
208 | if (ex.getReason() != SystemException.NO_TRANSIENT_SPACE) {
209 | throw (ex);
210 | }
211 | }
212 |
213 | isTransient = false;
214 | data = new byte[EEPROM_ALLOC_SIZE];
215 | offsetStep = (short)((short)data.length >> 4);
216 | }
217 | }
218 |
--------------------------------------------------------------------------------
/src/net/cooperi/pivapplet/TlvReader.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | *
6 | * Copyright (c) 2017, Alex Wilson
7 | */
8 |
9 | package net.cooperi.pivapplet;
10 |
11 | import javacard.framework.JCSystem;
12 | import javacard.framework.ISO7816;
13 | import javacard.framework.ISOException;
14 |
15 | public class TlvReader {
16 | /*#if APPLET_LOW_TRANSIENT
17 | private static final short STACK_SIZE = (short)4;
18 | #else*/
19 | private static final short STACK_SIZE = (short)8;
20 | //#endif
21 |
22 | private static final byte OFFSET = 0;
23 | private static final byte PTR = 1;
24 | private static final byte LEN = 2;
25 | private static final byte END = 3;
26 |
27 | private Object[] target = null;
28 | private short[] s = null;
29 | private short[] stack = null;
30 |
31 | public
32 | TlvReader()
33 | {
34 | target = JCSystem.makeTransientObjectArray((short)1,
35 | JCSystem.CLEAR_ON_DESELECT);
36 | stack = JCSystem.makeTransientShortArray(STACK_SIZE,
37 | JCSystem.CLEAR_ON_DESELECT);
38 | s = JCSystem.makeTransientShortArray((short)(END + 1),
39 | JCSystem.CLEAR_ON_DESELECT);
40 | }
41 |
42 | public void
43 | start(Readable newTarget)
44 | {
45 | target[0] = (Object)newTarget;
46 | s[OFFSET] = (short)0;
47 | s[PTR] = (short)0;
48 | s[LEN] = newTarget.available();
49 | s[END] = s[LEN];
50 | }
51 |
52 | public void
53 | rewind()
54 | {
55 | final Readable src = (Readable)target[0];
56 | src.rewind();
57 | s[OFFSET] = (short)0;
58 | s[PTR] = (short)0;
59 | s[LEN] = src.available();
60 | s[END] = s[LEN];
61 | }
62 |
63 | public boolean
64 | atEnd()
65 | {
66 | if (s[OFFSET] >= s[END])
67 | return (true);
68 | return (false);
69 | }
70 |
71 | public byte
72 | readTag()
73 | {
74 | stack[s[PTR]++] = s[END];
75 |
76 | final Readable src = (Readable)target[0];
77 | final byte tag = src.readByte();
78 | s[OFFSET]++;
79 | final byte firstLen = src.readByte();
80 | final short firstLenOff = s[OFFSET]++;
81 | switch (firstLen) {
82 | case (byte)0x81:
83 | s[OFFSET] += 1;
84 | s[LEN] = (short)(
85 | (short)src.readByte() & (short)0x00FF);
86 | break;
87 | case (byte)0x82:
88 | s[OFFSET] += 2;
89 | s[LEN] = src.readShort();
90 | break;
91 | default:
92 | if (firstLen > (byte)0x7f) {
93 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
94 | return (0);
95 | }
96 | s[LEN] = (short)firstLen;
97 | break;
98 | }
99 | final short end = (short)(s[OFFSET] + s[LEN]);
100 | if (end > s[END]) {
101 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
102 | return (0);
103 | }
104 | return (tag);
105 | }
106 |
107 | public void
108 | end()
109 | {
110 | if (s[LEN] > 0) {
111 | ISOException.throwIt(PivApplet.SW_TAG_END_ASSERT);
112 | return;
113 | }
114 | final short oldEnd = stack[--s[PTR]];
115 | s[END] = oldEnd;
116 | s[LEN] = (short)(oldEnd - s[OFFSET]);
117 | }
118 |
119 | public void
120 | abort()
121 | {
122 | final short oldEnd = stack[0];
123 | s[END] = oldEnd;
124 | s[LEN] = (short)(oldEnd - s[OFFSET]);
125 | s[PTR] = 1;
126 | skip();
127 | }
128 |
129 | public void
130 | finish()
131 | {
132 | if (s[LEN] > 0) {
133 | ISOException.throwIt(PivApplet.SW_TAG_END_ASSERT);
134 | return;
135 | }
136 | if (s[PTR] != 0) {
137 | ISOException.throwIt(PivApplet.SW_DATA_END_ASSERT);
138 | return;
139 | }
140 | }
141 |
142 | public short
143 | tagLength()
144 | {
145 | return (s[LEN]);
146 | }
147 |
148 | public byte
149 | readByte()
150 | {
151 | final Readable src = (Readable)target[0];
152 | s[OFFSET]++;
153 | s[LEN]--;
154 | if (s[LEN] < 0) {
155 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
156 | return ((byte)0);
157 | }
158 | return (src.readByte());
159 | }
160 |
161 | public short
162 | readShort()
163 | {
164 | final Readable src = (Readable)target[0];
165 | s[OFFSET] += 2;
166 | s[LEN] -= 2;
167 | if (s[LEN] < 0) {
168 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
169 | return ((byte)0);
170 | }
171 | return (src.readShort());
172 | }
173 |
174 | public void
175 | skip()
176 | {
177 | final Readable src = (Readable)target[0];
178 | src.skip(s[LEN]);
179 | s[OFFSET] += s[LEN];
180 | s[LEN] = (short)0;
181 | end();
182 | }
183 |
184 | public short
185 | read(byte[] dest, short offset, short maxLen)
186 | {
187 | final Readable src = (Readable)target[0];
188 | final short toCopy = (s[LEN] > maxLen) ? maxLen : s[LEN];
189 | final short done = src.read(dest, offset, toCopy);
190 | s[LEN] -= done;
191 | s[OFFSET] += done;
192 | return (done);
193 | }
194 |
195 | public short
196 | read(TransientBuffer into, short maxLen)
197 | {
198 | final Readable src = (Readable)target[0];
199 | final short toCopy = (s[LEN] > maxLen) ? maxLen : s[LEN];
200 | final short done = src.read(into, toCopy);
201 | s[LEN] -= done;
202 | s[OFFSET] += done;
203 | return (done);
204 | }
205 |
206 | public short
207 | readPartial(TransientBuffer into, short maxLen)
208 | {
209 | final Readable src = (Readable)target[0];
210 | final short toCopy = (s[LEN] > maxLen) ? maxLen : s[LEN];
211 | final short done = src.readPartial(into, toCopy);
212 | s[LEN] -= done;
213 | s[OFFSET] += done;
214 | return (done);
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/src/net/cooperi/pivapplet/TransientBuffer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | *
6 | * Copyright (c) 2018, Alex Wilson
7 | */
8 |
9 | package net.cooperi.pivapplet;
10 |
11 | import javacard.framework.APDU;
12 | import javacard.framework.ISOException;
13 | import javacard.framework.JCSystem;
14 |
15 | public class TransientBuffer implements Buffer {
16 | private static final short PTR_PARENT = 0;
17 | private static final short PTR_BUF = 1;
18 |
19 | private static final short ST_MASK = 0;
20 | private static final short ST_FLAGS = 1;
21 | private static final short ST_BEGIN = 2;
22 | private static final short ST_END = 3;
23 | private static final short ST_RPOS = 4;
24 | private static final short ST_WPOS = 5;
25 |
26 | private static final short FL_APDU = 1;
27 | private static final short FL_BUF = 2;
28 | private static final short FL_ALLOC = 4;
29 | private static final short FL_EXPANDED = 8;
30 |
31 | private Object[] ptrs;
32 | private short[] state;
33 |
34 | public
35 | TransientBuffer()
36 | {
37 | /*#if APPLET_LOW_TRANSIENT
38 | ptrs = new Object[PTR_BUF + 1];
39 | #else*/
40 | ptrs = JCSystem.makeTransientObjectArray((short)(PTR_BUF + 1),
41 | JCSystem.CLEAR_ON_DESELECT);
42 | //#endif
43 | state = JCSystem.makeTransientShortArray((short)(ST_WPOS + 1),
44 | JCSystem.CLEAR_ON_DESELECT);
45 | }
46 |
47 | public boolean
48 | isAllocated()
49 | {
50 | return (state[ST_FLAGS] != (short)0);
51 | }
52 |
53 | public BaseBuffer
54 | parent()
55 | {
56 | if (ptrs[PTR_PARENT] == null)
57 | return (null);
58 | if ((short)(state[ST_FLAGS] & FL_ALLOC) == 0)
59 | return (null);
60 | return ((BaseBuffer)ptrs[PTR_PARENT]);
61 | }
62 |
63 | public short
64 | mask()
65 | {
66 | return (state[ST_MASK]);
67 | }
68 |
69 | public void
70 | setApdu(final short offset, final short len)
71 | {
72 | ptrs[PTR_PARENT] = null;
73 | ptrs[PTR_BUF] = null;
74 | state[ST_BEGIN] = offset;
75 | state[ST_END] = (short)(offset + len);
76 | state[ST_FLAGS] = FL_APDU;
77 | reset();
78 | }
79 |
80 | public void
81 | setWriteSlice(final TransientBuffer other, final short len)
82 | {
83 | if (other.isApdu())
84 | setApdu(other.wpos(), len);
85 | else
86 | setBuffer(other.data(), other.wpos(), len);
87 | }
88 |
89 | public void
90 | setReadSlice(final TransientBuffer other, final short len)
91 | {
92 | if (other.isApdu())
93 | setApdu(other.rpos(), len);
94 | else
95 | setBuffer(other.data(), other.rpos(), len);
96 | }
97 |
98 | public void
99 | setBuffer(final byte[] buffer, final short offset, final short len)
100 | {
101 | ptrs[PTR_PARENT] = null;
102 | ptrs[PTR_BUF] = buffer;
103 | state[ST_BEGIN] = offset;
104 | state[ST_END] = (short)(offset + len);
105 | state[ST_FLAGS] = FL_BUF;
106 | reset();
107 | }
108 |
109 | public void
110 | allocFromBase(final BaseBuffer parent, final short offset,
111 | final short mask, final short size)
112 | {
113 | ptrs[PTR_PARENT] = parent;
114 | ptrs[PTR_BUF] = parent.data();
115 | state[ST_BEGIN] = offset;
116 | state[ST_END] = (short)(offset + size);
117 | state[ST_MASK] = mask;
118 | state[ST_FLAGS] = FL_ALLOC;
119 | reset();
120 | }
121 |
122 | public void
123 | expandFromBase(final short newMask, final short newSize)
124 | {
125 | state[ST_END] = (short)(state[ST_BEGIN] + newSize);
126 | state[ST_MASK] = newMask;
127 | state[ST_FLAGS] |= FL_EXPANDED;
128 | }
129 |
130 | public void
131 | expand(final short sizeIncr)
132 | {
133 | final BaseBuffer parent = (BaseBuffer)ptrs[PTR_PARENT];
134 | if (parent != null) {
135 | final short size = (short)(
136 | state[ST_END] - state[ST_BEGIN]);
137 | final short newSize = (short)(size + sizeIncr);
138 | parent.manager.realloc(newSize, this);
139 | }
140 | }
141 |
142 | public void
143 | free()
144 | {
145 | final BaseBuffer parent = (BaseBuffer)ptrs[PTR_PARENT];
146 | if (parent != null)
147 | parent.clearMask(state[ST_MASK]);
148 |
149 | ptrs[PTR_PARENT] = null;
150 | ptrs[PTR_BUF] = null;
151 | state[ST_BEGIN] = 0;
152 | state[ST_END] = 0;
153 | state[ST_RPOS] = 0;
154 | state[ST_WPOS] = 0;
155 | state[ST_FLAGS] = 0;
156 | }
157 |
158 | public short
159 | offset()
160 | {
161 | return (state[ST_BEGIN]);
162 | }
163 |
164 | public short
165 | len()
166 | {
167 | return ((short)(state[ST_END] - state[ST_BEGIN]));
168 | }
169 |
170 | public byte[]
171 | data()
172 | {
173 | final short flags = state[ST_FLAGS];
174 | if ((short)(flags & FL_APDU) != 0)
175 | return (APDU.getCurrentAPDUBuffer());
176 | if (flags == 0)
177 | return (null);
178 | return ((byte[])ptrs[PTR_BUF]);
179 | }
180 |
181 | public boolean
182 | isApdu()
183 | {
184 | return ((short)(state[ST_FLAGS] & FL_APDU) != 0);
185 | }
186 |
187 | public short
188 | rpos()
189 | {
190 | return (state[ST_RPOS]);
191 | }
192 |
193 | public short
194 | wpos()
195 | {
196 | return (state[ST_WPOS]);
197 | }
198 |
199 | public void
200 | jumpWpos(final short newpos)
201 | {
202 | if (newpos < state[ST_BEGIN] || newpos > state[ST_END]) {
203 | ISOException.throwIt(PivApplet.SW_BAD_REWRITE);
204 | return;
205 | }
206 | state[ST_WPOS] = newpos;
207 | }
208 |
209 | public void
210 | read(short bytes)
211 | {
212 | state[ST_RPOS] += bytes;
213 | }
214 |
215 | public void
216 | write(short bytes)
217 | {
218 | state[ST_WPOS] += bytes;
219 | if (state[ST_WPOS] < state[ST_BEGIN] ||
220 | state[ST_WPOS] > state[ST_END]) {
221 | ISOException.throwIt(PivApplet.SW_WRITE_OVER_END);
222 | return;
223 | }
224 | }
225 |
226 | public short
227 | remaining()
228 | {
229 | return ((short)(state[ST_WPOS] - state[ST_RPOS]));
230 | }
231 |
232 | public short
233 | available()
234 | {
235 | return ((short)(state[ST_END] - state[ST_WPOS]));
236 | }
237 |
238 | public void
239 | reset()
240 | {
241 | state[ST_RPOS] = state[ST_BEGIN];
242 | state[ST_WPOS] = state[ST_BEGIN];
243 | }
244 |
245 | public void
246 | rewind()
247 | {
248 | state[ST_RPOS] = state[ST_BEGIN];
249 | }
250 | }
251 |
--------------------------------------------------------------------------------
/src/net/cooperi/pivapplet/BufferManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | *
6 | * Copyright (c) 2018, Alex Wilson
7 | */
8 |
9 | package net.cooperi.pivapplet;
10 |
11 | import javacard.framework.JCSystem;
12 |
13 | /*
14 | * BufferManager represents a simple first-fit memory allocator. It keeps an
15 | * array of BaseBuffer instances which each represent a memory chunk (preferably
16 | * transient memory, but falling back to regular), and splits each into 16
17 | * pieces. Each BaseBuffer has a bitmap of the 16 pieces as a short.
18 | *
19 | * When someone requests a chunk of memory, we round it up to the nearest 1/16th
20 | * chunk and use a bitwise AND against the bitmap to test if the space is
21 | * available. If it is, we use it; otherwise we slide the mask to the left and
22 | * try again (searching for a higher offset).
23 | *
24 | * Note that the whole allocation state is all kept in DESELECT transient memory
25 | * -- so the worst "memory leak" we can have lasts until the applet is
26 | * de-selected.
27 | *
28 | * The reason why this is done dynamically is to enable the TlvWriter, crypto
29 | * etc to grab small chunks of memory for "scratch" space for constructing
30 | * tags or decrypting data while re-using the precious transient memory we used
31 | * for spooling in chained packets. It also enables easy re-use between rx and
32 | * tx, even when we allocate something in between that has to be kept (e.g. we
33 | * rx some data, then encrypt part of it, put the encrypted data in a temp
34 | * buffer, then release all the rx state to start txing, but we need to keep
35 | * that ciphertext to put it into the tx payload).
36 | */
37 | public class BufferManager {
38 | /*#if APPLET_LOW_TRANSIENT
39 | public static final byte MAX_BUFS = 5;
40 | #else*/
41 | public static final byte MAX_BUFS = 10;
42 | //#endif
43 |
44 | private final BaseBuffer[] buffers;
45 | public boolean gcBlewUp = false;
46 |
47 | public
48 | BufferManager()
49 | {
50 | buffers = new BaseBuffer[MAX_BUFS];
51 | for (short i = 0; i < MAX_BUFS; ++i)
52 | buffers[i] = new BaseBuffer(this, i);
53 | }
54 |
55 | public void
56 | cullNonTransient()
57 | {
58 | if (!JCSystem.isObjectDeletionSupported() || gcBlewUp)
59 | return;
60 | boolean dogc = false;
61 | for (short i = 0; i < MAX_BUFS; ++i) {
62 | final BaseBuffer buf = buffers[i];
63 | if (!buf.isTransient && buf.data() != null) {
64 | buf.free();
65 | dogc = true;
66 | }
67 | }
68 | if (dogc) {
69 | try {
70 | JCSystem.requestObjectDeletion();
71 | } catch (Exception e) {
72 | gcBlewUp = true;
73 | }
74 | }
75 | }
76 |
77 | public boolean
78 | alloc(final short size, final TransientBuffer buf)
79 | {
80 | /*
81 | * Lots of the backing buffers are the same size, so we can
82 | * re-use these calculations.
83 | */
84 | short lastBaseMask = 0;
85 | short lastBufSize = 0;
86 | short lastBits = 0;
87 |
88 | /*
89 | * If we already allocated this buffer and can re-use it, just
90 | * do that.
91 | */
92 | if (buf.isAllocated()) {
93 | final short curLen = buf.len();
94 | if (curLen < size)
95 | buf.expand((short)(size - curLen));
96 | if (buf.len() >= size) {
97 | buf.reset();
98 | return (true);
99 | }
100 | }
101 |
102 | buf.free();
103 |
104 | for (short idx = 0; idx < MAX_BUFS; ++idx) {
105 | final BaseBuffer buffer = buffers[idx];
106 |
107 | if (buffer.maskFull())
108 | continue;
109 |
110 | /*
111 | * We've never used this buffer before? Try to allocate
112 | * some space to it.
113 | */
114 | if (buffer.data() == null)
115 | buffer.alloc();
116 |
117 | final byte[] data = buffer.data();
118 | if (data == null)
119 | continue;
120 |
121 | final short bufSize = (short)data.length;
122 |
123 | /*
124 | * offsetStep is the size of one of the 1/16th chunks
125 | * which are represented by 1 bit in the bitmap.
126 | */
127 | final short offsetStep = buffer.offsetStep;
128 |
129 | /*
130 | * baseMask will contain the number of set bits that
131 | * we're trying to allocate, starting at bit 0 (e.g.
132 | * if we need to alloc 4 bits worth, it will be 0b1111
133 | * or 0x0f).
134 | */
135 | final short baseMask;
136 | /* Number of set bits */
137 | short bits;
138 |
139 | if (bufSize == lastBufSize) {
140 | baseMask = lastBaseMask;
141 | bits = lastBits;
142 | } else {
143 | short baseSize = offsetStep;
144 | bits = 1;
145 | while (baseSize < size) {
146 | ++bits;
147 | baseSize += offsetStep;
148 | }
149 | baseMask = (short)(
150 | (short)((short)1 << bits) - 1);
151 | lastBaseMask = baseMask;
152 | lastBufSize = bufSize;
153 | lastBits = bits;
154 | }
155 |
156 | /*
157 | * Now shift the mask left until we've got free space
158 | * or we run out of bits to check.
159 | */
160 | short mask = baseMask;
161 | short offset = 0;
162 | bits = (short)(16 - bits);
163 | while (!buffer.maskAvailable(mask) && bits >= 0) {
164 | --bits;
165 | offset += offsetStep;
166 | mask = (short)(mask << 1);
167 | }
168 |
169 | if ((short)(offset + size) > bufSize)
170 | continue;
171 |
172 | if (buffer.setMaskIfAvailable(mask)) {
173 | buf.allocFromBase(buffer, offset, mask, size);
174 | return (true);
175 | }
176 | }
177 | return (false);
178 | }
179 |
180 | /* Called by TransientBuffer#expand */
181 | public boolean
182 | realloc(final short size, final TransientBuffer buf)
183 | {
184 | final BaseBuffer buffer = buf.parent();
185 | final short bufSize = buffer.len();
186 | final short offset = buf.offset();
187 |
188 | /*
189 | * We can't possibly fit this in the buffer, even if the
190 | * bitmap is empty.
191 | */
192 | if ((short)(offset + size) > bufSize)
193 | return (false);
194 |
195 | /*
196 | * mask = our current mask (before realloc)
197 | * nmask = our desired mask
198 | */
199 | final short mask = buf.mask();
200 | final short offsetStep = (short)(bufSize >> 4);
201 | short baseSize = offsetStep;
202 | short nmask = mask;
203 | while (baseSize < buf.len())
204 | baseSize += offsetStep;
205 | while (baseSize < size) {
206 | nmask |= (short)(nmask << 1);
207 | baseSize += offsetStep;
208 | }
209 |
210 | /*
211 | * If all of the new bits that we want to add are zero, then
212 | * buffer's bitmap AND nmask will be just mask (only our old
213 | * existing used bits are set)
214 | */
215 | if (buffer.maskAnd(nmask) != mask)
216 | return (false);
217 |
218 | buffer.setMask(nmask);
219 | buf.expandFromBase(nmask, size);
220 | return (true);
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/src/net/cooperi/pivapplet/TlvWriter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | *
6 | * Copyright (c) 2017, Alex Wilson
7 | */
8 |
9 | package net.cooperi.pivapplet;
10 |
11 | import javacard.framework.JCSystem;
12 | import javacard.framework.Util;
13 |
14 | public class TlvWriter {
15 | /*#if APPLET_LOW_TRANSIENT
16 | private static final short STACK_SIZE = (short)4;
17 | #else*/
18 | private static final short STACK_SIZE = (short)8;
19 | //#endif
20 |
21 | private static final byte PTR = 0;
22 |
23 | private Object[] target = null;
24 | private short[] s = null;
25 | private short[] stackBuf = null;
26 | private short[] stackOff = null;
27 | private short[] stackWPtr = null;
28 | private byte[] tmp = null;
29 |
30 | private SGList scratch = null;
31 |
32 | public
33 | TlvWriter(BufferManager bufmgr)
34 | {
35 | stackBuf = JCSystem.makeTransientShortArray(STACK_SIZE,
36 | JCSystem.CLEAR_ON_DESELECT);
37 | stackOff = JCSystem.makeTransientShortArray(STACK_SIZE,
38 | JCSystem.CLEAR_ON_DESELECT);
39 | stackWPtr = JCSystem.makeTransientShortArray(STACK_SIZE,
40 | JCSystem.CLEAR_ON_DESELECT);
41 | /*#if APPLET_LOW_TRANSIENT
42 | tmp = new byte[5];
43 | target = new Object[1];
44 | #else*/
45 | tmp = JCSystem.makeTransientByteArray((short)5,
46 | JCSystem.CLEAR_ON_DESELECT);
47 | target = JCSystem.makeTransientObjectArray((short)1,
48 | JCSystem.CLEAR_ON_DESELECT);
49 | //#endif
50 | s = JCSystem.makeTransientShortArray((short)(PTR + 1),
51 | JCSystem.CLEAR_ON_DESELECT);
52 | this.scratch = new SGList(bufmgr, (short)4);
53 | }
54 |
55 | public void
56 | start(SGList newTarget)
57 | {
58 | target[0] = (Object)newTarget;
59 | s[PTR] = (short)0;
60 | scratch.reset();
61 | }
62 |
63 | /*
64 | * Set our scratch SGList to use the APDU buffer space first -- so
65 | * if everything we write fits into the APDU buffer, we don't have to
66 | * double-copy it.
67 | */
68 | public void
69 | useApdu(final short offset, final short len)
70 | {
71 | scratch.useApdu(offset, len);
72 | }
73 |
74 | private void
75 | saveStackFrame()
76 | {
77 | stackBuf[s[PTR]] = scratch.wPtrBuf();
78 | stackOff[s[PTR]] = scratch.wPtrOff();
79 | final SGList dest = (SGList)target[0];
80 | stackWPtr[s[PTR]] = (short)(dest.wPtr() + scratch.available());
81 | s[PTR]++;
82 | }
83 |
84 | public void
85 | startReserve(short len, TransientBuffer into)
86 | {
87 | scratch.startReserve(len, into);
88 | }
89 |
90 | public void
91 | endReserve(short used)
92 | {
93 | scratch.endReserve(used);
94 | }
95 |
96 | public void
97 | push(byte tag)
98 | {
99 | scratch.writeByte(tag);
100 | saveStackFrame();
101 | scratch.writeByte((byte)0);
102 | }
103 |
104 | public void
105 | push(short tag)
106 | {
107 | scratch.writeShort(tag);
108 | saveStackFrame();
109 | scratch.writeByte((byte)0);
110 | }
111 |
112 | public void
113 | push(byte tag, short expLen)
114 | {
115 | if (expLen > (short)250) {
116 | push64k(tag);
117 | } else if (expLen > (short)124) {
118 | push256(tag);
119 | } else {
120 | push(tag);
121 | }
122 | }
123 |
124 | public void
125 | push(short tag, short expLen)
126 | {
127 | if (expLen > (short)250) {
128 | push64k(tag);
129 | } else if (expLen > (short)124) {
130 | push256(tag);
131 | } else {
132 | push(tag);
133 | }
134 | }
135 |
136 | public static short
137 | sizeWithByteTag(final short len)
138 | {
139 | if (len > (short)250) {
140 | return ((short)(len + 4));
141 | } else if (len > (short)124) {
142 | return ((short)(len + 3));
143 | } else {
144 | return ((short)(len + 2));
145 | }
146 | }
147 |
148 | public static short
149 | sizeWithShortTag(final short len)
150 | {
151 | if (len > (short)250) {
152 | return ((short)(len + 5));
153 | } else if (len > (short)124) {
154 | return ((short)(len + 4));
155 | } else {
156 | return ((short)(len + 3));
157 | }
158 | }
159 |
160 | /*
161 | * Optimised tag writing for when we have a known length in advance
162 | * for the tag.
163 | */
164 | public void
165 | writeTagRealLen(final byte tag, final short len)
166 | {
167 | tmp[0] = tag;
168 | if (len > (short)250) {
169 | tmp[1] = (byte)0x82;
170 | Util.setShort(tmp, (short)2, len);
171 | scratch.write(tmp, (short)0, (short)4);
172 | } else if (len > (short)124) {
173 | tmp[1] = (byte)0x81;
174 | tmp[2] = (byte)len;
175 | scratch.write(tmp, (short)0, (short)3);
176 | } else {
177 | tmp[1] = (byte)len;
178 | scratch.write(tmp, (short)0, (short)2);
179 | }
180 | }
181 |
182 | public void
183 | writeTagRealLen(short tag, short len)
184 | {
185 | Util.setShort(tmp, (short)0, tag);
186 | if (len > (short)250) {
187 | tmp[2] = (byte)0x82;
188 | Util.setShort(tmp, (short)3, len);
189 | scratch.write(tmp, (short)0, (short)5);
190 | } else if (len > (short)124) {
191 | tmp[2] = (byte)0x81;
192 | tmp[3] = (byte)len;
193 | scratch.write(tmp, (short)0, (short)4);
194 | } else {
195 | tmp[2] = (byte)len;
196 | scratch.write(tmp, (short)0, (short)3);
197 | }
198 | }
199 |
200 | public void
201 | push256(byte tag)
202 | {
203 | scratch.writeByte(tag);
204 | saveStackFrame();
205 | scratch.writeByte((byte)0x81);
206 | scratch.writeByte((byte)0);
207 | }
208 |
209 | public void
210 | push256(short tag)
211 | {
212 | scratch.writeShort(tag);
213 | saveStackFrame();
214 | scratch.writeByte((byte)0x81);
215 | scratch.writeByte((byte)0);
216 | }
217 |
218 | public void
219 | push64k(byte tag)
220 | {
221 | scratch.writeByte(tag);
222 | saveStackFrame();
223 | scratch.writeByte((byte)0x82);
224 | scratch.writeByte((byte)0);
225 | scratch.writeByte((byte)0);
226 | }
227 |
228 | public void
229 | push64k(short tag)
230 | {
231 | scratch.writeShort(tag);
232 | saveStackFrame();
233 | scratch.writeByte((byte)0x82);
234 | scratch.writeByte((byte)0);
235 | scratch.writeByte((byte)0);
236 | }
237 |
238 | public void
239 | write(byte[] data, short off, short len)
240 | {
241 | /* For short strings, just write directly into scratch. */
242 | if (len <= 128) {
243 | scratch.write(data, off, len);
244 | return;
245 | }
246 | final SGList dest = (SGList)target[0];
247 | scratch.readInto(dest, scratch.available());
248 | dest.append(data, off, len);
249 | }
250 |
251 | public void
252 | write(Buffer buf)
253 | {
254 | write(buf.data(), buf.rpos(), buf.remaining());
255 | }
256 |
257 | public void
258 | writeByte(byte data)
259 | {
260 | scratch.writeByte(data);
261 | }
262 |
263 | public void
264 | writeShort(short data)
265 | {
266 | scratch.writeShort(data);
267 | }
268 |
269 | public void
270 | pop()
271 | {
272 | final SGList dest = (SGList)target[0];
273 |
274 | s[PTR]--;
275 | final short curOff = (short)(dest.wPtr() + scratch.available());
276 | final short off = stackWPtr[s[PTR]];
277 |
278 | scratch.rewriteAt(stackBuf[s[PTR]], stackOff[s[PTR]]);
279 |
280 | final short len;
281 | switch (scratch.peekByteW()) {
282 | case (byte)0x00:
283 | len = (short)(curOff - (short)(off + 1));
284 | scratch.writeByte((byte)len);
285 | break;
286 | case (byte)0x81:
287 | len = (short)(curOff - (short)(off + 2));
288 | scratch.writeByte((byte)0x81);
289 | scratch.writeByte((byte)len);
290 | break;
291 | case (byte)0x82:
292 | len = (short)(curOff - (short)(off + 3));
293 | scratch.writeByte((byte)0x82);
294 | scratch.writeShort(len);
295 | break;
296 | }
297 |
298 | scratch.endRewrite();
299 | }
300 |
301 | public void
302 | end()
303 | {
304 | final SGList dest = (SGList)target[0];
305 | scratch.readInto(dest, scratch.available());
306 | }
307 | }
308 |
--------------------------------------------------------------------------------
/src/net/cooperi/pivapplet/ECParams.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | *
6 | * Copyright (c) 2017, Alex Wilson
7 | */
8 |
9 | package net.cooperi.pivapplet;
10 |
11 | import javacard.security.ECKey;
12 |
13 | public class ECParams {
14 | public final static byte ALG_ECDSA_SHA_256 = (byte)33;
15 | public final static byte ALG_ECDSA_SHA_384 = (byte)34;
16 |
17 | //#if PIV_SUPPORT_EC
18 | public static final byte[] nistp256_p = {
19 | (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0x0,
20 | (byte)0x0, (byte)0x0, (byte)0x1, (byte)0x0, (byte)0x0, (byte)0x0,
21 | (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0,
22 | (byte)0x0, (byte)0x0, (byte)0x0, (byte)0xff, (byte)0xff, (byte)0xff,
23 | (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
24 | (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff
25 | };
26 | public static final byte[] nistp256_a = {
27 | (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0x0,
28 | (byte)0x0, (byte)0x0, (byte)0x1, (byte)0x0, (byte)0x0, (byte)0x0,
29 | (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0,
30 | (byte)0x0, (byte)0x0, (byte)0x0, (byte)0xff, (byte)0xff, (byte)0xff,
31 | (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
32 | (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xfc
33 | };
34 | public static final byte[] nistp256_b = {
35 | (byte)0x5a, (byte)0xc6, (byte)0x35, (byte)0xd8, (byte)0xaa,
36 | (byte)0x3a, (byte)0x93, (byte)0xe7, (byte)0xb3, (byte)0xeb,
37 | (byte)0xbd, (byte)0x55, (byte)0x76, (byte)0x98, (byte)0x86,
38 | (byte)0xbc, (byte)0x65, (byte)0x1d, (byte)0x6, (byte)0xb0,
39 | (byte)0xcc, (byte)0x53, (byte)0xb0, (byte)0xf6, (byte)0x3b,
40 | (byte)0xce, (byte)0x3c, (byte)0x3e, (byte)0x27, (byte)0xd2,
41 | (byte)0x60, (byte)0x4b
42 | };
43 | public static final byte[] nistp256_R = {
44 | (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0x00,
45 | (byte)0x00, (byte)0x00, (byte)0x00, (byte)0xFF, (byte)0xFF,
46 | (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF,
47 | (byte)0xFF, (byte)0xBC, (byte)0xE6, (byte)0xFA, (byte)0xAD,
48 | (byte)0xA7, (byte)0x17, (byte)0x9E, (byte)0x84, (byte)0xF3,
49 | (byte)0xB9, (byte)0xCA, (byte)0xC2, (byte)0xFC, (byte)0x63,
50 | (byte)0x25, (byte)0x51
51 | };
52 | public static final byte[] nistp256_G = {
53 | (byte)0x4, (byte)0x6b, (byte)0x17, (byte)0xd1, (byte)0xf2,
54 | (byte)0xe1, (byte)0x2c, (byte)0x42, (byte)0x47, (byte)0xf8,
55 | (byte)0xbc, (byte)0xe6, (byte)0xe5, (byte)0x63, (byte)0xa4,
56 | (byte)0x40, (byte)0xf2, (byte)0x77, (byte)0x3, (byte)0x7d,
57 | (byte)0x81, (byte)0x2d, (byte)0xeb, (byte)0x33, (byte)0xa0,
58 | (byte)0xf4, (byte)0xa1, (byte)0x39, (byte)0x45, (byte)0xd8,
59 | (byte)0x98, (byte)0xc2, (byte)0x96, (byte)0x4f, (byte)0xe3,
60 | (byte)0x42, (byte)0xe2, (byte)0xfe, (byte)0x1a, (byte)0x7f,
61 | (byte)0x9b, (byte)0x8e, (byte)0xe7, (byte)0xeb, (byte)0x4a,
62 | (byte)0x7c, (byte)0xf, (byte)0x9e, (byte)0x16, (byte)0x2b,
63 | (byte)0xce, (byte)0x33, (byte)0x57, (byte)0x6b, (byte)0x31,
64 | (byte)0x5e, (byte)0xce, (byte)0xcb, (byte)0xb6, (byte)0x40,
65 | (byte)0x68, (byte)0x37, (byte)0xbf, (byte)0x51, (byte)0xf5
66 | };
67 | public static void setCurveParametersP256(ECKey eckey) {
68 | eckey.setFieldFP(
69 | nistp256_p, (short)0, (short)(nistp256_p.length));
70 | eckey.setA(nistp256_a, (short)0, (short)(nistp256_a.length));
71 | eckey.setB(nistp256_b, (short)0, (short)(nistp256_b.length));
72 | eckey.setG(nistp256_G, (short)0, (short)(nistp256_G.length));
73 | eckey.setR(nistp256_R, (short)0, (short)(nistp256_R.length));
74 | //eckey.setK((short)1);
75 | }
76 |
77 | public static final byte[] nistp384_p = {
78 | (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
79 | (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
80 | (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
81 | (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
82 | (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
83 | (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
84 | (byte)0xff, (byte)0xfe, (byte)0xff, (byte)0xff, (byte)0xff,
85 | (byte)0xff, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0,
86 | (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0xff,
87 | (byte)0xff, (byte)0xff, (byte)0xff
88 | };
89 | public static final byte[] nistp384_a = {
90 | (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
91 | (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
92 | (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
93 | (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
94 | (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
95 | (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
96 | (byte)0xff, (byte)0xfe, (byte)0xff, (byte)0xff, (byte)0xff,
97 | (byte)0xff, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0,
98 | (byte)0x0, (byte)0x0, (byte)0x0, (byte)0x0, (byte)0xff,
99 | (byte)0xff, (byte)0xff, (byte)0xfc
100 | };
101 | public static final byte[] nistp384_b = {
102 | (byte)0xb3, (byte)0x31, (byte)0x2f, (byte)0xa7, (byte)0xe2,
103 | (byte)0x3e, (byte)0xe7, (byte)0xe4, (byte)0x98, (byte)0x8e,
104 | (byte)0x5, (byte)0x6b, (byte)0xe3, (byte)0xf8, (byte)0x2d,
105 | (byte)0x19, (byte)0x18, (byte)0x1d, (byte)0x9c, (byte)0x6e,
106 | (byte)0xfe, (byte)0x81, (byte)0x41, (byte)0x12, (byte)0x3,
107 | (byte)0x14, (byte)0x8, (byte)0x8f, (byte)0x50, (byte)0x13,
108 | (byte)0x87, (byte)0x5a, (byte)0xc6, (byte)0x56, (byte)0x39,
109 | (byte)0x8d, (byte)0x8a, (byte)0x2e, (byte)0xd1, (byte)0x9d,
110 | (byte)0x2a, (byte)0x85, (byte)0xc8, (byte)0xed, (byte)0xd3,
111 | (byte)0xec, (byte)0x2a, (byte)0xef
112 | };
113 | public static final byte[] nistp384_R = {
114 | (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
115 | (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
116 | (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
117 | (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff,
118 | (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xc7,
119 | (byte)0x63, (byte)0x4d, (byte)0x81, (byte)0xf4, (byte)0x37,
120 | (byte)0x2d, (byte)0xdf, (byte)0x58, (byte)0x1a, (byte)0xd,
121 | (byte)0xb2, (byte)0x48, (byte)0xb0, (byte)0xa7, (byte)0x7a,
122 | (byte)0xec, (byte)0xec, (byte)0x19, (byte)0x6a, (byte)0xcc,
123 | (byte)0xc5, (byte)0x29, (byte)0x73
124 | };
125 | public static final byte[] nistp384_G = {
126 | (byte)0x4, (byte)0xaa, (byte)0x87, (byte)0xca, (byte)0x22,
127 | (byte)0xbe, (byte)0x8b, (byte)0x5, (byte)0x37, (byte)0x8e,
128 | (byte)0xb1, (byte)0xc7, (byte)0x1e, (byte)0xf3, (byte)0x20,
129 | (byte)0xad, (byte)0x74, (byte)0x6e, (byte)0x1d, (byte)0x3b,
130 | (byte)0x62, (byte)0x8b, (byte)0xa7, (byte)0x9b, (byte)0x98,
131 | (byte)0x59, (byte)0xf7, (byte)0x41, (byte)0xe0, (byte)0x82,
132 | (byte)0x54, (byte)0x2a, (byte)0x38, (byte)0x55, (byte)0x2,
133 | (byte)0xf2, (byte)0x5d, (byte)0xbf, (byte)0x55, (byte)0x29,
134 | (byte)0x6c, (byte)0x3a, (byte)0x54, (byte)0x5e, (byte)0x38,
135 | (byte)0x72, (byte)0x76, (byte)0xa, (byte)0xb7, (byte)0x36,
136 | (byte)0x17, (byte)0xde, (byte)0x4a, (byte)0x96, (byte)0x26,
137 | (byte)0x2c, (byte)0x6f, (byte)0x5d, (byte)0x9e, (byte)0x98,
138 | (byte)0xbf, (byte)0x92, (byte)0x92, (byte)0xdc, (byte)0x29,
139 | (byte)0xf8, (byte)0xf4, (byte)0x1d, (byte)0xbd, (byte)0x28,
140 | (byte)0x9a, (byte)0x14, (byte)0x7c, (byte)0xe9, (byte)0xda,
141 | (byte)0x31, (byte)0x13, (byte)0xb5, (byte)0xf0, (byte)0xb8,
142 | (byte)0xc0, (byte)0xa, (byte)0x60, (byte)0xb1, (byte)0xce,
143 | (byte)0x1d, (byte)0x7e, (byte)0x81, (byte)0x9d, (byte)0x7a,
144 | (byte)0x43, (byte)0x1d, (byte)0x7c, (byte)0x90, (byte)0xea,
145 | (byte)0xe, (byte)0x5f
146 | };
147 | public static void setCurveParametersP384(ECKey eckey) {
148 | eckey.setFieldFP(
149 | nistp384_p, (short)0, (short)(nistp384_p.length));
150 | eckey.setA(nistp384_a, (short)0, (short)(nistp384_a.length));
151 | eckey.setB(nistp384_b, (short)0, (short)(nistp384_b.length));
152 | eckey.setG(nistp384_G, (short)0, (short)(nistp384_G.length));
153 | eckey.setR(nistp384_R, (short)0, (short)(nistp384_R.length));
154 | //eckey.setK((short)1);
155 | }
156 | //#endif
157 | }
158 |
--------------------------------------------------------------------------------
/test/simulator-tests:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'minitest/autorun'
3 | require 'openssl'
4 |
5 | Reader = ARGV[0]
6 | DefaultPin = "123456"
7 | DefaultPuk = "12345678"
8 | TestDir = __dir__
9 |
10 | $pin = DefaultPin
11 | $guid = nil
12 |
13 | if Reader.nil?
14 | STDERR.puts "requires reader name"
15 | exit 1
16 | end
17 |
18 | def ykpivtool(*opts)
19 | cmd = "yubico-piv-tool -r '#{Reader}'"
20 | opts.each do |opt|
21 | if opt.kind_of? Symbol
22 | if opt == :verify_pin
23 | cmd += " -P #{$pin}"
24 | end
25 | cmd += " -a #{opt.to_s.gsub('_', '-')}"
26 | else
27 | cmd += " #{opt}"
28 | end
29 | end
30 | `#{cmd} 2>&1`
31 | end
32 |
33 | class TestYubicoBasic < Minitest::Test
34 | i_suck_and_my_tests_are_order_dependent!
35 |
36 | def test_01_reader_exists
37 | out = `yubico-piv-tool -r '' -a list-readers`.split("\n")
38 | assert_equal(0, $?)
39 | assert_includes(out, Reader)
40 | end
41 |
42 | def test_02_status
43 | out = ykpivtool(:status)
44 | assert_equal(0, $?)
45 | assert_match(/^CHUID:/, out)
46 | assert_match(/^CCC:/, out)
47 | assert_match(/^PIN tries left:\s5$/, out)
48 | end
49 |
50 | def test_03_generate
51 | out = ykpivtool(:generate, '-s 9a', '-A ECCP256')
52 | assert_equal(0, $?)
53 | assert_match(/^-----BEGIN PUBLIC KEY-----$/, out)
54 |
55 | @pubkey = OpenSSL::PKey.read(out)
56 | assert(@pubkey.kind_of? OpenSSL::PKey::EC)
57 |
58 | f = File.new("#{TestDir}/pubkey-9a.pem", 'w')
59 | f.write(out)
60 | f.close
61 | end
62 |
63 | def test_04_verify_pin
64 | out = ykpivtool(:verify_pin)
65 | assert_equal(0, $?)
66 | end
67 |
68 | def test_05_selfsign
69 | out = ykpivtool(:verify_pin, :selfsign_certificate,
70 | '-s 9a', '-S "/CN=test/"', "< #{TestDir}/pubkey-9a.pem")
71 | assert_equal(0, $?)
72 | assert_match(/^-----BEGIN CERTIFICATE-----$/, out)
73 |
74 | cert = OpenSSL::X509::Certificate.new(out)
75 | assert_match(/^ecdsa-with-/, cert.signature_algorithm)
76 | @pubkey = OpenSSL::PKey.read(
77 | File.new("#{TestDir}/pubkey-9a.pem").read())
78 | assert(cert.verify(@pubkey))
79 |
80 | f = File.new("#{TestDir}/cert-9a.pem", 'w')
81 | f.write(out)
82 | f.close
83 | end
84 |
85 | def test_06_import_cert
86 | out = ykpivtool(:import_certificate, '-s 9a',
87 | "< #{TestDir}/cert-9a.pem")
88 | assert_equal(0, $?)
89 | end
90 |
91 | def test_07_status_again
92 | out = ykpivtool(:status)
93 | assert_equal(0, $?)
94 | assert_match(/^Slot 9a:/, out)
95 | assert_match(/Algorithm:\s+ECCP256$/, out)
96 | assert_match(/Subject DN:\s+CN=test$/, out)
97 | end
98 | end
99 |
100 | class TestYubicoAlgs < Minitest::Test
101 | i_suck_and_my_tests_are_order_dependent!
102 |
103 | def test_01_generate_rsa1024
104 | out = ykpivtool(:generate, '-s 9e', '-A RSA1024')
105 | assert_equal(0, $?)
106 | assert_match(/^-----BEGIN PUBLIC KEY-----$/, out)
107 |
108 | @pubkey = OpenSSL::PKey.read(out)
109 | assert(@pubkey.kind_of? OpenSSL::PKey::RSA)
110 |
111 | f = File.new("#{TestDir}/pubkey-9e.pem", 'w')
112 | f.write(out)
113 | f.close
114 | end
115 |
116 | def test_02_selfsign_rsa1024
117 | out = ykpivtool(:selfsign_certificate, '-s 9e',
118 | '-S "/CN=test/"', "< #{TestDir}/pubkey-9e.pem")
119 | assert_equal(0, $?, out)
120 | assert_match(/^-----BEGIN CERTIFICATE-----$/, out)
121 |
122 | cert = OpenSSL::X509::Certificate.new(out)
123 | assert_match(/WithRSAEncryption$/, cert.signature_algorithm)
124 | @pubkey = OpenSSL::PKey.read(
125 | File.new("#{TestDir}/pubkey-9e.pem").read())
126 | assert(cert.verify(@pubkey))
127 | end
128 |
129 | def test_03_generate_rsa2048
130 | out = ykpivtool(:generate, '-s 9e', '-A RSA2048')
131 | assert_equal(0, $?)
132 | assert_match(/^-----BEGIN PUBLIC KEY-----$/, out)
133 |
134 | @pubkey = OpenSSL::PKey.read(out)
135 | assert(@pubkey.kind_of? OpenSSL::PKey::RSA)
136 |
137 | f = File.new("#{TestDir}/pubkey-9e.pem", 'w')
138 | f.write(out)
139 | f.close
140 | end
141 |
142 | def test_04_selfsign_rsa2048
143 | out = ykpivtool(:selfsign_certificate, '-s 9e',
144 | '-S "/CN=test/"', "< #{TestDir}/pubkey-9e.pem")
145 | assert_equal(0, $?)
146 | assert_match(/^-----BEGIN CERTIFICATE-----$/, out)
147 |
148 | cert = OpenSSL::X509::Certificate.new(out)
149 | assert_match(/WithRSAEncryption$/, cert.signature_algorithm)
150 | @pubkey = OpenSSL::PKey.read(
151 | File.new("#{TestDir}/pubkey-9e.pem").read())
152 | assert(cert.verify(@pubkey))
153 | end
154 |
155 | def test_05_generate_eccp384
156 | out = ykpivtool(:generate, '-s 9e', '-A ECCP384')
157 | assert_equal(0, $?)
158 | assert_match(/^-----BEGIN PUBLIC KEY-----$/, out)
159 |
160 | @pubkey = OpenSSL::PKey.read(out)
161 | assert(@pubkey.kind_of? OpenSSL::PKey::EC)
162 |
163 | f = File.new("#{TestDir}/pubkey-9e.pem", 'w')
164 | f.write(out)
165 | f.close
166 | end
167 |
168 | def test_06_selfsign_eccp384
169 | out = ykpivtool(:selfsign_certificate, '-s 9e',
170 | '-S "/CN=test/"', "< #{TestDir}/pubkey-9e.pem")
171 | assert_equal(0, $?)
172 | assert_match(/^-----BEGIN CERTIFICATE-----$/, out)
173 |
174 | cert = OpenSSL::X509::Certificate.new(out)
175 | assert_match(/^ecdsa-with-/, cert.signature_algorithm)
176 | @pubkey = OpenSSL::PKey.read(
177 | File.new("#{TestDir}/pubkey-9e.pem").read())
178 | assert(cert.verify(@pubkey))
179 | end
180 | end
181 |
182 | class TestYubicoPIN < Minitest::Test
183 | i_suck_and_my_tests_are_order_dependent!
184 |
185 | def test_01_block_pin
186 | # pin is ok
187 | ykpivtool(:verify_pin)
188 | assert_equal(0, $?)
189 |
190 | # deliberately block the pin
191 | $pin = '111111'
192 | 5.times do
193 | ykpivtool(:verify_pin)
194 | refute_equal(0, $?)
195 | end
196 |
197 | # is it blocked?
198 | $pin = DefaultPin
199 | ykpivtool(:verify_pin)
200 | refute_equal(0, $?)
201 | end
202 |
203 | def test_02_unblock_pin
204 | ykpivtool(:unblock_pin, "-P #{DefaultPuk}", "-N #{DefaultPin}")
205 | assert_equal(0, $?)
206 |
207 | ykpivtool(:verify_pin)
208 | assert_equal(0, $?)
209 | end
210 |
211 | def test_03_change_pin
212 | @newpin = '9919919'
213 | ykpivtool(:change_pin, "-P #{DefaultPin}", "-N #{@newpin}")
214 | assert_equal(0, $?)
215 |
216 | ykpivtool(:verify_pin)
217 | refute_equal(0, $?)
218 |
219 | $pin = @newpin
220 | ykpivtool(:verify_pin)
221 | assert_equal(0, $?)
222 |
223 | ykpivtool(:change_pin, "-P #{@newpin}", "-N #{DefaultPin}")
224 | assert_equal(0, $?)
225 |
226 | $pin = DefaultPin
227 | ykpivtool(:verify_pin)
228 | assert_equal(0, $?)
229 | end
230 |
231 | def test_04_change_puk
232 | @newpuk = '87654321'
233 | ykpivtool(:change_puk, "-P #{DefaultPuk}", "-N #{@newpuk}")
234 | assert_equal(0, $?)
235 |
236 | $pin = '111111'
237 | 5.times do
238 | ykpivtool(:verify_pin)
239 | refute_equal(0, $?)
240 | end
241 | $pin = DefaultPin
242 | ykpivtool(:verify_pin)
243 | refute_equal(0, $?)
244 |
245 | ykpivtool(:unblock_pin, "-P #{DefaultPuk}", "-N #{DefaultPin}")
246 | refute_equal(0, $?)
247 | ykpivtool(:verify_pin)
248 | refute_equal(0, $?)
249 |
250 | ykpivtool(:unblock_pin, "-P #{@newpuk}", "-N #{DefaultPin}")
251 | assert_equal(0, $?)
252 |
253 | ykpivtool(:verify_pin)
254 | assert_equal(0, $?)
255 |
256 | ykpivtool(:change_puk, "-P #{@newpuk}", "-N #{DefaultPuk}")
257 | assert_equal(0, $?)
258 | end
259 |
260 | def test_05_applet_reset
261 | $pin = '111111'
262 | 5.times do
263 | ykpivtool(:verify_pin)
264 | refute_equal(0, $?)
265 | end
266 | $pin = DefaultPin
267 | ykpivtool(:verify_pin)
268 | refute_equal(0, $?)
269 |
270 | 5.times do
271 | ykpivtool(:unblock_pin, "-P 11111111", "-N #{DefaultPin}")
272 | refute_equal(0, $?)
273 | end
274 | ykpivtool(:unblock_pin, "-P #{DefaultPuk}", "-N #{DefaultPin}")
275 | refute_equal(0, $?)
276 |
277 | ykpivtool(:reset)
278 | assert_equal(0, $?)
279 |
280 | ykpivtool(:verify_pin)
281 | assert_equal(0, $?)
282 | end
283 | end
284 |
285 | class TestAttestation < Minitest::Test
286 | i_suck_and_my_tests_are_order_dependent!
287 |
288 | def test_01_attest
289 | out = ykpivtool(:generate, '-s 9e', '-A ECCP256')
290 | assert_equal(0, $?)
291 | assert_match(/^-----BEGIN PUBLIC KEY-----$/, out)
292 |
293 | @pubkey = OpenSSL::PKey.read(out)
294 | assert(@pubkey.kind_of? OpenSSL::PKey::EC)
295 |
296 | out = ykpivtool(:read_certificate, '-s f9')
297 | assert_equal(0, $?)
298 | assert_match(/^-----BEGIN CERTIFICATE-----$/, out)
299 | @f9cert = OpenSSL::X509::Certificate.new(out)
300 |
301 | out = ykpivtool(:attest, '-s 9e')
302 | assert_equal(0, $?)
303 | assert_match(/^-----BEGIN CERTIFICATE-----$/, out)
304 |
305 | @cert = OpenSSL::X509::Certificate.new(out)
306 | assert_equal(@pubkey.to_der, @cert.public_key.to_der)
307 | assert(@cert.verify(@f9cert.public_key))
308 |
309 | exts = @cert.extensions
310 | oids = exts.map { |x| x.oid }
311 | assert_includes(oids, '1.3.6.1.4.1.41482.3.3')
312 | assert_includes(oids, '1.3.6.1.4.1.41482.3.8')
313 | end
314 | end
315 |
316 | class TestPivyBasic < Minitest::Test
317 | i_suck_and_my_tests_are_order_dependent!
318 |
319 | def test_01_list
320 | out = `pivy-tool -p list`.split("\n").map { |r| r.split(":") }
321 | assert_equal(0, $?)
322 | out.each do |r|
323 | $guid = r[1] if r[0].include?(Reader)
324 | end
325 | out = `pivy-tool list -g #{$guid}`
326 | assert_equal(0, $?)
327 | assert_match(/device: #{Reader}/, out)
328 | assert_match(/applet: PivApplet v[0-9.]+/, out)
329 | assert_match(/yubico: .* \(v5\.[0-9]+\.[0-9]+\)$/, out)
330 | end
331 |
332 | def test_02_set_admin
333 | out = `pivy-tool -g #{$guid} -P #{DefaultPin} set-admin random`
334 | assert_equal(0, $?)
335 |
336 | ykpivtool(:generate, '-s 9e', '-A RSA1024')
337 | refute_equal(0, $?)
338 | end
339 |
340 | def test_03_gen_9d
341 | out = `pivy-tool -P #{DefaultPin} -g #{$guid} generate -a eccp256 9d`
342 | assert_equal(0, $?)
343 | end
344 |
345 | def test_04_unbox
346 | out = `echo hello world | pivy-tool -g #{$guid} box 9d`
347 | assert_equal(0, $?)
348 |
349 | f = File.new("#{TestDir}/test.ebox", 'w')
350 | f.write(out)
351 | f.close()
352 |
353 | out = `pivy-tool unbox -P #{DefaultPin} < #{TestDir}/test.ebox`
354 | assert_equal(0, $?)
355 | assert_match(/^hello world$/, out)
356 | end
357 |
358 | def test_05_import
359 | @key = OpenSSL::PKey::RSA.new(2048)
360 | f = File.new("#{TestDir}/testkey.pem", 'w')
361 | f.write(@key.to_pem)
362 | f.close()
363 |
364 | out = `pivy-tool -g #{$guid} -P #{DefaultPin} import 82 < #{TestDir}/testkey.pem`
365 | assert_equal(0, $?)
366 |
367 | out = `pivy-tool -g #{$guid} cert 82`
368 | assert_equal(0, $?)
369 | @cert = OpenSSL::X509::Certificate.new(out)
370 | assert(@cert.verify(@key.public_key))
371 | end
372 |
373 | def test_06_import_ec
374 | @key = OpenSSL::PKey::EC.generate('prime256v1')
375 | f = File.new("#{TestDir}/testkey2.pem", 'w')
376 | f.write(@key.to_pem)
377 | f.close()
378 |
379 | out = `pivy-tool -g #{$guid} -P #{DefaultPin} import 82 < #{TestDir}/testkey2.pem`
380 | assert_equal(0, $?)
381 |
382 | out = `pivy-tool -g #{$guid} cert 82`
383 | assert_equal(0, $?)
384 | @cert = OpenSSL::X509::Certificate.new(out)
385 | assert(@cert.verify(@key))
386 | end
387 |
388 | def test_07_reset_admin
389 | out = `pivy-tool -g #{$guid} -P #{DefaultPin} set-admin default`
390 | assert_equal(0, $?)
391 |
392 | ykpivtool(:generate, '-s 9e', '-A RSA1024')
393 | assert_equal(0, $?)
394 | end
395 | end
396 |
--------------------------------------------------------------------------------
/README.adoc:
--------------------------------------------------------------------------------
1 | :toc: left
2 | :source-highlighter: pygments
3 | :doctype: book
4 | :idprefix:
5 | :docinfo:
6 |
7 | # PivApplet
8 |
9 | ## About
10 |
11 | This is an attempt at making a PIV (NIST SP 800-73-4) compatible JavaCard
12 | applet. Target is JavaCard 2.2.2+, with 1-3k of transient memory.
13 |
14 | ## Current status
15 |
16 | What works:
17 |
18 | * OpenSC, MacOS (`piv.tokend` for login), Windows PIV
19 | * RSA-1024 and -2048 key generation on card, signing
20 | * EC P-256 and P-384 key generation
21 | * EC Diffie-Hellman on P-256 and P-384
22 | * ECDSA (only when using JavaCard 3.0.4+ builds)
23 | - JC 2.2.2 builds have a hash-on-card workaround (see below), but this is
24 | not standard PIV
25 | * PINs and change of PIN, PUK reset
26 | * Support for both 3DES and AES card administration keys
27 | * Most https://developers.yubico.com/PIV/Introduction/Yubico_extensions.html[
28 | YubiKeyPIV-compatible extensions] are implemented and working:
29 | - PIN policy
30 | - Version indicator (we pretend to be a YK5)
31 | - Fetch serial number (randomly generated)
32 | - Set management key
33 | - Import asymmetric key
34 | - Reset after PIN+PUK blocked
35 | - Change PIN/PUK retry limits
36 | - Attestation using slot F9
37 | - Get metadata (5.3.0)
38 |
39 | What doesn't work:
40 |
41 | * YubicoPIV touch policy
42 | - No standard JavaCard APIs for accessing GPIO or sensors so this is
43 | probably not happening.
44 |
45 | ## Installing
46 |
47 | The pre-built `.cap` files for each release can be found on the
48 | https://github.com/arekinath/pivapplet/releases[project release page].
49 |
50 | You can use the
51 | https://github.com/martinpaljak/GlobalPlatformPro[Global Platform] command-line
52 | tool (`gp`) to upload the applet to your JavaCard:
53 |
54 | -----
55 | $ gp -install PivApplet.cap
56 | CAP loaded
57 | -----
58 |
59 | Now you have a PIV card ready to initialise. It's easiest to do the
60 | initialisation with the
61 | https://developers.yubico.com/yubico-piv-tool/[`yubico-piv-tool`]:
62 |
63 | -----
64 | $ yubico-piv-tool -r '' -a list-readers
65 | Alcor Micro AU9560 00 00
66 | Yubico Yubikey 4 OTP+U2F+CCID 01 00
67 |
68 | $ yubico-piv-tool -r Alcor -a generate -s 9e > pubkey-9e.pem
69 | Successfully generated a new private key.
70 |
71 | $ yubico-piv-tool -r Alcor -a selfsign-certificate -s 9e \
72 | -S '/CN=test' < pubkey-9e.pem > cert-9e.pem
73 | Successfully generated a new self signed certificate.
74 |
75 | $ yubico-piv-tool -r Alcor -a import-certificate -s 9e < cert-9e.pem
76 | Successfully imported a new certificate.
77 | -----
78 |
79 | Now your PIV token is set up with a self-signed Card Authentication (`9e`)
80 | key. You can generate keys and certificates in the other slots in a similar
81 | fashion (remember that most other slots default to requiring a PIN entry,
82 | which you have to do with `-a verify-pin -a selfsign-certificate ...` when
83 | using `yubico-piv-tool`).
84 |
85 | Sample output of `yubico-piv-tool -a status`:
86 |
87 | -----
88 | CHUID: 301900000000000000000000000000000000000000000000000000341047132924dfd1f7581290d383781dc81a350832303530303130313e00fe00
89 | CCC: f015a000000116ff02b8907468b1e6d143231c5c7c452df10121f20121f300f400f50110f600f700fa00fb00fc00fd00fe00
90 | Slot 9e:
91 | Algorithm: RSA2048
92 | Subject DN: CN=test
93 | Issuer DN: CN=test
94 | Fingerprint: acbc68b8ec8a25432a296801e4deb375a14b4d78f35016f8729a7c481040eb9a
95 | Not Before: Jun 19 05:33:06 2017 GMT
96 | Not After: Jun 19 05:33:06 2018 GMT
97 | PIN tries left: 5
98 | -----
99 |
100 | ## Default admin key & PINs
101 |
102 | Default PIN:: `123456`
103 | Default PUK:: `12345678`
104 | Default card administration (`9B`) key:: `01 02 03 04 05 06 07 08 01 02 03 04 05 06 07 08 ...`
105 |
106 | (This is the default used by Yubikeys, so that the `yubico-piv-tool` will
107 | work with PivApplet.)
108 |
109 | Note that if 3DES admin key support is disabled, the default card
110 | administration key will be AES-128 instead (with the first 16 bytes of the
111 | default 3DES key as its value).
112 |
113 | ## ECDSA hash-on-card extension
114 |
115 | When built for JavaCard 2.2.2, this applet supports an extension for doing ECDSA
116 | with hash-on-card, which client software will have to specifically add support
117 | for if it wants to use ECDSA signing. As of writing, the only PIV client with
118 | support for this extension known to the authors is
119 | https://github.com/arekinath/pivy[`pivy`].
120 |
121 | Unfortunately, regular PIV ECDSA signing is not possible with the JavaCard
122 | standard crypto functions in JC2.2.2 (it is possible in JC3.0.4 and later,
123 | and builds of this applet for 3.0.4 use the standardised method), which only
124 | support a single operation that combines hashing and signing. The current PIV
125 | standard demands that the data to be signed is hashed by the host, and then the
126 | hash is sent to the card.
127 |
128 | In addition to the algorithm ID `0x11` for `ECCP256`, we introduce two new IDs,
129 | `ECCP256-SHA1` (`0xF0`) and `ECCP256-SHA256` (`0xF1`). For key generation the
130 | client should continue to use `ECCP256` (as well as for ECDH), but for signing
131 | one of the two new algorithm IDs must be used (`ECCP256` will be rejected).
132 |
133 | These two new algorithms are "hash-on-card" algorithms, where the "challenge"
134 | tag sent by the host to the card should include the full data to be signed
135 | without any hashing applied. The card will hash the data and return the
136 | signature in the same was as a normal EC signature.
137 |
138 | For example, to sign the payload `"foo\n"` with the Card Authentication (9e)
139 | key, with SHA-256, the host could send the following APDU:
140 |
141 | ```
142 | 00 87 F1 9E 0A 7C 08 82 00 81 04 66 6F 6F 0A
143 | ```
144 |
145 | This extension, naturally, will not work with existing PIV host software that is
146 | not aware of it. It is supported as a workaround for users who are ok with
147 | customising their host software who really want to use ECDSA.
148 |
149 | Support for these new algorithms is advertised in the `0xAC` (supported
150 | algorithms) tag in the response to `INS_SELECT`. Client software may detect
151 | it there to decide whether to attempt use hash-on-card or not.
152 |
153 | ## Building the project
154 |
155 | We use https://github.com/martinpaljak/ant-javacard[ant-javacard] for builds.
156 |
157 | -----
158 | $ git clone https://github.com/arekinath/PivApplet
159 | ...
160 |
161 | $ cd PivApplet
162 | $ git submodule init && git submodule update
163 | ...
164 |
165 | $ export JC_HOME=/path/to/jckit-2.2.2
166 | $ ant
167 | -----
168 |
169 | The capfile will be output in the `./bin` directory, along with the `.class`
170 | files (which can be used with jCardSim).
171 |
172 | You can also download pre-built capfiles from the
173 | https://github.com/arekinath/PivApplet/releases[releases page] here on GitHub.
174 |
175 | The applet can be configured to suit different cards or needs by adjusting
176 | the feature flags in `build.xml` before running `ant`.
177 |
178 | Currently available feature flags:
179 |
180 | |===
181 | |`PIV_SUPPORT_3DES` | `D` | Enable support for 3DES admin keys
182 | |`PIV_SUPPORT_AES` | `a` | Enable support for AES admin keys
183 | |`PIV_SUPPORT_RSA` | `R` | Enable RSA support
184 | |`PIV_SUPPORT_EC` | `E` | Enable ECDSA and ECDH support
185 | |`PIV_SUPPORT_ECCP384` | `e` | Enable P-384 support with ECDSA/ECDH
186 | |`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)
187 | |`PIV_STRICT_CONTACTLESS` | `S` | Block most slots and keys from use over contactless (strictly conform to the PIV spec)
188 | |`YKPIV_ATTESTATION` | `A` | Enable YubicoPIV-style attestation slot and command
189 | |`APPLET_EXTLEN` | `x` | Support for extended APDUs. Some cards have bugs that make this feature malfunction (e.g. ACOSJ)
190 | |`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`.
191 | |===
192 |
193 | Tested card configurations:
194 |
195 | |===
196 | |NXP J3H145 | JC3.0.4 | `REePSAx`, `REePSAxaD`
197 | |NXP J3D081 | JC2.2.2 | `RESAx`
198 | |NXP J2A040 | JC2.2.2 | `RESAx`
199 | |JC30M48CR | JC3.0.4 | `ESPxL`, `RSxL`
200 | |ACOSJ 40k D1 | JC3.0.4 | `REePSA`
201 | |G&D StarSign CUT | JC3.0.4 | `REePSAx`, `REePSAxaD`
202 | |===
203 |
204 | As of v0.8.0, the builds on the releases page are labelled with these same
205 | abbreviations.
206 |
207 | ## Simulation
208 |
209 | Simulator testing for this project has so far been done on Linux, using
210 | jCardSim (both with and without a Virtual Smartcard Reader).
211 |
212 | The easiest way to do it on Linux is with a virtual reader:
213 |
214 | 1. Install `vsmartcard` (see
215 | http://frankmorgner.github.io/vsmartcard/virtualsmartcard/README.html[here],
216 | but it might also be in your distro's package manager). Once it's installed
217 | (and PCSCd restarted) your list of smartcard readers on the system (try
218 | `opensc-tool -l` or `yubico-piv-tool -a list-readers`) should include a
219 | bunch of `Virtual PCD` entries.
220 | 2. Clone my fork of `jCardSim` (https://github.com/arekinath/jcardsim)
221 | and build it (using `mvn initialize && mvn clean install`)
222 | 3. From the `pivapplet` directory (after running `ant` to build), run:
223 | `java -noverify -cp bin/:../jcardsim/target/jcardsim-3.0.5-SNAPSHOT.jar com.licel.jcardsim.remote.VSmartCard test/jcardsim.cfg`
224 |
225 | Now you should see a card appear in the first of the `Virtual PCD` readers. To
226 | start the PivApplet up, send it a command like this:
227 |
228 | ```
229 | $ 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'
230 | ```
231 |
232 | Then you should see the simulated PivApplet come to life! The forked jCardSim
233 | currently spits out debug output on the console including full APDUs sent and
234 | received, and stack traces of exceptions (very useful!).
235 |
236 | You can also use the simulator with `jdb`, the Java debugger:
237 |
238 | ```
239 | $ jdb -noverify -classpath bin/:../jcardsim/target/jcardsim-3.0.5-SNAPSHOT.jar com.licel.jcardsim.remote.VSmartCard test/jcardsim.cfg
240 | Initializing jdb ...
241 | > stop at net.cooperi.pivapplet.PivApplet:1769
242 | Deferring breakpoint net.cooperi.pivapplet.PivApplet:1769.
243 | It will be set after the class is loaded.
244 | > run
245 | run com.licel.jcardsim.remote.VSmartCard test/jcardsim.cfg
246 | Set uncaught java.lang.Throwable
247 | Set deferred uncaught java.lang.Throwable
248 | >
249 | VM Started:
250 |
251 | == APDU
252 | 0000: 00 A4 04 00 09 A0 00 00
253 | 0008: 03 08 00 00 10 00 00
254 | javacard.framework.ISOException
255 | at javacard.framework.ISOException.throwIt(Unknown Source)
256 | at net.cooperi.pivapplet.PivApplet.sendOutgoing(PivApplet.java:470)
257 | at net.cooperi.pivapplet.PivApplet.sendSelectResponse(PivApplet.java:435)
258 | at net.cooperi.pivapplet.PivApplet.process(PivApplet.java:284)
259 | at com.licel.jcardsim.base.SimulatorRuntime.transmitCommand(SimulatorRuntime.java:303)
260 | at com.licel.jcardsim.base.Simulator.transmitCommand(Simulator.java:263)
261 | at com.licel.jcardsim.base.CardManager.dispatchApduImpl(CardManager.java:66)
262 | at com.licel.jcardsim.base.CardManager.dispatchApdu(CardManager.java:36)
263 | at com.licel.jcardsim.remote.VSmartCard$IOThread.run(VSmartCard.java:151)
264 | == Reply APDU
265 | 0000: 61 3D 4F 0B A0 00 00 03
266 | 0008: 08 00 00 10 00 01 00 79
267 | 0010: 0D 4F 0B A0 00 00 03 08
268 | 0018: 00 00 10 00 01 00 50 09
269 | 0020: 50 69 76 41 70 70 6C 65
270 | 0028: 74 AC 14 80 01 03 80 01
271 | 0030: 06 80 01 07 80 01 11 80
272 | 0038: 01 F0 80 01 F1 06 00 90
273 | 0040: 00
274 | == APDU
275 | 0000: 00 CB 3F FF 03 5C 01 7E
276 | 0008: 08
277 |
278 | Breakpoint hit: "thread=Thread-0", net.cooperi.pivapplet.PivApplet.processGetData(), line=1,769 bci=70
279 |
280 | Thread-0[1] wherei
281 | [1] net.cooperi.pivapplet.PivApplet.processGetData (PivApplet.java:1,769), pc = 70
282 | [2] net.cooperi.pivapplet.PivApplet.process (PivApplet.java:290), pc = 146
283 | [3] com.licel.jcardsim.base.SimulatorRuntime.transmitCommand (SimulatorRuntime.java:303), pc = 223
284 | [4] com.licel.jcardsim.base.Simulator.transmitCommand (Simulator.java:263), pc = 12
285 | [5] com.licel.jcardsim.base.CardManager.dispatchApduImpl (CardManager.java:66), pc = 102
286 | [6] com.licel.jcardsim.base.CardManager.dispatchApdu (CardManager.java:36), pc = 5
287 | [7] com.licel.jcardsim.remote.VSmartCard$IOThread.run (VSmartCard.java:151), pc = 109
288 | Thread-0[1] dump buffer
289 | buffer = {
290 | 0, -53, 63, -1, 3, 92, 1, 126, 8
291 | }
292 | Thread-0[1] dump tlv.s
293 | tlv.s = {
294 | 0, 0, 3, 3
295 | }
296 | Thread-0[1] dump incoming.state
297 | incoming.state = {
298 | 0, 63, 63, 0, 63, 63, 0, 0
299 | }
300 | Thread-0[1] ...
301 | ```
302 |
--------------------------------------------------------------------------------
/src/net/cooperi/pivapplet/SGList.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | *
6 | * Copyright (c) 2018, Alex Wilson
7 | */
8 |
9 | package net.cooperi.pivapplet;
10 |
11 | import javacard.framework.APDU;
12 | import javacard.framework.ISO7816;
13 | import javacard.framework.ISOException;
14 | import javacard.framework.JCSystem;
15 | import javacard.framework.Util;
16 |
17 | public class SGList implements Readable {
18 | /*#if APPLET_LOW_TRANSIENT
19 | public static final short DEFAULT_MAX_BUFS = 4;
20 | #else*/
21 | public static final short DEFAULT_MAX_BUFS = 10;
22 | //#endif
23 |
24 | /*
25 | * Minimum number of bytes we will ask our BufferManager for if
26 | * allocating for a small request.
27 | * */
28 | public static final short MIN_ALLOC_LEN = 16;
29 |
30 | private static final byte WPTR_BUF = 0;
31 | private static final byte WPTR_TOTOFF = 1;
32 | private static final byte RPTR_BUF = 2;
33 | private static final byte RPTR_TOTOFF = 3;
34 | private static final byte OWPTR_BUF = 4;
35 | private static final byte OWPTR_WPOS = 5;
36 | private static final byte STATE_MAX = OWPTR_WPOS;
37 |
38 | private final BufferManager mgr;
39 | private final short maxBufs;
40 | private final TransientBuffer[] buffers;
41 | private final short[] state;
42 |
43 | public
44 | SGList(final BufferManager srcmgr)
45 | {
46 | this(srcmgr, DEFAULT_MAX_BUFS);
47 | }
48 |
49 | public
50 | SGList(final BufferManager srcmgr, short maxBufs)
51 | {
52 | mgr = srcmgr;
53 | this.maxBufs = maxBufs;
54 | buffers = new TransientBuffer[maxBufs];
55 | for (short i = 0; i < maxBufs; ++i)
56 | buffers[i] = new TransientBuffer();
57 | state = JCSystem.makeTransientShortArray((short)(STATE_MAX + 1),
58 | JCSystem.CLEAR_ON_DESELECT);
59 | this.reset();
60 | }
61 |
62 | public short
63 | writeDebugInfo(final byte[] buf, short off)
64 | {
65 | off = Util.setShort(buf, off, state[WPTR_BUF]);
66 | off = Util.setShort(buf, off, buffers[state[WPTR_BUF]].wpos());
67 | for (short i = 0; i < maxBufs; ++i) {
68 | final BaseBuffer parent = buffers[i].parent();
69 | if (buffers[i].data() == null)
70 | break;
71 | buf[off++] = (byte)i;
72 | byte status = (byte)0;
73 | if (parent != null && parent.isTransient)
74 | status |= (byte)0x02;
75 | buf[off++] = status;
76 | off = Util.setShort(buf, off,
77 | (short)buffers[i].data().length);
78 | off = Util.setShort(buf, off, buffers[i].offset());
79 | off = Util.setShort(buf, off, buffers[i].len());
80 | }
81 | return (off);
82 | }
83 |
84 | public void
85 | reset()
86 | {
87 | for (short i = 0; i <= state[WPTR_BUF]; ++i) {
88 | final Buffer buf = buffers[i];
89 | buf.reset();
90 | }
91 | state[WPTR_BUF] = (short)0;
92 | state[RPTR_BUF] = (short)0;
93 | state[RPTR_TOTOFF] = (short)0;
94 | state[WPTR_TOTOFF] = (short)0;
95 | }
96 |
97 | public void
98 | resetAndFree()
99 | {
100 | for (short i = 0; i <= state[WPTR_BUF]; ++i) {
101 | final Buffer buf = buffers[i];
102 | buf.free();
103 | }
104 | state[WPTR_BUF] = (short)0;
105 | state[RPTR_BUF] = (short)0;
106 | state[RPTR_TOTOFF] = (short)0;
107 | state[WPTR_TOTOFF] = (short)0;
108 | }
109 |
110 | /*
111 | * Use the APDU buffer as the first element in the SGList -- this way
112 | * whatever data is written there can be directly sent without any
113 | * copying.
114 | */
115 | public void
116 | useApdu(final short offset, final short len)
117 | {
118 | final TransientBuffer buf = buffers[0];
119 | if (len > 0) {
120 | buf.free();
121 | buf.setApdu(offset, len);
122 | }
123 | }
124 |
125 |
126 | public void
127 | rewind()
128 | {
129 | for (short i = 0; i <= state[RPTR_BUF]; ++i) {
130 | final Buffer buf = buffers[i];
131 | buf.rewind();
132 | }
133 | state[RPTR_BUF] = (short)0;
134 | state[RPTR_TOTOFF] = (short)0;
135 | }
136 |
137 | public short
138 | wPtrBuf()
139 | {
140 | return (state[WPTR_BUF]);
141 | }
142 |
143 | public short
144 | wPtrOff()
145 | {
146 | return (buffers[state[WPTR_BUF]].wpos());
147 | }
148 |
149 | public short
150 | wPtr()
151 | {
152 | return (state[WPTR_TOTOFF]);
153 | }
154 |
155 | public void
156 | rewriteAt(final short buf, final short wpos)
157 | {
158 | if (state[OWPTR_BUF] != 0 || state[OWPTR_WPOS] != 0) {
159 | ISOException.throwIt(PivApplet.SW_BAD_REWRITE);
160 | return;
161 | }
162 | state[OWPTR_BUF] = state[WPTR_BUF];
163 | state[OWPTR_WPOS] = buffers[buf].wpos();
164 | state[WPTR_BUF] = buf;
165 | buffers[buf].jumpWpos(wpos);
166 | }
167 |
168 | public void
169 | endRewrite()
170 | {
171 | if (state[OWPTR_BUF] == 0 && state[OWPTR_WPOS] == 0) {
172 | ISOException.throwIt(PivApplet.SW_BAD_REWRITE);
173 | return;
174 | }
175 | buffers[state[WPTR_BUF]].jumpWpos(state[OWPTR_WPOS]);
176 | state[WPTR_BUF] = state[OWPTR_BUF];
177 | state[OWPTR_BUF] = (short)0;
178 | state[OWPTR_WPOS] = (short)0;
179 | }
180 |
181 | private short
182 | takeForRead(final short len)
183 | {
184 | final Buffer buf = buffers[state[RPTR_BUF]];
185 | short take = buf.remaining();
186 | if (take > len)
187 | take = len;
188 | return (take);
189 | }
190 |
191 | private void
192 | incRPtr(final short take)
193 | {
194 | final Buffer buf = buffers[state[RPTR_BUF]];
195 | buf.read(take);
196 | state[RPTR_TOTOFF] += take;
197 | if (buf.remaining() == 0 &&
198 | state[RPTR_BUF] != state[WPTR_BUF]) {
199 | state[RPTR_BUF]++;
200 | }
201 | }
202 |
203 | private short
204 | takeForWrite(final short len)
205 | {
206 | if (state[WPTR_BUF] >= maxBufs) {
207 | ISOException.throwIt(PivApplet.SW_RESERVE_FAILURE);
208 | return ((short)0);
209 | }
210 | final TransientBuffer buf = buffers[state[WPTR_BUF]];
211 | if (!buf.isAllocated()) {
212 | final short allocLen =
213 | (len < MIN_ALLOC_LEN) ? MIN_ALLOC_LEN : len;
214 | mgr.alloc(allocLen, buf);
215 | }
216 | short take = buf.available();
217 | if (take > len) {
218 | take = len;
219 | } else if (take < len) {
220 | buf.expand((short)(len - take));
221 | take = buf.available();
222 | if (take > len)
223 | take = len;
224 | }
225 | if (take == (short)0)
226 | ISOException.throwIt(PivApplet.SW_RESERVE_FAILURE);
227 | return (take);
228 | }
229 |
230 | private void
231 | incWPtr(final short take)
232 | {
233 | final TransientBuffer buf = buffers[state[WPTR_BUF]];
234 | buf.write(take);
235 | if (state[OWPTR_BUF] == 0 && state[OWPTR_WPOS] == 0)
236 | state[WPTR_TOTOFF] += take;
237 | if (buf.available() == 0) {
238 | buf.expand(MIN_ALLOC_LEN);
239 | if (buf.available() == 0 &&
240 | state[WPTR_BUF] < maxBufs) {
241 | state[WPTR_BUF]++;
242 | final TransientBuffer nbuf;
243 | nbuf = buffers[state[WPTR_BUF]];
244 | if (nbuf.data() == null)
245 | mgr.alloc(MIN_ALLOC_LEN, nbuf);
246 | }
247 | }
248 | }
249 |
250 | public void
251 | write(final byte[] source, short offset, short len)
252 | {
253 | while (len > 0) {
254 | final short take = takeForWrite(len);
255 | final Buffer buf = buffers[state[WPTR_BUF]];
256 | Util.arrayCopyNonAtomic(source, offset,
257 | buf.data(), buf.wpos(), take);
258 | offset += take;
259 | len -= take;
260 | incWPtr(take);
261 | }
262 | }
263 |
264 | public void
265 | startReserve(final short len, final TransientBuffer into)
266 | {
267 | final TransientBuffer curBuf = buffers[state[WPTR_BUF]];
268 | short rem = curBuf.available();
269 | if (rem >= len) {
270 | into.setWriteSlice(curBuf, len);
271 | return;
272 | }
273 | curBuf.expand((short)(curBuf.len() + len));
274 | rem = curBuf.available();
275 | if (rem >= len) {
276 | into.setWriteSlice(curBuf, len);
277 | return;
278 | }
279 |
280 | state[WPTR_BUF]++;
281 | for (; state[WPTR_BUF] < maxBufs; state[WPTR_BUF]++) {
282 | final TransientBuffer buf = buffers[state[WPTR_BUF]];
283 | if (buf.data() == null)
284 | mgr.alloc(len, buf);
285 | if (buf.available() < len)
286 | buf.expand(len);
287 | if (buf.available() < len)
288 | continue;
289 | into.setWriteSlice(buf, len);
290 | return;
291 | }
292 |
293 | ISOException.throwIt(PivApplet.SW_RESERVE_FAILURE);
294 | }
295 |
296 | public void
297 | endReserve(final short used)
298 | {
299 | incWPtr(used);
300 | }
301 |
302 | public short
303 | read(final TransientBuffer into, final short len)
304 | {
305 | final short rem = takeForRead(len);
306 | if (rem >= len) {
307 | return (readPartial(into, len));
308 | }
309 | if (!mgr.alloc(len, into))
310 | return (0);
311 | final short wrote = read(into.data(), into.wpos(), len);
312 | into.write(wrote);
313 | return (wrote);
314 | }
315 |
316 | public short
317 | readPartial(final TransientBuffer into, final short maxLen)
318 | {
319 | final short take = takeForRead(maxLen);
320 | final TransientBuffer buf = buffers[state[RPTR_BUF]];
321 | if (take == (short)0)
322 | return (0);
323 | into.setReadSlice(buf, take);
324 | incRPtr(take);
325 | return (take);
326 | }
327 |
328 | public void
329 | writeByte(final byte val)
330 | {
331 | final short rem = takeForWrite((short)1);
332 | if (rem < 1) {
333 | ISOException.throwIt(PivApplet.SW_WRITE_OVER_END);
334 | return;
335 | }
336 | final Buffer buf = buffers[state[WPTR_BUF]];
337 | buf.data()[buf.wpos()] = val;
338 | incWPtr((short)1);
339 | }
340 |
341 | public void
342 | writeShort(final short val)
343 | {
344 | final short rem = takeForWrite((short)2);
345 | final Buffer buf = buffers[state[WPTR_BUF]];
346 | if (rem < 2) {
347 | final short upper = (short)(
348 | (short)((val & (short)0xFF00) >> 8) & (short)0xFF);
349 | final short lower = (short)(val & (short)0xFF);
350 | writeByte((byte)upper);
351 | writeByte((byte)lower);
352 | return;
353 | }
354 | Util.setShort(buf.data(), buf.wpos(), val);
355 | incWPtr((short)2);
356 | }
357 |
358 | public short
359 | readInto(final SGList dest, short len)
360 | {
361 | short done = (short)0;
362 | while (len > 0) {
363 | final short take = takeForRead(len);
364 | final TransientBuffer buf = buffers[state[RPTR_BUF]];
365 | if (take == (short)0)
366 | break;
367 | dest.append(buf, take);
368 | len -= take;
369 | done += take;
370 | incRPtr(take);
371 | }
372 | return (done);
373 | }
374 |
375 |
376 | public boolean
377 | atEnd()
378 | {
379 | if (state[RPTR_BUF] < state[WPTR_BUF])
380 | return (false);
381 | if (buffers[state[RPTR_BUF]].remaining() > 0)
382 | return (false);
383 | return (true);
384 | }
385 |
386 |
387 | public short
388 | available()
389 | {
390 | return ((short)(state[WPTR_TOTOFF] - state[RPTR_TOTOFF]));
391 | }
392 |
393 | public byte
394 | peekByte()
395 | {
396 | final short take = takeForRead((short)1);
397 | final Buffer buf = buffers[state[RPTR_BUF]];
398 | if (take == (short)0) {
399 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
400 | return (0);
401 | }
402 | return (buf.data()[buf.rpos()]);
403 | }
404 |
405 | public byte
406 | peekByteW()
407 | {
408 | final Buffer buf = buffers[state[WPTR_BUF]];
409 | return (buf.data()[buf.wpos()]);
410 | }
411 |
412 |
413 | public byte
414 | readByte()
415 | {
416 | final short take = takeForRead((short)1);
417 | final Buffer buf = buffers[state[RPTR_BUF]];
418 | if (take == (short)0) {
419 | ISOException.throwIt(ISO7816.SW_DATA_INVALID);
420 | return (0);
421 | }
422 | final byte v = buf.data()[buf.rpos()];
423 | incRPtr((short)1);
424 | return (v);
425 | }
426 |
427 |
428 | public short
429 | readShort()
430 | {
431 | final short rem = takeForRead((short)2);
432 | final Buffer buf = buffers[state[RPTR_BUF]];
433 | if (rem < 2) {
434 | short val = (short)((short)readByte() & 0xFF);
435 | val <<= 8;
436 | val |= (short)((short)readByte() & 0xFF);
437 | return (val);
438 | }
439 | final short v = Util.getShort(buf.data(), buf.rpos());
440 | incRPtr((short)2);
441 | return (v);
442 | }
443 |
444 |
445 | public void
446 | skip(short len)
447 | {
448 | while (len > 0) {
449 | final short take = takeForRead(len);
450 | if (take == (short)0) {
451 | ISOException.throwIt(
452 | PivApplet.SW_SKIPPED_OVER_WPTR);
453 | return;
454 | }
455 | len -= take;
456 | incRPtr(take);
457 | }
458 | }
459 |
460 | public void
461 | append(final byte[] data, final short offset, final short len)
462 | {
463 | if (buffers[state[WPTR_BUF]].remaining() > 0)
464 | state[WPTR_BUF]++;
465 | final TransientBuffer buf = buffers[state[WPTR_BUF]];
466 | buf.free();
467 | buf.setBuffer(data, offset, len);
468 | buf.write(len);
469 | state[WPTR_TOTOFF] += len;
470 | }
471 |
472 | public void
473 | append(final TransientBuffer obuf, final short len)
474 | {
475 | if (buffers[state[WPTR_BUF]].remaining() > 0)
476 | state[WPTR_BUF]++;
477 | final TransientBuffer buf = buffers[state[WPTR_BUF]];
478 | buf.free();
479 | buf.setReadSlice(obuf, len);
480 | buf.write(len);
481 | state[WPTR_TOTOFF] += len;
482 | }
483 |
484 |
485 | public short
486 | read(final byte[] dest, short offset, final short maxLen)
487 | {
488 | short done = (short)0;
489 | while (done < maxLen) {
490 | final short take = takeForRead((short)(maxLen - done));
491 | if (take == 0)
492 | return (done);
493 | final Buffer buf = buffers[state[RPTR_BUF]];
494 | Util.arrayCopyNonAtomic(buf.data(), buf.rpos(),
495 | dest, offset, take);
496 | offset += take;
497 | done += take;
498 | incRPtr(take);
499 | }
500 | return (done);
501 | }
502 |
503 | public short
504 | readToApdu(short offset, short maxLen)
505 | {
506 | final byte[] buf = APDU.getCurrentAPDUBuffer();
507 | final TransientBuffer buffer = buffers[state[RPTR_BUF]];
508 | if (buffer.isApdu() && buffer.rpos() == offset) {
509 | final short alreadyDone = buffer.remaining();
510 | offset += alreadyDone;
511 | maxLen -= alreadyDone;
512 | incRPtr(alreadyDone);
513 | }
514 | return (read(buf, offset, maxLen));
515 | }
516 | }
517 |
--------------------------------------------------------------------------------
/src/net/cooperi/pivapplet/PivApplet.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This Source Code Form is subject to the terms of the Mozilla Public
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 | *
6 | * Copyright (c) 2017, Alex Wilson
7 | */
8 |
9 | package net.cooperi.pivapplet;
10 |
11 | import javacard.framework.APDU;
12 | import javacard.framework.Applet;
13 | import javacard.framework.ISO7816;
14 | import javacard.framework.ISOException;
15 | import javacard.framework.JCSystem;
16 | import javacard.framework.OwnerPIN;
17 | import javacard.framework.SystemException;
18 | import javacard.framework.Util;
19 | import javacard.security.CryptoException;
20 | import javacard.security.DESKey;
21 | import javacard.security.AESKey;
22 | import javacard.security.ECPrivateKey;
23 | import javacard.security.ECPublicKey;
24 | import javacard.security.KeyAgreement;
25 | import javacard.security.KeyBuilder;
26 | import javacard.security.KeyPair;
27 | import javacard.security.PrivateKey;
28 | import javacard.security.PublicKey;
29 | import javacard.security.RSAPrivateCrtKey;
30 | import javacard.security.RSAPublicKey;
31 | import javacard.security.RandomData;
32 | import javacard.security.Signature;
33 | import javacardx.crypto.Cipher;
34 | //#if APPLET_EXTLEN
35 | import javacardx.apdu.ExtendedLength;
36 | //#endif
37 |
38 | //#if APPLET_EXTLEN
39 | public class PivApplet extends Applet implements ExtendedLength
40 | {
41 | /*#else
42 | public class PivApplet extends Applet
43 | {
44 | #endif*/
45 | private static final byte[] PIV_AID = {
46 | (byte)0xa0, (byte)0x00, (byte)0x00, (byte)0x03, (byte)0x08,
47 | (byte)0x00, (byte)0x00, (byte)0x10, (byte)0x00, (byte)0x01,
48 | (byte)0x00
49 | };
50 |
51 | private static final byte[] APP_NAME = {
52 | 'P', 'i', 'v', 'A', 'p', 'p', 'l', 'e', 't', ' ',
53 | 'v', '0', '.', '9', '.', '0', '/',
54 | //#if PIV_SUPPORT_RSA
55 | 'R',
56 | //#endif
57 | //#if PIV_SUPPORT_EC
58 | 'E',
59 | //#endif
60 | //#if PIV_SUPPORT_ECCP384
61 | 'e',
62 | //#endif
63 | //#if PIV_STRICT_CONTACTLESS
64 | 'S',
65 | //#endif
66 | //#if YKPIV_ATTESTATION
67 | 'A',
68 | //#endif
69 | //#if APPLET_EXTLEN
70 | 'x',
71 | //#endif
72 | //#if APPLET_LOW_TRANSIENT
73 | 'L',
74 | //#endif
75 | //#if APPLET_USE_RESET_MEM
76 | 'r',
77 | //#endif
78 | //#if PIV_SUPPORT_AES
79 | 'a',
80 | //#endif
81 | //#if PIV_SUPPORT_3DES
82 | 'D',
83 | //#endif
84 | };
85 |
86 | private static final byte[] APP_URI = {
87 | 'g', 'i', 't', 'h', 'u', 'b', '.', 'c', 'o', 'm', '/',
88 | 'a', 'r', 'e', 'k', 'i', 'n', 'a', 't', 'h', '/',
89 | 'P', 'i', 'v', 'A', 'p', 'p', 'l', 'e', 't'
90 | };
91 |
92 | private static final byte[] DEFAULT_ADMIN_KEY = {
93 | (byte)0x01, (byte)0x02, (byte)0x03, (byte)0x04,
94 | (byte)0x05, (byte)0x06, (byte)0x07, (byte)0x08,
95 | (byte)0x01, (byte)0x02, (byte)0x03, (byte)0x04,
96 | (byte)0x05, (byte)0x06, (byte)0x07, (byte)0x08,
97 | (byte)0x01, (byte)0x02, (byte)0x03, (byte)0x04,
98 | (byte)0x05, (byte)0x06, (byte)0x07, (byte)0x08
99 | };
100 |
101 | private static final byte[] DEFAULT_PIN = {
102 | '1', '2', '3', '4', '5', '6', (byte)0xFF, (byte)0xFF
103 | };
104 | private static final byte[] DEFAULT_PUK = {
105 | '1', '2', '3', '4', '5', '6', '7', '8'
106 | };
107 |
108 | private static final byte[] CARD_ID_FIXED = {
109 | /* GSC-RID: GSC-IS data model */
110 | (byte)0xa0, (byte)0x00, (byte)0x00, (byte)0x01, (byte)0x16,
111 | /* Manufacturer: ff (unknown) */
112 | (byte)0xff,
113 | /* Card type: JavaCard */
114 | (byte)0x02
115 | };
116 |
117 | private static final byte[] YKPIV_VERSION = {
118 | (byte)5, (byte)4, (byte)0
119 | };
120 |
121 | /* Standard PIV commands we support. */
122 | private static final byte INS_VERIFY = (byte)0x20;
123 | private static final byte INS_CHANGE_PIN = (byte)0x24;
124 | private static final byte INS_RESET_PIN = (byte)0x2C;
125 | private static final byte INS_GEN_AUTH = (byte)0x87;
126 | private static final byte INS_GET_DATA = (byte)0xCB;
127 | private static final byte INS_PUT_DATA = (byte)0xDB;
128 | private static final byte INS_GEN_ASYM = (byte)0x47;
129 | private static final byte INS_GET_RESPONSE = (byte)0xC0;
130 |
131 | /* YubicoPIV extensions we support. */
132 | private static final byte INS_SET_MGMT = (byte)0xff;
133 | private static final byte INS_IMPORT_ASYM = (byte)0xfe;
134 | private static final byte INS_GET_VER = (byte)0xfd;
135 | private static final byte INS_RESET = (byte)0xfb;
136 | private static final byte INS_SET_PIN_RETRIES = (byte)0xfa;
137 | private static final byte INS_ATTEST = (byte)0xf9;
138 | private static final byte INS_GET_SERIAL = (byte)0xf8;
139 | private static final byte INS_GET_MDATA = (byte)0xf7;
140 |
141 | /* Our own private extensions. */
142 | private static final byte INS_SG_DEBUG = (byte)0xe0;
143 |
144 | /* ASSERT: tag.end() was called but tag has bytes left. */
145 | protected static final short SW_TAG_END_ASSERT = (short)0x6F60;
146 | protected static final short SW_DATA_END_ASSERT = (short)0x6F63;
147 | /* ASSERT: SGList#startReserve() ran out of Buffers. */
148 | protected static final short SW_RESERVE_FAILURE = (short)0x6F61;
149 | /* ASSERT: SGList#skip */
150 | protected static final short SW_SKIPPED_OVER_WPTR = (short)0x6F62;
151 | /* ASSERT: TransientBuffer#write, SGList#writes */
152 | protected static final short SW_WRITE_OVER_END = (short)0x6F65;
153 | protected static final short SW_BAD_REWRITE = (short)0x6F64;
154 |
155 | private static final boolean USE_EXT_LEN = false;
156 |
157 | private BufferManager bufmgr = null;
158 | private SGList incoming = null;
159 | private SGList outgoing = null;
160 | private APDUStream apduStream = null;
161 | private TransientBuffer tempBuf = null;
162 | private TransientBuffer outBuf = null;
163 | private short outgoingLe = 0;
164 |
165 | private byte[] challenge = null;
166 | private boolean[] chalValid = null;
167 | private byte[] iv = null;
168 | private byte[] certSerial = null;
169 |
170 | private byte[] guid = null;
171 | private byte[] cardId = null;
172 | private byte[] serial = null;
173 | private byte[] fascn = null;
174 | private byte[] expiry = null;
175 |
176 | private TlvReader tlv = null;
177 | private TlvWriter wtlv = null;
178 |
179 | private OwnerPIN pivPin = null;
180 | private OwnerPIN pukPin = null;
181 | private boolean pivPinIsDefault = true;
182 | private boolean pukPinIsDefault = true;
183 | private boolean mgmtKeyIsDefault = true;
184 | private byte pinRetries;
185 | private byte pukRetries;
186 |
187 | private RandomData randData = null;
188 | private Cipher tripleDes = null;
189 | private Cipher aes = null;
190 | private Cipher rsaPkcs1 = null;
191 | private Signature ecdsaSha = null;
192 | private Signature ecdsaSha256 = null;
193 | private Signature ecdsaSha384 = null;
194 | private Signature rsaSha = null;
195 | private Signature rsaSha256 = null;
196 | private KeyAgreement ecdh = null;
197 | private KeyAgreement ecdhSha = null;
198 |
199 | private static final byte MAX_SLOTS = (byte)17;
200 |
201 | private static final byte SLOT_9A = (byte)0;
202 | private static final byte SLOT_9B = (byte)1;
203 | private static final byte SLOT_9C = (byte)2;
204 | private static final byte SLOT_9D = (byte)3;
205 | private static final byte SLOT_9E = (byte)4;
206 | private static final byte SLOT_82 = (byte)5;
207 | private static final byte SLOT_8C = (byte)15;
208 | private static final byte SLOT_F9 = (byte)16;
209 | private PivSlot[] slots = null;
210 | private byte retiredKeys = 0;
211 |
212 | private static final byte SLOT_MIN_HIST = SLOT_82;
213 | private static final byte MIN_HIST_SLOT = (byte)0x82;
214 | private static final byte MAX_HIST_SLOT = (byte)0x8C;
215 |
216 | private static final byte PIV_ALG_DEFAULT = (byte)0x00;
217 | private static final byte PIV_ALG_3DES = (byte)0x03;
218 | private static final byte PIV_ALG_RSA1024 = (byte)0x06;
219 | private static final byte PIV_ALG_RSA2048 = (byte)0x07;
220 | private static final byte PIV_ALG_AES128 = (byte)0x08;
221 | private static final byte PIV_ALG_AES192 = (byte)0x0A;
222 | private static final byte PIV_ALG_AES256 = (byte)0x0C;
223 | private static final byte PIV_ALG_ECCP256 = (byte)0x11;
224 | private static final byte PIV_ALG_ECCP384 = (byte)0x14;
225 |
226 | private static final byte GA_TAG_WITNESS = (byte)0x80;
227 | private static final byte GA_TAG_CHALLENGE = (byte)0x81;
228 | private static final byte GA_TAG_RESPONSE = (byte)0x82;
229 | private static final byte GA_TAG_EXP = (byte)0x85;
230 |
231 | private static final byte TAG_CERT_9E = (byte)0x01;
232 | private static final byte TAG_CHUID = (byte)0x02;
233 | private static final byte TAG_FINGERPRINTS = (byte)0x03;
234 | private static final byte TAG_CERT_9A = (byte)0x05;
235 | private static final byte TAG_SECOBJ = (byte)0x06;
236 | private static final byte TAG_CARDCAP = (byte)0x07;
237 | private static final byte TAG_FACE = (byte)0x08;
238 | private static final byte TAG_PRINTED_INFO = (byte)0x09;
239 | private static final byte TAG_CERT_9C = (byte)0x0A;
240 | private static final byte TAG_CERT_9D = (byte)0x0B;
241 | private static final byte TAG_KEYHIST = (byte)0x0C;
242 | private static final byte TAG_CERT_82 = (byte)0x0D;
243 | private static final byte TAG_CERT_8C = (byte)0x17;
244 |
245 | private static final byte TAG_MAX = TAG_CERT_8C;
246 | private File[] files = null;
247 |
248 | private static final byte TAG_YK_PIVMAN = (byte)0x00;
249 | private static final byte TAG_YK_ATTEST = (byte)0x01;
250 | private static final byte YK_TAG_MAX = TAG_YK_ATTEST;
251 | private File[] ykFiles = null;
252 |
253 | private static final byte ALG_EC_SVDP_DH_PLAIN = (byte)3;
254 | private static final byte ALG_EC_SVDP_DHC_PLAIN = (byte)4;
255 | private static final byte ALG_EC_SVDP_DH_PLAIN_XY = (byte)6;
256 | private static final byte ALG_RSA_SHA_256_PKCS1 = (byte)40;
257 |
258 | public static void
259 | install(byte[] info, short off, byte len)
260 | {
261 | final PivApplet applet = new PivApplet();
262 | applet.register();
263 | }
264 |
265 | /*#if APPLET_USE_RESET_MEM
266 | private static final boolean useResetMem = true;
267 | #else*/
268 | private static final boolean useResetMem = false;
269 | //#endif
270 |
271 | protected
272 | PivApplet()
273 | {
274 | randData = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
275 | //#if PIV_SUPPORT_3DES
276 | tripleDes = Cipher.getInstance(Cipher.ALG_DES_CBC_NOPAD, false);
277 | //#endif
278 | //#if PIV_SUPPORT_AES
279 | aes = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD,
280 | false);
281 | //#endif
282 |
283 | //#if PIV_SUPPORT_RSA
284 | rsaPkcs1 = Cipher.getInstance(Cipher.ALG_RSA_NOPAD, useResetMem);
285 |
286 | //#if YKPIV_ATTESTATION
287 | try {
288 | rsaSha = Signature.getInstance(
289 | Signature.ALG_RSA_SHA_PKCS1, useResetMem);
290 | } catch (CryptoException ex) {
291 | if (ex.getReason() != CryptoException.NO_SUCH_ALGORITHM)
292 | throw (ex);
293 | }
294 | try {
295 | rsaSha256 = Signature.getInstance(
296 | ALG_RSA_SHA_256_PKCS1, useResetMem);
297 | } catch (CryptoException ex) {
298 | if (ex.getReason() != CryptoException.NO_SUCH_ALGORITHM)
299 | throw (ex);
300 | }
301 | //#endif
302 | //#endif
303 |
304 | //#if PIV_SUPPORT_EC
305 | try {
306 | ecdh = KeyAgreement.getInstance(ALG_EC_SVDP_DH_PLAIN,
307 | useResetMem);
308 | } catch (CryptoException ex) {
309 | if (ex.getReason() != CryptoException.NO_SUCH_ALGORITHM)
310 | throw (ex);
311 | }
312 |
313 | if (ecdh == null) {
314 | try {
315 | ecdh = KeyAgreement.getInstance(
316 | ALG_EC_SVDP_DHC_PLAIN, useResetMem);
317 | } catch (CryptoException ex) {
318 | if (ex.getReason() !=
319 | CryptoException.NO_SUCH_ALGORITHM)
320 | throw (ex);
321 | }
322 | }
323 |
324 | if (ecdh == null) {
325 | try {
326 | ecdh = KeyAgreement.getInstance(
327 | ALG_EC_SVDP_DH_PLAIN_XY, useResetMem);
328 | } catch (CryptoException ex) {
329 | if (ex.getReason() !=
330 | CryptoException.NO_SUCH_ALGORITHM)
331 | throw (ex);
332 | }
333 | }
334 |
335 | if (ecdh == null) {
336 | try {
337 | ecdhSha = KeyAgreement.getInstance(
338 | KeyAgreement.ALG_EC_SVDP_DH, useResetMem);
339 | } catch (CryptoException ex) {
340 | if (ex.getReason() !=
341 | CryptoException.NO_SUCH_ALGORITHM)
342 | throw (ex);
343 | }
344 | }
345 |
346 | try {
347 | ecdsaSha = Signature.getInstance(
348 | Signature.ALG_ECDSA_SHA, useResetMem);
349 | } catch (CryptoException ex) {
350 | if (ex.getReason() != CryptoException.NO_SUCH_ALGORITHM)
351 | throw (ex);
352 | }
353 | try {
354 | ecdsaSha256 = Signature.getInstance(
355 | ECParams.ALG_ECDSA_SHA_256, useResetMem);
356 | } catch (CryptoException ex) {
357 | if (ex.getReason() != CryptoException.NO_SUCH_ALGORITHM)
358 | throw (ex);
359 | }
360 | //#if PIV_SUPPORT_ECCP384
361 | try {
362 | ecdsaSha384 = Signature.getInstance(
363 | ECParams.ALG_ECDSA_SHA_384, useResetMem);
364 | } catch (CryptoException ex) {
365 | if (ex.getReason() != CryptoException.NO_SUCH_ALGORITHM)
366 | throw (ex);
367 | }
368 | //#endif
369 | //#endif
370 |
371 | challenge = JCSystem.makeTransientByteArray((short)16,
372 | JCSystem.CLEAR_ON_DESELECT);
373 | chalValid = JCSystem.makeTransientBooleanArray((short)1,
374 | JCSystem.CLEAR_ON_DESELECT);
375 | iv = JCSystem.makeTransientByteArray((short)16,
376 | JCSystem.CLEAR_ON_DESELECT);
377 |
378 | guid = new byte[16];
379 | randData.generateData(guid, (short)0, (short)16);
380 | cardId = new byte[21];
381 | Util.arrayCopy(CARD_ID_FIXED, (short)0, cardId, (short)0,
382 | (short)CARD_ID_FIXED.length);
383 | randData.generateData(cardId, (short)CARD_ID_FIXED.length,
384 | (short)(21 - (short)CARD_ID_FIXED.length));
385 |
386 | serial = new byte[4];
387 | randData.generateData(serial, (short)0, (short)4);
388 | serial[0] |= (byte)0x80;
389 |
390 | certSerial = new byte[16];
391 | fascn = new byte[25];
392 | expiry = new byte[] { '2', '0', '5', '0', '0', '1', '0', '1' };
393 |
394 | slots = new PivSlot[MAX_SLOTS];
395 | for (byte i = SLOT_9A; i <= SLOT_9E; ++i)
396 | slots[i] = new PivSlot((byte)((byte)0x9A + i));
397 | for (byte i = SLOT_82; i <= SLOT_8C; ++i)
398 | slots[i] = new PivSlot((byte)((byte)0x82 + i));
399 | //#if YKPIV_ATTESTATION
400 | slots[SLOT_F9] = new PivSlot((byte)0xF9);
401 | //#endif
402 |
403 | files = new File[TAG_MAX + 1];
404 | ykFiles = new File[YK_TAG_MAX + 1];
405 |
406 | bufmgr = new BufferManager();
407 | incoming = new SGList(bufmgr);
408 | outgoing = new SGList(bufmgr);
409 | apduStream = new APDUStream();
410 |
411 | tempBuf = new TransientBuffer();
412 | outBuf = new TransientBuffer();
413 |
414 | tlv = new TlvReader();
415 | wtlv = new TlvWriter(bufmgr);
416 |
417 | /* Initialize the admin key */
418 | //#if PIV_SUPPORT_3DES
419 | final DESKey dk = (DESKey)KeyBuilder.buildKey(
420 | KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES3_3KEY, false);
421 | slots[SLOT_9B].sym = dk;
422 | dk.setKey(DEFAULT_ADMIN_KEY, (short)0);
423 | slots[SLOT_9B].symAlg = PIV_ALG_3DES;
424 | /*#else
425 | final AESKey ak = (AESKey)KeyBuilder.buildKey(
426 | KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_192, false);
427 | slots[SLOT_9B].sym = ak;
428 | ak.setKey(DEFAULT_ADMIN_KEY, (short)0);
429 | slots[SLOT_9B].symAlg = PIV_ALG_AES192;
430 | #endif*/
431 | /*
432 | * Allow the admin key to be "used" (for auth) without a
433 | * PIN VERIFY command first.
434 | */
435 | slots[SLOT_9B].pinPolicy = PivSlot.P_NEVER;
436 |
437 | pinRetries = (byte)5;
438 | pivPin = new OwnerPIN(pinRetries, (byte)8);
439 | pivPin.update(DEFAULT_PIN, (short)0, (byte)8);
440 | pukRetries = (byte)3;
441 | pukPin = new OwnerPIN(pukRetries, (byte)8);
442 | pukPin.update(DEFAULT_PUK, (short)0, (byte)8);
443 |
444 | files[TAG_CERT_9A] = new File();
445 | slots[SLOT_9A].cert = files[TAG_CERT_9A];
446 |
447 | files[TAG_CERT_9C] = new File();
448 | slots[SLOT_9C].cert = files[TAG_CERT_9C];
449 | slots[SLOT_9C].pinPolicy = PivSlot.P_ALWAYS;
450 |
451 | files[TAG_CERT_9D] = new File();
452 | slots[SLOT_9D].cert = files[TAG_CERT_9D];
453 |
454 | files[TAG_CERT_9E] = new File();
455 | slots[SLOT_9E].cert = files[TAG_CERT_9E];
456 | slots[SLOT_9E].pinPolicy = PivSlot.P_NEVER;
457 |
458 | files[TAG_FINGERPRINTS] = new File();
459 | files[TAG_FINGERPRINTS].contact = File.P_PIN;
460 |
461 | files[TAG_FACE] = new File();
462 | files[TAG_FACE].contact = File.P_PIN;
463 |
464 | files[TAG_PRINTED_INFO] = new File();
465 | files[TAG_PRINTED_INFO].contact = File.P_PIN;
466 |
467 | //#if YKPIV_ATTESTATION
468 | ykFiles[TAG_YK_ATTEST] = new File();
469 | slots[SLOT_F9].cert = ykFiles[TAG_YK_ATTEST];
470 | //#endif
471 |
472 | //#if PIV_STRICT_CONTACTLESS
473 | files[TAG_CERT_9A].contactless = File.P_NEVER;
474 | files[TAG_CERT_9C].contactless = File.P_NEVER;
475 | files[TAG_CERT_9D].contactless = File.P_NEVER;
476 | files[TAG_FINGERPRINTS].contactless = File.P_NEVER;
477 | files[TAG_PRINTED_INFO].contactless = File.P_PIN;
478 | files[TAG_FACE].contactless = File.P_PIN;
479 | //#endif
480 |
481 | initCARDCAP();
482 | initCHUID();
483 | initKEYHIST();
484 | //#if YKPIV_ATTESTATION
485 | initAttestation();
486 | //#endif
487 | }
488 |
489 | public void
490 | process(APDU apdu)
491 | {
492 | final byte[] buffer = apdu.getBuffer();
493 | final byte ins = buffer[ISO7816.OFFSET_INS];
494 | final byte chainBit =
495 | (byte)(buffer[ISO7816.OFFSET_CLA] & (byte)0x10);
496 |
497 | //#if APPLET_EXTLEN
498 | if (!apdu.isISOInterindustryCLA()) {
499 | ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
500 | return;
501 | }
502 | /*#else
503 | final byte isoInterBit =
504 | (byte)(buffer[ISO7816.OFFSET_CLA] & (byte)0x80);
505 | if (isoInterBit != 0) {
506 | ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
507 | return;
508 | }
509 | #endif*/
510 |
511 | if (selectingApplet()) {
512 | sendSelectResponse(apdu);
513 | return;
514 | }
515 |
516 | /*
517 | * Slots that are marked as "PIN always" only work when the
518 | * APDU directly before them was VERIFY.
519 | *
520 | * If we process any other type of APDU, we set the flag
521 | * AFTER_VERIFY and then before the next APDU we lock them
522 | * here. The VERIFY command unsets the flag.
523 | */
524 | if (chainBit == 0)
525 | lockPINAlwaysSlots();
526 |
527 | switch (ins) {
528 | case INS_GET_DATA:
529 | processGetData(apdu);
530 | break;
531 | case INS_GEN_AUTH:
532 | processGeneralAuth(apdu);
533 | break;
534 | case INS_PUT_DATA:
535 | processPutData(apdu);
536 | break;
537 | case INS_CHANGE_PIN:
538 | processChangePin(apdu);
539 | break;
540 | case INS_VERIFY:
541 | processVerify(apdu);
542 | break;
543 | case INS_GEN_ASYM:
544 | processGenAsym(apdu);
545 | break;
546 | case INS_RESET_PIN:
547 | processResetPin(apdu);
548 | break;
549 | case INS_SET_PIN_RETRIES:
550 | processSetPinRetries(apdu);
551 | break;
552 | case INS_RESET:
553 | processReset(apdu);
554 | break;
555 | case INS_GET_VER:
556 | processGetVersion(apdu);
557 | break;
558 | case INS_IMPORT_ASYM:
559 | processImportAsym(apdu);
560 | break;
561 | case INS_SET_MGMT:
562 | processSetMgmtKey(apdu);
563 | break;
564 | case INS_SG_DEBUG:
565 | processSGDebug(apdu);
566 | break;
567 | case INS_GET_RESPONSE:
568 | continueResponse(apdu);
569 | break;
570 | //#if YKPIV_ATTESTATION
571 | case INS_ATTEST:
572 | processAttest(apdu);
573 | break;
574 | //#endif
575 | case INS_GET_SERIAL:
576 | processGetSerial(apdu);
577 | break;
578 | case INS_GET_MDATA:
579 | processGetMetadata(apdu);
580 | break;
581 | default:
582 | ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
583 | }
584 | }
585 |
586 | private boolean
587 | isContact()
588 | {
589 | final byte media = (byte)(
590 | APDU.getProtocol() & APDU.PROTOCOL_MEDIA_MASK);
591 | if (media == APDU.PROTOCOL_MEDIA_DEFAULT)
592 | return (true);
593 | if (media == APDU.PROTOCOL_MEDIA_USB)
594 | return (true);
595 | return (false);
596 | }
597 |
598 | /*#if !APPLET_EXTLEN
599 | private short
600 | getIncomingLengthCompat(final APDU apdu)
601 | {
602 | final byte[] buf = apdu.getBuffer();
603 | return ((short)((short)buf[ISO7816.OFFSET_LC] & 0x00FF));
604 | }
605 | #endif*/
606 |
607 | private void
608 | lockPINAlwaysSlots()
609 | {
610 | for (short idx = (short)0; idx < MAX_SLOTS; ++idx) {
611 | final PivSlot slot = slots[idx];
612 | if (slot == null)
613 | continue;
614 | if (slot.pinPolicy != PivSlot.P_ALWAYS)
615 | continue;
616 | if (slot.flags[PivSlot.F_UNLOCKED] &&
617 | slot.flags[PivSlot.F_AFTER_VERIFY]) {
618 | slot.flags[PivSlot.F_UNLOCKED] = false;
619 | } else if (slot.flags[PivSlot.F_UNLOCKED]) {
620 | slot.flags[PivSlot.F_AFTER_VERIFY] = true;
621 | }
622 | }
623 | }
624 |
625 | private void
626 | processGetVersion(APDU apdu)
627 | {
628 | short len = 0;
629 | final short le;
630 | final byte[] buffer = apdu.getBuffer();
631 |
632 | le = apdu.setOutgoing();
633 | buffer[len++] = YKPIV_VERSION[0];
634 | buffer[len++] = YKPIV_VERSION[1];
635 | buffer[len++] = YKPIV_VERSION[2];
636 |
637 | len = le > 0 ? (le > len ? len : le) : len;
638 | apdu.setOutgoingLength(len);
639 | apdu.sendBytes((short)0, len);
640 | }
641 |
642 | private void
643 | processGetSerial(APDU apdu)
644 | {
645 | short len = 0;
646 | final short le;
647 | final byte[] buffer = apdu.getBuffer();
648 |
649 | le = apdu.setOutgoing();
650 | buffer[len++] = serial[0];
651 | buffer[len++] = serial[1];
652 | buffer[len++] = serial[2];
653 | buffer[len++] = serial[3];
654 |
655 | len = le > 0 ? (le > len ? len : le) : len;
656 | apdu.setOutgoingLength(len);
657 | apdu.sendBytes((short)0, len);
658 | }
659 |
660 | private void
661 | processGetMetadata(APDU apdu)
662 | {
663 | final byte[] buffer = apdu.getBuffer();
664 | final byte key = buffer[ISO7816.OFFSET_P2];
665 | final PivSlot slot;
666 |
667 | // PIN_P2 || PUK_P2
668 | if (key == (byte)0x80 || key == (byte)0x81) {
669 | outgoing.reset();
670 | wtlv.start(outgoing);
671 | outgoingLe = apdu.setOutgoing();
672 | wtlv.useApdu((short)0, outgoingLe);
673 |
674 | // Tag 0x05, one byte, whether the PIN or PUK is default
675 | wtlv.writeTagRealLen((byte)0x05, (short)1);
676 | if (key == (byte)0x80 && pivPinIsDefault)
677 | wtlv.writeByte((byte)1);
678 | else if (key == (byte)0x81 && pukPinIsDefault)
679 | wtlv.writeByte((byte)1);
680 | else
681 | wtlv.writeByte((byte)0);
682 |
683 | // Tag 0x06, two bytes, [total number of retries] [remaining retries]
684 | wtlv.writeTagRealLen((byte)0x06, (short)2);
685 | if (key == (byte)0x80) {
686 | wtlv.writeByte(pinRetries);
687 | wtlv.writeByte(pivPin.getTriesRemaining());
688 | } else if (key == (byte)0x81) {
689 | wtlv.writeByte(pukRetries);
690 | wtlv.writeByte(pukPin.getTriesRemaining());
691 | }
692 |
693 | wtlv.end();
694 | sendOutgoing(apdu);
695 | return;
696 | }
697 |
698 | if (key >= (byte)0x9A && key <= (byte)0x9E) {
699 | final byte idx = (byte)(key - (byte)0x9A);
700 | slot = slots[idx];
701 | } else if (key >= MIN_HIST_SLOT && key <= MAX_HIST_SLOT) {
702 | final byte idx = (byte)(SLOT_MIN_HIST +
703 | (byte)(key - MIN_HIST_SLOT));
704 | slot = slots[idx];
705 | } else {
706 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
707 | return;
708 | }
709 |
710 | if (slot == null) {
711 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
712 | return;
713 | }
714 |
715 | if (buffer[ISO7816.OFFSET_P1] != (byte)0x00) {
716 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
717 | return;
718 | }
719 |
720 | outgoing.reset();
721 | wtlv.start(outgoing);
722 | outgoingLe = apdu.setOutgoing();
723 | wtlv.useApdu((short)0, outgoingLe);
724 |
725 | // Tag 0x01, one byte, [algo]
726 | if (slot.asym != null) {
727 | wtlv.writeTagRealLen((byte)0x01, (short)1);
728 | wtlv.writeByte(slot.asymAlg);
729 | } else if (slot.sym != null) {
730 | wtlv.writeTagRealLen((byte)0x01, (short)1);
731 | wtlv.writeByte(slot.symAlg);
732 | }
733 |
734 | if (key == (byte)0x9B) {
735 | // Tag 0x05, one byte, whether the management key is default
736 | wtlv.writeTagRealLen((byte)0x05, (short)1);
737 | if (mgmtKeyIsDefault)
738 | wtlv.writeByte((byte)1);
739 | else
740 | wtlv.writeByte((byte)0);
741 | }
742 |
743 | // Tag 0x02, two bytes, PIN and touch policy (never)
744 | wtlv.writeTagRealLen((byte)0x02, (short)2);
745 | wtlv.writeByte(slot.pinPolicy);
746 | wtlv.writeByte((byte)0x01);
747 |
748 | wtlv.writeTagRealLen((byte)0x03, (short)1);
749 | if (slot.imported)
750 | wtlv.writeByte((byte)1);
751 | else
752 | wtlv.writeByte((byte)0);
753 |
754 | if (slot.asym != null) {
755 | short len;
756 | switch (slot.asymAlg) {
757 | //#if PIV_SUPPORT_RSA
758 | case PIV_ALG_RSA1024:
759 | case PIV_ALG_RSA2048:
760 | RSAPublicKey rpubk =
761 | (RSAPublicKey)slot.asym.getPublic();
762 | final short rsalen = (short)(rpubk.getSize() / 8);
763 |
764 | wtlv.push((byte)0x04, (short)(rsalen + 12));
765 | wtlv.push((byte)0x81, (short)(rsalen + 1));
766 | wtlv.startReserve((short)(rsalen + 1), tempBuf);
767 | len = rpubk.getModulus(tempBuf.data(), tempBuf.wpos());
768 | wtlv.endReserve(len);
769 | wtlv.pop();
770 |
771 | wtlv.push((byte)0x82);
772 | wtlv.startReserve((short)9, tempBuf);
773 | len = rpubk.getExponent(tempBuf.data(), tempBuf.wpos());
774 | wtlv.endReserve(len);
775 | wtlv.pop();
776 | break;
777 | //#endif
778 | //#if PIV_SUPPORT_EC
779 | case PIV_ALG_ECCP256:
780 | case PIV_ALG_ECCP384:
781 | ECPublicKey epubk =
782 | (ECPublicKey)slot.asym.getPublic();
783 | final short eclen = (short)(epubk.getSize() / 2);
784 |
785 | wtlv.push((byte)0x04, (short)(eclen + 4));
786 | wtlv.push((byte)0x86, (short)(eclen + 1));
787 | wtlv.startReserve((short)(eclen + 1), tempBuf);
788 | len = epubk.getW(tempBuf.data(), tempBuf.wpos());
789 | wtlv.endReserve(len);
790 | wtlv.pop();
791 | break;
792 | //#endif
793 | default:
794 | return;
795 | }
796 | wtlv.pop();
797 | }
798 |
799 | wtlv.end();
800 | sendOutgoing(apdu);
801 | return;
802 | }
803 |
804 | //#if YKPIV_ATTESTATION
805 | private void
806 | processAttest(APDU apdu)
807 | {
808 | final byte[] buffer = apdu.getBuffer();
809 | final byte key = buffer[ISO7816.OFFSET_P1];
810 | final PivSlot slot;
811 |
812 | if (key >= (byte)0x9A && key <= (byte)0x9E) {
813 | final byte idx = (byte)(key - (byte)0x9A);
814 | slot = slots[idx];
815 | } else if (key >= MIN_HIST_SLOT && key <= MAX_HIST_SLOT) {
816 | final byte idx = (byte)(SLOT_MIN_HIST +
817 | (byte)(key - MIN_HIST_SLOT));
818 | slot = slots[idx];
819 | } else {
820 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
821 | return;
822 | }
823 |
824 | if (slot == null) {
825 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
826 | return;
827 | }
828 |
829 | if (buffer[ISO7816.OFFSET_P2] != (byte)0x00) {
830 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
831 | return;
832 | }
833 |
834 | writeAttestationCert(slot);
835 |
836 | sendOutgoing(apdu);
837 | }
838 | //#endif
839 |
840 | private void
841 | processSGDebug(APDU apdu)
842 | {
843 | short len = (short)0;
844 | final short le;
845 | final byte[] buffer = apdu.getBuffer();
846 |
847 | le = apdu.setOutgoing();
848 | len = incoming.writeDebugInfo(buffer, len);
849 |
850 | len = le > 0 ? (le > len ? len : le) : len;
851 | apdu.setOutgoingLength(len);
852 | apdu.sendBytes((short)0, len);
853 | }
854 |
855 | private void
856 | pushAlgorithm(byte algid)
857 | {
858 | wtlv.push((byte)0xAC);
859 | wtlv.writeTagRealLen((byte)0x80, (short)1);
860 | wtlv.writeByte(algid);
861 | /*
862 | * SP 800-83-4 part 2, 3.1 says: "Its value is set to 0x00"
863 | * regarding this 0x06 tag. Previous PivApplet releases
864 | * interpreted this as an empty 0x06 tag with no contents
865 | * (length = 0), but it seems more logical that it should
866 | * contain a single zero byte.
867 | */
868 | wtlv.writeTagRealLen((byte)0x06, (short)1);
869 | wtlv.writeByte((byte)0x00);
870 | wtlv.pop();
871 | }
872 |
873 | private void
874 | sendSelectResponse(APDU apdu)
875 | {
876 | outgoing.reset();
877 | wtlv.start(outgoing);
878 | outgoingLe = apdu.setOutgoing();
879 | wtlv.useApdu((short)0, outgoingLe);
880 |
881 | wtlv.push256((byte)0x61);
882 |
883 | wtlv.writeTagRealLen((byte)0x4F, (short)PIV_AID.length);
884 | wtlv.write(PIV_AID, (short)0, (short)PIV_AID.length);
885 |
886 | /*
887 | * The NIST demo cards only return the first 5 bytes of the AID
888 | * here (the NIST RID). The spec is not especially explicit
889 | * about it, but we'll go with that.
890 | */
891 | wtlv.push((byte)0x79);
892 | wtlv.writeTagRealLen((byte)0x4F, (short)5);
893 | wtlv.write(PIV_AID, (short)0, (short)5);
894 | wtlv.pop();
895 |
896 | wtlv.writeTagRealLen((byte)0x50, (short)APP_NAME.length);
897 | wtlv.write(APP_NAME, (short)0, (short)APP_NAME.length);
898 |
899 | wtlv.writeTagRealLen((short)0x5F50, (short)APP_URI.length);
900 | wtlv.write(APP_URI, (short)0, (short)APP_URI.length);
901 |
902 | /*
903 | * Small spec divergence: PIV doesn't require us to list all
904 | * of the algorithms we support here in 'AC' '80' tags. We
905 | * do, though, so that client impls specifically aware of
906 | * PivApplet can work out which ones this build/card supports.
907 | *
908 | * Normally, this 'AC' tags would be expected to only contain
909 | * any supported PIV SM algorihtms. The specs allow multiple
910 | * 'AC' tags, however, and don't seem to explicitly outlaw
911 | * what we're doing.
912 | *
913 | * If we eventually support PIV SM, we should put the SM algos
914 | * first here so that client/middlware impls which stop at the
915 | * first 'AC' tag correctly find our SM algo.
916 | */
917 |
918 | //#if PIV_SUPPORT_3DES
919 | pushAlgorithm(PIV_ALG_3DES);
920 | //#endif
921 | //#if PIV_SUPPORT_AES
922 | /*
923 | * Just write AES256 to indicate AES support. No need to be
924 | * too verbose.
925 | */
926 | pushAlgorithm(PIV_ALG_AES256);
927 | //#endif
928 | //#if PIV_SUPPORT_RSA
929 | /*
930 | * Similar to AES, just write RSA2048 if we support RSA. Let
931 | * RSA1024 support be implied.
932 | */
933 | pushAlgorithm(PIV_ALG_RSA2048);
934 | //#endif
935 | //#if PIV_SUPPORT_EC
936 | if (ecdsaSha != null || ecdsaSha256 != null) {
937 | pushAlgorithm(PIV_ALG_ECCP256);
938 | }
939 | if (ecdsaSha384 != null) {
940 | pushAlgorithm(PIV_ALG_ECCP384);
941 | }
942 | //#endif
943 |
944 | wtlv.pop();
945 | wtlv.end();
946 |
947 | sendOutgoing(apdu);
948 | }
949 |
950 | private void
951 | sendOutgoing(APDU apdu)
952 | {
953 | final short len = outgoing.available();
954 | if (len < 1) {
955 | ISOException.throwIt(
956 | ISO7816.SW_CONDITIONS_NOT_SATISFIED);
957 | return;
958 | }
959 |
960 | final short le;
961 | if (apdu.getCurrentState() == APDU.STATE_OUTGOING)
962 | le = outgoingLe;
963 | else
964 | le = apdu.setOutgoing();
965 |
966 | short toSend = len;
967 | if (le > 0 && toSend > le)
968 | toSend = le;
969 | if (toSend > (short)0xFF)
970 | toSend = (short)0xFF;
971 |
972 | final short rem = (short)(len - toSend);
973 | final byte wantNext =
974 | rem > (short)0xFF ? (byte)0xFF : (byte)rem;
975 |
976 | apdu.setOutgoingLength(toSend);
977 | outgoing.readToApdu((short)0, toSend);
978 | apdu.sendBytes((short)0, toSend);
979 |
980 | if (rem > 0) {
981 | ISOException.throwIt(
982 | (short)(ISO7816.SW_BYTES_REMAINING_00 |
983 | ((short)wantNext & (short)0x00ff)));
984 | } else {
985 | outgoing.reset();
986 | ISOException.throwIt(ISO7816.SW_NO_ERROR);
987 | }
988 | }
989 |
990 | private void
991 | continueResponse(APDU apdu)
992 | {
993 | sendOutgoing(apdu);
994 | }
995 |
996 | private Readable
997 | receiveChain(APDU apdu)
998 | {
999 | final byte[] buf = apdu.getBuffer();
1000 | final byte chainBit =
1001 | (byte)(buf[ISO7816.OFFSET_CLA] & (byte)0x10);
1002 |
1003 | short recvLen = apdu.setIncomingAndReceive();
1004 | //#if APPLET_EXTLEN
1005 | final short cdata = apdu.getOffsetCdata();
1006 | /*#else
1007 | final short cdata = ISO7816.OFFSET_CDATA;
1008 | #endif*/
1009 |
1010 | if (chainBit == 0 && incoming.atEnd()) {
1011 | //#if APPLET_EXTLEN
1012 | final short lc = apdu.getIncomingLength();
1013 | /*#else
1014 | final short lc = getIncomingLengthCompat(apdu);
1015 | #endif*/
1016 | if (recvLen != lc) {
1017 | ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
1018 | return (null);
1019 | }
1020 | apduStream.reset(cdata, recvLen);
1021 | return (apduStream);
1022 | }
1023 |
1024 | if (incoming.atEnd())
1025 | incoming.reset();
1026 |
1027 | while (recvLen > 0) {
1028 | incoming.write(buf, cdata, recvLen);
1029 | recvLen = apdu.receiveBytes(cdata);
1030 | }
1031 |
1032 | if (chainBit != 0) {
1033 | ISOException.throwIt(ISO7816.SW_NO_ERROR);
1034 | return (null);
1035 | }
1036 |
1037 | return (incoming);
1038 | }
1039 |
1040 | private void
1041 | processGenAsym(APDU apdu)
1042 | {
1043 | final byte[] buffer = apdu.getBuffer();
1044 | short lc, len, cLen;
1045 | byte tag, alg = (byte)0xFF;
1046 | final byte key;
1047 | final PivSlot slot;
1048 |
1049 | if (buffer[ISO7816.OFFSET_P1] != (byte)0x00) {
1050 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
1051 | return;
1052 | }
1053 |
1054 | key = buffer[ISO7816.OFFSET_P2];
1055 |
1056 | if (key >= (byte)0x9A && key <= (byte)0x9E) {
1057 | final byte idx = (byte)(key - (byte)0x9A);
1058 | slot = slots[idx];
1059 | } else if (key >= MIN_HIST_SLOT && key <= MAX_HIST_SLOT) {
1060 | final byte idx = (byte)(SLOT_MIN_HIST +
1061 | (byte)(key - MIN_HIST_SLOT));
1062 | slot = slots[idx];
1063 | } else if (key == (byte)0xF9) {
1064 | slot = slots[SLOT_F9];
1065 | } else {
1066 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
1067 | return;
1068 | }
1069 |
1070 | if (slot == null) {
1071 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
1072 | return;
1073 | }
1074 |
1075 | if (!slots[SLOT_9B].flags[PivSlot.F_UNLOCKED]) {
1076 | ISOException.throwIt(
1077 | ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
1078 | return;
1079 | }
1080 |
1081 | lc = apdu.setIncomingAndReceive();
1082 | //#if APPLET_EXTLEN
1083 | final short inLc = apdu.getIncomingLength();
1084 | /*#else
1085 | final short inLc = getIncomingLengthCompat(apdu);
1086 | #endif*/
1087 | if (lc != inLc) {
1088 | ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
1089 | return;
1090 | }
1091 |
1092 | //#if APPLET_EXTLEN
1093 | apduStream.reset(apdu.getOffsetCdata(), lc);
1094 | /*#else
1095 | apduStream.reset(ISO7816.OFFSET_CDATA, lc);
1096 | #endif*/
1097 | tlv.start(apduStream);
1098 |
1099 | if (tlv.readTag() != (byte)0xAC) {
1100 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
1101 | return;
1102 | }
1103 |
1104 | while (!tlv.atEnd()) {
1105 | tag = tlv.readTag();
1106 | switch (tag) {
1107 | case (byte)0x80:
1108 | if (tlv.tagLength() != 1) {
1109 | ISOException.throwIt(
1110 | ISO7816.SW_WRONG_DATA);
1111 | return;
1112 | }
1113 | alg = tlv.readByte();
1114 | tlv.end();
1115 | break;
1116 | case (byte)0x81:
1117 | tlv.skip();
1118 | break;
1119 | case (byte)0xab:
1120 | if (tlv.tagLength() != 1) {
1121 | ISOException.throwIt(
1122 | ISO7816.SW_WRONG_DATA);
1123 | return;
1124 | }
1125 | tag = tlv.readByte();
1126 | if (tag != (byte)0x00 && tag != (byte)0x01) {
1127 | ISOException.throwIt(
1128 | ISO7816.SW_FUNC_NOT_SUPPORTED);
1129 | return;
1130 | }
1131 | tlv.end();
1132 | break;
1133 | case (byte)0xaa:
1134 | if (tlv.tagLength() != 1) {
1135 | ISOException.throwIt(
1136 | ISO7816.SW_WRONG_DATA);
1137 | return;
1138 | }
1139 | tag = tlv.readByte();
1140 | switch (tag) {
1141 | case PivSlot.P_DEFAULT:
1142 | if (key == (byte)0x9e) {
1143 | slot.pinPolicy =
1144 | PivSlot.P_NEVER;
1145 | } else if (key == (byte)0x9c) {
1146 | slot.pinPolicy =
1147 | PivSlot.P_ALWAYS;
1148 | } else {
1149 | slot.pinPolicy =
1150 | PivSlot.P_ONCE;
1151 | }
1152 | break;
1153 | case PivSlot.P_NEVER:
1154 | case PivSlot.P_ONCE:
1155 | case PivSlot.P_ALWAYS:
1156 | slot.pinPolicy = tag;
1157 | break;
1158 | default:
1159 | ISOException.throwIt(
1160 | ISO7816.SW_FUNC_NOT_SUPPORTED);
1161 | return;
1162 | }
1163 | tlv.end();
1164 | break;
1165 | }
1166 | }
1167 |
1168 | tlv.end();
1169 |
1170 | if (alg == (byte)0xFF) {
1171 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
1172 | return;
1173 | }
1174 |
1175 | //#if PIV_SUPPORT_EC
1176 | final ECPrivateKey ecPriv;
1177 | final ECPublicKey ecPub;
1178 | //#endif
1179 |
1180 | switch (alg) {
1181 | //#if PIV_SUPPORT_RSA
1182 | case PIV_ALG_RSA1024:
1183 | if (slot.asym == null || slot.asymAlg != alg) {
1184 | slot.asym = new KeyPair(KeyPair.ALG_RSA_CRT,
1185 | (short)1024);
1186 | }
1187 | slot.asymAlg = alg;
1188 | break;
1189 | case PIV_ALG_RSA2048:
1190 | if (slot.asym == null || slot.asymAlg != alg) {
1191 | slot.asym = new KeyPair(KeyPair.ALG_RSA_CRT,
1192 | (short)2048);
1193 | }
1194 | slot.asymAlg = alg;
1195 | break;
1196 | //#endif
1197 | //#if PIV_SUPPORT_EC
1198 | case PIV_ALG_ECCP256:
1199 | if (ecdsaSha == null && ecdsaSha256 == null) {
1200 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
1201 | return;
1202 | }
1203 |
1204 | if (slot.asym == null || slot.asymAlg != alg) {
1205 | ecPriv = (ECPrivateKey)KeyBuilder.buildKey(
1206 | KeyBuilder.TYPE_EC_FP_PRIVATE,
1207 | (short)256, false);
1208 | ecPub = (ECPublicKey)KeyBuilder.buildKey(
1209 | KeyBuilder.TYPE_EC_FP_PUBLIC,
1210 | (short)256, false);
1211 | slot.asym = new KeyPair(
1212 | (PublicKey)ecPub, (PrivateKey)ecPriv);
1213 | } else {
1214 | ecPriv = (ECPrivateKey)slot.asym.getPrivate();
1215 | ecPub = (ECPublicKey)slot.asym.getPublic();
1216 | ecPriv.clearKey();
1217 | ecPub.clearKey();
1218 | }
1219 | ECParams.setCurveParametersP256(ecPriv);
1220 | ECParams.setCurveParametersP256(ecPub);
1221 | slot.asymAlg = alg;
1222 | break;
1223 | case PIV_ALG_ECCP384:
1224 | if (ecdsaSha == null && ecdsaSha256 == null) {
1225 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
1226 | return;
1227 | }
1228 | if (slot.asym == null || slot.asymAlg != alg) {
1229 | ecPriv = (ECPrivateKey)KeyBuilder.buildKey(
1230 | KeyBuilder.TYPE_EC_FP_PRIVATE,
1231 | (short)384, false);
1232 | ecPub = (ECPublicKey)KeyBuilder.buildKey(
1233 | KeyBuilder.TYPE_EC_FP_PUBLIC,
1234 | (short)384, false);
1235 | slot.asym = new KeyPair(
1236 | (PublicKey)ecPub, (PrivateKey)ecPriv);
1237 | } else {
1238 | ecPriv = (ECPrivateKey)slot.asym.getPrivate();
1239 | ecPub = (ECPublicKey)slot.asym.getPublic();
1240 | ecPriv.clearKey();
1241 | ecPub.clearKey();
1242 | }
1243 | ECParams.setCurveParametersP384(ecPriv);
1244 | ECParams.setCurveParametersP384(ecPub);
1245 | slot.asymAlg = alg;
1246 | break;
1247 | //#endif
1248 | default:
1249 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
1250 | return;
1251 | }
1252 |
1253 | try {
1254 | slot.asym.genKeyPair();
1255 | } catch (CryptoException ex) {
1256 | ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
1257 | return;
1258 | } catch (SystemException ex) {
1259 | switch (ex.getReason()) {
1260 | case SystemException.NO_TRANSIENT_SPACE:
1261 | case SystemException.NO_RESOURCE:
1262 | ISOException.throwIt(ISO7816.SW_FILE_FULL);
1263 | return;
1264 | default:
1265 | ISOException.throwIt((short)(
1266 | (short)0x6f90 + ex.getReason()));
1267 | return;
1268 | }
1269 | }
1270 |
1271 | slot.imported = false;
1272 |
1273 | outgoing.reset();
1274 | wtlv.start(outgoing);
1275 | outgoingLe = apdu.setOutgoing();
1276 | wtlv.useApdu((short)0, outgoingLe);
1277 |
1278 | switch (alg) {
1279 | //#if PIV_SUPPORT_RSA
1280 | case PIV_ALG_RSA1024:
1281 | case PIV_ALG_RSA2048:
1282 | RSAPublicKey rpubk =
1283 | (RSAPublicKey)slot.asym.getPublic();
1284 | final short rsalen = (short)(rpubk.getSize() / 8);
1285 |
1286 | wtlv.push((short)0x7F49, (short)(rsalen + 14));
1287 |
1288 | wtlv.push((byte)0x81, (short)(rsalen + 1));
1289 | wtlv.startReserve((short)(rsalen + 1), tempBuf);
1290 | cLen = rpubk.getModulus(tempBuf.data(), tempBuf.wpos());
1291 | wtlv.endReserve(cLen);
1292 | wtlv.pop();
1293 |
1294 | wtlv.push((byte)0x82);
1295 | wtlv.startReserve((short)9, tempBuf);
1296 | cLen = rpubk.getExponent(tempBuf.data(), tempBuf.wpos());
1297 | wtlv.endReserve(cLen);
1298 | wtlv.pop();
1299 | break;
1300 | //#endif
1301 | //#if PIV_SUPPORT_EC
1302 | case PIV_ALG_ECCP256:
1303 | case PIV_ALG_ECCP384:
1304 | ECPublicKey epubk =
1305 | (ECPublicKey)slot.asym.getPublic();
1306 | final short eclen = (short)(epubk.getSize() / 4);
1307 |
1308 | wtlv.push((short)0x7F49, (short)(eclen + 3));
1309 |
1310 | wtlv.push((byte)0x86, (short)(eclen + 1));
1311 | wtlv.startReserve((short)(eclen + 1), tempBuf);
1312 | cLen = epubk.getW(tempBuf.data(), tempBuf.wpos());
1313 | wtlv.endReserve(cLen);
1314 | wtlv.pop();
1315 | break;
1316 | //#endif
1317 | default:
1318 | return;
1319 | }
1320 |
1321 | wtlv.pop();
1322 | wtlv.end();
1323 |
1324 | sendOutgoing(apdu);
1325 | }
1326 |
1327 | private void
1328 | processImportAsym(APDU apdu)
1329 | {
1330 | final byte[] buffer = apdu.getBuffer();
1331 | final byte key, alg;
1332 | short lc, len;
1333 | byte tag;
1334 | final PivSlot slot;
1335 | final Readable input;
1336 |
1337 | alg = buffer[ISO7816.OFFSET_P1];
1338 | key = buffer[ISO7816.OFFSET_P2];
1339 |
1340 | if (!slots[SLOT_9B].flags[PivSlot.F_UNLOCKED]) {
1341 | ISOException.throwIt(
1342 | ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
1343 | return;
1344 | }
1345 |
1346 | if (key >= (byte)0x9A && key <= (byte)0x9E) {
1347 | final byte idx = (byte)(key - (byte)0x9A);
1348 | slot = slots[idx];
1349 | } else if (key >= MIN_HIST_SLOT && key <= MAX_HIST_SLOT) {
1350 | final byte idx = (byte)(SLOT_MIN_HIST +
1351 | (byte)(key - MIN_HIST_SLOT));
1352 | slot = slots[idx];
1353 | } else {
1354 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
1355 | return;
1356 | }
1357 |
1358 | if (slot == null) {
1359 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
1360 | return;
1361 | }
1362 |
1363 | input = receiveChain(apdu);
1364 | if (input == null)
1365 | return;
1366 |
1367 | tlv.start(input);
1368 |
1369 | switch (alg) {
1370 | //#if PIV_SUPPORT_RSA
1371 | case PIV_ALG_RSA1024:
1372 | if (slot.asym == null || slot.asymAlg != alg) {
1373 | slot.asym = new KeyPair(KeyPair.ALG_RSA_CRT,
1374 | (short)1024);
1375 | }
1376 | slot.asymAlg = alg;
1377 | break;
1378 | case PIV_ALG_RSA2048:
1379 | if (slot.asym == null || slot.asymAlg != alg) {
1380 | slot.asym = new KeyPair(KeyPair.ALG_RSA_CRT,
1381 | (short)2048);
1382 | }
1383 | slot.asymAlg = alg;
1384 | break;
1385 | //#endif
1386 | //#if PIV_SUPPORT_EC
1387 | case PIV_ALG_ECCP256:
1388 | if (ecdsaSha == null && ecdsaSha256 == null) {
1389 | tlv.abort();
1390 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
1391 | return;
1392 | }
1393 | if (slot.asym == null || slot.asymAlg != alg) {
1394 | final ECPrivateKey ecPriv;
1395 | final ECPublicKey ecPub;
1396 | ecPriv = (ECPrivateKey)KeyBuilder.buildKey(
1397 | KeyBuilder.TYPE_EC_FP_PRIVATE,
1398 | (short)256, false);
1399 | ecPub = (ECPublicKey)KeyBuilder.buildKey(
1400 | KeyBuilder.TYPE_EC_FP_PUBLIC,
1401 | (short)256, false);
1402 | slot.asym = new KeyPair(
1403 | (PublicKey)ecPub, (PrivateKey)ecPriv);
1404 | }
1405 | slot.asymAlg = alg;
1406 | break;
1407 | case PIV_ALG_ECCP384:
1408 | if (ecdsaSha == null && ecdsaSha256 == null) {
1409 | tlv.abort();
1410 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
1411 | return;
1412 | }
1413 | if (slot.asym == null || slot.asymAlg != alg) {
1414 | final ECPrivateKey ecPriv;
1415 | final ECPublicKey ecPub;
1416 | ecPriv = (ECPrivateKey)KeyBuilder.buildKey(
1417 | KeyBuilder.TYPE_EC_FP_PRIVATE,
1418 | (short)384, false);
1419 | ecPub = (ECPublicKey)KeyBuilder.buildKey(
1420 | KeyBuilder.TYPE_EC_FP_PUBLIC,
1421 | (short)384, false);
1422 | slot.asym = new KeyPair(
1423 | (PublicKey)ecPub, (PrivateKey)ecPriv);
1424 | }
1425 | slot.asymAlg = alg;
1426 | break;
1427 | //#endif
1428 | default:
1429 | tlv.abort();
1430 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
1431 | return;
1432 | }
1433 |
1434 | slot.imported = true;
1435 |
1436 | switch (alg) {
1437 | //#if PIV_SUPPORT_RSA
1438 | case PIV_ALG_RSA1024:
1439 | case PIV_ALG_RSA2048:
1440 | final RSAPublicKey rpubk =
1441 | (RSAPublicKey)slot.asym.getPublic();
1442 | final RSAPrivateCrtKey rprivk =
1443 | (RSAPrivateCrtKey)slot.asym.getPrivate();
1444 | rpubk.clearKey();
1445 | rprivk.clearKey();
1446 |
1447 | while (!tlv.atEnd()) {
1448 | tag = tlv.readTag();
1449 | final short tlen = tlv.tagLength();
1450 | switch (tag) {
1451 | case (byte)0x01:
1452 | tlv.read(tempBuf, tlen);
1453 | rprivk.setP(tempBuf.data(),
1454 | tempBuf.rpos(), tlen);
1455 | tlv.end();
1456 | break;
1457 | case (byte)0x02:
1458 | tlv.read(tempBuf, tlen);
1459 | rprivk.setQ(tempBuf.data(),
1460 | tempBuf.rpos(), tlen);
1461 | tlv.end();
1462 | break;
1463 | case (byte)0x03:
1464 | tlv.read(tempBuf, tlen);
1465 | rprivk.setDP1(tempBuf.data(),
1466 | tempBuf.rpos(), tlen);
1467 | tlv.end();
1468 | break;
1469 | case (byte)0x04:
1470 | tlv.read(tempBuf, tlen);
1471 | rprivk.setDQ1(tempBuf.data(),
1472 | tempBuf.rpos(), tlen);
1473 | tlv.end();
1474 | break;
1475 | case (byte)0x05:
1476 | tlv.read(tempBuf, tlen);
1477 | rprivk.setPQ(tempBuf.data(),
1478 | tempBuf.rpos(), tlen);
1479 | tlv.end();
1480 | break;
1481 | case (byte)0xaa:
1482 | if (tlv.tagLength() != 1) {
1483 | tlv.abort();
1484 | ISOException.throwIt(
1485 | ISO7816.SW_WRONG_DATA);
1486 | return;
1487 | }
1488 | tag = tlv.readByte();
1489 | tlv.end();
1490 | switch (tag) {
1491 | case PivSlot.P_DEFAULT:
1492 | if (key == (byte)0x9e) {
1493 | slot.pinPolicy =
1494 | PivSlot.P_NEVER;
1495 | } else if (key == (byte)0x9c) {
1496 | slot.pinPolicy =
1497 | PivSlot.P_ALWAYS;
1498 | } else {
1499 | slot.pinPolicy =
1500 | PivSlot.P_ONCE;
1501 | }
1502 | break;
1503 | case PivSlot.P_NEVER:
1504 | case PivSlot.P_ONCE:
1505 | case PivSlot.P_ALWAYS:
1506 | slot.pinPolicy = tag;
1507 | break;
1508 | default:
1509 | tlv.abort();
1510 | ISOException.throwIt(
1511 | ISO7816.
1512 | SW_FUNC_NOT_SUPPORTED);
1513 | return;
1514 | }
1515 | break;
1516 | case (byte)0xab:
1517 | if (tlv.tagLength() != 1) {
1518 | tlv.abort();
1519 | ISOException.throwIt(
1520 | ISO7816.SW_WRONG_DATA);
1521 | return;
1522 | }
1523 | final byte touchPolicy = tlv.readByte();
1524 | tlv.end();
1525 | if (touchPolicy != (byte)0x00 &&
1526 | touchPolicy != (byte)0x01) {
1527 | tlv.abort();
1528 | ISOException.throwIt(
1529 | ISO7816.
1530 | SW_FUNC_NOT_SUPPORTED);
1531 | return;
1532 | }
1533 | break;
1534 | default:
1535 | tlv.abort();
1536 | ISOException.throwIt(
1537 | ISO7816.SW_WRONG_DATA);
1538 | return;
1539 | }
1540 | }
1541 | break;
1542 | //#endif
1543 | //#if PIV_SUPPORT_EC
1544 | case PIV_ALG_ECCP256:
1545 | case PIV_ALG_ECCP384:
1546 | final ECPublicKey epubk =
1547 | (ECPublicKey)slot.asym.getPublic();
1548 | final ECPrivateKey eprivk =
1549 | (ECPrivateKey)slot.asym.getPrivate();
1550 | epubk.clearKey();
1551 | eprivk.clearKey();
1552 |
1553 | if (alg == PIV_ALG_ECCP256) {
1554 | ECParams.setCurveParametersP256(eprivk);
1555 | ECParams.setCurveParametersP256(epubk);
1556 | } else if (alg == PIV_ALG_ECCP384) {
1557 | ECParams.setCurveParametersP384(eprivk);
1558 | ECParams.setCurveParametersP384(epubk);
1559 | }
1560 |
1561 | while (!tlv.atEnd()) {
1562 | tag = tlv.readTag();
1563 | final short tlen = tlv.tagLength();
1564 | switch (tag) {
1565 | case (byte)0x06:
1566 | tlv.read(tempBuf, tlen);
1567 | eprivk.setS(tempBuf.data(),
1568 | tempBuf.rpos(), tlen);
1569 | tlv.end();
1570 | break;
1571 | case (byte)0xaa:
1572 | if (tlen != 1) {
1573 | tlv.abort();
1574 | ISOException.throwIt(
1575 | ISO7816.SW_WRONG_DATA);
1576 | return;
1577 | }
1578 | tag = tlv.readByte();
1579 | tlv.end();
1580 | switch (tag) {
1581 | case PivSlot.P_DEFAULT:
1582 | if (key == (byte)0x9e) {
1583 | slot.pinPolicy =
1584 | PivSlot.P_NEVER;
1585 | } else if (key == (byte)0x9c) {
1586 | slot.pinPolicy =
1587 | PivSlot.P_ALWAYS;
1588 | } else {
1589 | slot.pinPolicy =
1590 | PivSlot.P_ONCE;
1591 | }
1592 | break;
1593 | case PivSlot.P_NEVER:
1594 | case PivSlot.P_ONCE:
1595 | case PivSlot.P_ALWAYS:
1596 | slot.pinPolicy = tag;
1597 | break;
1598 | default:
1599 | tlv.abort();
1600 | ISOException.throwIt(
1601 | ISO7816.
1602 | SW_FUNC_NOT_SUPPORTED);
1603 | return;
1604 | }
1605 | break;
1606 | case (byte)0xab:
1607 | if (tlen != 1) {
1608 | tlv.abort();
1609 | ISOException.throwIt(
1610 | ISO7816.SW_WRONG_DATA);
1611 | return;
1612 | }
1613 | final byte touchPolicy = tlv.readByte();
1614 | tlv.end();
1615 | if (touchPolicy != (byte)0x00 &&
1616 | touchPolicy != (byte)0x01) {
1617 | tlv.abort();
1618 | ISOException.throwIt(
1619 | ISO7816.
1620 | SW_FUNC_NOT_SUPPORTED);
1621 | return;
1622 | }
1623 | break;
1624 | default:
1625 | tlv.abort();
1626 | ISOException.throwIt(
1627 | ISO7816.SW_WRONG_DATA);
1628 | return;
1629 | }
1630 | }
1631 | break;
1632 | //#endif
1633 | default:
1634 | tlv.abort();
1635 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
1636 | return;
1637 | }
1638 |
1639 | tlv.finish();
1640 |
1641 | if (!slot.asym.getPrivate().isInitialized()) {
1642 | slot.asym.getPrivate().clearKey();
1643 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
1644 | return;
1645 | }
1646 | }
1647 |
1648 | private void
1649 | processSetMgmtKey(APDU apdu)
1650 | {
1651 | final byte[] buffer = apdu.getBuffer();
1652 | short lc, len, off;
1653 |
1654 | if (buffer[ISO7816.OFFSET_P1] != (byte)0xFF ||
1655 | buffer[ISO7816.OFFSET_P2] != (byte)0xFF) {
1656 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
1657 | return;
1658 | }
1659 |
1660 | if (!slots[SLOT_9B].flags[PivSlot.F_UNLOCKED]) {
1661 | ISOException.throwIt(
1662 | ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
1663 | return;
1664 | }
1665 |
1666 | lc = apdu.setIncomingAndReceive();
1667 | //#if APPLET_EXTLEN
1668 | final short inLc = apdu.getIncomingLength();
1669 | /*#else
1670 | final short inLc = getIncomingLengthCompat(apdu);
1671 | #endif*/
1672 | if (lc != inLc) {
1673 | ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
1674 | return;
1675 | }
1676 | if (lc < 4) {
1677 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
1678 | return;
1679 | }
1680 |
1681 | //#if APPLET_EXTLEN
1682 | off = apdu.getOffsetCdata();
1683 | /*#else
1684 | off = ISO7816.OFFSET_CDATA;
1685 | #endif*/
1686 | final byte alg = buffer[off++];
1687 | final byte key = buffer[off++];
1688 | final byte keyLen = buffer[off++];
1689 | final short bitLen = (short)(keyLen << 3);
1690 |
1691 | final byte wantLen = (byte)(keyLen + 3);
1692 | if (lc < wantLen) {
1693 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
1694 | return;
1695 | }
1696 |
1697 | if (key != (byte)0x9b) {
1698 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
1699 | return;
1700 | }
1701 |
1702 | switch (alg) {
1703 | //#if PIV_SUPPORT_3DES
1704 | case PIV_ALG_3DES:
1705 | if (bitLen != KeyBuilder.LENGTH_DES3_3KEY) {
1706 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
1707 | return;
1708 | }
1709 | final DESKey dk;
1710 | if (slots[SLOT_9B].symAlg != alg) {
1711 | dk = (DESKey)KeyBuilder.buildKey(
1712 | KeyBuilder.TYPE_DES, bitLen, false);
1713 | slots[SLOT_9B].sym = dk;
1714 | } else {
1715 | dk = (DESKey)slots[SLOT_9B].sym;
1716 | }
1717 | slots[SLOT_9B].symAlg = alg;
1718 | dk.setKey(buffer, off);
1719 | mgmtKeyIsDefault = false;
1720 | break;
1721 | //#endif
1722 | //#if PIV_SUPPORT_AES
1723 | case PIV_ALG_AES128:
1724 | case PIV_ALG_AES192:
1725 | case PIV_ALG_AES256:
1726 | switch (alg) {
1727 | case PIV_ALG_AES128:
1728 | if (bitLen != KeyBuilder.LENGTH_AES_128) {
1729 | ISOException.throwIt(
1730 | ISO7816.SW_WRONG_DATA);
1731 | return;
1732 | }
1733 | break;
1734 | case PIV_ALG_AES192:
1735 | if (bitLen != KeyBuilder.LENGTH_AES_192) {
1736 | ISOException.throwIt(
1737 | ISO7816.SW_WRONG_DATA);
1738 | return;
1739 | }
1740 | break;
1741 | case PIV_ALG_AES256:
1742 | if (bitLen != KeyBuilder.LENGTH_AES_256) {
1743 | ISOException.throwIt(
1744 | ISO7816.SW_WRONG_DATA);
1745 | return;
1746 | }
1747 | break;
1748 | }
1749 |
1750 | final AESKey ak;
1751 | if (slots[SLOT_9B].symAlg != alg) {
1752 | ak = (AESKey)KeyBuilder.buildKey(
1753 | KeyBuilder.TYPE_AES, bitLen, false);
1754 | slots[SLOT_9B].sym = ak;
1755 | } else {
1756 | ak = (AESKey)slots[SLOT_9B].sym;
1757 | }
1758 | slots[SLOT_9B].symAlg = alg;
1759 | ak.setKey(buffer, off);
1760 | mgmtKeyIsDefault = false;
1761 | break;
1762 | //#endif
1763 | default:
1764 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
1765 | return;
1766 | }
1767 | }
1768 |
1769 | private void
1770 | processGenAuthSym(final APDU apdu, final PivSlot slot,
1771 | byte alg, final byte key, final Readable input,
1772 | byte wanted, byte tag, boolean hasChal, boolean hasWitness,
1773 | boolean hasResp)
1774 | {
1775 | final Cipher ci;
1776 | final short len;
1777 | short cLen;
1778 |
1779 | if (alg == PIV_ALG_DEFAULT)
1780 | alg = PIV_ALG_3DES;
1781 | if (slot.symAlg != alg || slot.sym == null) {
1782 | tlv.abort();
1783 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
1784 | return;
1785 | }
1786 | switch (alg) {
1787 | //#if PIV_SUPPORT_3DES
1788 | case PIV_ALG_3DES:
1789 | ci = tripleDes;
1790 | len = (short)8;
1791 | break;
1792 | //#endif
1793 | //#if PIV_SUPPORT_AES
1794 | case PIV_ALG_AES128:
1795 | case PIV_ALG_AES192:
1796 | case PIV_ALG_AES256:
1797 | ci = aes;
1798 | len = (short)16;
1799 | break;
1800 | //#endif
1801 | default:
1802 | tlv.abort();
1803 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
1804 | return;
1805 | }
1806 |
1807 | /*
1808 | * yubico-piv-tool likes to send general auth sometimes
1809 | * without any empty tag, while expecting a response.
1810 | * SP 800-73-4 doesn't seem 100% clear on whether this should
1811 | * work or not, but we'll special-case it.
1812 | */
1813 | if (wanted == (byte)0 && hasChal) {
1814 | wanted = GA_TAG_RESPONSE;
1815 | }
1816 |
1817 | if (hasWitness || hasResp) {
1818 | byte comp = -1;
1819 | if (hasWitness) {
1820 | tlv.rewind();
1821 | tlv.readTag(); /* The 0x7C outer tag */
1822 |
1823 | while (!tlv.atEnd()) {
1824 | tag = tlv.readTag();
1825 | if (tag == GA_TAG_WITNESS)
1826 | break;
1827 | tlv.skip();
1828 | }
1829 |
1830 | if (tlv.tagLength() == len && chalValid[0]) {
1831 | tlv.read(tempBuf, len);
1832 | comp = Util.arrayCompare(tempBuf.data(),
1833 | tempBuf.rpos(), challenge,
1834 | (short)0, len);
1835 | chalValid[0] = false;
1836 | tlv.end();
1837 | } else {
1838 | tlv.abort();
1839 | ISOException.throwIt(
1840 | ISO7816.SW_WRONG_DATA);
1841 | return;
1842 | }
1843 | }
1844 | if (hasResp && chalValid[0]) {
1845 | tlv.rewind();
1846 | tlv.readTag(); /* The 0x7C outer tag */
1847 |
1848 | while (!tlv.atEnd()) {
1849 | tag = tlv.readTag();
1850 | if (tag == GA_TAG_RESPONSE)
1851 | break;
1852 | tlv.skip();
1853 | }
1854 |
1855 | ci.init(slot.sym, Cipher.MODE_DECRYPT, iv,
1856 | (short)0, len);
1857 | tlv.read(tempBuf, len);
1858 | if (!bufmgr.alloc(len, outBuf)) {
1859 | ISOException.throwIt(
1860 | ISO7816.SW_FILE_FULL);
1861 | return;
1862 | }
1863 | cLen = ci.doFinal(tempBuf.data(), tempBuf.rpos(),
1864 | len, outBuf.data(), outBuf.wpos());
1865 | outBuf.write(cLen);
1866 |
1867 | if (tlv.tagLength() != 0) {
1868 | tlv.abort();
1869 | ISOException.throwIt(
1870 | ISO7816.SW_WRONG_DATA);
1871 | return;
1872 | }
1873 | comp = Util.arrayCompare(outBuf.data(),
1874 | outBuf.rpos(), challenge,
1875 | (short)0, cLen);
1876 | tlv.end();
1877 | chalValid[0] = false;
1878 | }
1879 |
1880 | if (comp == 0) {
1881 | slot.flags[PivSlot.F_UNLOCKED] = true;
1882 | } else {
1883 | tlv.abort();
1884 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
1885 | return;
1886 | }
1887 |
1888 | if (wanted == (byte)0) {
1889 | tlv.abort();
1890 | ISOException.throwIt(ISO7816.SW_NO_ERROR);
1891 | return;
1892 | }
1893 | }
1894 |
1895 | switch (wanted) {
1896 | case GA_TAG_CHALLENGE:
1897 | /*
1898 | * The host is asking us for a challenge value
1899 | * for them to encrypt and return in a RESPONSE
1900 | */
1901 | outgoing.reset();
1902 | wtlv.start(outgoing);
1903 | outgoingLe = apdu.setOutgoing();
1904 | wtlv.useApdu((short)0, outgoingLe);
1905 |
1906 | randData.generateData(challenge, (short)0, len);
1907 | chalValid[0] = true;
1908 | /*for (byte i = 0; i < (byte)len; ++i)
1909 | challenge[i] = (byte)(i + 1);*/
1910 |
1911 | wtlv.push((byte)0x7C);
1912 |
1913 | wtlv.push(GA_TAG_CHALLENGE);
1914 | wtlv.write(challenge, (short)0, len);
1915 | wtlv.pop();
1916 |
1917 | wtlv.pop();
1918 |
1919 | wtlv.end();
1920 | sendOutgoing(apdu);
1921 | break;
1922 |
1923 | case GA_TAG_WITNESS:
1924 | outgoing.reset();
1925 | wtlv.start(outgoing);
1926 | outgoingLe = apdu.setOutgoing();
1927 | wtlv.useApdu((short)0, outgoingLe);
1928 |
1929 | randData.generateData(challenge, (short)0, len);
1930 | chalValid[0] = true;
1931 | /*for (byte i = 0; i < (byte)len; ++i)
1932 | challenge[i] = (byte)(i + 1);*/
1933 |
1934 | wtlv.push((byte)0x7C);
1935 |
1936 | wtlv.push(GA_TAG_WITNESS);
1937 | ci.init(slot.sym, Cipher.MODE_ENCRYPT, iv,
1938 | (short)0, len);
1939 | wtlv.startReserve(len, tempBuf);
1940 | cLen = ci.doFinal(challenge, (short)0, len,
1941 | tempBuf.data(), tempBuf.wpos());
1942 | wtlv.endReserve(cLen);
1943 | wtlv.pop();
1944 |
1945 | wtlv.pop();
1946 | wtlv.end();
1947 | sendOutgoing(apdu);
1948 | break;
1949 |
1950 | case GA_TAG_RESPONSE:
1951 | if (!hasChal) {
1952 | tlv.abort();
1953 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
1954 | return;
1955 | }
1956 |
1957 | if (!slot.flags[PivSlot.F_UNLOCKED]) {
1958 | tlv.abort();
1959 | ISOException.throwIt(
1960 | ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
1961 | return;
1962 | }
1963 |
1964 | tlv.rewind();
1965 | tlv.readTag(); /* The 0x7C outer tag */
1966 |
1967 | while (!tlv.atEnd()) {
1968 | tag = tlv.readTag();
1969 | if (tag == GA_TAG_CHALLENGE)
1970 | break;
1971 | tlv.skip();
1972 | }
1973 |
1974 | final short sLen = tlv.tagLength();
1975 | tlv.read(tempBuf, sLen);
1976 | tlv.end();
1977 |
1978 | if (!bufmgr.alloc(sLen, outBuf)) {
1979 | ISOException.throwIt(ISO7816.SW_FILE_FULL);
1980 | return;
1981 | }
1982 | ci.init(slot.sym, Cipher.MODE_ENCRYPT,
1983 | iv, (short)0, len);
1984 | cLen = ci.doFinal(tempBuf.data(),
1985 | tempBuf.rpos(), sLen,
1986 | outBuf.data(), outBuf.wpos());
1987 | outBuf.write(cLen);
1988 |
1989 | outgoing.reset();
1990 | wtlv.start(outgoing);
1991 | outgoingLe = apdu.setOutgoing();
1992 | wtlv.useApdu((short)0, outgoingLe);
1993 |
1994 | wtlv.writeTagRealLen((byte)0x7c,
1995 | TlvWriter.sizeWithByteTag(cLen));
1996 | wtlv.writeTagRealLen(GA_TAG_RESPONSE, cLen);
1997 | wtlv.write(outBuf);
1998 |
1999 | wtlv.end();
2000 | sendOutgoing(apdu);
2001 | break;
2002 |
2003 | default:
2004 | tlv.abort();
2005 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
2006 | return;
2007 | }
2008 | }
2009 |
2010 | //#if PIV_SUPPORT_RSA
2011 | private void
2012 | processGenAuthRsa(final APDU apdu, final PivSlot slot,
2013 | byte alg, final byte key, final Readable input,
2014 | final byte wanted, final byte tag)
2015 | {
2016 | final Cipher ci = rsaPkcs1;
2017 | short cLen;
2018 | if (slot.asymAlg != alg || slot.asym == null) {
2019 | tlv.abort();
2020 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
2021 | return;
2022 | }
2023 | if (ci == null) {
2024 | tlv.abort();
2025 | ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
2026 | return;
2027 | }
2028 | final short sLen = tlv.tagLength();
2029 | cLen = sLen;
2030 | switch (alg) {
2031 | case PIV_ALG_RSA1024:
2032 | cLen = (short)128;
2033 | break;
2034 | case PIV_ALG_RSA2048:
2035 | cLen = (short)256;
2036 | break;
2037 | }
2038 | if (!bufmgr.alloc(cLen, outBuf)) {
2039 | ISOException.throwIt(ISO7816.SW_FILE_FULL);
2040 | return;
2041 | }
2042 | tlv.read(tempBuf, sLen);
2043 | tlv.end();
2044 | ci.init(slot.asym.getPrivate(), Cipher.MODE_ENCRYPT);
2045 | cLen = ci.doFinal(tempBuf.data(), tempBuf.rpos(), sLen,
2046 | outBuf.data(), outBuf.wpos());
2047 | outBuf.write(cLen);
2048 |
2049 | incoming.resetAndFree();
2050 |
2051 | outgoing.reset();
2052 | wtlv.start(outgoing);
2053 | outgoingLe = apdu.setOutgoing();
2054 | wtlv.useApdu((short)0, outgoingLe);
2055 |
2056 | wtlv.writeTagRealLen((byte)0x7c,
2057 | TlvWriter.sizeWithByteTag(cLen));
2058 | wtlv.writeTagRealLen(GA_TAG_RESPONSE, cLen);
2059 | wtlv.write(outBuf);
2060 |
2061 | wtlv.end();
2062 | sendOutgoing(apdu);
2063 | }
2064 | //#endif
2065 |
2066 | //#if PIV_SUPPORT_EC
2067 | private void
2068 | processGenAuthEcPlain(final APDU apdu, final PivSlot slot,
2069 | byte alg, final byte key, final Readable input,
2070 | final byte wanted, final byte tag)
2071 | {
2072 | short cLen;
2073 |
2074 | if (slot.asymAlg != alg || slot.asym == null) {
2075 | tlv.abort();
2076 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
2077 | return;
2078 | }
2079 |
2080 | /* Are they asking for ECDH? */
2081 | if (tag == GA_TAG_EXP) {
2082 | final KeyAgreement ag = ecdh;
2083 | if (ag == null) {
2084 | tlv.abort();
2085 | ISOException.throwIt(
2086 | ISO7816.SW_FUNC_NOT_SUPPORTED);
2087 | return;
2088 | }
2089 | processGenAuthEcdh(apdu, slot, ag);
2090 | return;
2091 | }
2092 |
2093 | /* Otherwise they must be asking for a signature. */
2094 | if (tag != GA_TAG_CHALLENGE) {
2095 | tlv.abort();
2096 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
2097 | return;
2098 | }
2099 |
2100 | final short sLen = tlv.tagLength();
2101 | cLen = sLen;
2102 | switch (alg) {
2103 | case PIV_ALG_ECCP256:
2104 | cLen = (short)75;
2105 | break;
2106 | case PIV_ALG_ECCP384:
2107 | cLen = (short)107;
2108 | break;
2109 | }
2110 |
2111 | final Signature si;
2112 | switch (sLen) {
2113 | case 20:
2114 | si = ecdsaSha;
2115 | break;
2116 | case 32:
2117 | si = ecdsaSha256;
2118 | break;
2119 | case 48:
2120 | si = ecdsaSha384;
2121 | break;
2122 | default:
2123 | tlv.abort();
2124 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
2125 | return;
2126 | }
2127 |
2128 | tlv.read(tempBuf, sLen);
2129 | tlv.end();
2130 |
2131 | if (!bufmgr.alloc(cLen, outBuf)) {
2132 | ISOException.throwIt(ISO7816.SW_FILE_FULL);
2133 | return;
2134 | }
2135 |
2136 | si.init(slot.asym.getPrivate(), Signature.MODE_SIGN);
2137 | cLen = si.signPreComputedHash(tempBuf.data(),
2138 | tempBuf.rpos(), sLen,
2139 | outBuf.data(), outBuf.wpos());
2140 | outBuf.write(cLen);
2141 |
2142 | outgoing.reset();
2143 | wtlv.start(outgoing);
2144 | outgoingLe = apdu.setOutgoing();
2145 | wtlv.useApdu((short)0, outgoingLe);
2146 |
2147 | wtlv.writeTagRealLen((byte)0x7c,
2148 | TlvWriter.sizeWithByteTag(cLen));
2149 | wtlv.writeTagRealLen(GA_TAG_RESPONSE, cLen);
2150 | wtlv.write(outBuf);
2151 |
2152 | wtlv.end();
2153 | sendOutgoing(apdu);
2154 | }
2155 |
2156 | private void
2157 | processGenAuthEcdh(final APDU apdu, final PivSlot slot,
2158 | final KeyAgreement ag)
2159 | {
2160 | final ECPublicKey pubK =
2161 | (ECPublicKey)slot.asym.getPublic();
2162 | final short eclen = (short)(pubK.getSize() / 4);
2163 | short cLen;
2164 |
2165 | if (!bufmgr.alloc((short)(eclen + 1), outBuf)) {
2166 | ISOException.throwIt(ISO7816.SW_FILE_FULL);
2167 | return;
2168 | }
2169 |
2170 | cLen = tlv.read(tempBuf, tlv.tagLength());
2171 | tlv.end();
2172 |
2173 | ag.init(slot.asym.getPrivate());
2174 | cLen = ag.generateSecret(tempBuf.data(), tempBuf.rpos(), cLen,
2175 | outBuf.data(), outBuf.wpos());
2176 | outBuf.write(cLen);
2177 |
2178 | incoming.reset();
2179 | outgoing.reset();
2180 | wtlv.start(outgoing);
2181 | outgoingLe = apdu.setOutgoing();
2182 | wtlv.useApdu((short)0, outgoingLe);
2183 |
2184 | wtlv.writeTagRealLen((byte)0x7c,
2185 | TlvWriter.sizeWithByteTag(cLen));
2186 | wtlv.writeTagRealLen(GA_TAG_RESPONSE, cLen);
2187 | wtlv.write(outBuf);
2188 |
2189 | wtlv.end();
2190 | sendOutgoing(apdu);
2191 | }
2192 | //#endif
2193 |
2194 | private void
2195 | processGeneralAuth(final APDU apdu)
2196 | {
2197 | final byte[] buffer = apdu.getBuffer();
2198 | final byte key;
2199 | byte alg, tag = 0, wanted = 0;
2200 | final PivSlot slot;
2201 | final Readable input;
2202 | boolean hasWitness = false, hasResp = false, hasChal = false;
2203 |
2204 | alg = buffer[ISO7816.OFFSET_P1];
2205 | key = buffer[ISO7816.OFFSET_P2];
2206 |
2207 | if (key >= (byte)0x9A && key <= (byte)0x9E) {
2208 | final byte idx = (byte)(key - (byte)0x9A);
2209 | slot = slots[idx];
2210 | } else if (key >= MIN_HIST_SLOT && key <= MAX_HIST_SLOT) {
2211 | final byte idx = (byte)(SLOT_MIN_HIST +
2212 | (byte)(key - MIN_HIST_SLOT));
2213 | slot = slots[idx];
2214 | } else {
2215 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
2216 | return;
2217 | }
2218 |
2219 | if (slot == null) {
2220 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
2221 | return;
2222 | }
2223 |
2224 | if (slot.pinPolicy != PivSlot.P_NEVER &&
2225 | !slot.flags[PivSlot.F_UNLOCKED]) {
2226 | ISOException.throwIt(
2227 | ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
2228 | return;
2229 | }
2230 |
2231 | if (!isContact() && slot.cert != null &&
2232 | slot.cert.contactless == File.P_NEVER) {
2233 | ISOException.throwIt(
2234 | ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
2235 | return;
2236 | }
2237 |
2238 | input = receiveChain(apdu);
2239 | if (input == null)
2240 | return;
2241 |
2242 | tlv.start(input);
2243 |
2244 | if (tlv.readTag() != (byte)0x7C) {
2245 | tlv.abort();
2246 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
2247 | return;
2248 | }
2249 |
2250 | /*
2251 | * First, scan through the TLVs to figure out what the host
2252 | * actually wants from us, and mark which fields they gave us
2253 | * data for.
2254 | */
2255 | while (!tlv.atEnd()) {
2256 | tag = tlv.readTag();
2257 | if (tlv.tagLength() == 0) {
2258 | /* There can only be one empty tag. */
2259 | if (wanted != 0) {
2260 | tlv.abort();
2261 | ISOException.throwIt(
2262 | ISO7816.SW_WRONG_DATA);
2263 | return;
2264 | }
2265 | switch (tag) {
2266 | case GA_TAG_WITNESS:
2267 | case GA_TAG_RESPONSE:
2268 | case GA_TAG_CHALLENGE:
2269 | break;
2270 | default:
2271 | tlv.abort();
2272 | ISOException.throwIt(
2273 | ISO7816.SW_WRONG_DATA);
2274 | return;
2275 | }
2276 | wanted = tag;
2277 | } else {
2278 | switch (tag) {
2279 | case GA_TAG_WITNESS:
2280 | hasWitness = true;
2281 | break;
2282 | case GA_TAG_RESPONSE:
2283 | hasResp = true;
2284 | break;
2285 | case GA_TAG_CHALLENGE:
2286 | case GA_TAG_EXP:
2287 | hasChal = true;
2288 | break;
2289 | default:
2290 | tlv.abort();
2291 | ISOException.throwIt(
2292 | ISO7816.SW_WRONG_DATA);
2293 | return;
2294 | }
2295 | }
2296 | tlv.skip();
2297 | }
2298 |
2299 | switch (alg) {
2300 | case PIV_ALG_DEFAULT:
2301 | case PIV_ALG_3DES:
2302 | case PIV_ALG_AES128:
2303 | case PIV_ALG_AES192:
2304 | case PIV_ALG_AES256:
2305 | processGenAuthSym(apdu, slot, alg, key, input,
2306 | wanted, tag, hasChal, hasWitness, hasResp);
2307 | return;
2308 | }
2309 |
2310 | /*
2311 | * All asymmetric algos are only allowed a challenge
2312 | * and response.
2313 | */
2314 | if (!(hasChal && !hasResp && !hasWitness) ||
2315 | wanted != GA_TAG_RESPONSE) {
2316 | tlv.abort();
2317 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
2318 | return;
2319 | }
2320 |
2321 | /* Read through to the first non-wanted tag. */
2322 | tlv.rewind();
2323 | tlv.readTag(); /* Outer 0x7C tag */
2324 | while (!tlv.atEnd()) {
2325 | tag = tlv.readTag();
2326 | if (tag == wanted) {
2327 | tlv.skip();
2328 | continue;
2329 | }
2330 | break;
2331 | }
2332 |
2333 | switch (alg) {
2334 | //#if PIV_SUPPORT_RSA
2335 | case PIV_ALG_RSA1024:
2336 | case PIV_ALG_RSA2048:
2337 | processGenAuthRsa(apdu, slot, alg, key, input,
2338 | wanted, tag);
2339 | break;
2340 | //#endif
2341 | //#if PIV_SUPPORT_EC
2342 | case PIV_ALG_ECCP256:
2343 | case PIV_ALG_ECCP384:
2344 | processGenAuthEcPlain(apdu, slot, alg, key, input,
2345 | wanted, tag);
2346 | break;
2347 | //#endif
2348 | default:
2349 | tlv.abort();
2350 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
2351 | return;
2352 | }
2353 | }
2354 |
2355 | private void
2356 | processVerify(APDU apdu)
2357 | {
2358 | final byte[] buffer = apdu.getBuffer();
2359 | short lc, pinOff, idx;
2360 | final OwnerPIN pin;
2361 | short sw = (short)0;
2362 |
2363 | if (buffer[ISO7816.OFFSET_P1] != (byte)0x00 &&
2364 | buffer[ISO7816.OFFSET_P1] != (byte)0xFF) {
2365 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
2366 | return;
2367 | }
2368 |
2369 | switch (buffer[ISO7816.OFFSET_P2]) {
2370 | case (byte)0x80:
2371 | pin = pivPin;
2372 | break;
2373 | default:
2374 | ISOException.throwIt((short)0x6A88);
2375 | return;
2376 | }
2377 |
2378 | lc = apdu.setIncomingAndReceive();
2379 | //#if APPLET_EXTLEN
2380 | final short inLc = apdu.getIncomingLength();
2381 | /*#else
2382 | final short inLc = getIncomingLengthCompat(apdu);
2383 | #endif*/
2384 | if (lc != inLc) {
2385 | ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
2386 | return;
2387 | }
2388 | //#if APPLET_EXTLEN
2389 | pinOff = apdu.getOffsetCdata();
2390 | /*#else
2391 | pinOff = ISO7816.OFFSET_CDATA;
2392 | #endif*/
2393 |
2394 | /*
2395 | * According to the PIV spec, if the PIN is blocked we should
2396 | * return 0x6983 here (SW_FILE_INVALID).
2397 | */
2398 | if (pin.getTriesRemaining() == 0) {
2399 | ISOException.throwIt(ISO7816.SW_FILE_INVALID);
2400 | return;
2401 | }
2402 |
2403 | /* P1 = FF means "set security status to false" */
2404 | if (buffer[ISO7816.OFFSET_P1] == (byte)0xFF) {
2405 | if (pin.isValidated())
2406 | pin.reset();
2407 | syncSecurityStatus();
2408 | ISOException.throwIt(ISO7816.SW_NO_ERROR);
2409 | return;
2410 | }
2411 |
2412 | if (lc == 0) {
2413 | /* P1 = 00 with Lc = 00 and no data: is PIN cached? */
2414 | if (pin.isValidated()) {
2415 | ISOException.throwIt(ISO7816.SW_NO_ERROR);
2416 | return;
2417 |
2418 | } else {
2419 | ISOException.throwIt((short)(
2420 | (short)0x63C0 | pin.getTriesRemaining()));
2421 | return;
2422 | }
2423 | }
2424 |
2425 | if (lc != 8) {
2426 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
2427 | return;
2428 | }
2429 |
2430 | if (!pin.check(buffer, pinOff, (byte)8)) {
2431 | eraseKeysIfPukPinBlocked();
2432 | syncSecurityStatus();
2433 | ISOException.throwIt((short)(
2434 | (short)0x63C0 | pin.getTriesRemaining()));
2435 | return;
2436 | }
2437 |
2438 | syncSecurityStatus();
2439 | }
2440 |
2441 | private void
2442 | processChangePin(APDU apdu)
2443 | {
2444 | final byte[] buffer = apdu.getBuffer();
2445 | short lc, oldPinOff, newPinOff, idx;
2446 | final OwnerPIN pin;
2447 |
2448 | if (buffer[ISO7816.OFFSET_P1] != (byte)0x00) {
2449 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
2450 | return;
2451 | }
2452 |
2453 | switch (buffer[ISO7816.OFFSET_P2]) {
2454 | case (byte)0x80:
2455 | pin = pivPin;
2456 | break;
2457 | case (byte)0x81:
2458 | pin = pukPin;
2459 | break;
2460 | default:
2461 | ISOException.throwIt((short)0x6A88);
2462 | return;
2463 | }
2464 |
2465 | lc = apdu.setIncomingAndReceive();
2466 | //#if APPLET_EXTLEN
2467 | final short inLc = apdu.getIncomingLength();
2468 | /*#else
2469 | final short inLc = getIncomingLengthCompat(apdu);
2470 | #endif*/
2471 | if (lc != inLc) {
2472 | ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
2473 | return;
2474 | }
2475 |
2476 | //#if APPLET_EXTLEN
2477 | oldPinOff = apdu.getOffsetCdata();
2478 | /*#else
2479 | oldPinOff = ISO7816.OFFSET_CDATA;
2480 | #endif*/
2481 | if (lc != 16) {
2482 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
2483 | return;
2484 | }
2485 |
2486 | /*
2487 | * According to the PIV spec, if the PIN is blocked we should
2488 | * return 0x6983 here (SW_FILE_INVALID).
2489 | */
2490 | if (pin.getTriesRemaining() == 0) {
2491 | ISOException.throwIt(ISO7816.SW_FILE_INVALID);
2492 | return;
2493 | }
2494 |
2495 | if (!pin.check(buffer, oldPinOff, (byte)8)) {
2496 | eraseKeysIfPukPinBlocked();
2497 | ISOException.throwIt((short)(
2498 | (short)0x63C0 | pin.getTriesRemaining()));
2499 | return;
2500 | }
2501 |
2502 | newPinOff = (short)(oldPinOff + 8);
2503 | for (idx = newPinOff; idx < (short)(newPinOff + 6); ++idx) {
2504 | if (buffer[idx] < (byte)0x30 ||
2505 | buffer[idx] > (byte)0x39) {
2506 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
2507 | return;
2508 | }
2509 | }
2510 | for (; idx < (short)(newPinOff + 8); ++idx) {
2511 | if (buffer[idx] != (byte)0xFF && (
2512 | buffer[idx] < (byte)0x30 ||
2513 | buffer[idx] > (byte)0x39)) {
2514 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
2515 | return;
2516 | }
2517 | }
2518 | pin.update(buffer, newPinOff, (byte)8);
2519 | if (pin == pivPin)
2520 | pivPinIsDefault = false;
2521 | if (pin == pukPin)
2522 | pukPinIsDefault = false;
2523 | }
2524 |
2525 | private void
2526 | processResetPin(APDU apdu)
2527 | {
2528 | final byte[] buffer = apdu.getBuffer();
2529 | short lc, pukOff, newPinOff, idx;
2530 | final OwnerPIN pin;
2531 |
2532 | if (buffer[ISO7816.OFFSET_P1] != (byte)0x00) {
2533 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
2534 | return;
2535 | }
2536 |
2537 | switch (buffer[ISO7816.OFFSET_P2]) {
2538 | case (byte)0x80:
2539 | pin = pivPin;
2540 | break;
2541 | default:
2542 | /*
2543 | * 800-73-4 part 2 3.2.3
2544 | */
2545 | ISOException.throwIt((short)0x6A88);
2546 | return;
2547 | }
2548 |
2549 | lc = apdu.setIncomingAndReceive();
2550 | //#if APPLET_EXTLEN
2551 | final short inLc = apdu.getIncomingLength();
2552 | /*#else
2553 | final short inLc = getIncomingLengthCompat(apdu);
2554 | #endif*/
2555 | if (lc != inLc) {
2556 | ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
2557 | return;
2558 | }
2559 |
2560 | //#if APPLET_EXTLEN
2561 | pukOff = apdu.getOffsetCdata();
2562 | /*#else
2563 | pukOff = ISO7816.OFFSET_CDATA;
2564 | #endif*/
2565 | if (lc != 16) {
2566 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
2567 | return;
2568 | }
2569 |
2570 | /*
2571 | * According to the PIV spec, if the PUK is blocked we should
2572 | * return 0x6983 here (SW_FILE_INVALID).
2573 | */
2574 | if (pukPin.getTriesRemaining() == 0) {
2575 | ISOException.throwIt(ISO7816.SW_FILE_INVALID);
2576 | return;
2577 | }
2578 |
2579 | if (!pukPin.check(buffer, pukOff, (byte)8)) {
2580 | eraseKeysIfPukPinBlocked();
2581 | ISOException.throwIt((short)(
2582 | (short)0x63C0 | pukPin.getTriesRemaining()));
2583 | return;
2584 | }
2585 |
2586 | newPinOff = (short)(pukOff + 8);
2587 | for (idx = newPinOff; idx < (short)(newPinOff + 6); ++idx) {
2588 | if (buffer[idx] < (byte)0x30 ||
2589 | buffer[idx] > (byte)0x39) {
2590 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
2591 | return;
2592 | }
2593 | }
2594 | for (; idx < (short)(newPinOff + 8); ++idx) {
2595 | if (buffer[idx] != (byte)0xFF && (
2596 | buffer[idx] < (byte)0x30 ||
2597 | buffer[idx] > (byte)0x39)) {
2598 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
2599 | return;
2600 | }
2601 | }
2602 | pin.update(buffer, newPinOff, (byte)8);
2603 | pin.resetAndUnblock();
2604 | }
2605 |
2606 | private void
2607 | processSetPinRetries(APDU apdu)
2608 | {
2609 | final byte[] buffer = apdu.getBuffer();
2610 | final byte pinTries = buffer[ISO7816.OFFSET_P1];
2611 | final byte pukTries = buffer[ISO7816.OFFSET_P2];
2612 |
2613 | if (!slots[SLOT_9B].flags[PivSlot.F_UNLOCKED]) {
2614 | ISOException.throwIt(
2615 | ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
2616 | return;
2617 | }
2618 |
2619 | if (!pivPin.isValidated()) {
2620 | ISOException.throwIt(
2621 | ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
2622 | return;
2623 | }
2624 |
2625 | pivPin = new OwnerPIN(pinTries, (byte)8);
2626 | pivPin.update(DEFAULT_PIN, (short)0, (byte)8);
2627 | pivPinIsDefault = true;
2628 | pinRetries = pinTries;
2629 | pukPin = new OwnerPIN(pukTries, (byte)8);
2630 | pukPin.update(DEFAULT_PUK, (short)0, (byte)8);
2631 | pukPinIsDefault = true;
2632 | pukRetries = pukTries;
2633 | }
2634 |
2635 | private void
2636 | processReset(APDU apdu)
2637 | {
2638 | byte idx;
2639 |
2640 | if (pivPin.getTriesRemaining() > (byte)0 ||
2641 | pukPin.getTriesRemaining() > (byte)0) {
2642 | ISOException.throwIt(
2643 | ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
2644 | return;
2645 | }
2646 |
2647 | for (idx = (byte)0; idx < MAX_SLOTS; ++idx) {
2648 | final PivSlot slot = slots[idx];
2649 | if (slot == null)
2650 | continue;
2651 | if (slot.asym != null)
2652 | slot.asym.getPrivate().clearKey();
2653 | slot.asymAlg = (byte)-1;
2654 | slot.imported = false;
2655 | if (slot.cert != null) {
2656 | slot.cert.len = (short)0;
2657 | }
2658 | }
2659 |
2660 | for (idx = (byte)0; idx < TAG_MAX; ++idx) {
2661 | final File file = files[idx];
2662 | if (file == null)
2663 | continue;
2664 | file.len = (short)0;
2665 | }
2666 |
2667 | //#if PIV_SUPPORT_3DES
2668 | final DESKey dk = (DESKey)slots[SLOT_9B].sym;
2669 | dk.setKey(DEFAULT_ADMIN_KEY, (short)0);
2670 | /*#else
2671 | final AESKey ak = (AESKey)slots[SLOT_9B].sym;
2672 | ak.setKey(DEFAULT_ADMIN_KEY, (short)0);
2673 | #endif*/
2674 | mgmtKeyIsDefault = true;
2675 |
2676 | pinRetries = (byte)5;
2677 | pivPin = new OwnerPIN(pinRetries, (byte)8);
2678 | pivPin.update(DEFAULT_PIN, (short)0, (byte)8);
2679 | pivPinIsDefault = true;
2680 | pukRetries = (byte)3;
2681 | pukPin = new OwnerPIN(pukRetries, (byte)8);
2682 | pukPin.update(DEFAULT_PUK, (short)0, (byte)8);
2683 | pukPinIsDefault = true;
2684 |
2685 | randData.generateData(guid, (short)0, (short)16);
2686 |
2687 | randData.generateData(cardId, (short)CARD_ID_FIXED.length,
2688 | (short)(21 - (short)CARD_ID_FIXED.length));
2689 |
2690 | /*
2691 | * These can be changed during generate or import, so reset
2692 | * them to defaults.
2693 | */
2694 | for (idx = 0; idx < MAX_SLOTS; ++idx) {
2695 | slots[idx].pinPolicy = PivSlot.P_ONCE;
2696 | }
2697 | slots[SLOT_9C].pinPolicy = PivSlot.P_ALWAYS;
2698 | slots[SLOT_9E].pinPolicy = PivSlot.P_NEVER;
2699 | slots[SLOT_9B].pinPolicy = PivSlot.P_NEVER;
2700 |
2701 | initCARDCAP();
2702 | initCHUID();
2703 | initKEYHIST();
2704 | //#if YKPIV_ATTESTATION
2705 | initAttestation();
2706 | //#endif
2707 |
2708 | try {
2709 | JCSystem.requestObjectDeletion();
2710 | } catch (Exception e) {
2711 | bufmgr.gcBlewUp = true;
2712 | }
2713 | }
2714 |
2715 | private void
2716 | processPutData(APDU apdu)
2717 | {
2718 | final byte[] buffer = apdu.getBuffer();
2719 | short lc;
2720 | byte tag;
2721 | PivSlot slot;
2722 | final Readable input;
2723 |
2724 | if (buffer[ISO7816.OFFSET_P1] != (byte)0x3F ||
2725 | buffer[ISO7816.OFFSET_P2] != (byte)0xFF) {
2726 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
2727 | return;
2728 | }
2729 |
2730 | input = receiveChain(apdu);
2731 | if (input == null)
2732 | return;
2733 | tlv.start(input);
2734 |
2735 | if (tlv.readTag() != (byte)0x5C) {
2736 | tlv.abort();
2737 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
2738 | return;
2739 | }
2740 |
2741 | if (!slots[SLOT_9B].flags[PivSlot.F_UNLOCKED]) {
2742 | tlv.abort();
2743 | ISOException.throwIt(
2744 | ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
2745 | return;
2746 | }
2747 |
2748 | final short taglen = tlv.tagLength();
2749 |
2750 | if (taglen == (short)3) {
2751 | final byte tag0 = tlv.readByte();
2752 | final byte tag1 = tlv.readByte();
2753 | final byte tag2 = tlv.readByte();
2754 | final File file;
2755 |
2756 | if (tag0 != (short)0x5F) {
2757 | ISOException.throwIt(
2758 | ISO7816.SW_FUNC_NOT_SUPPORTED);
2759 | return;
2760 | }
2761 |
2762 | if (tag1 == (byte)0xFF) {
2763 | if (tag2 < 0 || tag2 > YK_TAG_MAX) {
2764 | file = null;
2765 | } else {
2766 | if (ykFiles[tag2] == null)
2767 | ykFiles[tag2] = new File();
2768 | file = ykFiles[tag2];
2769 | }
2770 | } else if (tag1 == (byte)0xC1) {
2771 | if (tag2 < 0 || tag2 > TAG_MAX) {
2772 | file = null;
2773 | } else {
2774 | if (files[tag2] == null)
2775 | files[tag2] = new File();
2776 | file = files[tag2];
2777 | }
2778 | } else {
2779 | file = null;
2780 | }
2781 | tlv.end();
2782 |
2783 | if (file == null) {
2784 | ISOException.throwIt(
2785 | ISO7816.SW_FUNC_NOT_SUPPORTED);
2786 | return;
2787 | }
2788 |
2789 | if (tlv.readTag() != (byte)0x53) {
2790 | tlv.abort();
2791 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
2792 | return;
2793 | }
2794 |
2795 | boolean needGC = false;
2796 | final short len = tlv.tagLength();
2797 | if (file.data == null)
2798 | file.data = new byte[len];
2799 | if (file.data.length < len) {
2800 | file.data = new byte[len];
2801 | needGC = true;
2802 | }
2803 | file.len = tlv.read(file.data, (short)0, len);
2804 | tlv.end();
2805 | tlv.finish();
2806 |
2807 | if (needGC && !bufmgr.gcBlewUp) {
2808 | try {
2809 | JCSystem.requestObjectDeletion();
2810 | } catch (Exception e) {
2811 | bufmgr.gcBlewUp = true;
2812 | }
2813 | }
2814 | bufmgr.cullNonTransient();
2815 |
2816 | } else {
2817 | tlv.abort();
2818 | ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED);
2819 | }
2820 | }
2821 |
2822 | private void
2823 | processGetData(APDU apdu)
2824 | {
2825 | final byte[] buffer = apdu.getBuffer();
2826 | short lc;
2827 | byte tag;
2828 |
2829 | if (buffer[ISO7816.OFFSET_P1] != (byte)0x3F ||
2830 | buffer[ISO7816.OFFSET_P2] != (byte)0xFF) {
2831 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
2832 | return;
2833 | }
2834 |
2835 | lc = apdu.setIncomingAndReceive();
2836 | //#if APPLET_EXTLEN
2837 | final short inLc = apdu.getIncomingLength();
2838 | /*#else
2839 | final short inLc = getIncomingLengthCompat(apdu);
2840 | #endif*/
2841 | if (lc != inLc) {
2842 | ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
2843 | return;
2844 | }
2845 |
2846 | //#if APPLET_EXTLEN
2847 | apduStream.reset(apdu.getOffsetCdata(), lc);
2848 | /*#else
2849 | apduStream.reset(ISO7816.OFFSET_CDATA, lc);
2850 | #endif*/
2851 | tlv.start(apduStream);
2852 |
2853 | tag = tlv.readTag();
2854 | if (tag != (byte)0x5C) {
2855 | tlv.abort();
2856 | ISOException.throwIt(ISO7816.SW_WRONG_DATA);
2857 | return;
2858 | }
2859 |
2860 | final short taglen = tlv.tagLength();
2861 |
2862 | if (taglen == (short)3) {
2863 | final byte tag0 = tlv.readByte();
2864 | final byte tag1 = tlv.readByte();
2865 | final byte tag2 = tlv.readByte();
2866 | final File file;
2867 |
2868 | if (tag0 != (short)0x5F) {
2869 | ISOException.throwIt(
2870 | ISO7816.SW_FILE_NOT_FOUND);
2871 | return;
2872 | }
2873 |
2874 | if (tag1 == (byte)0xFF) {
2875 | if (tag2 < 0 || tag2 > YK_TAG_MAX) {
2876 | file = null;
2877 | } else {
2878 | file = ykFiles[tag2];
2879 | }
2880 | } else if (tag1 == (byte)0xC1) {
2881 | if (tag2 < 0 || tag2 > TAG_MAX) {
2882 | file = null;
2883 | } else {
2884 | file = files[tag2];
2885 | }
2886 | } else {
2887 | file = null;
2888 | }
2889 | tlv.end();
2890 | tlv.finish();
2891 |
2892 | if (file == null) {
2893 | ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
2894 | return;
2895 | }
2896 |
2897 | final byte policy;
2898 | if (isContact())
2899 | policy = file.contact;
2900 | else
2901 | policy = file.contactless;
2902 |
2903 | if (policy == File.P_NEVER) {
2904 | ISOException.throwIt(
2905 | ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
2906 | return;
2907 | }
2908 |
2909 | if (policy == File.P_PIN && !pivPin.isValidated()) {
2910 | ISOException.throwIt(
2911 | ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
2912 | return;
2913 | }
2914 |
2915 | if (file.data == null || file.len == 0) {
2916 | ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
2917 | return;
2918 | }
2919 |
2920 | outgoing.reset();
2921 | wtlv.start(outgoing);
2922 | outgoingLe = apdu.setOutgoing();
2923 | wtlv.useApdu((short)0, outgoingLe);
2924 | wtlv.writeTagRealLen((byte)0x53, file.len);
2925 | wtlv.write(file.data, (short)0, file.len);
2926 | wtlv.end();
2927 | sendOutgoing(apdu);
2928 |
2929 | } else if (taglen == (short)1 &&
2930 | tlv.readByte() == (byte)0x7E) {
2931 | tlv.end();
2932 | tlv.finish();
2933 | /* The special discovery object */
2934 | sendDiscoveryObject(apdu);
2935 |
2936 | } else {
2937 | tlv.abort();
2938 | ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
2939 | }
2940 | }
2941 |
2942 | private void
2943 | sendDiscoveryObject(APDU apdu)
2944 | {
2945 | outgoing.reset();
2946 | wtlv.start(outgoing);
2947 | outgoingLe = apdu.setOutgoing();
2948 | wtlv.useApdu((short)0, outgoingLe);
2949 |
2950 | wtlv.push((byte)0x7E);
2951 |
2952 | /* AID */
2953 | wtlv.writeTagRealLen((byte)0x4F, (short)PIV_AID.length);
2954 | wtlv.write(PIV_AID, (short)0, (short)PIV_AID.length);
2955 |
2956 | /* PIN policy */
2957 | wtlv.writeTagRealLen((short)0x5F2F, (short)2);
2958 | wtlv.writeByte((byte)0x40); /* PIV pin only, no others */
2959 | wtlv.writeByte((byte)0x00); /* RFU, since no global PIN */
2960 |
2961 | wtlv.pop();
2962 | wtlv.end();
2963 | sendOutgoing(apdu);
2964 | }
2965 |
2966 | private void
2967 | syncSecurityStatus()
2968 | {
2969 | short idx;
2970 |
2971 | for (idx = (short)0; idx < MAX_SLOTS; ++idx) {
2972 | final PivSlot slot = slots[idx];
2973 | if (slot == null)
2974 | continue;
2975 | if (idx == SLOT_9B)
2976 | continue;
2977 | slot.flags[PivSlot.F_UNLOCKED] = pivPin.isValidated();
2978 | slot.flags[PivSlot.F_AFTER_VERIFY] = false;
2979 | }
2980 | }
2981 |
2982 | private void
2983 | eraseKeysIfPukPinBlocked()
2984 | {
2985 | short idx;
2986 |
2987 | if (pivPin.isValidated() || pukPin.isValidated())
2988 | return;
2989 | if (pivPin.getTriesRemaining() > (byte)0)
2990 | return;
2991 | if (pukPin.getTriesRemaining() > (byte)0)
2992 | return;
2993 |
2994 | for (idx = (short)0; idx < MAX_SLOTS; ++idx) {
2995 | final PivSlot slot = slots[idx];
2996 | if (slot == null)
2997 | continue;
2998 | if (slot.asym == null)
2999 | continue;
3000 | slot.asym.getPrivate().clearKey();
3001 | }
3002 | }
3003 |
3004 | private void
3005 | initCARDCAP()
3006 | {
3007 | outgoing.reset();
3008 | wtlv.start(outgoing);
3009 |
3010 | /* Card Identifier */
3011 | wtlv.push((byte)0xF0);
3012 | wtlv.write(cardId, (short)0, (short)cardId.length);
3013 | wtlv.pop();
3014 |
3015 | /* Container version number */
3016 | wtlv.push((byte)0xF1);
3017 | wtlv.writeByte((byte)0x21);
3018 | wtlv.pop();
3019 | wtlv.push((byte)0xF2);
3020 | wtlv.writeByte((byte)0x21);
3021 | wtlv.pop();
3022 |
3023 | wtlv.push((byte)0xF3);
3024 | wtlv.pop();
3025 |
3026 | wtlv.push((byte)0xF4);
3027 | wtlv.pop();
3028 |
3029 | /* Data Model Number */
3030 | wtlv.push((byte)0xF5);
3031 | wtlv.writeByte((byte)0x10);
3032 | wtlv.pop();
3033 |
3034 | wtlv.push((byte)0xF6);
3035 | wtlv.pop();
3036 |
3037 | wtlv.push((byte)0xF7);
3038 | wtlv.pop();
3039 |
3040 | wtlv.push((byte)0xFA);
3041 | wtlv.pop();
3042 |
3043 | wtlv.push((byte)0xFB);
3044 | wtlv.pop();
3045 |
3046 | wtlv.push((byte)0xFC);
3047 | wtlv.pop();
3048 |
3049 | wtlv.push((byte)0xFD);
3050 | wtlv.pop();
3051 |
3052 | wtlv.push((byte)0xFE);
3053 | wtlv.pop();
3054 |
3055 | wtlv.end();
3056 |
3057 | final short len = outgoing.available();
3058 |
3059 | if (files[TAG_CARDCAP] == null)
3060 | files[TAG_CARDCAP] = new File();
3061 | final File f = files[TAG_CARDCAP];
3062 | f.len = len;
3063 | if (f.data == null || f.data.length < len)
3064 | f.data = new byte[len];
3065 | outgoing.read(f.data, (short)0, len);
3066 | }
3067 |
3068 | private void
3069 | initCHUID()
3070 | {
3071 | outgoing.reset();
3072 | wtlv.start(outgoing);
3073 |
3074 | /* FASC-N identifier */
3075 | wtlv.push((byte)0x30);
3076 | wtlv.write(fascn, (short)0, (short)fascn.length);
3077 | wtlv.pop();
3078 |
3079 | /* Card GUID */
3080 | wtlv.push((byte)0x34);
3081 | wtlv.write(guid, (short)0, (short)guid.length);
3082 | wtlv.pop();
3083 |
3084 | /* Expiry date */
3085 | wtlv.push((byte)0x35);
3086 | wtlv.write(expiry, (short)0, (short)expiry.length);
3087 | wtlv.pop();
3088 |
3089 | /* Issuer signature */
3090 | wtlv.push((byte)0x3E);
3091 | wtlv.pop();
3092 |
3093 | wtlv.push((byte)0xFE);
3094 | wtlv.pop();
3095 |
3096 | wtlv.end();
3097 |
3098 | final short len = outgoing.available();
3099 |
3100 | if (files[TAG_CHUID] == null)
3101 | files[TAG_CHUID] = new File();
3102 | final File f = files[TAG_CHUID];
3103 | f.len = len;
3104 | if (f.data == null || f.data.length < len)
3105 | f.data = new byte[len];
3106 | outgoing.read(f.data, (short)0, len);
3107 | }
3108 |
3109 | private void
3110 | initKEYHIST()
3111 | {
3112 | outgoing.reset();
3113 | wtlv.start(outgoing);
3114 |
3115 | wtlv.push((byte)0xC1);
3116 | wtlv.writeByte(retiredKeys);
3117 | wtlv.pop();
3118 |
3119 | wtlv.push((byte)0xC2);
3120 | wtlv.writeByte((byte)0);
3121 | wtlv.pop();
3122 |
3123 | wtlv.push((byte)0xFE);
3124 | wtlv.pop();
3125 |
3126 | wtlv.end();
3127 |
3128 | final short len = outgoing.available();
3129 |
3130 | if (files[TAG_KEYHIST] == null)
3131 | files[TAG_KEYHIST] = new File();
3132 | final File f = files[TAG_KEYHIST];
3133 | f.len = len;
3134 | if (f.data == null || f.data.length < len)
3135 | f.data = new byte[len];
3136 | outgoing.read(f.data, (short)0, len);
3137 | }
3138 |
3139 | //#if YKPIV_ATTESTATION
3140 | private void
3141 | initAttestation()
3142 | {
3143 | final PivSlot atslot = slots[SLOT_F9];
3144 |
3145 | //#if PIV_SUPPORT_EC
3146 | if (ecdsaSha != null || ecdsaSha256 != null) {
3147 | atslot.asymAlg = PIV_ALG_ECCP256;
3148 | final ECPrivateKey ecPriv;
3149 | final ECPublicKey ecPub;
3150 | ecPriv = (ECPrivateKey)KeyBuilder.buildKey(
3151 | KeyBuilder.TYPE_EC_FP_PRIVATE,
3152 | (short)256, false);
3153 | ecPub = (ECPublicKey)KeyBuilder.buildKey(
3154 | KeyBuilder.TYPE_EC_FP_PUBLIC,
3155 | (short)256, false);
3156 | atslot.asym = new KeyPair(
3157 | (PublicKey)ecPub, (PrivateKey)ecPriv);
3158 | ECParams.setCurveParametersP256(ecPriv);
3159 | ECParams.setCurveParametersP256(ecPub);
3160 | } else {
3161 | return;
3162 | }
3163 | atslot.asym.genKeyPair();
3164 | atslot.imported = false;
3165 |
3166 | try {
3167 | writeAttestationCert(atslot);
3168 | } catch (Exception ex) {
3169 | /* Ignore it, we just won't make a self-signed one */
3170 | outgoing.reset();
3171 | incoming.reset();
3172 | bufmgr.cullNonTransient();
3173 | return;
3174 | }
3175 |
3176 | final short len = outgoing.available();
3177 | final File file = atslot.cert;
3178 |
3179 | if (file.data == null || file.data.length < len)
3180 | file.data = new byte[len];
3181 | file.len = outgoing.read(file.data, (short)0, len);
3182 |
3183 | outgoing.reset();
3184 | incoming.reset();
3185 | bufmgr.cullNonTransient();
3186 | //#endif
3187 | }
3188 |
3189 | private static final byte ASN1_SEQ = (byte)0x30;
3190 | private static final byte ASN1_SET = (byte)0x31;
3191 | private static final byte ASN1_INTEGER = (byte)0x02;
3192 | private static final byte ASN1_NULL = (byte)0x05;
3193 | private static final byte ASN1_OID = (byte)0x06;
3194 | private static final byte ASN1_UTF8STRING = (byte)0x0c;
3195 | private static final byte ASN1_UTCTIME = (byte)0x17;
3196 | private static final byte ASN1_GENTIME = (byte)0x18;
3197 | private static final byte ASN1_BITSTRING = (byte)0x03;
3198 | private static final byte ASN1_OCTETSTRING = (byte)0x04;
3199 |
3200 | private static final byte ASN1_APP_0 = (byte)0xA0;
3201 | private static final byte ASN1_APP_3 = (byte)0xA3;
3202 |
3203 | //#if PIV_SUPPORT_RSA
3204 | private static final byte[] OID_RSA = {
3205 | (byte)0x2A, (byte)0x86, (byte)0x48, (byte)0x86, (byte)0xF7,
3206 | (byte)0x0D, (byte)0x01, (byte)0x01, (byte)0x01
3207 | };
3208 | private static final byte[] OID_RSA_SHA = {
3209 | (byte)0x2A, (byte)0x86, (byte)0x48, (byte)0x86, (byte)0xF7,
3210 | (byte)0x0D, (byte)0x01, (byte)0x01, (byte)0x05
3211 | };
3212 | private static final byte[] OID_RSA_SHA256 = {
3213 | (byte)0x2A, (byte)0x86, (byte)0x48, (byte)0x86, (byte)0xF7,
3214 | (byte)0x0D, (byte)0x01, (byte)0x01, (byte)0x0B
3215 | };
3216 | //#endif
3217 | //#if PIV_SUPPORT_EC
3218 | private static final byte[] OID_ECDSA_SHA = {
3219 | (byte)0x2A, (byte)0x86, (byte)0x48, (byte)0xCE, (byte)0x3D,
3220 | (byte)0x04, (byte)0x01
3221 | };
3222 | private static final byte[] OID_ECDSA_SHA256 = {
3223 | (byte)0x2A, (byte)0x86, (byte)0x48, (byte)0xCE, (byte)0x3D,
3224 | (byte)0x04, (byte)0x03, (byte)0x02
3225 | };
3226 | //#endif
3227 |
3228 | private static final byte[] OID_CN = {
3229 | (byte)0x55, (byte)0x04, (byte)0x03
3230 | };
3231 | //#if PIV_SUPPORT_EC
3232 | private static final byte[] OID_ECPUBKEY = {
3233 | (byte)0x2A, (byte)0x86, (byte)0x48, (byte)0xCE, (byte)0x3D,
3234 | (byte)0x02, (byte)0x01
3235 | };
3236 | private static final byte[] OID_SECP256 = {
3237 | (byte)0x2A, (byte)0x86, (byte)0x48, (byte)0xCE, (byte)0x3D,
3238 | (byte)0x03, (byte)0x01, (byte)0x07
3239 | };
3240 | private static final byte[] OID_SECP384 = {
3241 | (byte)0x2B, (byte)0x81, (byte)0x04, (byte)0x00, (byte)0x22
3242 | };
3243 | //#endif
3244 | private static final byte[] OID_YUBICOX = {
3245 | (byte)0x2B, (byte)0x06, (byte)0x01, (byte)0x04, (byte)0x01,
3246 | (byte)0x82, (byte)0xC4, (byte)0x0A, (byte)0x03
3247 | };
3248 |
3249 | private static final byte[] X509_NOTBEFORE = {
3250 | '1', '8', '0', '1', '0', '1',
3251 | '0', '0', '0', '0', '0', '0', 'Z'
3252 | };
3253 | private static final byte[] X509_NOTAFTER = {
3254 | '2', '0', '5', '0', '0', '1', '0', '1',
3255 | '0', '0', '0', '0', '0', '0', 'Z'
3256 | };
3257 |
3258 | private static final byte[] CN_STRING = {
3259 | 'P', 'I', 'V', 'A', 'p', 'p', 'l', 'e', 't', ' ',
3260 | 'A', 't', 't', 'e', 's', 't', 'a', 't', 'i', 'o', 'n'
3261 | };
3262 |
3263 | private void
3264 | writeHex(byte val)
3265 | {
3266 | final byte lowNybble = (byte)(val & (byte)0x0F);
3267 | final byte highNybble = (byte)((byte)(val >> 4) & (byte)0xF);
3268 | final byte hexLow;
3269 | if (lowNybble <= (byte)9)
3270 | hexLow = (byte)('0' + lowNybble);
3271 | else
3272 | hexLow = (byte)('A' + (byte)(lowNybble - (byte)0xA));
3273 | final byte hexHigh;
3274 | if (highNybble <= (byte)9)
3275 | hexHigh = (byte)('0' + highNybble);
3276 | else
3277 | hexHigh = (byte)('A' + (byte)(highNybble - (byte)0xA));
3278 | wtlv.writeByte(hexHigh);
3279 | wtlv.writeByte(hexLow);
3280 | }
3281 |
3282 | private void
3283 | writeX509CertInfo(PivSlot slot)
3284 | {
3285 | short len;
3286 | final PivSlot atslot = slots[SLOT_F9];
3287 |
3288 | wtlv.push64k(ASN1_SEQ);
3289 |
3290 | /* Version */
3291 | wtlv.push(ASN1_APP_0);
3292 | wtlv.push(ASN1_INTEGER);
3293 | wtlv.writeByte((byte)0x02);
3294 | wtlv.pop();
3295 | wtlv.pop();
3296 |
3297 | /* Serial */
3298 | wtlv.push(ASN1_INTEGER);
3299 | wtlv.write(certSerial, (short)0, (short)certSerial.length);
3300 | wtlv.pop();
3301 |
3302 | /* Signature alg */
3303 | wtlv.push(ASN1_SEQ);
3304 | //#if PIV_SUPPORT_RSA
3305 | if (atslot.asymAlg == PIV_ALG_RSA1024 ||
3306 | atslot.asymAlg == PIV_ALG_RSA2048) {
3307 | wtlv.push(ASN1_OID);
3308 | if (rsaSha256 != null) {
3309 | wtlv.write(OID_RSA_SHA256, (short)0,
3310 | (short)OID_RSA_SHA256.length);
3311 | } else if (rsaSha != null) {
3312 | wtlv.write(OID_RSA_SHA, (short)0,
3313 | (short)OID_RSA_SHA.length);
3314 | }
3315 | wtlv.pop();
3316 | wtlv.push(ASN1_NULL);
3317 | wtlv.pop();
3318 | }
3319 | //#endif
3320 | //#if PIV_SUPPORT_EC
3321 | if (atslot.asymAlg == PIV_ALG_ECCP256) {
3322 | wtlv.push(ASN1_OID);
3323 | if (ecdsaSha256 != null) {
3324 | wtlv.write(OID_ECDSA_SHA256, (short)0,
3325 | (short)OID_ECDSA_SHA256.length);
3326 | } else if (ecdsaSha != null) {
3327 | wtlv.write(OID_ECDSA_SHA, (short)0,
3328 | (short)OID_ECDSA_SHA.length);
3329 | }
3330 | wtlv.pop();
3331 | }
3332 | if (atslot.asymAlg == PIV_ALG_ECCP384) {
3333 | wtlv.push(ASN1_OID);
3334 | if (ecdsaSha256 != null) {
3335 | wtlv.write(OID_ECDSA_SHA256, (short)0,
3336 | (short)OID_ECDSA_SHA256.length);
3337 | } else if (ecdsaSha != null) {
3338 | wtlv.write(OID_ECDSA_SHA, (short)0,
3339 | (short)OID_ECDSA_SHA.length);
3340 | }
3341 | wtlv.pop();
3342 | }
3343 | //#endif
3344 | wtlv.pop();
3345 |
3346 | /* Issuer */
3347 | wtlv.push(ASN1_SEQ);
3348 | wtlv.push(ASN1_SET);
3349 | wtlv.push(ASN1_SEQ);
3350 | wtlv.push(ASN1_OID);
3351 | wtlv.write(OID_CN, (short)0, (short)OID_CN.length);
3352 | wtlv.pop();
3353 | wtlv.push(ASN1_UTF8STRING);
3354 | wtlv.write(CN_STRING, (short)0, (short)CN_STRING.length);
3355 | wtlv.pop();
3356 | wtlv.pop();
3357 | wtlv.pop();
3358 | wtlv.pop();
3359 |
3360 | /* Validity */
3361 | wtlv.push(ASN1_SEQ);
3362 | wtlv.push(ASN1_UTCTIME);
3363 | wtlv.write(X509_NOTBEFORE, (short)0,
3364 | (short)X509_NOTBEFORE.length);
3365 | wtlv.pop();
3366 | wtlv.push(ASN1_GENTIME);
3367 | wtlv.write(X509_NOTAFTER, (short)0,
3368 | (short)X509_NOTAFTER.length);
3369 | wtlv.pop();
3370 | wtlv.pop();
3371 |
3372 | /* Subject */
3373 | wtlv.push(ASN1_SEQ);
3374 | wtlv.push(ASN1_SET);
3375 | wtlv.push(ASN1_SEQ);
3376 | wtlv.push(ASN1_OID);
3377 | wtlv.write(OID_CN, (short)0, (short)OID_CN.length);
3378 | wtlv.pop();
3379 | wtlv.push(ASN1_UTF8STRING);
3380 | wtlv.write(CN_STRING, (short)0, (short)CN_STRING.length);
3381 | if (slot.id != (byte)0xF9) {
3382 | wtlv.writeByte((byte)' ');
3383 | writeHex(slot.id);
3384 | }
3385 | wtlv.pop();
3386 | wtlv.pop();
3387 | wtlv.pop();
3388 | wtlv.pop();
3389 |
3390 | /* Public key */
3391 | //#if PIV_SUPPORT_EC
3392 | if (slot.asymAlg == PIV_ALG_ECCP256) {
3393 | final ECPublicKey ecpub =
3394 | (ECPublicKey)slot.asym.getPublic();
3395 | wtlv.push(ASN1_SEQ);
3396 | /* Alg info */
3397 | wtlv.push(ASN1_SEQ);
3398 | wtlv.push(ASN1_OID);
3399 | wtlv.write(OID_ECPUBKEY, (short)0,
3400 | (short)OID_ECPUBKEY.length);
3401 | wtlv.pop();
3402 | wtlv.push(ASN1_OID);
3403 | wtlv.write(OID_SECP256, (short)0,
3404 | (short)OID_SECP256.length);
3405 | wtlv.pop();
3406 | wtlv.pop();
3407 | /* Key material */
3408 | wtlv.push(ASN1_BITSTRING);
3409 | wtlv.writeByte((byte)0x00); /* no borrowed bits */
3410 | wtlv.startReserve((short)33, tempBuf);
3411 | len = ecpub.getW(tempBuf.data(), tempBuf.wpos());
3412 | wtlv.endReserve(len);
3413 | wtlv.pop();
3414 | }
3415 | if (slot.asymAlg == PIV_ALG_ECCP384) {
3416 | final ECPublicKey ecpub =
3417 | (ECPublicKey)slot.asym.getPublic();
3418 | wtlv.push(ASN1_SEQ);
3419 | /* Alg info */
3420 | wtlv.push(ASN1_SEQ);
3421 | wtlv.push(ASN1_OID);
3422 | wtlv.write(OID_ECPUBKEY, (short)0,
3423 | (short)OID_ECPUBKEY.length);
3424 | wtlv.pop();
3425 | wtlv.push(ASN1_OID);
3426 | wtlv.write(OID_SECP384, (short)0,
3427 | (short)OID_SECP384.length);
3428 | wtlv.pop();
3429 | wtlv.pop();
3430 | /* Key material */
3431 | wtlv.push(ASN1_BITSTRING);
3432 | wtlv.writeByte((byte)0x00); /* no borrowed bits */
3433 | wtlv.startReserve((short)49, tempBuf);
3434 | len = ecpub.getW(tempBuf.data(), tempBuf.wpos());
3435 | wtlv.endReserve(len);
3436 | wtlv.pop();
3437 | }
3438 | //#endif
3439 | //#if PIV_SUPPORT_RSA
3440 | if (slot.asymAlg == PIV_ALG_RSA1024 ||
3441 | slot.asymAlg == PIV_ALG_RSA2048) {
3442 | final RSAPublicKey rpubk =
3443 | (RSAPublicKey)slot.asym.getPublic();
3444 | wtlv.push64k(ASN1_SEQ);
3445 |
3446 | /* Alg info */
3447 | wtlv.push(ASN1_SEQ);
3448 | wtlv.push(ASN1_OID);
3449 | wtlv.write(OID_RSA, (short)0, (short)OID_RSA.length);
3450 | wtlv.pop();
3451 | wtlv.push(ASN1_NULL);
3452 | wtlv.pop();
3453 | wtlv.pop();
3454 |
3455 | /* Key material */
3456 | wtlv.push64k(ASN1_BITSTRING);
3457 | wtlv.writeByte((byte)0x00); /* no borrowed bits */
3458 | wtlv.push64k(ASN1_SEQ);
3459 |
3460 | /* Modulus */
3461 | wtlv.push64k(ASN1_INTEGER);
3462 | wtlv.startReserve((short)257, tempBuf);
3463 | len = rpubk.getModulus(tempBuf.data(), tempBuf.wpos());
3464 | wtlv.endReserve(len);
3465 | wtlv.pop();
3466 |
3467 | /* Exponent */
3468 | wtlv.push(ASN1_INTEGER);
3469 | wtlv.startReserve((short)9, tempBuf);
3470 | len = rpubk.getExponent(tempBuf.data(), tempBuf.wpos());
3471 | wtlv.endReserve(len);
3472 | wtlv.pop();
3473 |
3474 | wtlv.pop();
3475 | wtlv.pop();
3476 | }
3477 | //#endif
3478 | wtlv.pop();
3479 |
3480 | /* Extensions */
3481 | wtlv.push(ASN1_APP_3);
3482 | wtlv.push(ASN1_SEQ);
3483 |
3484 | wtlv.push(ASN1_SEQ);
3485 | wtlv.push(ASN1_OID);
3486 | wtlv.write(OID_YUBICOX, (short)0, (short)OID_YUBICOX.length);
3487 | wtlv.writeByte((byte)0x03);
3488 | wtlv.pop();
3489 | wtlv.push(ASN1_OCTETSTRING);
3490 | wtlv.writeByte(YKPIV_VERSION[0]);
3491 | wtlv.writeByte(YKPIV_VERSION[1]);
3492 | wtlv.writeByte(YKPIV_VERSION[2]);
3493 | wtlv.pop();
3494 | wtlv.pop();
3495 |
3496 | wtlv.push(ASN1_SEQ);
3497 | wtlv.push(ASN1_OID);
3498 | wtlv.write(OID_YUBICOX, (short)0, (short)OID_YUBICOX.length);
3499 | wtlv.writeByte((byte)0x08);
3500 | wtlv.pop();
3501 | wtlv.push(ASN1_OCTETSTRING);
3502 | wtlv.writeByte(slot.pinPolicy);
3503 | wtlv.writeByte((byte)0x01);
3504 | wtlv.pop();
3505 | wtlv.pop();
3506 |
3507 | wtlv.pop();
3508 | wtlv.pop();
3509 |
3510 | wtlv.pop();
3511 | }
3512 |
3513 | private void
3514 | writeAttestationCert(PivSlot slot)
3515 | {
3516 | final PivSlot atslot = slots[SLOT_F9];
3517 | final Signature si;
3518 | short avail, len;
3519 |
3520 | if (atslot.asymAlg == PIV_ALG_RSA1024 ||
3521 | atslot.asymAlg == PIV_ALG_RSA2048) {
3522 | if (rsaSha256 != null)
3523 | si = rsaSha256;
3524 | else
3525 | si = rsaSha;
3526 | } else if (atslot.asymAlg == PIV_ALG_ECCP256 ||
3527 | atslot.asymAlg == PIV_ALG_ECCP384) {
3528 | if (ecdsaSha256 != null)
3529 | si = ecdsaSha256;
3530 | else
3531 | si = ecdsaSha;
3532 | } else {
3533 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
3534 | return;
3535 | }
3536 |
3537 | if (si == null) {
3538 | ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
3539 | return;
3540 | }
3541 |
3542 | randData.generateData(certSerial, (short)0,
3543 | (short)certSerial.length);
3544 | certSerial[0] = (byte)(certSerial[0] & (byte)0x7F);
3545 |
3546 | outgoing.reset();
3547 | wtlv.start(outgoing);
3548 | writeX509CertInfo(slot);
3549 | wtlv.end();
3550 |
3551 | si.init(atslot.asym.getPrivate(), Signature.MODE_SIGN);
3552 |
3553 | avail = outgoing.available();
3554 | while (avail > 0) {
3555 | final short read = outgoing.readPartial(tempBuf, avail);
3556 | si.update(tempBuf.data(), tempBuf.rpos(), read);
3557 | avail -= read;
3558 | }
3559 |
3560 | outgoing.reset();
3561 | wtlv.start(outgoing);
3562 |
3563 | if (slot.id == (byte)0xF9)
3564 | wtlv.push64k((byte)0x70);
3565 |
3566 | wtlv.push64k(ASN1_SEQ);
3567 | writeX509CertInfo(slot);
3568 |
3569 | wtlv.push(ASN1_SEQ);
3570 | //#if PIV_SUPPORT_RSA
3571 | if (atslot.asymAlg == PIV_ALG_RSA1024 ||
3572 | atslot.asymAlg == PIV_ALG_RSA2048) {
3573 | wtlv.push(ASN1_OID);
3574 | if (rsaSha256 != null) {
3575 | wtlv.write(OID_RSA_SHA256, (short)0,
3576 | (short)OID_RSA_SHA256.length);
3577 | } else if (rsaSha != null) {
3578 | wtlv.write(OID_RSA_SHA, (short)0,
3579 | (short)OID_RSA_SHA.length);
3580 | }
3581 | wtlv.pop();
3582 | wtlv.push(ASN1_NULL);
3583 | wtlv.pop();
3584 | }
3585 | //#endif
3586 | //#if PIV_SUPPORT_EC
3587 | if (atslot.asymAlg == PIV_ALG_ECCP256 ||
3588 | atslot.asymAlg == PIV_ALG_ECCP384) {
3589 | wtlv.push(ASN1_OID);
3590 | if (ecdsaSha256 != null) {
3591 | wtlv.write(OID_ECDSA_SHA256, (short)0,
3592 | (short)OID_ECDSA_SHA256.length);
3593 | } else if (ecdsaSha != null) {
3594 | wtlv.write(OID_ECDSA_SHA, (short)0,
3595 | (short)OID_ECDSA_SHA.length);
3596 | }
3597 | wtlv.pop();
3598 | }
3599 | //#endif
3600 | wtlv.pop();
3601 |
3602 | wtlv.push64k(ASN1_BITSTRING);
3603 | wtlv.writeByte((byte)0x00); /* no borrowed bits */
3604 | wtlv.startReserve((short)257, tempBuf);
3605 | len = si.sign(null, (short)0, (short)0, tempBuf.data(),
3606 | tempBuf.wpos());
3607 | wtlv.endReserve(len);
3608 | wtlv.pop();
3609 |
3610 | wtlv.pop();
3611 | if (slot.id == (byte)0xF9)
3612 | wtlv.pop();
3613 | wtlv.end();
3614 | }
3615 | //#endif
3616 | }
3617 |
--------------------------------------------------------------------------------