/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.sshd.common.util.io.der;
import java.io.ByteArrayInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StreamCorruptedException;
import java.math.BigInteger;
import java.util.Arrays;
import org.apache.sshd.common.util.NumberUtils;
import org.apache.sshd.common.util.buffer.BufferUtils;
/**
* A bare minimum DER parser - just enough to be able to decode
* signatures and private keys
*
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class DERParser extends FilterInputStream {
/**
* Maximum size of data allowed by {@link #readLength()} - it is a bit
* arbitrary since one can encode 32-bit length data, but it is good
* enough for the keys
*/
public static final int MAX_DER_VALUE_LENGTH = 2 * Short.MAX_VALUE;
private final byte[] lenBytes = new byte[Integer.BYTES];
public DERParser(byte... bytes) {
this(bytes, 0, NumberUtils.length(bytes));
}
public DERParser(byte[] bytes, int offset, int len) {
this(new ByteArrayInputStream(bytes, offset, len));
}
public DERParser(InputStream s) {
super(s);
}
/**
* Decode the length of the field. Can only support length
* encoding up to 4 octets. In BER/DER encoding, length can
* be encoded in 2 forms:
* <ul>
* <li><p>
* Short form - One octet. Bit 8 has value "0" and bits 7-1
* give the length.
* </p></li>
*
* <li><p>
* Long form - Two to 127 octets (only 4 is supported here).
* Bit 8 of first octet has value "1" and bits 7-1 give the
* number of additional length octets. Second and following
* octets give the length, base 256, most significant digit
* first.
* </p></li>
* </ul>
*
* @return The length as integer
* @throws IOException If invalid format found
*/
public int readLength() throws IOException {
int i = read();
if (i == -1) {
throw new StreamCorruptedException("Invalid DER: length missing");
}
// A single byte short length
if ((i & ~0x7F) == 0) {
return i;
}
int num = i & 0x7F;
// TODO We can't handle length longer than 4 bytes
if ((i >= 0xFF) || (num > lenBytes.length)) {
throw new StreamCorruptedException("Invalid DER: length field too big: " + i);
}
// place the read bytes last so that the 1st ones are zeroes as big endian
Arrays.fill(lenBytes, (byte) 0);
int n = read(lenBytes, 4 - num, num);
if (n < num) {
throw new StreamCorruptedException("Invalid DER: length data too short: expected=" + num + ", actual=" + n);
}
long len = BufferUtils.getUInt(lenBytes);
if (len < 0x7FL) { // according to standard: "the shortest possible length encoding must be used"
throw new StreamCorruptedException("Invalid DER: length not in shortest form: " + len);
}
if (len > MAX_DER_VALUE_LENGTH) {
throw new StreamCorruptedException("Invalid DER: data length too big: " + len + " (max=" + MAX_DER_VALUE_LENGTH + ")");
}
// we know the cast is safe since it is less than MAX_DER_VALUE_LENGTH which is ~64K
return (int) len;
}
public ASN1Object readObject() throws IOException {
int tag = read();
if (tag == -1) {
return null;
}
int length = readLength();
byte[] value = new byte[length];
int n = read(value);
if (n < length) {
throw new StreamCorruptedException("Invalid DER: stream too short, missing value: read " + n + " out of required " + length);
}
return new ASN1Object((byte) tag, length, value);
}
public BigInteger readBigInteger() throws IOException {
int type = read();
if (type != 0x02) {
throw new StreamCorruptedException("Invalid DER: data type is not an INTEGER: 0x" + Integer.toHexString(type));
}
int len = readLength();
byte[] value = new byte[len];
int n = read(value);
if (n < len) {
throw new StreamCorruptedException("Invalid DER: stream too short, missing value: read " + n + " out of required " + len);
}
return new BigInteger(value);
}
}