Java tutorial
/** * Copyright 2011, Tobias Senger * * This file is part of animamea. * * Animamea is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Animamea 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with animamea. If not, see <http://www.gnu.org/licenses/>. */ package de.tsenger.animamea.iso7816; import java.io.ByteArrayOutputStream; import java.io.IOException; import javax.smartcardio.CommandAPDU; import javax.smartcardio.ResponseAPDU; import org.bouncycastle.asn1.ASN1InputStream; import de.tsenger.animamea.crypto.AmCryptoException; import de.tsenger.animamea.crypto.AmCryptoProvider; import de.tsenger.animamea.tools.HexString; /** * Verpackt ungeschtzte CAPDU in SecureMessaging und entpackt SM-geschtzte RAPDU. * * @author Tobias Senger (tobias@t-senger.de) * */ public class SecureMessaging { private byte[] ks_enc = null; private byte[] ks_mac = null; private byte[] ssc = null; private AmCryptoProvider crypto = null; /** * Konstruktor * * @param acp * AmDESCrypto- oder AmAESCrypto-Instanz * @param ksenc * Session Key fr Verschlsselung (K_enc) * @param ksmac * Session Key fr Prfsummenberechnung (K_mac) * @param initssc * Initialer Wert des Send Sequence Counters */ public SecureMessaging(AmCryptoProvider acp, byte[] ksenc, byte[] ksmac, byte[] initialSSC) { crypto = acp; ks_enc = ksenc.clone(); ks_mac = ksmac.clone(); ssc = initialSSC.clone(); } /** * Erzeugt aus einer Plain-Command-APDU ohne Secure Messaging eine Command-APDU * mit Secure Messaging. * * @param capdu plain Command-APDU * @return CommandAPDU mit SM * @throws SecureMessagingException */ public CommandAPDU wrap(CommandAPDU capdu) throws SecureMessagingException { byte[] header = null; DO97 do97 = null; DO87 do87 = null; DO8E do8E = null; incrementAtIndex(ssc, ssc.length - 1); // Mask class byte and pad command header header = new byte[4]; // Die ersten 4 Bytes der CAPDU sind der Header System.arraycopy(capdu.getBytes(), 0, header, 0, 4); // Markiert das Secure Messaging im CLA-Byte header[0] = (byte) (header[0] | (byte) 0x0C); // build DO87 if (getAPDUStructure(capdu) == 3 || getAPDUStructure(capdu) == 4 || getAPDUStructure(capdu) == 6) { do87 = buildDO87(capdu.getData().clone()); } // build DO97 if (getAPDUStructure(capdu) == 2 || getAPDUStructure(capdu) == 4 || getAPDUStructure(capdu) == 7) { do97 = buildDO97(capdu.getNe()); } // build DO8E do8E = buildDO8E(header, do87, do97); // construct and return protected APDU ByteArrayOutputStream bOut = new ByteArrayOutputStream(); try { if (do87 != null) { //System.out.println("writing do87"); bOut.write(do87.getEncoded()); } if (do97 != null) { //System.out.println("writing do97"); bOut.write(do97.getEncoded()); } //System.out.println("writing do8e"); bOut.write(do8E.getEncoded()); } catch (IOException e) { throw new SecureMessagingException(e); } /* System.out.println("new command: "); System.out.println(header[0]); System.out.println(header[1]); System.out.println(header[2]); System.out.println(Hex.toHexString((header))); System.out.println(Hex.toHexString(bOut.toByteArray())); System.out.println("length: "); System.out.println(bOut.toByteArray().length); int l = bOut.toByteArray().length; //return new CommandAPDU(header[0], header[1], header[2], header[3], bOut.toByteArray(), 65536); CommandAPDU foo = new CommandAPDU(header[0], header[1], header[2], header[3], bOut.toByteArray(), 0, l, 65536); System.out.println("nc bytes:"); System.out.println(Hex.toHexString(foo.getBytes()));*/ //return new CommandAPDU(header[0], header[1], header[2], header[3], bOut.toByteArray(), 0, l, 65536); return new CommandAPDU(header[0], header[1], header[2], header[3], bOut.toByteArray(), 65536); } /** * Erzeugt aus einer SM geschtzten Response-APDU eine plain Response-APDU * ohne Secure Messaging. * @param rapdu SM protected RAPDU * @return plain RAPDU * @throws SecureMessagingException */ public ResponseAPDU unwrap(ResponseAPDU rapdu) throws SecureMessagingException { DO87 do87 = null; DO99 do99 = null; DO8E do8E = null; incrementAtIndex(ssc, ssc.length - 1); int pointer = 0; byte[] rapduBytes = rapdu.getData(); byte[] subArray = new byte[rapduBytes.length]; while (pointer < rapduBytes.length) { System.arraycopy(rapduBytes, pointer, subArray, 0, rapduBytes.length - pointer); ASN1InputStream asn1sp = new ASN1InputStream(subArray); byte[] encodedBytes = null; try { encodedBytes = asn1sp.readObject().getEncoded(); asn1sp.close(); } catch (IOException e) { throw new SecureMessagingException(e); } ASN1InputStream asn1in = new ASN1InputStream(encodedBytes); try { switch (encodedBytes[0]) { case (byte) 0x87: do87 = new DO87(); do87.fromByteArray(asn1in.readObject().getEncoded()); break; case (byte) 0x99: do99 = new DO99(); do99.fromByteArray(asn1in.readObject().getEncoded()); break; case (byte) 0x8E: do8E = new DO8E(); do8E.fromByteArray(asn1in.readObject().getEncoded()); } asn1in.close(); } catch (IOException e) { throw new SecureMessagingException(e); } pointer += encodedBytes.length; } if (do99 == null) throw new SecureMessagingException("Secure Messaging error: mandatory DO99 not found"); // DO99 is mandatory // and only absent // if SM error // occurs // Construct K (SSC||DO87||DO99) ByteArrayOutputStream bout = new ByteArrayOutputStream(); try { if (do87 != null) bout.write(do87.getEncoded()); bout.write(do99.getEncoded()); } catch (IOException e) { throw new SecureMessagingException(e); } crypto.init(ks_mac, ssc); byte[] cc = crypto.getMAC(bout.toByteArray()); byte[] do8eData = do8E.getData(); if (!java.util.Arrays.equals(cc, do8eData)) throw new SecureMessagingException("Checksum is incorrect!\n Calculated CC: " + HexString.bufferToHex(cc) + "\nCC in DO8E: " + HexString.bufferToHex(do8eData)); // Decrypt DO87 byte[] data = null; byte[] unwrappedAPDUBytes = null; if (do87 != null) { crypto.init(ks_enc, ssc); byte[] do87Data = do87.getData(); try { data = crypto.decrypt(do87Data); } catch (AmCryptoException e) { throw new SecureMessagingException(e); } // Build unwrapped RAPDU unwrappedAPDUBytes = new byte[data.length + 2]; System.arraycopy(data, 0, unwrappedAPDUBytes, 0, data.length); byte[] do99Data = do99.getData(); System.arraycopy(do99Data, 0, unwrappedAPDUBytes, data.length, do99Data.length); } else unwrappedAPDUBytes = do99.getData().clone(); return new ResponseAPDU(unwrappedAPDUBytes); } /** * encrypt data with KS.ENC and build DO87 * @param data * @return * @throws SecureMessagingException */ private DO87 buildDO87(byte[] data) throws SecureMessagingException { crypto.init(ks_enc, ssc); byte[] enc_data; try { enc_data = crypto.encrypt(data); } catch (AmCryptoException e) { throw new SecureMessagingException(e); } return new DO87(enc_data); } private DO8E buildDO8E(byte[] header, DO87 do87, DO97 do97) throws SecureMessagingException { ByteArrayOutputStream m = new ByteArrayOutputStream(); /** * Verhindert doppeltes Padden des Headers: Nur wenn do87 oder do97 * vorhanden sind, wird der Header gepaddet. Ansonsten wird erst beim * Berechnen des MAC gepaddet. */ try { if (do87 != null || do97 != null) m.write(crypto.addPadding(header)); else m.write(header); if (do87 != null) m.write(do87.getEncoded()); if (do97 != null) m.write(do97.getEncoded()); } catch (IOException e) { throw new SecureMessagingException(e); } crypto.init(ks_mac, ssc); return new DO8E(crypto.getMAC(m.toByteArray())); } private DO97 buildDO97(int le) { return new DO97(le); } /** * Bestimmt welchem Case die CAPDU enstpricht. (Siehe ISO/IEC 7816-3 Kapitel * 12.1) * * @return Strukurtype (1 = CASE1, ...) */ private byte getAPDUStructure(CommandAPDU capdu) { byte[] cardcmd = capdu.getBytes(); if (cardcmd.length == 4) return 1; if (cardcmd.length == 5) return 2; if (cardcmd.length == (5 + (cardcmd[4] & 0xff)) && cardcmd[4] != 0) return 3; if (cardcmd.length == (6 + (cardcmd[4] & 0xff)) && cardcmd[4] != 0) return 4; if (cardcmd.length == 7 && cardcmd[4] == 0) return 5; if (cardcmd.length == (7 + (cardcmd[5] & 0xff) * 256 + (cardcmd[6] & 0xff)) && cardcmd[4] == 0 && (cardcmd[5] != 0 || cardcmd[6] != 0)) return 6; if (cardcmd.length == (9 + (cardcmd[5] & 0xff) * 256 + (cardcmd[6] & 0xff)) && cardcmd[4] == 0 && (cardcmd[5] != 0 || cardcmd[6] != 0)) return 7; return 0; } private void incrementAtIndex(byte[] array, int index) { if (array[index] == Byte.MAX_VALUE) { array[index] = 0; if (index > 0) incrementAtIndex(array, index - 1); } else { array[index]++; } } }