/* * Copyright (C) 2017 The Android Open Source Project * * 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 com.android.apksig.internal.asn1.ber; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; /** * {@link BerDataValueReader} which reads from an {@link InputStream} returning BER-encoded data * values. See {@code X.690} for the encoding. */ public class InputStreamBerDataValueReader implements BerDataValueReader { private final InputStream mIn; public InputStreamBerDataValueReader(InputStream in) { if (in == null) { throw new NullPointerException("in == null"); } mIn = in; } @Override public BerDataValue readDataValue() throws BerDataValueFormatException { return readDataValue(mIn); } /** * Returns the next data value or {@code null} if end of input has been reached. * * @throws BerDataValueFormatException if the value being read is malformed. */ @SuppressWarnings("resource") private static BerDataValue readDataValue(InputStream input) throws BerDataValueFormatException { RecordingInputStream in = new RecordingInputStream(input); try { int firstIdentifierByte = in.read(); if (firstIdentifierByte == -1) { // End of input return null; } int tagNumber = readTagNumber(in, firstIdentifierByte); int firstLengthByte = in.read(); if (firstLengthByte == -1) { throw new BerDataValueFormatException("Missing length"); } boolean constructed = BerEncoding.isConstructed((byte) firstIdentifierByte); int contentsLength; int contentsOffsetInDataValue; if ((firstLengthByte & 0x80) == 0) { // short form length contentsLength = readShortFormLength(firstLengthByte); contentsOffsetInDataValue = in.getReadByteCount(); skipDefiniteLengthContents(in, contentsLength); } else if ((firstLengthByte & 0xff) != 0x80) { // long form length contentsLength = readLongFormLength(in, firstLengthByte); contentsOffsetInDataValue = in.getReadByteCount(); skipDefiniteLengthContents(in, contentsLength); } else { // indefinite length contentsOffsetInDataValue = in.getReadByteCount(); contentsLength = constructed ? skipConstructedIndefiniteLengthContents(in) : skipPrimitiveIndefiniteLengthContents(in); } byte[] encoded = in.getReadBytes(); ByteBuffer encodedContents = ByteBuffer.wrap(encoded, contentsOffsetInDataValue, contentsLength); return new BerDataValue( ByteBuffer.wrap(encoded), encodedContents, BerEncoding.getTagClass((byte) firstIdentifierByte), constructed, tagNumber); } catch (IOException e) { throw new BerDataValueFormatException("Failed to read data value", e); } } private static int readTagNumber(InputStream in, int firstIdentifierByte) throws IOException, BerDataValueFormatException { int tagNumber = BerEncoding.getTagNumber((byte) firstIdentifierByte); if (tagNumber == 0x1f) { // high-tag-number form return readHighTagNumber(in); } else { // low-tag-number form return tagNumber; } } private static int readHighTagNumber(InputStream in) throws IOException, BerDataValueFormatException { // Base-128 big-endian form, where each byte has the highest bit set, except for the last // byte where the highest bit is not set int b; int result = 0; do { b = in.read(); if (b == -1) { throw new BerDataValueFormatException("Truncated tag number"); } if (result > Integer.MAX_VALUE >>> 7) { throw new BerDataValueFormatException("Tag number too large"); } result <<= 7; result |= b & 0x7f; } while ((b & 0x80) != 0); return result; } private static int readShortFormLength(int firstLengthByte) { return firstLengthByte & 0x7f; } private static int readLongFormLength(InputStream in, int firstLengthByte) throws IOException, BerDataValueFormatException { // The low 7 bits of the first byte represent the number of bytes (following the first // byte) in which the length is in big-endian base-256 form int byteCount = firstLengthByte & 0x7f; if (byteCount > 4) { throw new BerDataValueFormatException("Length too large: " + byteCount + " bytes"); } int result = 0; for (int i = 0; i < byteCount; i++) { int b = in.read(); if (b == -1) { throw new BerDataValueFormatException("Truncated length"); } if (result > Integer.MAX_VALUE >>> 8) { throw new BerDataValueFormatException("Length too large"); } result <<= 8; result |= b & 0xff; } return result; } private static void skipDefiniteLengthContents(InputStream in, int len) throws IOException, BerDataValueFormatException { long bytesRead = 0; while (len > 0) { int skipped = (int) in.skip(len); if (skipped <= 0) { throw new BerDataValueFormatException( "Truncated definite-length contents: " + bytesRead + " bytes read" + ", " + len + " missing"); } len -= skipped; bytesRead += skipped; } } private static int skipPrimitiveIndefiniteLengthContents(InputStream in) throws IOException, BerDataValueFormatException { // Contents are terminated by 0x00 0x00 boolean prevZeroByte = false; int bytesRead = 0; while (true) { int b = in.read(); if (b == -1) { throw new BerDataValueFormatException( "Truncated indefinite-length contents: " + bytesRead + " bytes read"); } bytesRead++; if (bytesRead < 0) { throw new BerDataValueFormatException("Indefinite-length contents too long"); } if (b == 0) { if (prevZeroByte) { // End of contents reached -- we've read the value and its terminator 0x00 0x00 return bytesRead - 2; } prevZeroByte = true; continue; } else { prevZeroByte = false; } } } private static int skipConstructedIndefiniteLengthContents(RecordingInputStream in) throws BerDataValueFormatException { // Contents are terminated by 0x00 0x00. However, this data value is constructed, meaning it // can contain data values which are indefinite length encoded as well. As a result, we // must parse the direct children of this data value to correctly skip over the contents of // this data value. int readByteCountBefore = in.getReadByteCount(); while (true) { // We can't easily peek for the 0x00 0x00 terminator using the provided InputStream. // Thus, we use the fact that 0x00 0x00 parses as a data value whose encoded form we // then check below to see whether it's 0x00 0x00. BerDataValue dataValue = readDataValue(in); if (dataValue == null) { throw new BerDataValueFormatException( "Truncated indefinite-length contents: " + (in.getReadByteCount() - readByteCountBefore) + " bytes read"); } if (in.getReadByteCount() <= 0) { throw new BerDataValueFormatException("Indefinite-length contents too long"); } ByteBuffer encoded = dataValue.getEncoded(); if ((encoded.remaining() == 2) && (encoded.get(0) == 0) && (encoded.get(1) == 0)) { // 0x00 0x00 encountered return in.getReadByteCount() - readByteCountBefore - 2; } } } private static class RecordingInputStream extends InputStream { private final InputStream mIn; private final ByteArrayOutputStream mBuf; private RecordingInputStream(InputStream in) { mIn = in; mBuf = new ByteArrayOutputStream(); } public byte[] getReadBytes() { return mBuf.toByteArray(); } public int getReadByteCount() { return mBuf.size(); } @Override public int read() throws IOException { int b = mIn.read(); if (b != -1) { mBuf.write(b); } return b; } @Override public int read(byte[] b) throws IOException { int len = mIn.read(b); if (len > 0) { mBuf.write(b, 0, len); } return len; } @Override public int read(byte[] b, int off, int len) throws IOException { len = mIn.read(b, off, len); if (len > 0) { mBuf.write(b, off, len); } return len; } @Override public long skip(long n) throws IOException { if (n <= 0) { return mIn.skip(n); } byte[] buf = new byte[4096]; int len = mIn.read(buf, 0, (int) Math.min(buf.length, n)); if (len > 0) { mBuf.write(buf, 0, len); } return (len < 0) ? 0 : len; } @Override public int available() throws IOException { return super.available(); } @Override public void close() throws IOException { super.close(); } @Override public synchronized void mark(int readlimit) {} @Override public synchronized void reset() throws IOException { throw new IOException("mark/reset not supported"); } @Override public boolean markSupported() { return false; } } }