├── 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 | --------------------------------------------------------------------------------