/* * 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); } }