Path: blob/master/src/java.base/share/classes/sun/security/util/DerValue.java
41159 views
/*1* Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.security.util;2627import sun.nio.cs.UTF_32BE;28import sun.util.calendar.CalendarDate;29import sun.util.calendar.CalendarSystem;3031import java.io.*;32import java.math.BigInteger;33import java.nio.charset.Charset;34import java.util.*;3536import static java.nio.charset.StandardCharsets.*;3738/**39* Represents a single DER-encoded value. DER encoding rules are a subset40* of the "Basic" Encoding Rules (BER), but they only support a single way41* ("Definite" encoding) to encode any given value.42*43* <P>All DER-encoded data are triples <em>{type, length, data}</em>. This44* class represents such tagged values as they have been read (or constructed),45* and provides structured access to the encoded data.46*47* <P>At this time, this class supports only a subset of the types of DER48* data encodings which are defined. That subset is sufficient for parsing49* most X.509 certificates, and working with selected additional formats50* (such as PKCS #10 certificate requests, and some kinds of PKCS #7 data).51*52* A note with respect to T61/Teletex strings: From RFC 1617, section 4.1.353* and RFC 5280, section 8, we assume that this kind of string will contain54* ISO-8859-1 characters only.55*56*57* @author David Brownell58* @author Amit Kapoor59* @author Hemma Prafullchandra60*/61public class DerValue {6263/** The tag class types */64public static final byte TAG_UNIVERSAL = (byte)0x000;65public static final byte TAG_APPLICATION = (byte)0x040;66public static final byte TAG_CONTEXT = (byte)0x080;67public static final byte TAG_PRIVATE = (byte)0x0c0;6869/*70* The type starts at the first byte of the encoding, and71* is one of these tag_* values. That may be all the type72* data that is needed.73*/7475/*76* These tags are the "universal" tags ... they mean the same77* in all contexts. (Mask with 0x1f -- five bits.)78*/7980/** Tag value indicating an ASN.1 "BOOLEAN" value. */81public static final byte tag_Boolean = 0x01;8283/** Tag value indicating an ASN.1 "INTEGER" value. */84public static final byte tag_Integer = 0x02;8586/** Tag value indicating an ASN.1 "BIT STRING" value. */87public static final byte tag_BitString = 0x03;8889/** Tag value indicating an ASN.1 "OCTET STRING" value. */90public static final byte tag_OctetString = 0x04;9192/** Tag value indicating an ASN.1 "NULL" value. */93public static final byte tag_Null = 0x05;9495/** Tag value indicating an ASN.1 "OBJECT IDENTIFIER" value. */96public static final byte tag_ObjectId = 0x06;9798/** Tag value including an ASN.1 "ENUMERATED" value */99public static final byte tag_Enumerated = 0x0A;100101/** Tag value indicating an ASN.1 "UTF8String" value. */102public static final byte tag_UTF8String = 0x0C;103104/** Tag value including a "printable" string */105public static final byte tag_PrintableString = 0x13;106107/** Tag value including a "teletype" string */108public static final byte tag_T61String = 0x14;109110/** Tag value including an ASCII string */111public static final byte tag_IA5String = 0x16;112113/** Tag value indicating an ASN.1 "UTCTime" value. */114public static final byte tag_UtcTime = 0x17;115116/** Tag value indicating an ASN.1 "GeneralizedTime" value. */117public static final byte tag_GeneralizedTime = 0x18;118119/** Tag value indicating an ASN.1 "GenerallString" value. */120public static final byte tag_GeneralString = 0x1B;121122/** Tag value indicating an ASN.1 "UniversalString" value. */123public static final byte tag_UniversalString = 0x1C;124125/** Tag value indicating an ASN.1 "BMPString" value. */126public static final byte tag_BMPString = 0x1E;127128// CONSTRUCTED seq/set129130/**131* Tag value indicating an ASN.1132* "SEQUENCE" (zero to N elements, order is significant).133*/134public static final byte tag_Sequence = 0x30;135136/**137* Tag value indicating an ASN.1138* "SEQUENCE OF" (one to N elements, order is significant).139*/140public static final byte tag_SequenceOf = 0x30;141142/**143* Tag value indicating an ASN.1144* "SET" (zero to N members, order does not matter).145*/146public static final byte tag_Set = 0x31;147148/**149* Tag value indicating an ASN.1150* "SET OF" (one to N members, order does not matter).151*/152public static final byte tag_SetOf = 0x31;153154// This class is mostly immutable except that:155//156// 1. resetTag() modifies the tag157// 2. the data field is mutable158//159// For compatibility, data, getData() and resetTag() are preserved.160// A modern caller should call withTag() or data() instead.161//162// Also, some constructors have not cloned buffer, so the data could163// be modified externally.164165public /*final*/ byte tag;166final byte[] buffer;167private final int start;168final int end;169private final boolean allowBER;170171// Unsafe. Legacy. Never null.172public final DerInputStream data;173174/*175* These values are the high order bits for the other kinds of tags.176*/177178/**179* Returns true if the tag class is UNIVERSAL.180*/181public boolean isUniversal() { return ((tag & 0x0c0) == 0x000); }182183/**184* Returns true if the tag class is APPLICATION.185*/186public boolean isApplication() { return ((tag & 0x0c0) == 0x040); }187188/**189* Returns true iff the CONTEXT SPECIFIC bit is set in the type tag.190* This is associated with the ASN.1 "DEFINED BY" syntax.191*/192public boolean isContextSpecific() { return ((tag & 0x0c0) == 0x080); }193194/**195* Returns true iff the CONTEXT SPECIFIC TAG matches the passed tag.196*/197public boolean isContextSpecific(byte cntxtTag) {198if (!isContextSpecific()) {199return false;200}201return ((tag & 0x01f) == cntxtTag);202}203204boolean isPrivate() { return ((tag & 0x0c0) == 0x0c0); }205206/** Returns true iff the CONSTRUCTED bit is set in the type tag. */207public boolean isConstructed() { return ((tag & 0x020) == 0x020); }208209/**210* Returns true iff the CONSTRUCTED TAG matches the passed tag.211*/212public boolean isConstructed(byte constructedTag) {213if (!isConstructed()) {214return false;215}216return ((tag & 0x01f) == constructedTag);217}218219/**220* Creates a new DerValue by specifying all its fields.221*/222DerValue(byte tag, byte[] buffer, int start, int end, boolean allowBER) {223if ((tag & 0x1f) == 0x1f) {224throw new IllegalArgumentException("Tag number over 30 is not supported");225}226this.tag = tag;227this.buffer = buffer;228this.start = start;229this.end = end;230this.allowBER = allowBER;231this.data = data();232}233234/**235* Creates a PrintableString or UTF8string DER value from a string.236*/237public DerValue(String value) {238this(isPrintableString(value) ? tag_PrintableString : tag_UTF8String,239value);240}241242private static boolean isPrintableString(String value) {243for (int i = 0; i < value.length(); i++) {244if (!isPrintableStringChar(value.charAt(i))) {245return false;246}247}248return true;249}250251/**252* Creates a string type DER value from a String object253* @param stringTag the tag for the DER value to create254* @param value the String object to use for the DER value255*/256public DerValue(byte stringTag, String value) {257this(stringTag, string2bytes(stringTag, value), false);258}259260private static byte[] string2bytes(byte stringTag, String value) {261Charset charset = switch (stringTag) {262case tag_PrintableString, tag_IA5String, tag_GeneralString -> US_ASCII;263case tag_T61String -> ISO_8859_1;264case tag_BMPString -> UTF_16BE;265case tag_UTF8String -> UTF_8;266case tag_UniversalString -> Charset.forName("UTF_32BE");267default -> throw new IllegalArgumentException("Unsupported DER string type");268};269return value.getBytes(charset);270}271272DerValue(byte tag, byte[] buffer, boolean allowBER) {273this(tag, buffer, 0, buffer.length, allowBER);274}275276/**277* Creates a DerValue from a tag and some DER-encoded data.278*279* This is a public constructor.280*281* @param tag the DER type tag282* @param buffer the DER-encoded data283*/284public DerValue(byte tag, byte[] buffer) {285this(tag, buffer.clone(), true);286}287288/**289* Wraps an DerOutputStream. All bytes currently written290* into the stream will become the content of the newly291* created DerValue.292*293* Attention: do not reset the DerOutputStream after this call.294* No array copying is made.295*296* @param tag the tag297* @param out the DerOutputStream298* @returns a new DerValue using out as its content299*/300public static DerValue wrap(byte tag, DerOutputStream out) {301return new DerValue(tag, out.buf(), 0, out.size(), false);302}303304/**305* Parse an ASN.1/BER encoded datum. The entire encoding must hold exactly306* one datum, including its tag and length.307*308* This is a public constructor.309*/310public DerValue(byte[] encoding) throws IOException {311this(encoding.clone(), 0, encoding.length, true, false);312}313314/**315* Parse an ASN.1 encoded datum from a byte array.316*317* @param buf the byte array containing the DER-encoded datum318* @param offset where the encoded datum starts inside {@code buf}319* @param len length of bytes to parse inside {@code buf}320* @param allowBER whether BER is allowed321* @param allowMore whether extra bytes are allowed after the encoded datum.322* If true, {@code len} can be bigger than the length of323* the encoded datum.324*325* @throws IOException if it's an invalid encoding or there are extra bytes326* after the encoded datum and {@code allowMore} is false.327*/328DerValue(byte[] buf, int offset, int len, boolean allowBER, boolean allowMore)329throws IOException {330331if (len < 2) {332throw new IOException("Too short");333}334int pos = offset;335tag = buf[pos++];336if ((tag & 0x1f) == 0x1f) {337throw new IOException("Tag number over 30 at " + offset + " is not supported");338}339int lenByte = buf[pos++];340341int length;342if (lenByte == (byte) 0x80) { // indefinite length343if (!allowBER) {344throw new IOException("Indefinite length encoding " +345"not supported with DER");346}347if (!isConstructed()) {348throw new IOException("Indefinite length encoding " +349"not supported with non-constructed data");350}351352// Reconstruct data source353buf = DerIndefLenConverter.convertStream(354new ByteArrayInputStream(buf, pos, len - (pos - offset)), tag);355offset = 0;356len = buf.length;357pos = 2;358359if (tag != buf[0]) {360throw new IOException("Indefinite length encoding not supported");361}362lenByte = buf[1];363if (lenByte == (byte) 0x80) {364throw new IOException("Indefinite len conversion failed");365}366}367368if ((lenByte & 0x080) == 0x00) { // short form, 1 byte datum369length = lenByte;370} else { // long form371lenByte &= 0x07f;372if (lenByte > 4) {373throw new IOException("Invalid lenByte");374}375if (len < 2 + lenByte) {376throw new IOException("Not enough length bytes");377}378length = 0x0ff & buf[pos++];379lenByte--;380if (length == 0 && !allowBER) {381// DER requires length value be encoded in minimum number of bytes382throw new IOException("Redundant length bytes found");383}384while (lenByte-- > 0) {385length <<= 8;386length += 0x0ff & buf[pos++];387}388if (length < 0) {389throw new IOException("Invalid length bytes");390} else if (length <= 127 && !allowBER) {391throw new IOException("Should use short form for length");392}393}394// pos is now at the beginning of the content395if (len - length < pos - offset) {396throw new EOFException("not enough content");397}398if (len - length > pos - offset && !allowMore) {399throw new IOException("extra data at the end");400}401this.buffer = buf;402this.start = pos;403this.end = pos + length;404this.allowBER = allowBER;405this.data = data();406}407408// Get an ASN1/DER encoded datum from an input stream w/ additional409// arg to control whether DER checks are enforced.410DerValue(InputStream in, boolean allowBER) throws IOException {411this.tag = (byte)in.read();412if ((tag & 0x1f) == 0x1f) {413throw new IOException("Tag number over 30 is not supported");414}415int length = DerInputStream.getLength(in);416if (length == -1) { // indefinite length encoding found417if (!allowBER) {418throw new IOException("Indefinite length encoding " +419"not supported with DER");420}421if (!isConstructed()) {422throw new IOException("Indefinite length encoding " +423"not supported with non-constructed data");424}425this.buffer = DerIndefLenConverter.convertStream(in, tag);426ByteArrayInputStream bin = new ByteArrayInputStream(this.buffer);427if (tag != bin.read()) {428throw new IOException429("Indefinite length encoding not supported");430}431length = DerInputStream.getDefiniteLength(bin);432this.start = this.buffer.length - bin.available();433this.end = this.start + length;434// position of in is undetermined. Precisely, it might be n-bytes435// after DerValue, and these n bytes are at the end of this.buffer436// after this.end.437} else {438this.buffer = IOUtils.readExactlyNBytes(in, length);439this.start = 0;440this.end = length;441// position of in is right after the DerValue442}443this.allowBER = allowBER;444this.data = data();445}446447/**448* Get an ASN1/DER encoded datum from an input stream. The449* stream may have additional data following the encoded datum.450* In case of indefinite length encoded datum, the input stream451* must hold only one datum, i.e. all bytes in the stream might452* be consumed. Otherwise, only one DerValue will be consumed.453*454* @param in the input stream holding a single DER datum,455* which may be followed by additional data456*/457public DerValue(InputStream in) throws IOException {458this(in, true);459}460461/**462* Encode an ASN1/DER encoded datum onto a DER output stream.463*/464public void encode(DerOutputStream out) throws IOException {465out.write(tag);466out.putLength(end - start);467out.write(buffer, start, end - start);468data.pos = data.end; // Compatibility. Reach end.469}470471/**472* Returns a new DerInputStream pointing at the start of this473* DerValue's content.474*475* @return the new DerInputStream value476*/477public final DerInputStream data() {478return new DerInputStream(buffer, start, end - start, allowBER);479}480481/**482* Returns the data field inside this class directly.483*484* Both this method and the {@link #data} field should be avoided.485* Consider using {@link #data()} instead.486*/487public final DerInputStream getData() {488return data;489}490491public final byte getTag() {492return tag;493}494495/**496* Returns an ASN.1 BOOLEAN497*498* @return the boolean held in this DER value499*/500public boolean getBoolean() throws IOException {501if (tag != tag_Boolean) {502throw new IOException("DerValue.getBoolean, not a BOOLEAN " + tag);503}504if (end - start != 1) {505throw new IOException("DerValue.getBoolean, invalid length "506+ (end - start));507}508data.pos = data.end; // Compatibility. Reach end.509return buffer[start] != 0;510}511512/**513* Returns an ASN.1 OBJECT IDENTIFIER.514*515* @return the OID held in this DER value516*/517public ObjectIdentifier getOID() throws IOException {518if (tag != tag_ObjectId) {519throw new IOException("DerValue.getOID, not an OID " + tag);520}521data.pos = data.end; // Compatibility. Reach end.522return new ObjectIdentifier(Arrays.copyOfRange(buffer, start, end));523}524525/**526* Returns an ASN.1 OCTET STRING527*528* @return the octet string held in this DER value529*/530public byte[] getOctetString() throws IOException {531532if (tag != tag_OctetString && !isConstructed(tag_OctetString)) {533throw new IOException(534"DerValue.getOctetString, not an Octet String: " + tag);535}536// Note: do not attempt to call buffer.read(bytes) at all. There's a537// known bug that it returns -1 instead of 0.538if (end - start == 0) {539return new byte[0];540}541542data.pos = data.end; // Compatibility. Reach end.543if (!isConstructed()) {544return Arrays.copyOfRange(buffer, start, end);545} else {546ByteArrayOutputStream bout = new ByteArrayOutputStream();547DerInputStream dis = data();548while (dis.available() > 0) {549bout.write(dis.getDerValue().getOctetString());550}551return bout.toByteArray();552}553}554555/**556* Returns an ASN.1 INTEGER value as an integer.557*558* @return the integer held in this DER value.559*/560public int getInteger() throws IOException {561return getIntegerInternal(tag_Integer);562}563564private int getIntegerInternal(byte expectedTag) throws IOException {565BigInteger result = getBigIntegerInternal(expectedTag, false);566if (result.compareTo(BigInteger.valueOf(Integer.MIN_VALUE)) < 0) {567throw new IOException("Integer below minimum valid value");568}569if (result.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) > 0) {570throw new IOException("Integer exceeds maximum valid value");571}572return result.intValue();573}574575/**576* Returns an ASN.1 INTEGER value as a BigInteger.577*578* @return the integer held in this DER value as a BigInteger.579*/580public BigInteger getBigInteger() throws IOException {581return getBigIntegerInternal(tag_Integer, false);582}583584/**585* Returns an ASN.1 INTEGER value as a positive BigInteger.586* This is just to deal with implementations that incorrectly encode587* some values as negative.588*589* @return the integer held in this DER value as a BigInteger.590*/591public BigInteger getPositiveBigInteger() throws IOException {592return getBigIntegerInternal(tag_Integer, true);593}594595/**596* Returns a BigInteger value597*598* @param makePositive whether to always return a positive value,599* irrespective of actual encoding600* @return the integer as a BigInteger.601*/602private BigInteger getBigIntegerInternal(byte expectedTag, boolean makePositive) throws IOException {603if (tag != expectedTag) {604throw new IOException("DerValue.getBigIntegerInternal, not expected " + tag);605}606if (end == start) {607throw new IOException("Invalid encoding: zero length Int value");608}609data.pos = data.end; // Compatibility. Reach end.610if (!allowBER && (end - start >= 2 && (buffer[start] == 0) && (buffer[start + 1] >= 0))) {611throw new IOException("Invalid encoding: redundant leading 0s");612}613return makePositive614? new BigInteger(1, buffer, start, end - start)615: new BigInteger(buffer, start, end - start);616}617618/**619* Returns an ASN.1 ENUMERATED value.620*621* @return the integer held in this DER value.622*/623public int getEnumerated() throws IOException {624return getIntegerInternal(tag_Enumerated);625}626627/**628* Returns an ASN.1 BIT STRING value. The bit string must be byte-aligned.629*630* @return the bit string held in this value631*/632public byte[] getBitString() throws IOException {633return getBitString(false);634}635636/**637* Returns an ASN.1 BIT STRING value that need not be byte-aligned.638*639* @return a BitArray representing the bit string held in this value640*/641public BitArray getUnalignedBitString() throws IOException {642return getUnalignedBitString(false);643}644645/**646* Returns the name component as a Java string, regardless of its647* encoding restrictions (ASCII, T61, Printable, IA5, BMP, UTF8).648*/649// TBD: Need encoder for UniversalString before it can be handled.650public String getAsString() throws IOException {651return switch (tag) {652case tag_UTF8String -> getUTF8String();653case tag_PrintableString -> getPrintableString();654case tag_T61String -> getT61String();655case tag_IA5String -> getIA5String();656case tag_UniversalString -> getUniversalString();657case tag_BMPString -> getBMPString();658case tag_GeneralString -> getGeneralString();659default -> null;660};661}662663/**664* Returns an ASN.1 BIT STRING value, with the tag assumed implicit665* based on the parameter. The bit string must be byte-aligned.666*667* @param tagImplicit if true, the tag is assumed implicit.668* @return the bit string held in this value669*/670public byte[] getBitString(boolean tagImplicit) throws IOException {671if (!tagImplicit) {672if (tag != tag_BitString) {673throw new IOException("DerValue.getBitString, not a bit string "674+ tag);675}676}677if (end == start) {678throw new IOException("Invalid encoding: zero length bit string");679}680int numOfPadBits = buffer[start];681if ((numOfPadBits < 0) || (numOfPadBits > 7)) {682throw new IOException("Invalid number of padding bits");683}684// minus the first byte which indicates the number of padding bits685byte[] retval = Arrays.copyOfRange(buffer, start + 1, end);686if (numOfPadBits != 0) {687// get rid of the padding bits688retval[end - start - 2] &= (0xff << numOfPadBits);689}690data.pos = data.end; // Compatibility. Reach end.691return retval;692}693694/**695* Returns an ASN.1 BIT STRING value, with the tag assumed implicit696* based on the parameter. The bit string need not be byte-aligned.697*698* @param tagImplicit if true, the tag is assumed implicit.699* @return the bit string held in this value700*/701public BitArray getUnalignedBitString(boolean tagImplicit)702throws IOException {703if (!tagImplicit) {704if (tag != tag_BitString) {705throw new IOException("DerValue.getBitString, not a bit string "706+ tag);707}708}709if (end == start) {710throw new IOException("Invalid encoding: zero length bit string");711}712data.pos = data.end; // Compatibility. Reach end.713int numOfPadBits = buffer[start];714if ((numOfPadBits < 0) || (numOfPadBits > 7)) {715throw new IOException("Invalid number of padding bits");716}717if (end == start + 1) {718return new BitArray(0);719} else {720return new BitArray(((end - start - 1) << 3) - numOfPadBits,721Arrays.copyOfRange(buffer, start + 1, end));722}723}724725/**726* Helper routine to return all the bytes contained in the727* DerInputStream associated with this object.728*/729public byte[] getDataBytes() throws IOException {730data.pos = data.end; // Compatibility. Reach end.731return Arrays.copyOfRange(buffer, start, end);732}733734private String readStringInternal(byte expectedTag, Charset cs) throws IOException {735if (tag != expectedTag) {736throw new IOException("Incorrect string type " + tag + " is not " + expectedTag);737}738data.pos = data.end; // Compatibility. Reach end.739return new String(buffer, start, end - start, cs);740}741742/**743* Returns an ASN.1 STRING value744*745* @return the printable string held in this value746*/747public String getPrintableString() throws IOException {748return readStringInternal(tag_PrintableString, US_ASCII);749}750751/**752* Returns an ASN.1 T61 (Teletype) STRING value753*754* @return the teletype string held in this value755*/756public String getT61String() throws IOException {757return readStringInternal(tag_T61String, ISO_8859_1);758}759760/**761* Returns an ASN.1 IA5 (ASCII) STRING value762*763* @return the ASCII string held in this value764*/765public String getIA5String() throws IOException {766return readStringInternal(tag_IA5String, US_ASCII);767}768769/**770* Returns the ASN.1 BMP (Unicode) STRING value as a Java string.771*772* @return a string corresponding to the encoded BMPString held in773* this value774*/775public String getBMPString() throws IOException {776// BMPString is the same as Unicode in big endian, unmarked format.777return readStringInternal(tag_BMPString, UTF_16BE);778}779780/**781* Returns the ASN.1 UTF-8 STRING value as a Java String.782*783* @return a string corresponding to the encoded UTF8String held in784* this value785*/786public String getUTF8String() throws IOException {787return readStringInternal(tag_UTF8String, UTF_8);788}789790/**791* Returns the ASN.1 GENERAL STRING value as a Java String.792*793* @return a string corresponding to the encoded GeneralString held in794* this value795*/796public String getGeneralString() throws IOException {797return readStringInternal(tag_GeneralString, US_ASCII);798}799800/**801* Returns the ASN.1 UNIVERSAL (UTF-32) STRING value as a Java String.802*803* @return a string corresponding to the encoded UniversalString held in804* this value805*/806public String getUniversalString() throws IOException {807return readStringInternal(tag_UniversalString, new UTF_32BE());808}809810/**811* Reads the ASN.1 NULL value812*/813public void getNull() throws IOException {814if (tag != tag_Null) {815throw new IOException("DerValue.getNull, not NULL: " + tag);816}817if (end != start) {818throw new IOException("NULL should contain no data");819}820}821822/**823* Private helper routine to extract time from the der value.824* @param generalized true if Generalized Time is to be read, false825* if UTC Time is to be read.826*/827private Date getTimeInternal(boolean generalized) throws IOException {828829/*830* UTC time encoded as ASCII chars:831* YYMMDDhhmmZ832* YYMMDDhhmmssZ833* YYMMDDhhmm+hhmm834* YYMMDDhhmm-hhmm835* YYMMDDhhmmss+hhmm836* YYMMDDhhmmss-hhmm837* UTC Time is broken in storing only two digits of year.838* If YY < 50, we assume 20YY;839* if YY >= 50, we assume 19YY, as per RFC 5280.840*841* Generalized time has a four-digit year and allows any842* precision specified in ISO 8601. However, for our purposes,843* we will only allow the same format as UTC time, except that844* fractional seconds (millisecond precision) are supported.845*/846847int year, month, day, hour, minute, second, millis;848String type;849int pos = start;850int len = end - start;851852if (generalized) {853type = "Generalized";854year = 1000 * toDigit(buffer[pos++], type);855year += 100 * toDigit(buffer[pos++], type);856year += 10 * toDigit(buffer[pos++], type);857year += toDigit(buffer[pos++], type);858len -= 2; // For the two extra YY859} else {860type = "UTC";861year = 10 * toDigit(buffer[pos++], type);862year += toDigit(buffer[pos++], type);863864if (year < 50) { // origin 2000865year += 2000;866} else {867year += 1900; // origin 1900868}869}870871month = 10 * toDigit(buffer[pos++], type);872month += toDigit(buffer[pos++], type);873874day = 10 * toDigit(buffer[pos++], type);875day += toDigit(buffer[pos++], type);876877hour = 10 * toDigit(buffer[pos++], type);878hour += toDigit(buffer[pos++], type);879880minute = 10 * toDigit(buffer[pos++], type);881minute += toDigit(buffer[pos++], type);882883len -= 10; // YYMMDDhhmm884885/*886* We allow for non-encoded seconds, even though the887* IETF-PKIX specification says that the seconds should888* always be encoded even if it is zero.889*/890891millis = 0;892if (len > 2) {893second = 10 * toDigit(buffer[pos++], type);894second += toDigit(buffer[pos++], type);895len -= 2;896// handle fractional seconds (if present)897if (generalized && (buffer[pos] == '.' || buffer[pos] == ',')) {898len --;899if (len == 0) {900throw new IOException("Parse " + type +901" time, empty fractional part");902}903pos++;904int precision = 0;905while (buffer[pos] != 'Z' &&906buffer[pos] != '+' &&907buffer[pos] != '-') {908// Validate all digits in the fractional part but909// store millisecond precision only910int thisDigit = toDigit(buffer[pos], type);911precision++;912len--;913if (len == 0) {914throw new IOException("Parse " + type +915" time, invalid fractional part");916}917pos++;918switch (precision) {919case 1 -> millis += 100 * thisDigit;920case 2 -> millis += 10 * thisDigit;921case 3 -> millis += thisDigit;922}923}924if (precision == 0) {925throw new IOException("Parse " + type +926" time, empty fractional part");927}928}929} else930second = 0;931932if (month == 0 || day == 0933|| month > 12 || day > 31934|| hour >= 24 || minute >= 60 || second >= 60) {935throw new IOException("Parse " + type + " time, invalid format");936}937938/*939* Generalized time can theoretically allow any precision,940* but we're not supporting that.941*/942CalendarSystem gcal = CalendarSystem.getGregorianCalendar();943CalendarDate date = gcal.newCalendarDate(null); // no time zone944date.setDate(year, month, day);945date.setTimeOfDay(hour, minute, second, millis);946long time = gcal.getTime(date);947948/*949* Finally, "Z" or "+hhmm" or "-hhmm" ... offsets change hhmm950*/951if (! (len == 1 || len == 5)) {952throw new IOException("Parse " + type + " time, invalid offset");953}954955int hr, min;956957switch (buffer[pos++]) {958case '+':959if (len != 5) {960throw new IOException("Parse " + type + " time, invalid offset");961}962hr = 10 * toDigit(buffer[pos++], type);963hr += toDigit(buffer[pos++], type);964min = 10 * toDigit(buffer[pos++], type);965min += toDigit(buffer[pos++], type);966967if (hr >= 24 || min >= 60) {968throw new IOException("Parse " + type + " time, +hhmm");969}970971time -= ((hr * 60) + min) * 60 * 1000;972break;973974case '-':975if (len != 5) {976throw new IOException("Parse " + type + " time, invalid offset");977}978hr = 10 * toDigit(buffer[pos++], type);979hr += toDigit(buffer[pos++], type);980min = 10 * toDigit(buffer[pos++], type);981min += toDigit(buffer[pos++], type);982983if (hr >= 24 || min >= 60) {984throw new IOException("Parse " + type + " time, -hhmm");985}986987time += ((hr * 60) + min) * 60 * 1000;988break;989990case 'Z':991if (len != 1) {992throw new IOException("Parse " + type + " time, invalid format");993}994break;995996default:997throw new IOException("Parse " + type + " time, garbage offset");998}999return new Date(time);1000}10011002/**1003* Converts byte (represented as a char) to int.1004* @throws IOException if integer is not a valid digit in the specified1005* radix (10)1006*/1007private static int toDigit(byte b, String type) throws IOException {1008if (b < '0' || b > '9') {1009throw new IOException("Parse " + type + " time, invalid format");1010}1011return b - '0';1012}10131014/**1015* Returns a Date if the DerValue is UtcTime.1016*1017* @return the Date held in this DER value1018*/1019public Date getUTCTime() throws IOException {1020if (tag != tag_UtcTime) {1021throw new IOException("DerValue.getUTCTime, not a UtcTime: " + tag);1022}1023if (end - start < 11 || end - start > 17)1024throw new IOException("DER UTC Time length error");10251026data.pos = data.end; // Compatibility. Reach end.1027return getTimeInternal(false);1028}10291030/**1031* Returns a Date if the DerValue is GeneralizedTime.1032*1033* @return the Date held in this DER value1034*/1035public Date getGeneralizedTime() throws IOException {1036if (tag != tag_GeneralizedTime) {1037throw new IOException(1038"DerValue.getGeneralizedTime, not a GeneralizedTime: " + tag);1039}1040if (end - start < 13)1041throw new IOException("DER Generalized Time length error");10421043data.pos = data.end; // Compatibility. Reach end.1044return getTimeInternal(true);1045}10461047/**1048* Bitwise equality comparison. DER encoded values have a single1049* encoding, so that bitwise equality of the encoded values is an1050* efficient way to establish equivalence of the unencoded values.1051*1052* @param o the object being compared with this one1053*/1054@Override1055public boolean equals(Object o) {1056if (this == o) {1057return true;1058}1059if (!(o instanceof DerValue)) {1060return false;1061}1062DerValue other = (DerValue) o;1063if (tag != other.tag) {1064return false;1065}1066if (buffer == other.buffer && start == other.start && end == other.end) {1067return true;1068}1069return Arrays.equals(buffer, start, end, other.buffer, other.start, other.end);1070}10711072/**1073* Returns a printable representation of the value.1074*1075* @return printable representation of the value1076*/1077@Override1078public String toString() {1079return String.format("DerValue(%02x, %s, %d, %d)",10800xff & tag, buffer, start, end);1081}10821083/**1084* Returns a DER-encoded value, such that if it's passed to the1085* DerValue constructor, a value equivalent to "this" is returned.1086*1087* @return DER-encoded value, including tag and length.1088*/1089public byte[] toByteArray() throws IOException {1090data.pos = data.start; // Compatibility. At head.1091// Minimize content duplication by writing out tag and length only1092DerOutputStream out = new DerOutputStream();1093out.write(tag);1094out.putLength(end - start);1095int headLen = out.size();1096byte[] result = Arrays.copyOf(out.buf(), end - start + headLen);1097System.arraycopy(buffer, start, result, headLen, end - start);1098return result;1099}11001101/**1102* For "set" and "sequence" types, this function may be used1103* to return a DER stream of the members of the set or sequence.1104* This operation is not supported for primitive types such as1105* integers or bit strings.1106*/1107public DerInputStream toDerInputStream() throws IOException {1108if (tag == tag_Sequence || tag == tag_Set)1109return data;1110throw new IOException("toDerInputStream rejects tag type " + tag);1111}11121113/**1114* Get the length of the encoded value.1115*/1116public int length() {1117return end - start;1118}11191120/**1121* Determine if a character is one of the permissible characters for1122* PrintableString:1123* A-Z, a-z, 0-9, space, apostrophe (39), left and right parentheses,1124* plus sign, comma, hyphen, period, slash, colon, equals sign,1125* and question mark.1126*1127* Characters that are *not* allowed in PrintableString include1128* exclamation point, quotation mark, number sign, dollar sign,1129* percent sign, ampersand, asterisk, semicolon, less than sign,1130* greater than sign, at sign, left and right square brackets,1131* backslash, circumflex (94), underscore, back quote (96),1132* left and right curly brackets, vertical line, tilde,1133* and the control codes (0-31 and 127).1134*1135* This list is based on X.680 (the ASN.1 spec).1136*/1137public static boolean isPrintableStringChar(char ch) {1138if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||1139(ch >= '0' && ch <= '9')) {1140return true;1141} else {1142switch (ch) {1143case ' ': /* space */1144case '\'': /* apostrophe */1145case '(': /* left paren */1146case ')': /* right paren */1147case '+': /* plus */1148case ',': /* comma */1149case '-': /* hyphen */1150case '.': /* period */1151case '/': /* slash */1152case ':': /* colon */1153case '=': /* equals */1154case '?': /* question mark */1155return true;1156default:1157return false;1158}1159}1160}11611162/**1163* Create the tag of the attribute.1164*1165* @param tagClass the tag class type, one of UNIVERSAL, CONTEXT,1166* APPLICATION or PRIVATE1167* @param form if true, the value is constructed, otherwise it1168* is primitive.1169* @param val the tag value1170*/1171public static byte createTag(byte tagClass, boolean form, byte val) {1172if (val < 0 || val > 30) {1173throw new IllegalArgumentException("Tag number over 30 is not supported");1174}1175byte tag = (byte)(tagClass | val);1176if (form) {1177tag |= (byte)0x20;1178}1179return (tag);1180}11811182/**1183* Set the tag of the attribute. Commonly used to reset the1184* tag value used for IMPLICIT encodings.1185*1186* This method should be avoided, consider using withTag() instead.1187*1188* @param tag the tag value1189*/1190public void resetTag(byte tag) {1191this.tag = tag;1192}11931194/**1195* Returns a new DerValue with a different tag. This method is used1196* to convert a DerValue decoded from an IMPLICIT encoding to its real1197* tag. The content is not checked against the tag in this method.1198*1199* @param newTag the new tag1200* @return a new DerValue1201*/1202public DerValue withTag(byte newTag) {1203return new DerValue(newTag, buffer, start, end, allowBER);1204}12051206/**1207* Returns a hashcode for this DerValue.1208*1209* @return a hashcode for this DerValue.1210*/1211@Override1212public int hashCode() {1213int result = tag;1214for (int i = start; i < end; i++) {1215result = 31 * result + buffer[i];1216}1217return result;1218}12191220/**1221* Reads the sub-values in a constructed DerValue.1222*1223* @param expectedTag the expected tag, or zero if we don't check.1224* This is useful when this DerValue is IMPLICIT.1225* @param startLen estimated number of sub-values1226* @return the sub-values in an array1227*/1228DerValue[] subs(byte expectedTag, int startLen) throws IOException {1229if (expectedTag != 0 && expectedTag != tag) {1230throw new IOException("Not the correct tag");1231}1232List<DerValue> result = new ArrayList<>(startLen);1233DerInputStream dis = data();1234while (dis.available() > 0) {1235result.add(dis.getDerValue());1236}1237return result.toArray(new DerValue[0]);1238}12391240public void clear() {1241Arrays.fill(buffer, start, end, (byte)0);1242}1243}124412451246