| /* |
| * 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; |
| } |
| |
| @SuppressWarnings("resource") |
| @Override |
| public BerDataValue readDataValue() throws BerDataValueFormatException { |
| RecordingInputStream in = new RecordingInputStream(mIn); |
| |
| 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"); |
| } |
| |
| 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 = skipIndefiniteLengthContents(in); |
| } |
| |
| byte[] encoded = in.getReadBytes(); |
| ByteBuffer encodedContents = |
| ByteBuffer.wrap(encoded, contentsOffsetInDataValue, contentsLength); |
| return new BerDataValue( |
| ByteBuffer.wrap(encoded), |
| encodedContents, |
| BerEncoding.getTagClass((byte) firstIdentifierByte), |
| BerEncoding.isConstructed((byte) firstIdentifierByte), |
| 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 skipIndefiniteLengthContents(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 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; |
| } |
| } |
| } |