All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sun.security.util.DerValue Maven / Gradle / Ivy

/*
 * Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.security.util;

import sun.nio.cs.UTF_32BE;
import sun.util.calendar.CalendarDate;
import sun.util.calendar.CalendarSystem;

import java.io.*;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.util.*;

import static java.nio.charset.StandardCharsets.*;

/**
 * Represents a single DER-encoded value.  DER encoding rules are a subset
 * of the "Basic" Encoding Rules (BER), but they only support a single way
 * ("Definite" encoding) to encode any given value.
 *
 * 

All DER-encoded data are triples {type, length, data}. This * class represents such tagged values as they have been read (or constructed), * and provides structured access to the encoded data. * *

At this time, this class supports only a subset of the types of DER * data encodings which are defined. That subset is sufficient for parsing * most X.509 certificates, and working with selected additional formats * (such as PKCS #10 certificate requests, and some kinds of PKCS #7 data). * * A note with respect to T61/Teletex strings: From RFC 1617, section 4.1.3 * and RFC 5280, section 8, we assume that this kind of string will contain * ISO-8859-1 characters only. * * * @author David Brownell * @author Amit Kapoor * @author Hemma Prafullchandra */ public class DerValue { /** The tag class types */ public static final byte TAG_UNIVERSAL = (byte)0x000; public static final byte TAG_APPLICATION = (byte)0x040; public static final byte TAG_CONTEXT = (byte)0x080; public static final byte TAG_PRIVATE = (byte)0x0c0; /* * The type starts at the first byte of the encoding, and * is one of these tag_* values. That may be all the type * data that is needed. */ /* * These tags are the "universal" tags ... they mean the same * in all contexts. (Mask with 0x1f -- five bits.) */ /** Tag value indicating an ASN.1 "BOOLEAN" value. */ public static final byte tag_Boolean = 0x01; /** Tag value indicating an ASN.1 "INTEGER" value. */ public static final byte tag_Integer = 0x02; /** Tag value indicating an ASN.1 "BIT STRING" value. */ public static final byte tag_BitString = 0x03; /** Tag value indicating an ASN.1 "OCTET STRING" value. */ public static final byte tag_OctetString = 0x04; /** Tag value indicating an ASN.1 "NULL" value. */ public static final byte tag_Null = 0x05; /** Tag value indicating an ASN.1 "OBJECT IDENTIFIER" value. */ public static final byte tag_ObjectId = 0x06; /** Tag value including an ASN.1 "ENUMERATED" value */ public static final byte tag_Enumerated = 0x0A; /** Tag value indicating an ASN.1 "UTF8String" value. */ public static final byte tag_UTF8String = 0x0C; /** Tag value including a "printable" string */ public static final byte tag_PrintableString = 0x13; /** Tag value including a "teletype" string */ public static final byte tag_T61String = 0x14; /** Tag value including an ASCII string */ public static final byte tag_IA5String = 0x16; /** Tag value indicating an ASN.1 "UTCTime" value. */ public static final byte tag_UtcTime = 0x17; /** Tag value indicating an ASN.1 "GeneralizedTime" value. */ public static final byte tag_GeneralizedTime = 0x18; /** Tag value indicating an ASN.1 "GenerallString" value. */ public static final byte tag_GeneralString = 0x1B; /** Tag value indicating an ASN.1 "UniversalString" value. */ public static final byte tag_UniversalString = 0x1C; /** Tag value indicating an ASN.1 "BMPString" value. */ public static final byte tag_BMPString = 0x1E; // CONSTRUCTED seq/set /** * Tag value indicating an ASN.1 * "SEQUENCE" (zero to N elements, order is significant). */ public static final byte tag_Sequence = 0x30; /** * Tag value indicating an ASN.1 * "SEQUENCE OF" (one to N elements, order is significant). */ public static final byte tag_SequenceOf = 0x30; /** * Tag value indicating an ASN.1 * "SET" (zero to N members, order does not matter). */ public static final byte tag_Set = 0x31; /** * Tag value indicating an ASN.1 * "SET OF" (one to N members, order does not matter). */ public static final byte tag_SetOf = 0x31; // This class is mostly immutable except that: // // 1. resetTag() modifies the tag // 2. the data field is mutable // // For compatibility, data, getData() and resetTag() are preserved. // A modern caller should call withTag() or data() instead. // // Also, some constructors have not cloned buffer, so the data could // be modified externally. public /*final*/ byte tag; final byte[] buffer; private final int start; final int end; private final boolean allowBER; // Unsafe. Legacy. Never null. public final DerInputStream data; /* * These values are the high order bits for the other kinds of tags. */ /** * Returns true if the tag class is UNIVERSAL. */ public boolean isUniversal() { return ((tag & 0x0c0) == 0x000); } /** * Returns true if the tag class is APPLICATION. */ public boolean isApplication() { return ((tag & 0x0c0) == 0x040); } /** * Returns true iff the CONTEXT SPECIFIC bit is set in the type tag. * This is associated with the ASN.1 "DEFINED BY" syntax. */ public boolean isContextSpecific() { return ((tag & 0x0c0) == 0x080); } /** * Returns true iff the CONTEXT SPECIFIC TAG matches the passed tag. */ public boolean isContextSpecific(byte cntxtTag) { if (!isContextSpecific()) { return false; } return ((tag & 0x01f) == cntxtTag); } boolean isPrivate() { return ((tag & 0x0c0) == 0x0c0); } /** Returns true iff the CONSTRUCTED bit is set in the type tag. */ public boolean isConstructed() { return ((tag & 0x020) == 0x020); } /** * Returns true iff the CONSTRUCTED TAG matches the passed tag. */ public boolean isConstructed(byte constructedTag) { if (!isConstructed()) { return false; } return ((tag & 0x01f) == constructedTag); } /** * Creates a new DerValue by specifying all its fields. */ DerValue(byte tag, byte[] buffer, int start, int end, boolean allowBER) { if ((tag & 0x1f) == 0x1f) { throw new IllegalArgumentException("Tag number over 30 is not supported"); } this.tag = tag; this.buffer = buffer; this.start = start; this.end = end; this.allowBER = allowBER; this.data = data(); } /** * Creates a PrintableString or UTF8string DER value from a string. */ public DerValue(String value) { this(isPrintableString(value) ? tag_PrintableString : tag_UTF8String, value); } private static boolean isPrintableString(String value) { for (int i = 0; i < value.length(); i++) { if (!isPrintableStringChar(value.charAt(i))) { return false; } } return true; } /** * Creates a string type DER value from a String object * @param stringTag the tag for the DER value to create * @param value the String object to use for the DER value */ public DerValue(byte stringTag, String value) { this(stringTag, string2bytes(stringTag, value), false); } private static byte[] string2bytes(byte stringTag, String value) { Charset charset = switch (stringTag) { case tag_PrintableString, tag_IA5String, tag_GeneralString -> US_ASCII; case tag_T61String -> ISO_8859_1; case tag_BMPString -> UTF_16BE; case tag_UTF8String -> UTF_8; case tag_UniversalString -> Charset.forName("UTF_32BE"); default -> throw new IllegalArgumentException("Unsupported DER string type"); }; return value.getBytes(charset); } DerValue(byte tag, byte[] buffer, boolean allowBER) { this(tag, buffer, 0, buffer.length, allowBER); } /** * Creates a DerValue from a tag and some DER-encoded data. * * This is a public constructor. * * @param tag the DER type tag * @param buffer the DER-encoded data */ public DerValue(byte tag, byte[] buffer) { this(tag, buffer.clone(), true); } /** * Wraps an DerOutputStream. All bytes currently written * into the stream will become the content of the newly * created DerValue. * * Attention: do not reset the DerOutputStream after this call. * No array copying is made. * * @param tag the tag * @param out the DerOutputStream * @returns a new DerValue using out as its content */ public static DerValue wrap(byte tag, DerOutputStream out) { return new DerValue(tag, out.buf(), 0, out.size(), false); } /** * Parse an ASN.1/BER encoded datum. The entire encoding must hold exactly * one datum, including its tag and length. * * This is a public constructor. */ public DerValue(byte[] encoding) throws IOException { this(encoding.clone(), 0, encoding.length, true, false); } /** * Parse an ASN.1 encoded datum from a byte array. * * @param buf the byte array containing the DER-encoded datum * @param offset where the encoded datum starts inside {@code buf} * @param len length of bytes to parse inside {@code buf} * @param allowBER whether BER is allowed * @param allowMore whether extra bytes are allowed after the encoded datum. * If true, {@code len} can be bigger than the length of * the encoded datum. * * @throws IOException if it's an invalid encoding or there are extra bytes * after the encoded datum and {@code allowMore} is false. */ DerValue(byte[] buf, int offset, int len, boolean allowBER, boolean allowMore) throws IOException { if (len < 2) { throw new IOException("Too short"); } int pos = offset; tag = buf[pos++]; if ((tag & 0x1f) == 0x1f) { throw new IOException("Tag number over 30 at " + offset + " is not supported"); } int lenByte = buf[pos++]; int length; if (lenByte == (byte) 0x80) { // indefinite length if (!allowBER) { throw new IOException("Indefinite length encoding " + "not supported with DER"); } if (!isConstructed()) { throw new IOException("Indefinite length encoding " + "not supported with non-constructed data"); } // Reconstruct data source buf = DerIndefLenConverter.convertStream( new ByteArrayInputStream(buf, pos, len - (pos - offset)), tag); offset = 0; len = buf.length; pos = 2; if (tag != buf[0]) { throw new IOException("Indefinite length encoding not supported"); } lenByte = buf[1]; if (lenByte == (byte) 0x80) { throw new IOException("Indefinite len conversion failed"); } } if ((lenByte & 0x080) == 0x00) { // short form, 1 byte datum length = lenByte; } else { // long form lenByte &= 0x07f; if (lenByte > 4) { throw new IOException("Invalid lenByte"); } if (len < 2 + lenByte) { throw new IOException("Not enough length bytes"); } length = 0x0ff & buf[pos++]; lenByte--; if (length == 0 && !allowBER) { // DER requires length value be encoded in minimum number of bytes throw new IOException("Redundant length bytes found"); } while (lenByte-- > 0) { length <<= 8; length += 0x0ff & buf[pos++]; } if (length < 0) { throw new IOException("Invalid length bytes"); } else if (length <= 127 && !allowBER) { throw new IOException("Should use short form for length"); } } // pos is now at the beginning of the content if (len - length < pos - offset) { throw new EOFException("not enough content"); } if (len - length > pos - offset && !allowMore) { throw new IOException("extra data at the end"); } this.buffer = buf; this.start = pos; this.end = pos + length; this.allowBER = allowBER; this.data = data(); } // Get an ASN1/DER encoded datum from an input stream w/ additional // arg to control whether DER checks are enforced. DerValue(InputStream in, boolean allowBER) throws IOException { this.tag = (byte)in.read(); if ((tag & 0x1f) == 0x1f) { throw new IOException("Tag number over 30 is not supported"); } int length = DerInputStream.getLength(in); if (length == -1) { // indefinite length encoding found if (!allowBER) { throw new IOException("Indefinite length encoding " + "not supported with DER"); } if (!isConstructed()) { throw new IOException("Indefinite length encoding " + "not supported with non-constructed data"); } this.buffer = DerIndefLenConverter.convertStream(in, tag); ByteArrayInputStream bin = new ByteArrayInputStream(this.buffer); if (tag != bin.read()) { throw new IOException ("Indefinite length encoding not supported"); } length = DerInputStream.getDefiniteLength(bin); this.start = this.buffer.length - bin.available(); this.end = this.start + length; // position of in is undetermined. Precisely, it might be n-bytes // after DerValue, and these n bytes are at the end of this.buffer // after this.end. } else { this.buffer = IOUtils.readExactlyNBytes(in, length); this.start = 0; this.end = length; // position of in is right after the DerValue } this.allowBER = allowBER; this.data = data(); } /** * Get an ASN1/DER encoded datum from an input stream. The * stream may have additional data following the encoded datum. * In case of indefinite length encoded datum, the input stream * must hold only one datum, i.e. all bytes in the stream might * be consumed. Otherwise, only one DerValue will be consumed. * * @param in the input stream holding a single DER datum, * which may be followed by additional data */ public DerValue(InputStream in) throws IOException { this(in, true); } /** * Encode an ASN1/DER encoded datum onto a DER output stream. */ public void encode(DerOutputStream out) throws IOException { out.write(tag); out.putLength(end - start); out.write(buffer, start, end - start); data.pos = data.end; // Compatibility. Reach end. } /** * Returns a new DerInputStream pointing at the start of this * DerValue's content. * * @return the new DerInputStream value */ public final DerInputStream data() { return new DerInputStream(buffer, start, end - start, allowBER); } /** * Returns the data field inside this class directly. * * Both this method and the {@link #data} field should be avoided. * Consider using {@link #data()} instead. */ public final DerInputStream getData() { return data; } public final byte getTag() { return tag; } /** * Returns an ASN.1 BOOLEAN * * @return the boolean held in this DER value */ public boolean getBoolean() throws IOException { if (tag != tag_Boolean) { throw new IOException("DerValue.getBoolean, not a BOOLEAN " + tag); } if (end - start != 1) { throw new IOException("DerValue.getBoolean, invalid length " + (end - start)); } data.pos = data.end; // Compatibility. Reach end. return buffer[start] != 0; } /** * Returns an ASN.1 OBJECT IDENTIFIER. * * @return the OID held in this DER value */ public ObjectIdentifier getOID() throws IOException { if (tag != tag_ObjectId) { throw new IOException("DerValue.getOID, not an OID " + tag); } data.pos = data.end; // Compatibility. Reach end. return new ObjectIdentifier(Arrays.copyOfRange(buffer, start, end)); } /** * Returns an ASN.1 OCTET STRING * * @return the octet string held in this DER value */ public byte[] getOctetString() throws IOException { if (tag != tag_OctetString && !isConstructed(tag_OctetString)) { throw new IOException( "DerValue.getOctetString, not an Octet String: " + tag); } // Note: do not attempt to call buffer.read(bytes) at all. There's a // known bug that it returns -1 instead of 0. if (end - start == 0) { return new byte[0]; } data.pos = data.end; // Compatibility. Reach end. if (!isConstructed()) { return Arrays.copyOfRange(buffer, start, end); } else { ByteArrayOutputStream bout = new ByteArrayOutputStream(); DerInputStream dis = data(); while (dis.available() > 0) { bout.write(dis.getDerValue().getOctetString()); } return bout.toByteArray(); } } /** * Returns an ASN.1 INTEGER value as an integer. * * @return the integer held in this DER value. */ public int getInteger() throws IOException { return getIntegerInternal(tag_Integer); } private int getIntegerInternal(byte expectedTag) throws IOException { BigInteger result = getBigIntegerInternal(expectedTag, false); if (result.compareTo(BigInteger.valueOf(Integer.MIN_VALUE)) < 0) { throw new IOException("Integer below minimum valid value"); } if (result.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0) { throw new IOException("Integer exceeds maximum valid value"); } return result.intValue(); } /** * Returns an ASN.1 INTEGER value as a BigInteger. * * @return the integer held in this DER value as a BigInteger. */ public BigInteger getBigInteger() throws IOException { return getBigIntegerInternal(tag_Integer, false); } /** * Returns an ASN.1 INTEGER value as a positive BigInteger. * This is just to deal with implementations that incorrectly encode * some values as negative. * * @return the integer held in this DER value as a BigInteger. */ public BigInteger getPositiveBigInteger() throws IOException { return getBigIntegerInternal(tag_Integer, true); } /** * Returns a BigInteger value * * @param makePositive whether to always return a positive value, * irrespective of actual encoding * @return the integer as a BigInteger. */ private BigInteger getBigIntegerInternal(byte expectedTag, boolean makePositive) throws IOException { if (tag != expectedTag) { throw new IOException("DerValue.getBigIntegerInternal, not expected " + tag); } if (end == start) { throw new IOException("Invalid encoding: zero length Int value"); } data.pos = data.end; // Compatibility. Reach end. if (!allowBER && (end - start >= 2 && (buffer[start] == 0) && (buffer[start + 1] >= 0))) { throw new IOException("Invalid encoding: redundant leading 0s"); } return makePositive ? new BigInteger(1, buffer, start, end - start) : new BigInteger(buffer, start, end - start); } /** * Returns an ASN.1 ENUMERATED value. * * @return the integer held in this DER value. */ public int getEnumerated() throws IOException { return getIntegerInternal(tag_Enumerated); } /** * Returns an ASN.1 BIT STRING value. The bit string must be byte-aligned. * * @return the bit string held in this value */ public byte[] getBitString() throws IOException { return getBitString(false); } /** * Returns an ASN.1 BIT STRING value that need not be byte-aligned. * * @return a BitArray representing the bit string held in this value */ public BitArray getUnalignedBitString() throws IOException { return getUnalignedBitString(false); } /** * Returns the name component as a Java string, regardless of its * encoding restrictions (ASCII, T61, Printable, IA5, BMP, UTF8). */ // TBD: Need encoder for UniversalString before it can be handled. public String getAsString() throws IOException { return switch (tag) { case tag_UTF8String -> getUTF8String(); case tag_PrintableString -> getPrintableString(); case tag_T61String -> getT61String(); case tag_IA5String -> getIA5String(); case tag_UniversalString -> getUniversalString(); case tag_BMPString -> getBMPString(); case tag_GeneralString -> getGeneralString(); default -> null; }; } /** * Returns an ASN.1 BIT STRING value, with the tag assumed implicit * based on the parameter. The bit string must be byte-aligned. * * @param tagImplicit if true, the tag is assumed implicit. * @return the bit string held in this value */ public byte[] getBitString(boolean tagImplicit) throws IOException { if (!tagImplicit) { if (tag != tag_BitString) { throw new IOException("DerValue.getBitString, not a bit string " + tag); } } if (end == start) { throw new IOException("Invalid encoding: zero length bit string"); } int numOfPadBits = buffer[start]; if ((numOfPadBits < 0) || (numOfPadBits > 7)) { throw new IOException("Invalid number of padding bits"); } // minus the first byte which indicates the number of padding bits byte[] retval = Arrays.copyOfRange(buffer, start + 1, end); if (numOfPadBits != 0) { // get rid of the padding bits retval[end - start - 2] &= (0xff << numOfPadBits); } data.pos = data.end; // Compatibility. Reach end. return retval; } /** * Returns an ASN.1 BIT STRING value, with the tag assumed implicit * based on the parameter. The bit string need not be byte-aligned. * * @param tagImplicit if true, the tag is assumed implicit. * @return the bit string held in this value */ public BitArray getUnalignedBitString(boolean tagImplicit) throws IOException { if (!tagImplicit) { if (tag != tag_BitString) { throw new IOException("DerValue.getBitString, not a bit string " + tag); } } if (end == start) { throw new IOException("Invalid encoding: zero length bit string"); } data.pos = data.end; // Compatibility. Reach end. int numOfPadBits = buffer[start]; if ((numOfPadBits < 0) || (numOfPadBits > 7)) { throw new IOException("Invalid number of padding bits"); } if (end == start + 1) { return new BitArray(0); } else { return new BitArray(((end - start - 1) << 3) - numOfPadBits, Arrays.copyOfRange(buffer, start + 1, end)); } } /** * Helper routine to return all the bytes contained in the * DerInputStream associated with this object. */ public byte[] getDataBytes() throws IOException { data.pos = data.end; // Compatibility. Reach end. return Arrays.copyOfRange(buffer, start, end); } private String readStringInternal(byte expectedTag, Charset cs) throws IOException { if (tag != expectedTag) { throw new IOException("Incorrect string type " + tag + " is not " + expectedTag); } data.pos = data.end; // Compatibility. Reach end. return new String(buffer, start, end - start, cs); } /** * Returns an ASN.1 STRING value * * @return the printable string held in this value */ public String getPrintableString() throws IOException { return readStringInternal(tag_PrintableString, US_ASCII); } /** * Returns an ASN.1 T61 (Teletype) STRING value * * @return the teletype string held in this value */ public String getT61String() throws IOException { return readStringInternal(tag_T61String, ISO_8859_1); } /** * Returns an ASN.1 IA5 (ASCII) STRING value * * @return the ASCII string held in this value */ public String getIA5String() throws IOException { return readStringInternal(tag_IA5String, US_ASCII); } /** * Returns the ASN.1 BMP (Unicode) STRING value as a Java string. * * @return a string corresponding to the encoded BMPString held in * this value */ public String getBMPString() throws IOException { // BMPString is the same as Unicode in big endian, unmarked format. return readStringInternal(tag_BMPString, UTF_16BE); } /** * Returns the ASN.1 UTF-8 STRING value as a Java String. * * @return a string corresponding to the encoded UTF8String held in * this value */ public String getUTF8String() throws IOException { return readStringInternal(tag_UTF8String, UTF_8); } /** * Returns the ASN.1 GENERAL STRING value as a Java String. * * @return a string corresponding to the encoded GeneralString held in * this value */ public String getGeneralString() throws IOException { return readStringInternal(tag_GeneralString, US_ASCII); } /** * Returns the ASN.1 UNIVERSAL (UTF-32) STRING value as a Java String. * * @return a string corresponding to the encoded UniversalString held in * this value */ public String getUniversalString() throws IOException { return readStringInternal(tag_UniversalString, new UTF_32BE()); } /** * Reads the ASN.1 NULL value */ public void getNull() throws IOException { if (tag != tag_Null) { throw new IOException("DerValue.getNull, not NULL: " + tag); } if (end != start) { throw new IOException("NULL should contain no data"); } } /** * Private helper routine to extract time from the der value. * @param generalized true if Generalized Time is to be read, false * if UTC Time is to be read. */ private Date getTimeInternal(boolean generalized) throws IOException { /* * UTC time encoded as ASCII chars: * YYMMDDhhmmZ * YYMMDDhhmmssZ * YYMMDDhhmm+hhmm * YYMMDDhhmm-hhmm * YYMMDDhhmmss+hhmm * YYMMDDhhmmss-hhmm * UTC Time is broken in storing only two digits of year. * If YY < 50, we assume 20YY; * if YY >= 50, we assume 19YY, as per RFC 5280. * * Generalized time has a four-digit year and allows any * precision specified in ISO 8601. However, for our purposes, * we will only allow the same format as UTC time, except that * fractional seconds (millisecond precision) are supported. */ int year, month, day, hour, minute, second, millis; String type; int pos = start; int len = end - start; if (generalized) { type = "Generalized"; year = 1000 * toDigit(buffer[pos++], type); year += 100 * toDigit(buffer[pos++], type); year += 10 * toDigit(buffer[pos++], type); year += toDigit(buffer[pos++], type); len -= 2; // For the two extra YY } else { type = "UTC"; year = 10 * toDigit(buffer[pos++], type); year += toDigit(buffer[pos++], type); if (year < 50) { // origin 2000 year += 2000; } else { year += 1900; // origin 1900 } } month = 10 * toDigit(buffer[pos++], type); month += toDigit(buffer[pos++], type); day = 10 * toDigit(buffer[pos++], type); day += toDigit(buffer[pos++], type); hour = 10 * toDigit(buffer[pos++], type); hour += toDigit(buffer[pos++], type); minute = 10 * toDigit(buffer[pos++], type); minute += toDigit(buffer[pos++], type); len -= 10; // YYMMDDhhmm /* * We allow for non-encoded seconds, even though the * IETF-PKIX specification says that the seconds should * always be encoded even if it is zero. */ millis = 0; if (len > 2) { second = 10 * toDigit(buffer[pos++], type); second += toDigit(buffer[pos++], type); len -= 2; // handle fractional seconds (if present) if (generalized && (buffer[pos] == '.' || buffer[pos] == ',')) { len --; if (len == 0) { throw new IOException("Parse " + type + " time, empty fractional part"); } pos++; int precision = 0; while (buffer[pos] != 'Z' && buffer[pos] != '+' && buffer[pos] != '-') { // Validate all digits in the fractional part but // store millisecond precision only int thisDigit = toDigit(buffer[pos], type); precision++; len--; if (len == 0) { throw new IOException("Parse " + type + " time, invalid fractional part"); } pos++; switch (precision) { case 1 -> millis += 100 * thisDigit; case 2 -> millis += 10 * thisDigit; case 3 -> millis += thisDigit; } } if (precision == 0) { throw new IOException("Parse " + type + " time, empty fractional part"); } } } else second = 0; if (month == 0 || day == 0 || month > 12 || day > 31 || hour >= 24 || minute >= 60 || second >= 60) { throw new IOException("Parse " + type + " time, invalid format"); } /* * Generalized time can theoretically allow any precision, * but we're not supporting that. */ CalendarSystem gcal = CalendarSystem.getGregorianCalendar(); CalendarDate date = gcal.newCalendarDate(null); // no time zone date.setDate(year, month, day); date.setTimeOfDay(hour, minute, second, millis); long time = gcal.getTime(date); /* * Finally, "Z" or "+hhmm" or "-hhmm" ... offsets change hhmm */ if (! (len == 1 || len == 5)) { throw new IOException("Parse " + type + " time, invalid offset"); } int hr, min; switch (buffer[pos++]) { case '+': if (len != 5) { throw new IOException("Parse " + type + " time, invalid offset"); } hr = 10 * toDigit(buffer[pos++], type); hr += toDigit(buffer[pos++], type); min = 10 * toDigit(buffer[pos++], type); min += toDigit(buffer[pos++], type); if (hr >= 24 || min >= 60) { throw new IOException("Parse " + type + " time, +hhmm"); } time -= ((hr * 60) + min) * 60 * 1000; break; case '-': if (len != 5) { throw new IOException("Parse " + type + " time, invalid offset"); } hr = 10 * toDigit(buffer[pos++], type); hr += toDigit(buffer[pos++], type); min = 10 * toDigit(buffer[pos++], type); min += toDigit(buffer[pos++], type); if (hr >= 24 || min >= 60) { throw new IOException("Parse " + type + " time, -hhmm"); } time += ((hr * 60) + min) * 60 * 1000; break; case 'Z': if (len != 1) { throw new IOException("Parse " + type + " time, invalid format"); } break; default: throw new IOException("Parse " + type + " time, garbage offset"); } return new Date(time); } /** * Converts byte (represented as a char) to int. * @throws IOException if integer is not a valid digit in the specified * radix (10) */ private static int toDigit(byte b, String type) throws IOException { if (b < '0' || b > '9') { throw new IOException("Parse " + type + " time, invalid format"); } return b - '0'; } /** * Returns a Date if the DerValue is UtcTime. * * @return the Date held in this DER value */ public Date getUTCTime() throws IOException { if (tag != tag_UtcTime) { throw new IOException("DerValue.getUTCTime, not a UtcTime: " + tag); } if (end - start < 11 || end - start > 17) throw new IOException("DER UTC Time length error"); data.pos = data.end; // Compatibility. Reach end. return getTimeInternal(false); } /** * Returns a Date if the DerValue is GeneralizedTime. * * @return the Date held in this DER value */ public Date getGeneralizedTime() throws IOException { if (tag != tag_GeneralizedTime) { throw new IOException( "DerValue.getGeneralizedTime, not a GeneralizedTime: " + tag); } if (end - start < 13) throw new IOException("DER Generalized Time length error"); data.pos = data.end; // Compatibility. Reach end. return getTimeInternal(true); } /** * Bitwise equality comparison. DER encoded values have a single * encoding, so that bitwise equality of the encoded values is an * efficient way to establish equivalence of the unencoded values. * * @param o the object being compared with this one */ @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof DerValue)) { return false; } DerValue other = (DerValue) o; if (tag != other.tag) { return false; } if (buffer == other.buffer && start == other.start && end == other.end) { return true; } return Arrays.equals(buffer, start, end, other.buffer, other.start, other.end); } /** * Returns a printable representation of the value. * * @return printable representation of the value */ @Override public String toString() { return String.format("DerValue(%02x, %s, %d, %d)", 0xff & tag, buffer, start, end); } /** * Returns a DER-encoded value, such that if it's passed to the * DerValue constructor, a value equivalent to "this" is returned. * * @return DER-encoded value, including tag and length. */ public byte[] toByteArray() throws IOException { data.pos = data.start; // Compatibility. At head. // Minimize content duplication by writing out tag and length only DerOutputStream out = new DerOutputStream(); out.write(tag); out.putLength(end - start); int headLen = out.size(); byte[] result = Arrays.copyOf(out.buf(), end - start + headLen); System.arraycopy(buffer, start, result, headLen, end - start); return result; } /** * For "set" and "sequence" types, this function may be used * to return a DER stream of the members of the set or sequence. * This operation is not supported for primitive types such as * integers or bit strings. */ public DerInputStream toDerInputStream() throws IOException { if (tag == tag_Sequence || tag == tag_Set) return data; throw new IOException("toDerInputStream rejects tag type " + tag); } /** * Get the length of the encoded value. */ public int length() { return end - start; } /** * Determine if a character is one of the permissible characters for * PrintableString: * A-Z, a-z, 0-9, space, apostrophe (39), left and right parentheses, * plus sign, comma, hyphen, period, slash, colon, equals sign, * and question mark. * * Characters that are *not* allowed in PrintableString include * exclamation point, quotation mark, number sign, dollar sign, * percent sign, ampersand, asterisk, semicolon, less than sign, * greater than sign, at sign, left and right square brackets, * backslash, circumflex (94), underscore, back quote (96), * left and right curly brackets, vertical line, tilde, * and the control codes (0-31 and 127). * * This list is based on X.680 (the ASN.1 spec). */ public static boolean isPrintableStringChar(char ch) { if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')) { return true; } else { switch (ch) { case ' ': /* space */ case '\'': /* apostrophe */ case '(': /* left paren */ case ')': /* right paren */ case '+': /* plus */ case ',': /* comma */ case '-': /* hyphen */ case '.': /* period */ case '/': /* slash */ case ':': /* colon */ case '=': /* equals */ case '?': /* question mark */ return true; default: return false; } } } /** * Create the tag of the attribute. * * @param tagClass the tag class type, one of UNIVERSAL, CONTEXT, * APPLICATION or PRIVATE * @param form if true, the value is constructed, otherwise it * is primitive. * @param val the tag value */ public static byte createTag(byte tagClass, boolean form, byte val) { if (val < 0 || val > 30) { throw new IllegalArgumentException("Tag number over 30 is not supported"); } byte tag = (byte)(tagClass | val); if (form) { tag |= (byte)0x20; } return (tag); } /** * Set the tag of the attribute. Commonly used to reset the * tag value used for IMPLICIT encodings. * * This method should be avoided, consider using withTag() instead. * * @param tag the tag value */ public void resetTag(byte tag) { this.tag = tag; } /** * Returns a new DerValue with a different tag. This method is used * to convert a DerValue decoded from an IMPLICIT encoding to its real * tag. The content is not checked against the tag in this method. * * @param newTag the new tag * @return a new DerValue */ public DerValue withTag(byte newTag) { return new DerValue(newTag, buffer, start, end, allowBER); } /** * Returns a hashcode for this DerValue. * * @return a hashcode for this DerValue. */ @Override public int hashCode() { int result = tag; for (int i = start; i < end; i++) { result = 31 * result + buffer[i]; } return result; } /** * Reads the sub-values in a constructed DerValue. * * @param expectedTag the expected tag, or zero if we don't check. * This is useful when this DerValue is IMPLICIT. * @param startLen estimated number of sub-values * @return the sub-values in an array */ DerValue[] subs(byte expectedTag, int startLen) throws IOException { if (expectedTag != 0 && expectedTag != tag) { throw new IOException("Not the correct tag"); } List result = new ArrayList<>(startLen); DerInputStream dis = data(); while (dis.available() > 0) { result.add(dis.getDerValue()); } return result.toArray(new DerValue[0]); } public void clear() { Arrays.fill(buffer, start, end, (byte)0); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy