package de.persosim.simulator.tlv; import java.util.Arrays; import de.persosim.simulator.exception.ISO7816Exception; import de.persosim.simulator.utils.Utils; /** * This class implements the tag field of any TLV data object. * Tag fields are immutable in order to prevent accidental corruption of existing data structures. * In the field tags do not need to be changed for themselves. They may only need to be exchanged as part of another data structure, i.e. {@link PrimitiveTlvDataObject}. * The preferred way of doing so is to provide/use an according unchecked setter method for/of respective data structures. * * @author slutters * */ public final class TlvTag extends TlvElement implements Asn1 { private byte[] tagField; /*--------------------------------------------------------------------------------*/ /** * Constructor for this object based on a range defined on an array of raw bytes. * * WARNING: If validity checks are to be skipped, the tag will be set to the full range. * This may cause serious problems if the range is also expected to contain a length and * probably also a value field as it is the case during construction of any TLV data object * or container from raw byte arrays. * * @param tagFieldInput the byte array that in a certain range contains the TLV tag * @param minOffset the first offset of the range to contain the TLV tag (inclusive) * @param maxOffset the first offset not to be part of the range to contain the TLV tag (exclusive). * @param performValidityChecks true: perform validity checks, false: do not perform validity checks */ public TlvTag(byte[] tagFieldInput, int minOffset, int maxOffset, boolean performValidityChecks) { super(); if(performValidityChecks == PERFORM_VALIDITY_CHECKS) { this.setTagField(tagFieldInput, minOffset, maxOffset); } else{ if(tagFieldInput == null) {throw new NullPointerException("tag field must not be null");} if(minOffset < 0) {throw new IllegalArgumentException("min offset must not be less than 0");} if(maxOffset < minOffset) {throw new IllegalArgumentException("max offset must not be smaller than min offset");} if(maxOffset > tagFieldInput.length) {throw new IllegalArgumentException("selected array area must not lie outside of data array");} this.tagField = Arrays.copyOfRange(tagFieldInput, minOffset, maxOffset); } } /** * Constructor for this object based on a range defined on an array of raw bytes. * * @param tagFieldInput the byte array that in a certain range contains the TLV tag * @param minOffset the first offset of the range to contain the TLV tag (inclusive) * @param maxOffset the first offset not to be part of the range to contain the TLV tag (exclusive). */ public TlvTag(byte[] tagFieldInput, int minOffset, int maxOffset) { this(tagFieldInput, minOffset, maxOffset, PERFORM_VALIDITY_CHECKS); } /** * Constructor for this object based on an array of raw bytes. * * WARNING: If validity checks are to be skipped, the tag will be set to the full array length. * This may cause serious problems if the range is also expected to contain a length and * probably also a value field as it is the case during construction of any TLV data object * or container from raw byte arrays. * * @param tagFieldInput the byte array that contains the TLV tag * @param performValidityChecks true: perform validity checks, false: do not perform validity checks */ public TlvTag(byte[] tagFieldInput, boolean performValidityChecks) { this(tagFieldInput, 0, tagFieldInput.length, performValidityChecks); } /** * Constructor for this object based on an array of raw bytes. * * @param tagFieldInput the byte array that contains the TLV tag */ public TlvTag(byte[] tagFieldInput) { this(tagFieldInput, PERFORM_VALIDITY_CHECKS); } /** * Constructor for this object based on a short. * * WARNING: If validity checks are to be skipped, the tag will be set to the full short's length. * This may cause serious problems if the short is also expected to contain a length and * probably also a value field as it is the case during construction of any TLV data object * or container from raw byte arrays. * * @param tagFieldInput the short that contains the TLV tag * @param performValidityChecks true: perform validity checks, false: do not perform validity checks */ public TlvTag(short tagFieldInput, boolean performValidityChecks) { this(Utils.toUnsignedByteArray(tagFieldInput), 0, 2, performValidityChecks); } /** * Constructor for this object based on a short. * * @param tagFieldInput the short that contains the TLV tag */ public TlvTag(short tagFieldInput) { this(tagFieldInput, PERFORM_VALIDITY_CHECKS); } /** * Constructor for this object based on a byte. * * @param tagFieldInput the byte that contains the TLV tag * @param performValidityChecks true: perform validity checks, false: do not perform validity checks */ public TlvTag(byte tagFieldInput, boolean performValidityChecks) { this(Utils.toUnsignedByteArray(tagFieldInput), 0, 1, performValidityChecks); } /** * Constructor for this object based on a byte. * * @param tagFieldInput the byte that contains the TLV tag */ public TlvTag(byte tagFieldInput) { this(tagFieldInput, PERFORM_VALIDITY_CHECKS); } /** * Constructor for this object based on an already existing object of this type. * * @param tlvTag the existing {@link TlvTag} template * @param performValidityChecks true: perform validity checks, false: do not perform validity checks */ public TlvTag(TlvTag tlvTag, boolean performValidityChecks) { this(tlvTag.toByteArray(), performValidityChecks); } /** * Constructor for this object based on an already existing object of this type. * * @param tlvTag the existing {@link TlvTag} template */ public TlvTag(TlvTag tlvTag) { this(tlvTag.toByteArray(), PERFORM_VALIDITY_CHECKS); } /*--------------------------------------------------------------------------------*/ /** * This method sets the TLV tag based on a raw byte array. * * The variables minOffset and maxOffset specify a range that is supposed to contain the tag. * When parsing a raw byte array representation of a tag, the exact length of it is previously unknown. * This method will treat all bytes within the specified range as potentially being part of the tag field. * The tag finally will exclusively be created from the bytes that have been identified as being part of a valid * tag field. This explicitly allows for the range to contain more bytes than are actually part of the tag. * This is the exact reason why this method can not be executed without performing validity checks. * In case validity checks are to be omitted, the setting can be forced using the force method. * * @param tagFieldInput the data field that contains the range containing the tag field * @param minOffset the first offset of the tag field (inclusive) * @param maxOffset the first offset no longer belonging to the range containing the tag field (exclusive) */ private void setTagField(byte[] tagFieldInput, int minOffset, int maxOffset) { if(tagFieldInput == null) {throw new NullPointerException("tag field must not be null");} if(minOffset < 0) {throw new IllegalArgumentException("min offset must not be less than 0");} if(maxOffset < minOffset) {throw new IllegalArgumentException("max offset must not be smaller than min offset");} if(maxOffset > tagFieldInput.length) {throw new IllegalArgumentException("selected array area must not lie outside of data array");} if(minOffset == maxOffset) {throw new IllegalArgumentException("selected part of data field must be greater than 0");} int currentOff = minOffset; byte currentByte = tagFieldInput[currentOff]; /* * Determine Tag */ boolean isSecondByteOfMultiByteTag = false; if(!((byte) (currentByte & (byte) 0x1F) == (byte) 0x1F)) { /* if this tag has a short tag, i.e. 1 byte tag field (0 <= tag <= 30) */ this.tagField = Arrays.copyOfRange(tagFieldInput, currentOff, currentOff + 1); } else{ isSecondByteOfMultiByteTag = true; while(true) { /* assume tag to be at least one byte longer */ /* change to next byte */ currentOff++; if(currentOff >= maxOffset) {ISO7816Exception.throwIt(SW_6A85_NC_INCONSISTENT_WITH_TLV_STRUCTURE, "offset outside data array");} currentByte = tagFieldInput[currentOff]; if(isSecondByteOfMultiByteTag) { isSecondByteOfMultiByteTag = false; if(((byte) (currentByte & (byte) 0x7F) == (byte) 0x00)) { /* error, second byte must not be 0 in bits 7-1, i.e. indicated tag length would have also fit into smaller tag */ ISO7816Exception.throwIt(SW_6A80_WRONG_DATA); } if(((byte) (currentByte & (byte) 0x80)) == (byte) 0x00) { /* if this is a 2 byte Tag */ if(((byte) (currentByte & (byte) 0x7F)) <= 30) { /* tag number is unsigned as first bit has been checked as not set before */ /* error, 2 byte tag must not encode a tag number <= 30 */ /* tag number <= 30 would have also fit into 1-byte tag */ ISO7816Exception.throwIt(SW_6A80_WRONG_DATA); } } } if(((byte) (currentByte & (byte) 0x80)) == (byte) 0x00) { /* if this is the last byte of the tag */ if(((currentOff - minOffset) + 1) > 3) { /* error, tag is longer than the allowed 3 bytes */ ISO7816Exception.throwIt(SW_6A80_WRONG_DATA); } this.tagField = Arrays.copyOfRange(tagFieldInput, minOffset, currentOff + 1); return; } } } } @Override public boolean isValidBerEncoding() { int tagFieldLength = tagField.length; /* ensure valid length */ if((tagFieldLength < 1) || (tagFieldLength > 3)) {return false;}; /* ensure valid formatting of value */ if(tagFieldLength == 1) { if((tagField[0] & ((short) 0x1F)) == ((short) 0x1F)) {return false;}; } else{ if((tagField[1] & ((short) 0x7F)) == ((short) 0x00)) {return false;}; for(int i = 1; i < tagFieldLength; i++) { if(i == (tagFieldLength - 1)) { if((tagField[i] & ((short) 0x80)) == ((short) 0x80)) {return false;}; } else{ if((tagField[i] & ((short) 0x80)) != ((short) 0x80)) {return false;}; } } } return true; } @Override public boolean isValidDerEncoding() { // No different encoding for a tag is possible, so the checks for BER encoding suffice return isValidBerEncoding(); } /*--------------------------------------------------------------------------------*/ /** * Returns the tag number encoded within the tag field * @return the tag number encoded within the tag field */ public int getIndicatedTagNo() { int tagNo, currentOffset; currentOffset = 0; tagNo = 0; if(((byte) (this.tagField[currentOffset] & (byte) 0x1F)) == (byte) 0x1F) { /* if tag length > 1 */ for(int i = 1; i < this.getLength(); i++) { currentOffset++; tagNo <<= 7; tagNo |= (byte) (this.tagField[currentOffset] & (byte) 0x7F); } } else{ /* if tag length == 1 */ tagNo = Utils.maskUnsignedByteToInt((byte) (this.tagField[currentOffset] & (byte) 0x1F)); } return tagNo; } /** * Returns whether this tag field indicates primitive encoding (bit 6 of first tag field == 0) * @return whether this tag field indicates primitive encoding */ public boolean indicatesEncodingPrimitive() { return getEncoding(this.tagField) == ENCODING_PRIMITIVE; } /** * Returns whether this tag field indicates constructed encoding (bit 6 of first tag field == 1) * @return whether this tag field indicates constructed encoding */ public boolean indicatesEncodingConstructed() { return getEncoding(this.tagField) == ENCODING_CONSTRUCTED; } /** * Returns whether this tag field indicates the provided class. * Valid classes are: * {@link Asn1#CLASS_UNIVERSAL universal} * {@link Asn1#CLASS_APPLICATION application} * {@link Asn1#CLASS_CONTEXT_SPECIFIC context specific} * {@link Asn1#CLASS_PRIVATE private} * @return whether this tag field indicates the provided class */ public boolean indicatesClass(byte indicatedClass) { return getEncodedClass(this.tagField) == indicatedClass; } public boolean matches(TlvTag anotherTlvTag) { if (!Arrays.equals(this.tagField, anotherTlvTag.tagField)) { return false; } return true; } @Override public int hashCode() { return Arrays.hashCode(tagField); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; TlvTag other = (TlvTag) obj; if (!Arrays.equals(tagField, other.tagField)) return false; return true; } public byte getEncodedClass() { return getEncodedClass(this.tagField); } @Override public int getLength() { return tagField.length; } @Override public byte[] toByteArray() { return Arrays.copyOf(tagField, tagField.length); } @Override public TlvTag clone() { return new TlvTag(this.toByteArray(), SKIP_VALIDITY_CHECKS); } /** * Returns the class of the provided tag field * @param tagField the provided tag field * @return the class of the provided tag field */ public static byte getEncodedClass(byte[] tagField) { if(tagField == null) {throw new NullPointerException("tag field must not be null");} return (byte) (tagField[0] & CLASS); } /** * Returns the encoding of the provided tag field * @param tagField the provided tag field * @return the encoding of the provided tag field */ public static byte getEncoding(byte[] tagField) { if(tagField == null) {throw new NullPointerException("tag field must not be null");} return (byte) (tagField[0] & ENCODING); } }