blob: c517df8a43ea955c5806fea0a49c90815f2ff7aa [file] [log] [blame]
/*
* Copyright (c) 1996, 2010, 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 java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.Comparator;
import java.util.Arrays;
import java.math.BigInteger;
import java.util.Locale;
/**
* Output stream marshaling DER-encoded data. This is eventually provided
* in the form of a byte array; there is no advance limit on the size of
* that byte array.
*
* <P>At this time, this class supports only a subset of the types of
* DER data encodings which are defined. That subset is sufficient for
* generating most X.509 certificates.
*
*
* @author David Brownell
* @author Amit Kapoor
* @author Hemma Prafullchandra
*/
public class DerOutputStream
extends ByteArrayOutputStream implements DerEncoder {
/**
* Construct an DER output stream.
*
* @param size how large a buffer to preallocate.
*/
public DerOutputStream(int size) { super(size); }
/**
* Construct an DER output stream.
*/
public DerOutputStream() { }
/**
* Writes tagged, pre-marshaled data. This calcuates and encodes
* the length, so that the output data is the standard triple of
* { tag, length, data } used by all DER values.
*
* @param tag the DER value tag for the data, such as
* <em>DerValue.tag_Sequence</em>
* @param buf buffered data, which must be DER-encoded
*/
public void write(byte tag, byte[] buf) throws IOException {
write(tag);
putLength(buf.length);
write(buf, 0, buf.length);
}
/**
* Writes tagged data using buffer-to-buffer copy. As above,
* this writes a standard DER record. This is often used when
* efficiently encapsulating values in sequences.
*
* @param tag the DER value tag for the data, such as
* <em>DerValue.tag_Sequence</em>
* @param out buffered data
*/
public void write(byte tag, DerOutputStream out) throws IOException {
write(tag);
putLength(out.count);
write(out.buf, 0, out.count);
}
/**
* Writes implicitly tagged data using buffer-to-buffer copy. As above,
* this writes a standard DER record. This is often used when
* efficiently encapsulating implicitly tagged values.
*
* @param tag the DER value of the context-specific tag that replaces
* original tag of the value in the output, such as in
* <pre>
* <em> <field> [N] IMPLICIT <type></em>
* </pre>
* For example, <em>FooLength [1] IMPLICIT INTEGER</em>, with value=4;
* would be encoded as "81 01 04" whereas in explicit
* tagging it would be encoded as "A1 03 02 01 04".
* Notice that the tag is A1 and not 81, this is because with
* explicit tagging the form is always constructed.
* @param value original value being implicitly tagged
*/
public void writeImplicit(byte tag, DerOutputStream value)
throws IOException {
write(tag);
write(value.buf, 1, value.count-1);
}
/**
* Marshals pre-encoded DER value onto the output stream.
*/
public void putDerValue(DerValue val) throws IOException {
val.encode(this);
}
/*
* PRIMITIVES -- these are "universal" ASN.1 simple types.
*
* BOOLEAN, INTEGER, BIT STRING, OCTET STRING, NULL
* OBJECT IDENTIFIER, SEQUENCE(OF), SET(OF)
* PrintableString, T61String, IA5String, UTCTime
*/
/**
* Marshals a DER boolean on the output stream.
*/
public void putBoolean(boolean val) throws IOException {
write(DerValue.tag_Boolean);
putLength(1);
if (val) {
write(0xff);
} else {
write(0);
}
}
/**
* Marshals a DER enumerated on the output stream.
* @param i the enumerated value.
*/
public void putEnumerated(int i) throws IOException {
write(DerValue.tag_Enumerated);
putIntegerContents(i);
}
/**
* Marshals a DER integer on the output stream.
*
* @param i the integer in the form of a BigInteger.
*/
public void putInteger(BigInteger i) throws IOException {
write(DerValue.tag_Integer);
byte[] buf = i.toByteArray(); // least number of bytes
putLength(buf.length);
write(buf, 0, buf.length);
}
/**
* Marshals a DER integer on the output stream.
* @param i the integer in the form of an Integer.
*/
public void putInteger(Integer i) throws IOException {
putInteger(i.intValue());
}
/**
* Marshals a DER integer on the output stream.
* @param i the integer.
*/
public void putInteger(int i) throws IOException {
write(DerValue.tag_Integer);
putIntegerContents(i);
}
private void putIntegerContents(int i) throws IOException {
byte[] bytes = new byte[4];
int start = 0;
// Obtain the four bytes of the int
bytes[3] = (byte) (i & 0xff);
bytes[2] = (byte)((i & 0xff00) >>> 8);
bytes[1] = (byte)((i & 0xff0000) >>> 16);
bytes[0] = (byte)((i & 0xff000000) >>> 24);
// Reduce them to the least number of bytes needed to
// represent this int
if (bytes[0] == (byte)0xff) {
// Eliminate redundant 0xff
for (int j = 0; j < 3; j++) {
if ((bytes[j] == (byte)0xff) &&
((bytes[j+1] & 0x80) == 0x80))
start++;
else
break;
}
} else if (bytes[0] == 0x00) {
// Eliminate redundant 0x00
for (int j = 0; j < 3; j++) {
if ((bytes[j] == 0x00) &&
((bytes[j+1] & 0x80) == 0))
start++;
else
break;
}
}
putLength(4 - start);
for (int k = start; k < 4; k++)
write(bytes[k]);
}
/**
* Marshals a DER bit string on the output stream. The bit
* string must be byte-aligned.
*
* @param bits the bit string, MSB first
*/
public void putBitString(byte[] bits) throws IOException {
write(DerValue.tag_BitString);
putLength(bits.length + 1);
write(0); // all of last octet is used
write(bits);
}
/**
* Marshals a DER bit string on the output stream.
* The bit strings need not be byte-aligned.
*
* @param bits the bit string, MSB first
*/
public void putUnalignedBitString(BitArray ba) throws IOException {
byte[] bits = ba.toByteArray();
write(DerValue.tag_BitString);
putLength(bits.length + 1);
write(bits.length*8 - ba.length()); // excess bits in last octet
write(bits);
}
/**
* Marshals a truncated DER bit string on the output stream.
* The bit strings need not be byte-aligned.
*
* @param bits the bit string, MSB first
*/
public void putTruncatedUnalignedBitString(BitArray ba) throws IOException {
putUnalignedBitString(ba.truncate());
}
/**
* DER-encodes an ASN.1 OCTET STRING value on the output stream.
*
* @param octets the octet string
*/
public void putOctetString(byte[] octets) throws IOException {
write(DerValue.tag_OctetString, octets);
}
/**
* Marshals a DER "null" value on the output stream. These are
* often used to indicate optional values which have been omitted.
*/
public void putNull() throws IOException {
write(DerValue.tag_Null);
putLength(0);
}
/**
* Marshals an object identifier (OID) on the output stream.
* Corresponds to the ASN.1 "OBJECT IDENTIFIER" construct.
*/
public void putOID(ObjectIdentifier oid) throws IOException {
oid.encode(this);
}
/**
* Marshals a sequence on the output stream. This supports both
* the ASN.1 "SEQUENCE" (zero to N values) and "SEQUENCE OF"
* (one to N values) constructs.
*/
public void putSequence(DerValue[] seq) throws IOException {
DerOutputStream bytes = new DerOutputStream();
int i;
for (i = 0; i < seq.length; i++)
seq[i].encode(bytes);
write(DerValue.tag_Sequence, bytes);
}
/**
* Marshals the contents of a set on the output stream without
* ordering the elements. Ok for BER encoding, but not for DER
* encoding.
*
* For DER encoding, use orderedPutSet() or orderedPutSetOf().
*/
public void putSet(DerValue[] set) throws IOException {
DerOutputStream bytes = new DerOutputStream();
int i;
for (i = 0; i < set.length; i++)
set[i].encode(bytes);
write(DerValue.tag_Set, bytes);
}
/**
* Marshals the contents of a set on the output stream. Sets
* are semantically unordered, but DER requires that encodings of
* set elements be sorted into ascending lexicographical order
* before being output. Hence sets with the same tags and
* elements have the same DER encoding.
*
* This method supports the ASN.1 "SET OF" construct, but not
* "SET", which uses a different order.
*/
public void putOrderedSetOf(byte tag, DerEncoder[] set) throws IOException {
putOrderedSet(tag, set, lexOrder);
}
/**
* Marshals the contents of a set on the output stream. Sets
* are semantically unordered, but DER requires that encodings of
* set elements be sorted into ascending tag order
* before being output. Hence sets with the same tags and
* elements have the same DER encoding.
*
* This method supports the ASN.1 "SET" construct, but not
* "SET OF", which uses a different order.
*/
public void putOrderedSet(byte tag, DerEncoder[] set) throws IOException {
putOrderedSet(tag, set, tagOrder);
}
/**
* Lexicographical order comparison on byte arrays, for ordering
* elements of a SET OF objects in DER encoding.
*/
private static ByteArrayLexOrder lexOrder = new ByteArrayLexOrder();
/**
* Tag order comparison on byte arrays, for ordering elements of
* SET objects in DER encoding.
*/
private static ByteArrayTagOrder tagOrder = new ByteArrayTagOrder();
/**
* Marshals a the contents of a set on the output stream with the
* encodings of its sorted in increasing order.
*
* @param order the order to use when sorting encodings of components.
*/
private void putOrderedSet(byte tag, DerEncoder[] set,
Comparator<byte[]> order) throws IOException {
DerOutputStream[] streams = new DerOutputStream[set.length];
for (int i = 0; i < set.length; i++) {
streams[i] = new DerOutputStream();
set[i].derEncode(streams[i]);
}
// order the element encodings
byte[][] bufs = new byte[streams.length][];
for (int i = 0; i < streams.length; i++) {
bufs[i] = streams[i].toByteArray();
}
Arrays.<byte[]>sort(bufs, order);
DerOutputStream bytes = new DerOutputStream();
for (int i = 0; i < streams.length; i++) {
bytes.write(bufs[i]);
}
write(tag, bytes);
}
/**
* Marshals a string as a DER encoded UTF8String.
*/
public void putUTF8String(String s) throws IOException {
writeString(s, DerValue.tag_UTF8String, "UTF8");
}
/**
* Marshals a string as a DER encoded PrintableString.
*/
public void putPrintableString(String s) throws IOException {
writeString(s, DerValue.tag_PrintableString, "ASCII");
}
/**
* Marshals a string as a DER encoded T61String.
*/
public void putT61String(String s) throws IOException {
/*
* Works for characters that are defined in both ASCII and
* T61.
*/
writeString(s, DerValue.tag_T61String, "ISO-8859-1");
}
/**
* Marshals a string as a DER encoded IA5String.
*/
public void putIA5String(String s) throws IOException {
writeString(s, DerValue.tag_IA5String, "ASCII");
}
/**
* Marshals a string as a DER encoded BMPString.
*/
public void putBMPString(String s) throws IOException {
writeString(s, DerValue.tag_BMPString, "UnicodeBigUnmarked");
}
/**
* Marshals a string as a DER encoded GeneralString.
*/
public void putGeneralString(String s) throws IOException {
writeString(s, DerValue.tag_GeneralString, "ASCII");
}
/**
* Private helper routine for writing DER encoded string values.
* @param s the string to write
* @param stringTag one of the DER string tags that indicate which
* encoding should be used to write the string out.
* @param enc the name of the encoder that should be used corresponding
* to the above tag.
*/
private void writeString(String s, byte stringTag, String enc)
throws IOException {
byte[] data = s.getBytes(enc);
write(stringTag);
putLength(data.length);
write(data);
}
/**
* Marshals a DER UTC time/date value.
*
* <P>YYMMDDhhmmss{Z|+hhmm|-hhmm} ... emits only using Zulu time
* and with seconds (even if seconds=0) as per RFC 3280.
*/
public void putUTCTime(Date d) throws IOException {
putTime(d, DerValue.tag_UtcTime);
}
/**
* Marshals a DER Generalized Time/date value.
*
* <P>YYYYMMDDhhmmss{Z|+hhmm|-hhmm} ... emits only using Zulu time
* and with seconds (even if seconds=0) as per RFC 3280.
*/
public void putGeneralizedTime(Date d) throws IOException {
putTime(d, DerValue.tag_GeneralizedTime);
}
/**
* Private helper routine for marshalling a DER UTC/Generalized
* time/date value. If the tag specified is not that for UTC Time
* then it defaults to Generalized Time.
* @param d the date to be marshalled
* @param tag the tag for UTC Time or Generalized Time
*/
private void putTime(Date d, byte tag) throws IOException {
/*
* Format the date.
*/
TimeZone tz = TimeZone.getTimeZone("GMT");
String pattern = null;
if (tag == DerValue.tag_UtcTime) {
pattern = "yyMMddHHmmss'Z'";
} else {
tag = DerValue.tag_GeneralizedTime;
pattern = "yyyyMMddHHmmss'Z'";
}
SimpleDateFormat sdf = new SimpleDateFormat(pattern, Locale.US);
sdf.setTimeZone(tz);
byte[] time = (sdf.format(d)).getBytes("ISO-8859-1");
/*
* Write the formatted date.
*/
write(tag);
putLength(time.length);
write(time);
}
/**
* Put the encoding of the length in the stream.
*
* @params len the length of the attribute.
* @exception IOException on writing errors.
*/
public void putLength(int len) throws IOException {
if (len < 128) {
write((byte)len);
} else if (len < (1 << 8)) {
write((byte)0x081);
write((byte)len);
} else if (len < (1 << 16)) {
write((byte)0x082);
write((byte)(len >> 8));
write((byte)len);
} else if (len < (1 << 24)) {
write((byte)0x083);
write((byte)(len >> 16));
write((byte)(len >> 8));
write((byte)len);
} else {
write((byte)0x084);
write((byte)(len >> 24));
write((byte)(len >> 16));
write((byte)(len >> 8));
write((byte)len);
}
}
/**
* Put the tag of the attribute in the stream.
*
* @params class the tag class type, one of UNIVERSAL, CONTEXT,
* APPLICATION or PRIVATE
* @params form if true, the value is constructed, otherwise it is
* primitive.
* @params val the tag value
*/
public void putTag(byte tagClass, boolean form, byte val) {
byte tag = (byte)(tagClass | val);
if (form) {
tag |= (byte)0x20;
}
write(tag);
}
/**
* Write the current contents of this <code>DerOutputStream</code>
* to an <code>OutputStream</code>.
*
* @exception IOException on output error.
*/
public void derEncode(OutputStream out) throws IOException {
out.write(toByteArray());
}
}