proguard.io.DEROutputStream Maven / Gradle / Ivy
Show all versions of proguard-core Show documentation
/*
* ProGuardCORE -- library to process Java bytecode.
*
* Copyright (c) 2002-2020 Guardsquare NV
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package proguard.io;
import java.io.*;
import java.math.BigInteger;
import java.util.Stack;
/**
* This class provides methods to write data with Distinguished Encoding Rules to an underlying
* output stream. DER is an encoding of ASN.1 (Abstract Syntax Notation).
*
* This is for example the format of signature files like META-INF/CERT.RSA
*
*
You can view the resulting file with openssl asn1parse -inform DER META-INF/CERT.RSA
*
* @author Eric Lafortune
*/
class DEROutputStream // extends OutputStream
{
private static final byte BOOLEAN_TAG = 0x01;
private static final byte INTEGER_TAG = 0x02;
private static final byte BIT_STRING_TAG = 0x03;
private static final byte OCTET_STRING_TAG = 0x04;
private static final byte NULL_TAG = 0x05;
private static final byte OBJECT_IDENTIFIER_TAG = 0x06;
private static final byte UTF8_STRING_TAG = 0x0c;
private static final byte PRINTABLE_STRING_TAG = 0x13;
private static final byte T61_STRING_TAG = 0x14;
private static final byte IA5_STRING_TAG = 0x16;
private static final byte UTC_TIME_TAG = 0x17;
private static final byte SEQUENCE_TAG = 0x30;
private static final byte SET_TAG = 0x31;
private static final byte IMPLICIT_TAG = (byte) 0xa0;
private OutputStream outputStream;
// A stack to push output streams when we're starting sequences, sets,
// and implicits.
private final Stack outputStreams = new Stack();
/**
* Creates a new DEROutputStream.
*
* @param outputStream the delegate output stream.
*/
public DEROutputStream(OutputStream outputStream) {
this.outputStream = outputStream;
}
/** Writes the given integer, compressed in 2 to 5 bytes. */
public void writeInteger(int i) throws IOException {
outputStream.write(INTEGER_TAG);
if (i << 24 >>> 24 == i) {
outputStream.write(1);
outputStream.write(i);
} else if (i << 16 >>> 16 == i) {
outputStream.write(2);
outputStream.write(i >> 8);
outputStream.write(i);
} else if (i << 8 >>> 8 == i) {
outputStream.write(3);
outputStream.write(i >> 16);
outputStream.write(i >> 8);
outputStream.write(i);
} else {
outputStream.write(4);
outputStream.write(i >> 24);
outputStream.write(i >> 16);
outputStream.write(i >> 8);
outputStream.write(i);
}
}
/** Writes the given big integer. */
public void writeInteger(BigInteger bigInteger) throws IOException {
byte[] bytes = bigInteger.toByteArray();
write(INTEGER_TAG, bytes);
}
/** Writes the given byte array. */
public void writeOctetString(byte[] bytes) throws IOException {
write(OCTET_STRING_TAG, bytes);
}
/** Writes a null element. */
public void writeNull() throws IOException {
outputStream.write(NULL_TAG);
outputStream.write(0);
}
/** Writes the given object identifier. */
public void writeObjectIdentifier(byte[] bytes) throws IOException {
write(OBJECT_IDENTIFIER_TAG, bytes);
}
/**
* Writes the given string as a printable string, as a UTF-8 string (if necessary), or as an IA5
* string (if specified).
*/
public void writeString(String s, boolean asIA5String) throws IOException {
if (asIA5String) {
writeIA5String(s);
} else if (isPrintable(s)) {
writePrintableString(s);
} else {
writeUtf8String(s);
}
}
/** Writes the given string in UTF-8 encoding. */
public void writeUtf8String(String s) throws IOException {
byte[] bytes = s.getBytes("UTF8");
write(UTF8_STRING_TAG, bytes);
}
/** Writes the given string in T61 encoding. */
public void writeT61String(String s) throws IOException {
byte[] bytes = s.getBytes("ASCII");
write(T61_STRING_TAG, bytes);
}
/** Writes the given string in IA5 encoding. */
public void writeIA5String(String s) throws IOException {
byte[] bytes = s.getBytes("ASCII");
write(IA5_STRING_TAG, bytes);
}
/** Writes the given string in printable encoding. */
public void writePrintableString(String s) throws IOException {
byte[] bytes = s.getBytes("ASCII");
write(PRINTABLE_STRING_TAG, bytes);
}
/** Writes the given integer as a length, compressed in 1 to 5 bytes. */
private void writeLength(int i) throws IOException {
if (i <= 0x0000007f) {
outputStream.write(i);
} else if (i <= 0x000000ff) {
outputStream.write(0x81);
outputStream.write(i);
} else if (i <= 0x0000ffff) {
outputStream.write(0x82);
outputStream.write(i >> 8);
outputStream.write(i);
} else if (i <= 0x00ffffff) {
outputStream.write(0x83);
outputStream.write(i >> 16);
outputStream.write(i >> 8);
outputStream.write(i);
} else {
outputStream.write(0x84);
outputStream.write(i >> 24);
outputStream.write(i >> 16);
outputStream.write(i >> 8);
outputStream.write(i);
}
}
/** Starts a sequence. */
public void startSequence() throws IOException {
pushOutputStream();
}
/** Ends the most recently started sequence and writes it out. */
public void endSequence() throws IOException {
byte[] bytes = popOutputStream();
writeSequence(bytes);
}
/** Writes the given byte array representation of a sequence. */
private void writeSequence(byte[] bytes) throws IOException {
write(SEQUENCE_TAG, bytes);
}
/** Starts a set. */
public void startSet() throws IOException {
pushOutputStream();
}
/** Ends the most recently started set and writes it out. */
public void endSet() throws IOException {
byte[] bytes = popOutputStream();
writeSet(bytes);
}
/** Writes the given byte array representation of a set. */
private void writeSet(byte[] bytes) throws IOException {
write(SET_TAG, bytes);
}
/** Starts an implicit. */
public void startImplicit() throws IOException {
pushOutputStream();
}
/** Ends the most recently started implicit and writes it out. */
public void endImplicit() throws IOException {
byte[] bytes = popOutputStream();
writeImplicit(bytes);
}
/** Writes the given byte array representation of an implicit. */
public void writeImplicit(byte[] bytes) throws IOException {
write(IMPLICIT_TAG, bytes);
}
/** Writes the given tag and byte array, with the proper length. */
public void write(byte tag, byte[] bytes) throws IOException {
outputStream.write(tag);
writeLength(bytes.length);
outputStream.write(bytes);
}
// Implementations for OutputStream.
// public void write(int b) throws IOException
// {
// outputStream.write(b);
// }
//
//
// public void write(byte[] bytes) throws IOException
// {
// write(bytes, 0, bytes.length);
// }
//
//
// public void write(byte[] bytes, int offset, int size) throws IOException
// {
// outputStream.write(bytes, offset, size);
// }
/** Flushes the output stream. */
public void flush() throws IOException {
outputStream.flush();
}
/** Closes the output stream. */
public void close() throws IOException {
if (!outputStreams.isEmpty()) {
throw new IllegalStateException("DER sequences/sets not ended properly");
}
outputStream.close();
}
// Small utility methods.
/** Returns whether the given string can be represented as a printable string. */
private boolean isPrintable(String s) {
for (int index = 0; index < s.length(); index++) {
char c = s.charAt(index);
if (c < 32 || c >= 128) {
return false;
}
}
return true;
}
/**
* Pushes the current output stream, replacing it by a byte array output stream to temporarily
* collect output.
*/
private void pushOutputStream() {
outputStreams.push(outputStream);
outputStream = new ByteArrayOutputStream();
}
/**
* Returns the bytes from the current byte array output stream and restores the previous output
* stream.
*/
private byte[] popOutputStream() {
byte[] bytes = ((ByteArrayOutputStream) outputStream).toByteArray();
outputStream = (OutputStream) outputStreams.pop();
return bytes;
}
}