/* * DrivingLicenseApplet - A reference implementation of the ISO18013 standards. * Based on the passport applet code developed by the JMRTD team, see * http://jmrtd.org * * Copyright (C) 2006 SoS group, Radboud University * Copyright (C) 2009 Wojciech Mostowski, Radboud University * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ package org.isodl.applet; import javacard.framework.APDU; import javacard.framework.ISO7816; import javacard.framework.ISOException; import javacard.framework.JCSystem; import javacard.framework.Util; import javacard.security.CryptoException; import javacard.security.KeyAgreement; import javacard.security.MessageDigest; import javacard.security.RSAPublicKey; import javacard.security.Signature; import javacardx.crypto.Cipher; /** * The generic driving license crypto routines (the perfect world mode where all * the needed ciphers are supported. Current JCOP41 cards support all the * ciphers here, so no need to hacks like JCOP41LicenseCrypto class. Don't know * if e.g. EC DH key agreement is supported by CREF, don't have time to check * and test. * * @author Wojciech Mostowski <woj@cs.ru.nl> * */ public class LicenseCrypto { static final byte ENC_MODE = 1; static final byte MAC_MODE = 2; private static final byte PAD_INPUT = 7; private static final byte DONT_PAD_INPUT = 8; private static byte[] PAD_DATA = { (byte) 0x80, 0, 0, 0, 0, 0, 0, 0 }; private KeyStore keyStore; private Signature sig; private Signature rsaSig; private Cipher ciph; private KeyAgreement keyAgreement; private boolean[] eapChangeKeys; MessageDigest shaDigest; Cipher rsaCiph; LicenseCrypto(KeyStore keyStore) { this.keyStore = keyStore; sig = Signature.getInstance(Signature.ALG_DES_MAC8_ISO9797_1_M2_ALG3, false); ciph = Cipher.getInstance(Cipher.ALG_DES_CBC_NOPAD, false); shaDigest = MessageDigest.getInstance(MessageDigest.ALG_SHA, false); rsaSig = Signature.getInstance(Signature.ALG_RSA_SHA_PKCS1, false); rsaCiph = Cipher.getInstance(Cipher.ALG_RSA_NOPAD, false); keyAgreement = KeyAgreement.getInstance(KeyAgreement.ALG_EC_SVDP_DH, false); eapChangeKeys = JCSystem.makeTransientBooleanArray((short) 1, JCSystem.CLEAR_ON_DESELECT); } boolean eapVerifySignature(RSAPublicKey key, byte[] rnd, short rndLength, byte[] sicId, byte[] buffer, short offset, short length) { rsaSig.init(key, Signature.MODE_VERIFY); rsaSig.update(sicId, (short) 0, (short) sicId.length); return rsaSig.verify(rnd, (short) 0, rndLength, buffer, offset, length); } void createMacFinal(byte[] msg, short msg_offset, short msg_len, byte[] mac, short mac_offset) { sig.sign(msg, msg_offset, msg_len, mac, mac_offset); } boolean verifyMacFinal(byte[] msg, short msg_offset, short msg_len, byte[] mac, short mac_offset) { return sig.verify(msg, msg_offset, msg_len, mac, mac_offset, (short) 8); } void decryptInit() { ciph.init(keyStore.getCryptKey(), Cipher.MODE_DECRYPT); } short decrypt(byte[] ctext, short ctext_offset, short ctext_len, byte[] ptext, short ptext_offset) { return ciph.update(ctext, ctext_offset, ctext_len, ptext, ptext_offset); } short decryptFinal(byte[] ctext, short ctext_offset, short ctext_len, byte[] ptext, short ptext_offset) { return ciph .doFinal(ctext, ctext_offset, ctext_len, ptext, ptext_offset); } short encryptInit() { return encryptInit(DONT_PAD_INPUT, null, (short) 0, (short) 0); } short encryptInit(byte padding, byte[] ptext, short ptext_offset, short ptext_len) { short newlen = ptext_len; if (padding == PAD_INPUT) { newlen = LicenseUtil.pad(ptext, ptext_offset, ptext_len); } ciph.init(keyStore.getCryptKey(), Cipher.MODE_ENCRYPT); return newlen; } short encrypt(byte[] ptext, short ptext_offset, short ptext_len, byte[] ctext, short ctext_offset) { return ciph.update(ptext, ptext_offset, ptext_len, ctext, ctext_offset); } short encryptFinal(byte[] ptext, short ptext_offset, short ptext_len, byte[] ctext, short ctext_offset) { return ciph .doFinal(ptext, ptext_offset, ptext_len, ctext, ctext_offset); } void updateMac(byte[] msg, short msg_offset, short msg_len) { sig.update(msg, msg_offset, msg_len); } void initMac(byte mode) { sig.init(keyStore.getMacKey(), mode); } short unwrapCommandAPDU(byte[] ssc, APDU apdu) { byte[] buf = apdu.getBuffer(); short apdu_p = (short) (ISO7816.OFFSET_CDATA & 0xff); short start_p = apdu_p; short le = 0; short do87DataLen = 0; short do87Data_p = 0; short do87LenBytes = 0; short hdrLen = 4; short hdrPadLen = (short) (8 - hdrLen); apdu.setIncomingAndReceive(); incrementSSC(ssc); if (buf[apdu_p] == (byte) 0x87) { apdu_p++; // do87 if ((buf[apdu_p] & 0xff) > 0x80) { do87LenBytes = (short) (buf[apdu_p] & 0x7f); apdu_p++; } else { do87LenBytes = 1; } if (do87LenBytes > 2) { // sanity check ISOException.throwIt(LicenseApplet.SW_SM_DO_INCORRECT); } for (short i = 0; i < do87LenBytes; i++) { do87DataLen += (short) ((buf[(short) (apdu_p + i)] & 0xff) << (short) ((do87LenBytes - 1 - i) * 8)); } apdu_p += do87LenBytes; if (buf[apdu_p] != 1) { ISOException.throwIt(LicenseApplet.SW_SM_DO_INCORRECT); } // store pointer to data and defer decrypt to after mac check (do8e) do87Data_p = (short) (apdu_p + 1); apdu_p += do87DataLen; do87DataLen--; // compensate for 0x01 marker } if (buf[apdu_p] == (byte) 0x97) { // do97 if (buf[++apdu_p] != 1) ISOException.throwIt(LicenseApplet.SW_SM_DO_INCORRECT); le = (short) (buf[++apdu_p] & 0xff); apdu_p++; } // do8e if (buf[apdu_p] != (byte) 0x8e) { ISOException.throwIt(LicenseApplet.SW_SM_DO_MISSING); } if (buf[++apdu_p] != 8) { ISOException.throwIt(LicenseApplet.SW_SM_DO_INCORRECT); } // verify mac initMac(Signature.MODE_VERIFY); updateMac(ssc, (short) 0, (short) ssc.length); updateMac(buf, (short) 0, hdrLen); updateMac(PAD_DATA, (short) 0, hdrPadLen); if (!verifyMacFinal(buf, start_p, (short) (apdu_p - 1 - start_p), buf, (short) (apdu_p + 1))) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } // short plaintextLength = 0; short plaintextLc = 0; if (do87DataLen != 0) { // decrypt data, and leave room for lc decryptInit(); // plaintextLength = decryptFinal(buf, do87Data_p, do87DataLen, buf, (short) (hdrLen + 1)); plaintextLc = LicenseUtil.calcLcFromPaddedData(buf, (short) (hdrLen + 1), do87DataLen); buf[hdrLen] = (byte) (plaintextLc & 0xff); } return le; } /*************************************************************************** * Space to reserve in buffer when using secure messaging. * * @param plaintextLength * length of plaintext in which this offset depends. * @return */ short getApduBufferOffset(short plaintextLength) { short do87Bytes = 2; // 0x87 len data 0x01 // smallest multiple of 8 strictly larger than plaintextLen + 1 // byte is probably the length of the ciphertext (including do87 0x01) short do87DataLen = (short) ((((short) (plaintextLength + 8) / 8) * 8) + 1); if (do87DataLen < 0x80) { do87Bytes++; } else if (do87DataLen <= 0xff) { do87Bytes += 2; } else { do87Bytes += (short) (plaintextLength > 0xff ? 2 : 1); } return do87Bytes; } short wrapResponseAPDU(byte[] ssc, APDU aapdu, short plaintextOffset, short plaintextLen, short sw1sw2) { byte[] apdu = aapdu.getBuffer(); short apdu_p = 0; // smallest multiple of 8 strictly larger than plaintextLen short do87DataLen = LicenseUtil.lengthWithPadding(plaintextLen); // for 0x01 marker (indicating padding is used) do87DataLen++; short do87DataLenBytes = (short) (do87DataLen > 0xff ? 2 : 1); short do87HeaderBytes = getApduBufferOffset(plaintextLen); short do87Bytes = (short) (do87HeaderBytes + do87DataLen - 1); // 0x01 // is // counted // twice boolean hasDo87 = plaintextLen > 0; incrementSSC(ssc); short ciphertextLength = 0; short possiblyPaddedPlaintextLength = 0; if (hasDo87) { // put ciphertext in proper position. possiblyPaddedPlaintextLength = encryptInit(PAD_INPUT, apdu, plaintextOffset, plaintextLen); ciphertextLength = encryptFinal(apdu, plaintextOffset, possiblyPaddedPlaintextLength, apdu, do87HeaderBytes); } // sanity check // note that this check // (possiblyPaddedPlaintextLength != (short)(do87DataLen -1)) // does not always hold because some algs do the padding in the final, // some in the init. if (hasDo87 && (((short) (do87DataLen - 1) != ciphertextLength))) ISOException.throwIt((short) 0x6d66); if (hasDo87) { // build do87 apdu[apdu_p++] = (byte) 0x87; if (do87DataLen < 0x80) { apdu[apdu_p++] = (byte) do87DataLen; } else { apdu[apdu_p++] = (byte) (0x80 + do87DataLenBytes); for (short i = (short) (do87DataLenBytes - 1); i >= 0; i--) { apdu[apdu_p++] = (byte) ((do87DataLen >>> (i * 8)) & 0xff); } } apdu[apdu_p++] = 0x01; } if (hasDo87) { apdu_p = do87Bytes; } // build do99 apdu[apdu_p++] = (byte) 0x99; apdu[apdu_p++] = 0x02; Util.setShort(apdu, apdu_p, sw1sw2); apdu_p += 2; // calculate and write mac initMac(Signature.MODE_SIGN); updateMac(ssc, (short) 0, (short) ssc.length); createMacFinal(apdu, (short) 0, apdu_p, apdu, (short) (apdu_p + 2)); // write do8e apdu[apdu_p++] = (byte) 0x8e; apdu[apdu_p++] = 0x08; apdu_p += 8; // for mac written earlier // Reset the secure messaging keys after successful EAP Chip Authentication if (eapChangeKeys[0]) { eapChangeKeys[0] = false; keyStore.setSecureMessagingKeys(keyStore.tmpKeys, (short) 0, keyStore.tmpKeys, (short) 16); Util.arrayFillNonAtomic(keyStore.tmpKeys, (short) 0, (short) 32, (byte) 0x00); Util.arrayFillNonAtomic(ssc, (short) 0, (short) ssc.length, (byte) 0x00); } return apdu_p; } /** * Derives the ENC or MAC key from the keySeed. For ISO18013 the key seed * length can be "arbitrary", i.e. it is not fixed to be 16 like for * passports. * * @param keySeed * the key seed. * @param mode * either <code>ENC_MODE</code> or <code>MAC_MODE</code>. * * @return the key. */ private static byte[] c = { 0x00, 0x00, 0x00, 0x00 }; void deriveKey(byte[] buffer, short keySeed_offset, short keySeed_length, byte mode, short key_offset) throws CryptoException { // only key_offset is a write pointer // sanity checks if ((short) buffer.length < (short) ((short) (key_offset + keySeed_length) + c.length)) { ISOException.throwIt((short) 0x6d66); } if (keySeed_offset > key_offset) { ISOException.throwIt((short) 0x6d66); } c[(short) (c.length - 1)] = mode; // copy seed || c to key_offset Util.arrayCopyNonAtomic(buffer, keySeed_offset, buffer, key_offset, keySeed_length); Util.arrayCopyNonAtomic(c, (short) 0, buffer, (short) (key_offset + keySeed_length), (short) c.length); // compute hash on key_offset (+seed len +c len) shaDigest.doFinal(buffer, key_offset, (short) (keySeed_length + c.length), buffer, key_offset); shaDigest.reset(); // parity bits for (short i = key_offset; i < (short) (key_offset + LicenseApplet.KEY_LENGTH); i++) { if (LicenseUtil.evenBits(buffer[i]) == 0) buffer[i] = (byte) (buffer[i] ^ 1); } } private static void incrementSSC(byte[] ssc) { if (ssc == null || ssc.length <= 0) return; for (short s = (short) (ssc.length - 1); s >= 0; s--) { if ((short) ((ssc[s] & 0xff) + 1) > 0xff) ssc[s] = 0; else { ssc[s]++; break; } } } static void computeSSC(byte[] rndICC, short rndICC_offset, byte[] rndIFD, short rndIFD_offset, byte[] ssc) { if (rndICC == null || (short) (rndICC.length - rndICC_offset) < 8 || rndIFD == null || (short) (rndIFD.length - rndIFD_offset) < 8) { ISOException.throwIt((short) 0x6d66); } Util.arrayCopyNonAtomic(rndICC, (short) (rndICC_offset + 4), ssc, (short) 0, (short) 4); Util.arrayCopyNonAtomic(rndIFD, (short) (rndIFD_offset + 4), ssc, (short) 4, (short) 4); } void createHash(byte[] msg, short msg_offset, short length, byte[] dest, short dest_offset) throws CryptoException { if ((dest.length < (short) (dest_offset + length)) || (msg.length < (short) (msg_offset + length))) ISOException.throwIt((short) 0x6d66); try { shaDigest.doFinal(msg, msg_offset, length, dest, dest_offset); } finally { shaDigest.reset(); } } /** * Chip authentication part of EAP. * * @param pubData * the other parties public key data * @param offset * offset to the public key data * @param length * public key data length * * @return true when authentication successful */ public boolean authenticateChip(byte[] pubData, short offset, short length) { try { // Verify public key first. i.e. see if the data is correct and // makes up a valid EC public key. keyStore.ecPublicKey.setW(pubData, offset, length); if (!keyStore.ecPublicKey.isInitialized()) { CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); } // Do the key agreement and derive new session keys based on the // outcome: keyAgreement.init(keyStore.ecPrivateKey); short secOffset = (short) (offset + length); // FIXME The Java Card API hashes the generated secret with SHA1 // This is not what the spec mandates, so here we diverge from the spec short secLength = keyAgreement.generateSecret(pubData, offset, length, pubData, secOffset); short keysOffset = (short) (secOffset + secLength); deriveKey(pubData, secOffset, secLength, MAC_MODE, keysOffset); short macKeyOffset = keysOffset; keysOffset += 16; deriveKey(pubData, secOffset, secLength, ENC_MODE, keysOffset); short encKeyOffset = keysOffset; Util.arrayCopyNonAtomic(pubData, macKeyOffset, keyStore.tmpKeys, (short) 0, (short) 16); Util.arrayCopyNonAtomic(pubData, encKeyOffset, keyStore.tmpKeys, (short) 16, (short) 16); // The secure messaging keys should be replaced with the freshly // computed ones // just after the current APDU is completely processed. eapChangeKeys[0] = true; return true; } catch (Exception e) { eapChangeKeys[0] = false; return false; } } }