/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at * trunk/opends/resource/legal-notices/OpenDS.LICENSE * or https://OpenDS.dev.java.net/OpenDS.LICENSE. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, * add the following below this CDDL HEADER, with the fields enclosed * by brackets "[]" replaced with your own identifying information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Portions Copyright 2006-2007 Sun Microsystems, Inc. */ package org.opends.server.protocols.asn1; import org.opends.messages.Message; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import org.opends.server.api.ProtocolElement; import org.opends.server.types.ByteString; import static org.opends.messages.ProtocolMessages.*; import static org.opends.server.protocols.asn1.ASN1Constants.*; import static org.opends.server.util.ServerConstants.*; import static org.opends.server.util.StaticUtils.*; /** * This class defines the data structures and methods to use when interacting * with generic ASN.1 elements. Subclasses may provide more specific * functionality for individual element types. */ @org.opends.server.types.PublicAPI( stability=org.opends.server.types.StabilityLevel.UNCOMMITTED, mayInstantiate=true, mayExtend=false, mayInvoke=true) public class ASN1Element implements ProtocolElement, Serializable { /** * The serial version identifier required to satisfy the compiler because this * class implements the java.io.Serializable interface. This * value was generated using the serialver command-line utility * included with the Java SDK. */ private static final long serialVersionUID = -6085322427222358963L; // The BER type for this element. private byte type; // The encoded value for this element. private byte[] value; /** * Creates a new ASN.1 element with the specified type and no value. * * @param type The BER type for this ASN.1 element. */ public ASN1Element(byte type) { this.type = type; this.value = NO_VALUE; } /** * Creates a new ASN.1 element with the specified type and value. * * @param type The BER type for this ASN.1 element. * @param value The encoded value for this ASN.1 element. */ public ASN1Element(byte type, byte[] value) { this.type = type; if (value == null) { this.value = NO_VALUE; } else { this.value = value; } } /** * Retrieves the BER type for this ASN.1 element. * * @return The BER type for this ASN.1 element. */ public final byte getType() { return type; } /** * Specifies the BER type for this ASN.1 element. * * @param type The BER type for this ASN.1 element. */ public final void setType(byte type) { this.type = type; } /** * Indicates whether this ASN.1 element is in the universal class. * * @return true if this ASN.1 element is in the universal class, * or false if not. */ public final boolean isUniversal() { return ((type & TYPE_MASK_ALL_BUT_CLASS) == TYPE_MASK_UNIVERSAL); } /** * Indicates whether this ASN.1 element is in the application-specific class. * * @return true if this ASN.1 element is in the * application-specific class, or false if not. */ public final boolean isApplicationSpecific() { return ((type & TYPE_MASK_ALL_BUT_CLASS) == TYPE_MASK_APPLICATION); } /** * Indicates whether this ASN.1 element is in the context-specific class. * * @return true if this ASN.1 element is in the context-specific * class, or false if not. */ public final boolean isContextSpecific() { return ((type & TYPE_MASK_ALL_BUT_CLASS) == TYPE_MASK_CONTEXT); } /** * Indicates whether this ASN.1 element is in the private class. * * @return true if this ASN.1 element is in the private class, * or false if not. */ public final boolean isPrivate() { return ((type & TYPE_MASK_ALL_BUT_CLASS) == TYPE_MASK_PRIVATE); } /** * Indicates whether this ASN.1 element has a primitive value. * * @return true if this ASN.1 element has a primitive value, or * false if it is constructed. */ public final boolean isPrimitive() { return ((type & TYPE_MASK_ALL_BUT_PC) == TYPE_MASK_PRIMITIVE); } /** * Indicates whether this ASN.1 element has a constructed value. * * @return true if this ASN.1 element has a constructed value, * or false if it is primitive. */ public final boolean isConstructed() { return ((type & TYPE_MASK_ALL_BUT_PC) == TYPE_MASK_CONSTRUCTED); } /** * Retrieves the encoded value for this ASN.1 element. * * @return The encoded value for this ASN.1 element. */ public final byte[] value() { return value; } /** * Specifies the encoded value for this ASN.1 element. * * @param value The encoded value for this ASN.1 element. * * @throws ASN1Exception If the provided value is not appropriate for this * type of ASN.1 element. */ public void setValue(byte[] value) throws ASN1Exception { if (value == null) { this.value = NO_VALUE; } else { this.value = value; } } /** * Specifies the value to use for this ASN.1 element, but without performing * any validity checks. This should only be used by subclasses and they must * ensure that it is non-null and conforms to the appropriate requirements of * the underlying type. * * @param value The encoded value for this ASN.1 element. */ protected final void setValueInternal(byte[] value) { this.value = value; } /** * Encodes the provided value for use as the length of an ASN.1 element. * * @param length The length to encode for use in an ASN.1 element. * * @return The byte array containing the encoded length. */ public static byte[] encodeLength(int length) { if (length < 128) { return new byte[] { (byte) length }; } if ((length & 0x000000FF) == length) { return new byte[] { (byte) 0x81, (byte) (length & 0xFF) }; } else if ((length & 0x0000FFFF) == length) { return new byte[] { (byte) 0x82, (byte) ((length >> 8) & 0xFF), (byte) (length & 0xFF) }; } else if ((length & 0x00FFFFFF) == length) { return new byte[] { (byte) 0x83, (byte) ((length >> 16) & 0xFF), (byte) ((length >> 8) & 0xFF), (byte) (length & 0xFF) }; } else { return new byte[] { (byte) 0x84, (byte) ((length >> 24) & 0xFF), (byte) ((length >> 16) & 0xFF), (byte) ((length >> 8) & 0xFF), (byte) (length & 0xFF) }; } } /** * Encodes this ASN.1 element to a byte array. * * @return The byte array containing the encoded ASN.1 element. */ public final byte[] encode() { if (value.length == 0) { return new byte[] { type, 0x00 }; } else if (value.length < 128) { byte[] encodedElement = new byte[value.length + 2]; encodedElement[0] = type; encodedElement[1] = (byte) value.length; System.arraycopy(value, 0, encodedElement, 2, value.length); return encodedElement; } else { byte[] encodedLength = encodeLength(value.length); byte[] encodedElement = new byte[1 + value.length + encodedLength.length]; encodedElement[0] = type; System.arraycopy(encodedLength, 0, encodedElement, 1, encodedLength.length); System.arraycopy(value, 0, encodedElement, 1+encodedLength.length, value.length); return encodedElement; } } /** * Retrieves a byte array containing the encoded representation of the * provided boolean value. * * @param booleanValue The boolean value to encode. * * @return A byte array containing the encoded representation of the provided * boolean value. */ public static byte[] encodeValue(boolean booleanValue) { return (booleanValue ? BOOLEAN_VALUE_TRUE : BOOLEAN_VALUE_FALSE); } /** * Retrieves a byte array containing the encoded representation of the * provided integer value. * * @param intValue The integer value to encode. * * @return A byte array containing the encoded representation of the provided * integer value. */ public static byte[] encodeValue(int intValue) { if ((intValue & 0x0000007F) == intValue) { return new byte[] { (byte) (intValue & 0xFF) }; } else if ((intValue & 0x00007FFF) == intValue) { return new byte[] { (byte) ((intValue >> 8) & 0xFF), (byte) (intValue & 0xFF) }; } else if ((intValue & 0x007FFFFF) == intValue) { return new byte[] { (byte) ((intValue >> 16) & 0xFF), (byte) ((intValue >> 8) & 0xFF), (byte) (intValue & 0xFF) }; } else { return new byte[] { (byte) ((intValue >> 24) & 0xFF), (byte) ((intValue >> 16) & 0xFF), (byte) ((intValue >> 8) & 0xFF), (byte) (intValue & 0xFF) }; } } /** * Retrieves a byte array containing the encoded representation of the * provided long value. * * @param longValue The long value to encode. * * @return A byte array containing the encoded representation of the provided * long value. */ public static byte[] encodeLongValue(long longValue) { if ((longValue & 0x000000000000007FL) == longValue) { return new byte[] { (byte) (longValue & 0xFF) }; } else if ((longValue & 0x0000000000007FFFL) == longValue) { return new byte[] { (byte) ((longValue >> 8) & 0xFF), (byte) (longValue & 0xFF) }; } else if ((longValue & 0x00000000007FFFFFL) == longValue) { return new byte[] { (byte) ((longValue >> 16) & 0xFF), (byte) ((longValue >> 8) & 0xFF), (byte) (longValue & 0xFF) }; } else if ((longValue & 0x000000007FFFFFFFL) == longValue) { return new byte[] { (byte) ((longValue >> 24) & 0xFF), (byte) ((longValue >> 16) & 0xFF), (byte) ((longValue >> 8) & 0xFF), (byte) (longValue & 0xFF) }; } else if ((longValue & 0x0000007FFFFFFFFFL) == longValue) { return new byte[] { (byte) ((longValue >> 32) & 0xFF), (byte) ((longValue >> 24) & 0xFF), (byte) ((longValue >> 16) & 0xFF), (byte) ((longValue >> 8) & 0xFF), (byte) (longValue & 0xFF) }; } else if ((longValue & 0x00007FFFFFFFFFFFL) == longValue) { return new byte[] { (byte) ((longValue >> 40) & 0xFF), (byte) ((longValue >> 32) & 0xFF), (byte) ((longValue >> 24) & 0xFF), (byte) ((longValue >> 16) & 0xFF), (byte) ((longValue >> 8) & 0xFF), (byte) (longValue & 0xFF) }; } else if ((longValue & 0x007FFFFFFFFFFFFFL) == longValue) { return new byte[] { (byte) ((longValue >> 48) & 0xFF), (byte) ((longValue >> 40) & 0xFF), (byte) ((longValue >> 32) & 0xFF), (byte) ((longValue >> 24) & 0xFF), (byte) ((longValue >> 16) & 0xFF), (byte) ((longValue >> 8) & 0xFF), (byte) (longValue & 0xFF) }; } else { return new byte[] { (byte) ((longValue >> 56) & 0xFF), (byte) ((longValue >> 48) & 0xFF), (byte) ((longValue >> 40) & 0xFF), (byte) ((longValue >> 32) & 0xFF), (byte) ((longValue >> 24) & 0xFF), (byte) ((longValue >> 16) & 0xFF), (byte) ((longValue >> 8) & 0xFF), (byte) (longValue & 0xFF) }; } } /** * Retrieves a byte array containing the encoded representation of the * provided set of ASN.1 elements. * * @param elements The set of ASN.1 elements to encode into the value. * * @return A byte array containing the encoded representation of the * provided set of ASN.1 elements. */ public static byte[] encodeValue(ArrayList elements) { if (elements == null) { return NO_VALUE; } int totalLength = 0; byte[][] encodedElements = new byte[elements.size()][]; for (int i=0; i < encodedElements.length; i++) { encodedElements[i] = elements.get(i).encode(); totalLength += encodedElements[i].length; } byte[] encodedValue = new byte[totalLength]; int startPos = 0; for (byte[] b : encodedElements) { System.arraycopy(b, 0, encodedValue, startPos, b.length); startPos += b.length; } return encodedValue; } /** * Decodes the contents of the provided byte array as an ASN.1 element. * * @param encodedElement The byte array containing the ASN.1 element to * decode. * * @return The decoded ASN.1 element. * * @throws ASN1Exception If a problem occurs while attempting to decode the * byte array as an ASN.1 element. */ public static ASN1Element decode(byte[] encodedElement) throws ASN1Exception { // First make sure that the array is not null and long enough to contain // a valid ASN.1 element. if (encodedElement == null) { Message message = ERR_ASN1_NULL_ELEMENT.get(); throw new ASN1Exception(message); } else if (encodedElement.length < 2) { Message message = ERR_ASN1_SHORT_ELEMENT.get(encodedElement.length); throw new ASN1Exception(message); } // Next, decode the length. This allows multi-byte lengths with up to four // bytes used to indicate how many bytes are in the length. byte type = encodedElement[0]; int length = (encodedElement[1] & 0x7F); int valueStartPos = 2; if (length != encodedElement[1]) { int numLengthBytes = length; if (numLengthBytes > 4) { Message message = ERR_ASN1_INVALID_NUM_LENGTH_BYTES.get(numLengthBytes); throw new ASN1Exception(message); } else if (encodedElement.length < (2 + numLengthBytes)) { Message message = ERR_ASN1_TRUNCATED_LENGTH.get(numLengthBytes); throw new ASN1Exception(message); } length = 0x00; valueStartPos = 2 + numLengthBytes; for (int i=0; i < numLengthBytes; i++) { length = (length << 8) | (encodedElement[i+2] & 0xFF); } } // Make sure that the number of bytes left is equal to the number of bytes // in the value. if ((encodedElement.length - valueStartPos) != length) { Message message = ERR_ASN1_LENGTH_MISMATCH.get( length, (encodedElement.length - valueStartPos)); throw new ASN1Exception(message); } // Copy the value and construct the element to return. byte[] value = new byte[length]; System.arraycopy(encodedElement, valueStartPos, value, 0, length); return new ASN1Element(type, value); } /** * Decodes the specified portion of the provided byte array as an ASN.1 * element. * * @param encodedElement The byte array containing the ASN.1 element to * decode. * @param startPos The position in the provided array at which to * start decoding. * @param length The number of bytes in the set of data to decode as * an ASN.1 element. * * @return The decoded ASN.1 element. * * @throws ASN1Exception If a problem occurs while attempting to decode the * byte array as an ASN.1 element. */ public static ASN1Element decode(byte[] encodedElement, int startPos, int length) throws ASN1Exception { // First make sure that the array is not null and long enough to contain // a valid ASN.1 element. if (encodedElement == null) { Message message = ERR_ASN1_NULL_ELEMENT.get(); throw new ASN1Exception(message); } else if ((startPos < 0) || (startPos+length > encodedElement.length) || (length < 2)) { Message message = ERR_ASN1_SHORT_ELEMENT.get(encodedElement.length); throw new ASN1Exception(message); } // Next, decode the length. This allows multi-byte lengths with up to four // bytes used to indicate how many bytes are in the length. byte type = encodedElement[startPos]; int elementLength = (encodedElement[startPos+1] & 0x7F); int valueStartPos = startPos + 2; if (elementLength != encodedElement[startPos+1]) { int numLengthBytes = elementLength; if (numLengthBytes > 4) { Message message = ERR_ASN1_INVALID_NUM_LENGTH_BYTES.get(numLengthBytes); throw new ASN1Exception(message); } else if (startPos+length < (2 + numLengthBytes)) { Message message = ERR_ASN1_TRUNCATED_LENGTH.get(numLengthBytes); throw new ASN1Exception(message); } elementLength = 0x00; valueStartPos = startPos + 2 + numLengthBytes; for (int i=0; i < numLengthBytes; i++) { elementLength = (elementLength << 8) | (encodedElement[i+2] & 0xFF); } } // Make sure that the number of bytes left is equal to the number of bytes // in the value. if ((startPos+length - valueStartPos) != elementLength) { Message message = ERR_ASN1_LENGTH_MISMATCH.get( elementLength, (startPos+length - valueStartPos)); throw new ASN1Exception(message); } // Copy the value and construct the element to return. byte[] value = new byte[elementLength]; System.arraycopy(encodedElement, valueStartPos, value, 0, elementLength); return new ASN1Element(type, value); } /** * Decodes this ASN.1 element as an ASN.1 Boolean element. * * @return The ASN.1 Boolean element decoded from this element. * * @throws ASN1Exception If a problem occurs while attempting to decode this * element as an ASN.1 Boolean element. */ public final ASN1Boolean decodeAsBoolean() throws ASN1Exception { return ASN1Boolean.decodeAsBoolean(this); } /** * Decodes this ASN.1 element as an ASN.1 enumerated element. * * @return The ASN.1 enumerated element decoded from this element. * * @throws ASN1Exception If a problem occurs while attempting to decode this * element as an ASN.1 enumerated element. */ public final ASN1Enumerated decodeAsEnumerated() throws ASN1Exception { return ASN1Enumerated.decodeAsEnumerated(this); } /** * Decodes this ASN.1 element as an ASN.1 integer element. * * @return The ASN.1 integer element decoded from this element. * * @throws ASN1Exception If a problem occurs while attempting to decode this * element as an ASN.1 integer element. */ public final ASN1Integer decodeAsInteger() throws ASN1Exception { return ASN1Integer.decodeAsInteger(this); } /** * Decodes this ASN.1 element as an ASN.1 long element. * * @return The ASN.1 long element decoded from this element. * * @throws ASN1Exception If a problem occurs while attempting to decode this * element as an ASN.1 long element. */ public final ASN1Long decodeAsLong() throws ASN1Exception { return ASN1Long.decodeAsLong(this); } /** * Decodes this ASN.1 element as an ASN.1 null element. * * @return The ASN.1 null element decoded from this element. * * @throws ASN1Exception If a problem occurs while attempting to decode this * element as an ASN.1 null element. */ public final ASN1Null decodeAsNull() throws ASN1Exception { return ASN1Null.decodeAsNull(this); } /** * Decodes this ASN.1 element as an ASN.1 octet string element. * * @return The ASN.1 octet string element decoded from this element. * * @throws ASN1Exception If a problem occurs while attempting to decode this * element as an ASN.1 octet string element. */ public final ASN1OctetString decodeAsOctetString() throws ASN1Exception { return ASN1OctetString.decodeAsOctetString(this); } /** * Decodes this ASN.1 element as an ASN.1 sequence element. * * @return The ASN.1 sequence element decoded from this element. * * @throws ASN1Exception If a problem occurs while attempting to decode this * element as an ASN.1 sequence element. */ public final ASN1Sequence decodeAsSequence() throws ASN1Exception { return ASN1Sequence.decodeAsSequence(this); } /** * Decodes this ASN.1 element as an ASN.1 set element. * * @return The ASN.1 set element decoded from this element. * * @throws ASN1Exception If a problem occurs while attempting to decode this * element as an ASN.1 set element. */ public final ASN1Set decodeAsSet() throws ASN1Exception { return ASN1Set.decodeAsSet(this); } /** * Decodes the provided byte array as a collection of ASN.1 elements as would * be found in the value of a sequence or set. * * @param encodedElements The byte array containing the data to decode. * * @return The set of decoded ASN.1 elements. * * @throws ASN1Exception If a problem occurs while attempting to decode the * set of ASN.1 elements from the provided byte array. */ public static ArrayList decodeElements(byte[] encodedElements) throws ASN1Exception { // Make sure that the element array is not null. if (encodedElements == null) { Message message = ERR_ASN1_ELEMENT_SET_NULL.get(); throw new ASN1Exception(message); } // Iterate through the array and keep reading elements until the end is // reached. ArrayList elements = new ArrayList(); int startPos = 0; while (startPos < encodedElements.length) { byte type = encodedElements[startPos++]; if (startPos >= encodedElements.length) { Message message = ERR_ASN1_ELEMENT_SET_NO_LENGTH.get(); throw new ASN1Exception(message); } byte firstLengthByte = encodedElements[startPos++]; int length = (byte) (firstLengthByte & 0x7F); if (length != firstLengthByte) { int numLengthBytes = length; if (numLengthBytes > 4) { Message message = ERR_ASN1_ELEMENT_SET_INVALID_NUM_LENGTH_BYTES.get(numLengthBytes); throw new ASN1Exception(message); } if (numLengthBytes > encodedElements.length - startPos) { Message message = ERR_ASN1_ELEMENT_SET_TRUNCATED_LENGTH.get(numLengthBytes); throw new ASN1Exception(message); } length = 0x00; for (int i=0; i < numLengthBytes; i++) { length = (length << 8) | (encodedElements[startPos++] & 0xFF); } } // Make sure that there are at least enough bytes to hold the value. if (length > encodedElements.length - startPos) { Message message = ERR_ASN1_ELEMENT_SET_TRUNCATED_VALUE.get( length, (encodedElements.length-startPos)); throw new ASN1Exception(message); } // Create the element and add it to the list. byte[] value = new byte[length]; System.arraycopy(encodedElements, startPos, value, 0, length); elements.add(new ASN1Element(type, value)); startPos += length; } return elements; } /** * Retrieves the name of the protocol associated with this protocol element. * * @return The name of the protocol associated with this protocol element. */ public final String getProtocolElementName() { return "ASN.1"; } /** * Indicates whether the provided object is equal to this ASN.1 element. * * @param o The object for which to make the determination. * * @return true if the provided object is an ASN.1 element that * is equal to this element, or false if not. The * object will be considered equal if it is an ASN.1 element (or a * subclass) with the same type and encoded value. */ public final boolean equals(Object o) { if (this == o) { return true; } if ((o == null) || (! (o instanceof ASN1Element))) { return false; } ASN1Element e = (ASN1Element) o; return ((type == e.type) && Arrays.equals(value, e.value)); } /** * Indicates whether the provided ASN.1 element has a value that is equal to * the value of this ASN.1 element. * * @param element The ASN.1 element whose value should be compared against * the value of this element. * * @return true if the values of the elements are equal, or * false if not. */ public final boolean equalsIgnoreType(ASN1Element element) { return Arrays.equals(value, element.value); } /** * Indicates whether the provided byte string has a value that is equal to * the value of this ASN.1 element. * * @param byteString The byte string whose value should be compared against * the value of this element. * * @return true if the values are equal, or false * if not. */ public final boolean equalsIgnoreType(ByteString byteString) { return Arrays.equals(value, byteString.value()); } /** * Indicates whether the provided ASN.1 element is equal to this element. * * @param e The ASN.1 element for which to make the determination. * * @return true ASN.1 element is equal to this element, * or false if not. The elements will be considered * equal if they have the same type and encoded value. */ public final boolean equalsElement(ASN1Element e) { if (this == e) { return true; } if (e == null) { return false; } return ((type == e.type) && Arrays.equals(value, e.value)); } /** * Retrieves the hash code for this ASN.1 element. It will be constructed * from the sum of the type and up to the first twenty bytes of the value. * * @return The hash code for this ASN.1 element. */ public final int hashCode() { int hashCode = type; int length = Math.min(20, value.length); for (int i=0; i < length; i++) { hashCode += value[i]; } return hashCode; } /** * Retrieves a string representation of this ASN.1 element. * * @return A string representation of this ASN.1 element. */ public final String toString() { StringBuilder buffer = new StringBuilder(); toString(buffer); return buffer.toString(); } /** * Appends a string representation of this ASN.1 element to the provided * buffer. * * @param buffer The buffer to which the information should be appended. */ public void toString(StringBuilder buffer) { buffer.append("ASN1Element(type="); buffer.append(byteToHex(type)); buffer.append(", length="); buffer.append(value.length); buffer.append(")"); } /** * Appends a string representation of this protocol element to the provided * buffer. * * @param buffer The buffer into which the string representation should be * written. * @param indent The number of spaces that should be used to indent the * resulting string representation. */ public void toString(StringBuilder buffer, int indent) { StringBuilder indentBuf = new StringBuilder(indent); for (int i=0 ; i < indent; i++) { indentBuf.append(' '); } buffer.append(indentBuf); buffer.append("ASN.1 Element"); buffer.append(EOL); buffer.append(indentBuf); buffer.append(" BER Type: "); buffer.append(byteToHex(type)); buffer.append(EOL); buffer.append(indentBuf); buffer.append(" Value ("); buffer.append(value.length); buffer.append(" bytes)"); buffer.append(EOL); byteArrayToHexPlusAscii(buffer, value, indent+2); } }