/* * passportapplet - A reference implementation of the MRTD standards. * * Copyright (C) 2006 SoS group, 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 * * $Id: PassportCrypto.java 945 2009-05-12 08:31:57Z woj76 $ */ package sos.passportapplet; 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.DESKey; import javacard.security.KeyAgreement; import javacard.security.KeyBuilder; import javacard.security.MessageDigest; import javacard.security.RSAPublicKey; import javacard.security.Signature; import javacardx.crypto.Cipher; public class PassportCrypto { public static final byte ENC_MODE = 1; public static final byte MAC_MODE = 2; public static final byte CREF_MODE = 3; public static final byte PERFECTWORLD_MODE = 4; public static final byte JCOP41_MODE = 5; public static final byte INPUT_IS_NOT_PADDED = 5; public static final byte INPUT_IS_PADDED = 6; public static final byte PAD_INPUT = 7; public static final byte DONT_PAD_INPUT = 8; MessageDigest shaDigest; Signature sig; Cipher ciph; Cipher rsaCiph; KeyStore keyStore; Signature rsaSig; KeyAgreement keyAgreement; boolean[] eacChangeKeys; // byte[] eacTerminalKeyHash; private static final short EC_X_LENGTH = (short)((short)(KeyBuilder.LENGTH_EC_F2M_163 / 8)+1); public static byte[] PAD_DATA = { (byte) 0x80, 0, 0, 0, 0, 0, 0, 0 }; protected void init() { sig = Signature.getInstance(Signature.ALG_DES_MAC8_ISO9797_1_M2_ALG3, false); ciph = Cipher.getInstance(Cipher.ALG_DES_CBC_NOPAD, false); } public PassportCrypto(KeyStore keyStore) { this.keyStore = keyStore; init(); 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); eacChangeKeys = JCSystem.makeTransientBooleanArray((short) 1, JCSystem.CLEAR_ON_DESELECT); } public 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); } public 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); } public void decryptInit() { DESKey k = keyStore.getCryptKey(); ciph.init(k, Cipher.MODE_DECRYPT); } public 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); } public 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); } public short encryptInit() { return encryptInit(DONT_PAD_INPUT, null, (short)0, (short)0); } public short encryptInit(byte padding, byte[] ptext, short ptext_offset, short ptext_len) { DESKey k = keyStore.getCryptKey(); short newlen=ptext_len; if(padding == PAD_INPUT) { // pad input newlen = PassportUtil.pad(ptext, ptext_offset, ptext_len); } ciph.init(k, Cipher.MODE_ENCRYPT); return newlen; } public 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); } public 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); } public void updateMac(byte[] msg, short msg_offset, short msg_len) { sig.update(msg, msg_offset, msg_len); } public void initMac(byte mode) { DESKey k = keyStore.getMacKey(); sig.init(k, mode); } // public short unwrapCommandAPDU_werkt(byte[] ssc, APDU aapdu) { // byte[] apdu = aapdu.getBuffer(); // short apdu_p = (short) (ISO7816.OFFSET_CDATA & 0xff); // // offset // short lc = (short) (apdu[ISO7816.OFFSET_LC] & 0xff); // short le = 0; // short do87DataLen = 0; // short do87Data_p = 0; // short do87LenBytes = 0; // short hdrLen = 4; // short hdrPadLen = (short) (8 - hdrLen); // short apduLength = (short) (hdrLen + 1 + lc); // // aapdu.setIncomingAndReceive(); // // // sanity check // if (apdu.length < (short) (apduLength + hdrPadLen + ssc.length)) { // ISOException.throwIt((short)0x6d66); // } // // // pad the header, make room for ssc, so we don't have to // // modify pointers to locations in the apdu later. // Util.arrayCopy(apdu, (short) (hdrLen + 1), // toss away lc // apdu, (short) (hdrLen + hdrPadLen), lc); // Util.arrayCopy(PassportCrypto.PAD_DATA, // (short) 0, // apdu, // hdrLen, // hdrPadLen); // apduLength--; // apduLength += hdrPadLen; // // // add ssc in front (needed to calculate the mac) so we don't have to // // modify pointers to locations in the apdu later. // incrementSSC(ssc); // Util.arrayCopy(apdu, (short) 0, apdu, (short) ssc.length, apduLength); // Util.arrayCopy(ssc, (short) 0, apdu, (short) 0, (short) ssc.length); // // apdu_p = (short) (hdrLen + hdrPadLen); // apdu_p += (short) ssc.length; // // if (apdu[apdu_p] == (byte) 0x87) { // apdu_p++; // // do87 // if ((apdu[apdu_p] & 0xff) > 0x80) { // do87LenBytes = (short) (apdu[apdu_p] & 0x7f); // apdu_p++; // } else { // do87LenBytes = 1; // } // if (do87LenBytes > 2) { // sanity check // ISOException.throwIt((short) 0x6d66); // } // for (short i = 0; i < do87LenBytes; i++) { // do87DataLen += (short) ((apdu[(short)(apdu_p + i)] & 0xff) << (short) ((do87LenBytes - 1 - i) * 8)); // } // apdu_p += do87LenBytes; // // if (apdu[apdu_p] != 1) { // ISOException.throwIt((short) (0x6d66)); // } // // 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 (apdu[apdu_p] == (byte) 0x97) { // // do97 // if (apdu[++apdu_p] != 1) // ISOException.throwIt((short) (0x6d66)); // le = (short) (apdu[++apdu_p] & 0xff); // apdu_p++; // } // // // do8e // if (apdu[apdu_p] != (byte) 0x8e) { // ISOException.throwIt((short) (0x6d66)); // } // if (apdu[++apdu_p] != 8) { // ISOException.throwIt(ISO7816.SW_DATA_INVALID); // } // // initMac(); // if (!verifyMacFinal(apdu, // (short) 0, // (short) (apdu_p - 1), // apdu, // (short)(apdu_p + 1))) { // ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); // } // // // construct unprotected apdu // // copy back the hdr // Util.arrayCopy(apdu, (short) ssc.length, apdu, (short) 0, hdrLen); // apduLength -= 4; // // short plaintextLength = 0; // short plaintextLc = 0; // if (do87DataLen != 0) { // // decrypt data, and leave room for lc // decryptInit(); // plaintextLength = decryptFinal(apdu, // do87Data_p, // do87DataLen, // apdu, // (short) (hdrLen + 1)); // // plaintextLc = PassportUtil.calcLcFromPaddedData(apdu, // (short) (hdrLen + 1), // do87DataLen); // apdu[hdrLen] = (byte) (plaintextLc & 0xff); // } // // apduLength = (short) (hdrLen + 1 + plaintextLength); // // // empty out the rest // for (short i = apduLength; i < apdu.length; i++) { // apdu[i] = 0; // } // // return le; // } public short unwrapCommandAPDU(byte[] ssc, APDU apdu) { byte[] buf = apdu.getBuffer(); short apdu_p = (short) (ISO7816.OFFSET_CDATA & 0xff); short start_p = apdu_p; short lc = (short) (buf[ISO7816.OFFSET_LC] & 0xff); 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(PassportApplet.SW_INTERNAL_ERROR); } 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(PassportApplet.SW_INTERNAL_ERROR); } // 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(PassportApplet.SW_INTERNAL_ERROR); le = (short) (buf[++apdu_p] & 0xff); apdu_p++; } // do8e if (buf[apdu_p] != (byte) 0x8e) { ISOException.throwIt(PassportApplet.SW_INTERNAL_ERROR); } if (buf[++apdu_p] != 8) { ISOException.throwIt(ISO7816.SW_DATA_INVALID); } // 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 = PassportUtil.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 */ public 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; } public 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 = PassportUtil.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 if (eacChangeKeys[0]) { eacChangeKeys[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. * * @param keySeed * the key seed. * @param mode * either <code>ENC_MODE</code> or <code>MAC_MODE</code>. * * @return the key. */ static byte[] c = { 0x00, 0x00, 0x00, 0x00 }; public 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 + PassportApplet.KEY_LENGTH); i++) { if (PassportUtil.evenBits(buffer[i]) == 0) buffer[i] = (byte) (buffer[i] ^ 1); } } public 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; } } } public 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); } public 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); short secLength = keyAgreement.generateSecret(pubData, offset, length, pubData, secOffset); // use only first 16 bytes? // secLength = 16; short keysOffset = (short) (secOffset + secLength); deriveKey(pubData, secOffset, secLength, MAC_MODE, keysOffset); short macKeyOffset = keysOffset; keysOffset += PassportApplet.KEY_LENGTH; deriveKey(pubData, secOffset, secLength, ENC_MODE, keysOffset); short encKeyOffset = keysOffset; Util.arrayCopyNonAtomic(pubData, macKeyOffset, keyStore.tmpKeys, (short) 0, PassportApplet.KEY_LENGTH); Util.arrayCopyNonAtomic(pubData, encKeyOffset, keyStore.tmpKeys, PassportApplet.KEY_LENGTH, PassportApplet.KEY_LENGTH); // The secure messaging keys should be replaced with the freshly // computed ones // just after the current APDU is completely processed. eacChangeKeys[0] = true; return true; } catch (Exception e) { eacChangeKeys[0] = false; return false; } } boolean eacVerifySignature(RSAPublicKey key, byte[] rnd, byte[] docNr, byte[] buffer, short offset, short length) { short x_offset = (short)(offset+length); keyStore.ecPublicKey.getW(buffer, x_offset++); rsaSig.init(key, Signature.MODE_VERIFY); rsaSig.update(docNr, (short) 0, (short) docNr.length); rsaSig.update(rnd, (short) 0, PassportApplet.RND_LENGTH); return rsaSig.verify(buffer, x_offset, EC_X_LENGTH, buffer, offset, length); } }