/* * IsoApplet: A Java Card PKI applet aimiing for ISO 7816 compliance. * Copyright (C) 2014 Philip Wendland (wendlandphilip@gmail.com) * * This program 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. * * This program 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 this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package net.pwendland.javacard.pki.isoapplet; import javacard.framework.Util; /** * \brief Utility class for TLV-realted operations. */ public class UtilTLV { /** \brief Find the position of the tag at level 1. * * \attention This method only searches level 1 of TLV encoded arrays (i.e. no nested TLVs are searched). * * \param tlv The array containing the TLV-encoded object to search. * * \param tlvOffset The position at which the TLV structure begins. * * \param tlvLength The length of the TLV structure. * * \param tag The tag to search for. * * \return The position of the tag. * * \throw NotFoundException If the tag could not be found. * * \throw InvalidArgumentsException Malformatted TLV data. */ public static short findTag(byte[] tlv, short tlvOffset, short tlvLength, byte tag) throws NotFoundException, InvalidArgumentsException { short tagPos = tlvOffset; short len; while(tagPos < (short)(tlvLength+tlvOffset-1)) { if(tlv[tagPos] == tag) { return tagPos; } len = decodeLengthField(tlv, (short)(tagPos+1)); // Increase the position by: T length (1), L length, V length. // I.e. look at the next Tag, jump over current L and V field. // This saves execution time and ensures that no byte from V is misinterpreted. tagPos += 1 + getLengthFieldLength(len) + len; } throw NotFoundException.getInstance(); } /** * \brief Check the consistency of the TLV structure. * * Basically, we jump from one tag to the next. At the end, we must be at the position * where the next tag would be, if it was there. If the position is any other than that, * the TLV structure is not consistent. * * \param tlv The array containing the TLV-encoded object to search. * * \param offset The position at which the TLV structure begins. * * \param length The length of the TLV structure. * * \return True if the TLV structure is valid, else false. */ public static boolean isTLVconsistent(byte[] tlv, short offset, short length) { short pos = offset; short len; while(pos < (short)(length+offset-1)) { try { len = decodeLengthField(tlv, (short)(pos+1)); pos += 1 + getLengthFieldLength(len) + len; } catch (InvalidArgumentsException e) { return false; } } return (pos == (short)(offset+length)); } /** * \brief Decode the length field of a TLV-entry. * * The length field itself can be 1, 2 or 3 bytes long: * - If the length is between 0 and 127, it is 1 byte long. * - If the length is between 128 and 255, it is 2 bytes long. * The first byte is 0x81 to indicate this. * - If the length is between 256 and 65535, it is 3 bytes long. * The first byte is 0x82, the following 2 contain the actual length. * Note: Only lengths up to 0x7FFF (32767) are supported here, because a short in Java is signed. * * \param buf The buffer containing the length field. * * \param offset The offset at where the length field starts. * * \param length The length of the buffer (buf). This is to prevent that the index gets out of bounds. * * \return The (positive) length encoded by the length field, or in case of an error, -1. * * \throw InvalidArgumentsException If offset is too big for a signed Java short * If the first byte of the length field is invalid */ public static short decodeLengthField(byte[] buf, short offset) throws InvalidArgumentsException { if(buf[offset] == (byte)0x82) { // 256..65535 // Check for short overflow // (In Java, a short is signed: positive values are 0000..7FFF) if(buf[(short)(offset+1)] < 0) { // 80..FF throw InvalidArgumentsException.getInstance(); } return Util.getShort(buf, (short)(offset+1)); } else if(buf[offset] == (byte)0x81) { return (short) ( 0x00FF & buf[(short)(offset+1)]); } else if(buf[offset] > 0) { // 00..7F return (short) ( 0x007F & buf[offset]); } else { throw InvalidArgumentsException.getInstance(); } } /** * \brief Get the length of the length field of a TLV-entry. * * \attention Not the length of the value-field is returned, * but the length of the length field itself. * * \see decodeLengthField() * * \param length The decoded length from the TLV-entry. * * \return The length of the length field. * * \throw InvalidArgumentsException If the length would overflow the signed * short of Java. */ public static short getLengthFieldLength(short length) throws InvalidArgumentsException { if(length < 0) { throw InvalidArgumentsException.getInstance(); } else if(length < 128) { return 1; } else if(length < 256) { return 2; } else { return 3; } } /** * \brief Write the tag and length to a buffer. * * Only the tag and length field are written. The writing of the value field is left to the caller. * * \param tag A one- or two-byte tag. * * \param len The length that should be written to the length field. * * \param out The buffer to write the result to. * * \param outLen The length of out. * * \param outOffset The offset at which to start writing the tag. * * \return The length that was written. * * \throw InvalidArgumentsException If the tag is invalid. * If len is negative. * * \throw NotEnoughSpaceException If the buffer "out" is too small. */ public static short writeTagAndLen(short tag, short len, byte[] out, short outOffset) throws InvalidArgumentsException, NotEnoughSpaceException { byte tagLen; short pos = outOffset; short outLen = (short)out.length; if((short)(tag & (short)0xFF00) != 0) { if((short)(tag & (short)0x1F00) != (short)0x1F00) { /* Missing escape marker */ throw InvalidArgumentsException.getInstance(); } tagLen = 2; } else { tagLen = 1; } if(len < 0) { throw InvalidArgumentsException.getInstance(); } if((short)(tagLen + getLengthFieldLength(len)) > (short)(outLen - outOffset)) { throw NotEnoughSpaceException.getInstance(); } if(tagLen == 1) { out[pos] = (byte)(tag & (short)0x00FF); } else { Util.setShort(out, pos, tag); } pos += tagLen; if(len < 128) { out[pos++] = (byte)(len & (short)0x007F); } else if(len < 256) { out[pos++] = (byte)0x81; out[pos++] = (byte)(len & (short)0x00FF); } else { out[pos++] = (byte)0x82; Util.setShort(out, pos, len); pos += 2; } return (short)(pos - outOffset); } }