/* * 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$ */ package sos.passportapplet; import javacard.framework.ISOException; import javacard.framework.Util; /******************************************************************************* * Contains methods to initialize a fresh passport. * * @author Cees-Bart Breunesse (ceesb@cs.ru.nl) * @author Engelbert Hubbers (hubbers@cs.ru.nl) * @author Martijn Oostdijk (martijno@cs.ru.nl) * */ public class PassportInit { private PassportCrypto crypto; private static byte[] weights = { 7, 3, 1 }; PassportInit(PassportCrypto crypto) { this.crypto = crypto; } /** * Looks up the numerical value for MRZ characters. In order to be able to * compute check digits. * * @param ch * a character from the MRZ. * @return the numerical value of the character. */ private static byte decodeMRZDigit(byte ch) { switch (ch) { case '<': case '0': return 0; case '1': return 1; case '2': return 2; case '3': return 3; case '4': return 4; case '5': return 5; case '6': return 6; case '7': return 7; case '8': return 8; case '9': return 9; case 'a': case 'A': return 10; case 'b': case 'B': return 11; case 'c': case 'C': return 12; case 'd': case 'D': return 13; case 'e': case 'E': return 14; case 'f': case 'F': return 15; case 'g': case 'G': return 16; case 'h': case 'H': return 17; case 'i': case 'I': return 18; case 'j': case 'J': return 19; case 'k': case 'K': return 20; case 'l': case 'L': return 21; case 'm': case 'M': return 22; case 'n': case 'N': return 23; case 'o': case 'O': return 24; case 'p': case 'P': return 25; case 'q': case 'Q': return 26; case 'r': case 'R': return 27; case 's': case 'S': return 28; case 't': case 'T': return 29; case 'u': case 'U': return 30; case 'v': case 'V': return 31; case 'w': case 'W': return 32; case 'x': case 'X': return 33; case 'y': case 'Y': return 34; case 'z': case 'Z': return 35; default: throw new ISOException((short) 0x6d04); } } /** * Computes the 7-3-1 check digit for part of the MRZ. * * @param chars * a part of the MRZ. * @return the resulting check digit. */ static byte checkDigit(byte[] chars, short offset, short length) { byte result = 0; for (short i = 0; i < length; i++) { result = (byte) ((short) ((result + weights[i % 3] * decodeMRZDigit(chars[(short) (offset + i)]))) % 10); } return (byte) (result + 0x30); // return as character } public static short DOCNR_LEN = 9; public static short DOB_LEN = 6; public static short DOE_LEN = 6; /** * Computes the static key seed, based on information from the MRZ. * * @param buffer * containing docNr || dateOfBirth || dateOfExpiry * @param offset * pointing to docNr * @returns offset in buffer pointing to keySeed. */ public short computeKeySeed( byte[] buffer, short docNr_p, short docNr_length, short dateOfBirth_p, short dateOfBirth_length, short dateOfExpiry_p, short dateOfExpiry_length) { // sanity check: data is ordered if(!((docNr_p < dateOfBirth_p) & (dateOfBirth_p < dateOfExpiry_p))) { ISOException.throwIt((short)0x6d66); } // sanity check: no overlap if(((short)(docNr_p + docNr_length) > dateOfBirth_p) || ((short)(dateOfBirth_p + dateOfBirth_length) > dateOfExpiry_p)) { ISOException.throwIt((short)0x6d66); } short buffer_p = 0; Util.arrayCopyNonAtomic(buffer, docNr_p, buffer, buffer_p, docNr_length); short offset = buffer_p; buffer_p += docNr_length; buffer[buffer_p] = checkDigit(buffer, offset, docNr_length); buffer_p++; Util.arrayCopyNonAtomic(buffer, dateOfBirth_p, buffer, buffer_p, dateOfBirth_length); offset = buffer_p; buffer_p += dateOfBirth_length; buffer[buffer_p] = checkDigit(buffer, offset, dateOfBirth_length); buffer_p++; Util.arrayCopyNonAtomic(buffer, dateOfExpiry_p, buffer, buffer_p, dateOfExpiry_length); offset = buffer_p; buffer_p += dateOfExpiry_length; buffer[buffer_p] = checkDigit(buffer, offset, dateOfExpiry_length); buffer_p++; crypto.createHash(buffer, (short)0, buffer_p, buffer, (short)0); return 0; } /** * Computes the static key seed, based on information from the MRZ. * * @param buffer * containing docNr || dateOfBirth || dateOfExpiry * @param offset * pointing to docNr * @returns offset in buffer pointing to keySeed. */ public short computeKeySeed(byte[] buffer, short offset) { // sanity checks (80 for hash, 3 for checkdigits) if (buffer.length < (short) (offset + DOCNR_LEN + DOB_LEN + DOE_LEN + 80 + 3)) { ISOException.throwIt((short) 0x6d66); } short start_offset = offset; // offset must initially point to docNr offset += DOCNR_LEN; short len = (short) (DOB_LEN + DOE_LEN); // make room for checkdigit after docNr Util.arrayCopyNonAtomic(buffer, offset, buffer, (short) (offset + 1), len); buffer[offset] = checkDigit(buffer, (short) (offset - DOCNR_LEN), DOCNR_LEN); offset += (short) (1 + DOB_LEN); len -= DOB_LEN; // make room for checkdigit after dateOfBirth Util.arrayCopyNonAtomic(buffer, offset, buffer, (short) (offset + 1), len); buffer[offset] = checkDigit(buffer, (short) (offset - DOB_LEN), DOB_LEN); offset += (short) (1 + DOE_LEN); buffer[offset] = checkDigit(buffer, (short) (offset - DOE_LEN), DOE_LEN); offset++; crypto.createHash(buffer, start_offset, (short) (offset - start_offset), buffer, offset); return offset; } }