/*
* Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.security.provider;
import java.io.*;
import java.util.*;
import java.security.cert.*;
import sun.security.util.Pem;
import sun.security.x509.X509CertImpl;
import sun.security.x509.X509CRLImpl;
import sun.security.pkcs.PKCS7;
import sun.security.provider.certpath.X509CertPath;
import sun.security.provider.certpath.X509CertificatePair;
import sun.security.util.DerValue;
import sun.security.util.Cache;
import java.util.Base64;
import sun.security.pkcs.ParsingException;
This class defines a certificate factory for X.509 v3 certificates & certification paths, and X.509 v2 certificate revocation lists (CRLs). Author: Jan Luehe, Hemma Prafullchandra, Sean Mullan See Also:
/**
* This class defines a certificate factory for X.509 v3 certificates {@literal &}
* certification paths, and X.509 v2 certificate revocation lists (CRLs).
*
* @author Jan Luehe
* @author Hemma Prafullchandra
* @author Sean Mullan
*
*
* @see java.security.cert.CertificateFactorySpi
* @see java.security.cert.Certificate
* @see java.security.cert.CertPath
* @see java.security.cert.CRL
* @see java.security.cert.X509Certificate
* @see java.security.cert.X509CRL
* @see sun.security.x509.X509CertImpl
* @see sun.security.x509.X509CRLImpl
*/
public class X509Factory extends CertificateFactorySpi {
public static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----";
public static final String END_CERT = "-----END CERTIFICATE-----";
private static final int ENC_MAX_LENGTH = 4096 * 1024; // 4 MB MAX
private static final Cache<Object, X509CertImpl> certCache
= Cache.newSoftMemoryCache(750);
private static final Cache<Object, X509CRLImpl> crlCache
= Cache.newSoftMemoryCache(750);
Generates an X.509 certificate object and initializes it with
the data read from the input stream is
.
Params: - is – an input stream with the certificate data.
Throws: - CertificateException – on parsing errors.
Returns: an X.509 certificate object initialized with the data
from the input stream.
/**
* Generates an X.509 certificate object and initializes it with
* the data read from the input stream <code>is</code>.
*
* @param is an input stream with the certificate data.
*
* @return an X.509 certificate object initialized with the data
* from the input stream.
*
* @exception CertificateException on parsing errors.
*/
@Override
public Certificate engineGenerateCertificate(InputStream is)
throws CertificateException
{
if (is == null) {
// clear the caches (for debugging)
certCache.clear();
X509CertificatePair.clearCache();
throw new CertificateException("Missing input stream");
}
try {
byte[] encoding = readOneBlock(is);
if (encoding != null) {
X509CertImpl cert = getFromCache(certCache, encoding);
if (cert != null) {
return cert;
}
cert = new X509CertImpl(encoding);
addToCache(certCache, cert.getEncodedInternal(), cert);
return cert;
} else {
throw new IOException("Empty input");
}
} catch (IOException ioe) {
throw new CertificateException("Could not parse certificate: " +
ioe.toString(), ioe);
}
}
Read from the stream until length bytes have been read or EOF has
been reached. Return the number of bytes actually read.
/**
* Read from the stream until length bytes have been read or EOF has
* been reached. Return the number of bytes actually read.
*/
private static int readFully(InputStream in, ByteArrayOutputStream bout,
int length) throws IOException {
int read = 0;
byte[] buffer = new byte[2048];
while (length > 0) {
int n = in.read(buffer, 0, length<2048?length:2048);
if (n <= 0) {
break;
}
bout.write(buffer, 0, n);
read += n;
length -= n;
}
return read;
}
Return an interned X509CertImpl for the given certificate.
If the given X509Certificate or X509CertImpl is already present
in the cert cache, the cached object is returned. Otherwise,
if it is a X509Certificate, it is first converted to a X509CertImpl.
Then the X509CertImpl is added to the cache and returned.
Note that all certificates created via generateCertificate(InputStream)
are already interned and this method does not need to be called.
It is useful for certificates that cannot be created via
generateCertificate() and for converting other X509Certificate
implementations to an X509CertImpl.
Params: - c – The source X509Certificate
Throws: - CertificateException – if failures occur while obtaining the DER
encoding for certificate data.
Returns: An X509CertImpl object that is either a cached certificate or a
newly built X509CertImpl from the provided X509Certificate
/**
* Return an interned X509CertImpl for the given certificate.
* If the given X509Certificate or X509CertImpl is already present
* in the cert cache, the cached object is returned. Otherwise,
* if it is a X509Certificate, it is first converted to a X509CertImpl.
* Then the X509CertImpl is added to the cache and returned.
*
* Note that all certificates created via generateCertificate(InputStream)
* are already interned and this method does not need to be called.
* It is useful for certificates that cannot be created via
* generateCertificate() and for converting other X509Certificate
* implementations to an X509CertImpl.
*
* @param c The source X509Certificate
* @return An X509CertImpl object that is either a cached certificate or a
* newly built X509CertImpl from the provided X509Certificate
* @throws CertificateException if failures occur while obtaining the DER
* encoding for certificate data.
*/
public static synchronized X509CertImpl intern(X509Certificate c)
throws CertificateException {
if (c == null) {
return null;
}
boolean isImpl = c instanceof X509CertImpl;
byte[] encoding;
if (isImpl) {
encoding = ((X509CertImpl)c).getEncodedInternal();
} else {
encoding = c.getEncoded();
}
X509CertImpl newC = getFromCache(certCache, encoding);
if (newC != null) {
return newC;
}
if (isImpl) {
newC = (X509CertImpl)c;
} else {
newC = new X509CertImpl(encoding);
encoding = newC.getEncodedInternal();
}
addToCache(certCache, encoding, newC);
return newC;
}
Return an interned X509CRLImpl for the given certificate.
For more information, see intern(X509Certificate).
Params: - c – The source X509CRL
Throws: - CRLException – if failures occur while obtaining the DER
encoding for CRL data.
Returns: An X509CRLImpl object that is either a cached CRL or a
newly built X509CRLImpl from the provided X509CRL
/**
* Return an interned X509CRLImpl for the given certificate.
* For more information, see intern(X509Certificate).
*
* @param c The source X509CRL
* @return An X509CRLImpl object that is either a cached CRL or a
* newly built X509CRLImpl from the provided X509CRL
* @throws CRLException if failures occur while obtaining the DER
* encoding for CRL data.
*/
public static synchronized X509CRLImpl intern(X509CRL c)
throws CRLException {
if (c == null) {
return null;
}
boolean isImpl = c instanceof X509CRLImpl;
byte[] encoding;
if (isImpl) {
encoding = ((X509CRLImpl)c).getEncodedInternal();
} else {
encoding = c.getEncoded();
}
X509CRLImpl newC = getFromCache(crlCache, encoding);
if (newC != null) {
return newC;
}
if (isImpl) {
newC = (X509CRLImpl)c;
} else {
newC = new X509CRLImpl(encoding);
encoding = newC.getEncodedInternal();
}
addToCache(crlCache, encoding, newC);
return newC;
}
Get the X509CertImpl or X509CRLImpl from the cache.
/**
* Get the X509CertImpl or X509CRLImpl from the cache.
*/
private static synchronized <K,V> V getFromCache(Cache<K,V> cache,
byte[] encoding) {
Object key = new Cache.EqualByteArray(encoding);
return cache.get(key);
}
Add the X509CertImpl or X509CRLImpl to the cache.
/**
* Add the X509CertImpl or X509CRLImpl to the cache.
*/
private static synchronized <V> void addToCache(Cache<Object, V> cache,
byte[] encoding, V value) {
if (encoding.length > ENC_MAX_LENGTH) {
return;
}
Object key = new Cache.EqualByteArray(encoding);
cache.put(key, value);
}
Generates a CertPath
object and initializes it with
the data read from the InputStream
inStream. The data
is assumed to be in the default encoding.
Params: - inStream – an
InputStream
containing the data
Throws: - CertificateException – if an exception occurs while decoding
Returns: a CertPath
initialized with the data from the
InputStream
Since: 1.4
/**
* Generates a <code>CertPath</code> object and initializes it with
* the data read from the <code>InputStream</code> inStream. The data
* is assumed to be in the default encoding.
*
* @param inStream an <code>InputStream</code> containing the data
* @return a <code>CertPath</code> initialized with the data from the
* <code>InputStream</code>
* @exception CertificateException if an exception occurs while decoding
* @since 1.4
*/
@Override
public CertPath engineGenerateCertPath(InputStream inStream)
throws CertificateException
{
if (inStream == null) {
throw new CertificateException("Missing input stream");
}
try {
byte[] encoding = readOneBlock(inStream);
if (encoding != null) {
return new X509CertPath(new ByteArrayInputStream(encoding));
} else {
throw new IOException("Empty input");
}
} catch (IOException ioe) {
throw new CertificateException(ioe.getMessage());
}
}
Generates a CertPath
object and initializes it with
the data read from the InputStream
inStream. The data
is assumed to be in the specified encoding.
Params: - inStream – an
InputStream
containing the data - encoding – the encoding used for the data
Throws: - CertificateException – if an exception occurs while decoding or
the encoding requested is not supported
Returns: a CertPath
initialized with the data from the
InputStream
Since: 1.4
/**
* Generates a <code>CertPath</code> object and initializes it with
* the data read from the <code>InputStream</code> inStream. The data
* is assumed to be in the specified encoding.
*
* @param inStream an <code>InputStream</code> containing the data
* @param encoding the encoding used for the data
* @return a <code>CertPath</code> initialized with the data from the
* <code>InputStream</code>
* @exception CertificateException if an exception occurs while decoding or
* the encoding requested is not supported
* @since 1.4
*/
@Override
public CertPath engineGenerateCertPath(InputStream inStream,
String encoding) throws CertificateException
{
if (inStream == null) {
throw new CertificateException("Missing input stream");
}
try {
byte[] data = readOneBlock(inStream);
if (data != null) {
return new X509CertPath(new ByteArrayInputStream(data), encoding);
} else {
throw new IOException("Empty input");
}
} catch (IOException ioe) {
throw new CertificateException(ioe.getMessage());
}
}
Generates a CertPath
object and initializes it with
a List
of Certificate
s.
The certificates supplied must be of a type supported by the
CertificateFactory
. They will be copied out of the supplied
List
object.
Params: - certificates – a
List
of Certificate
s
Throws: - CertificateException – if an exception occurs
Returns: a CertPath
initialized with the supplied list of
certificates Since: 1.4
/**
* Generates a <code>CertPath</code> object and initializes it with
* a <code>List</code> of <code>Certificate</code>s.
* <p>
* The certificates supplied must be of a type supported by the
* <code>CertificateFactory</code>. They will be copied out of the supplied
* <code>List</code> object.
*
* @param certificates a <code>List</code> of <code>Certificate</code>s
* @return a <code>CertPath</code> initialized with the supplied list of
* certificates
* @exception CertificateException if an exception occurs
* @since 1.4
*/
@Override
public CertPath
engineGenerateCertPath(List<? extends Certificate> certificates)
throws CertificateException
{
return(new X509CertPath(certificates));
}
Returns an iteration of the CertPath
encodings supported
by this certificate factory, with the default encoding first.
Attempts to modify the returned Iterator
via its
remove
method result in an
UnsupportedOperationException
.
Returns: an Iterator
over the names of the supported
CertPath
encodings (as String
s) Since: 1.4
/**
* Returns an iteration of the <code>CertPath</code> encodings supported
* by this certificate factory, with the default encoding first.
* <p>
* Attempts to modify the returned <code>Iterator</code> via its
* <code>remove</code> method result in an
* <code>UnsupportedOperationException</code>.
*
* @return an <code>Iterator</code> over the names of the supported
* <code>CertPath</code> encodings (as <code>String</code>s)
* @since 1.4
*/
@Override
public Iterator<String> engineGetCertPathEncodings() {
return(X509CertPath.getEncodingsStatic());
}
Returns a (possibly empty) collection view of X.509 certificates read
from the given input stream is
.
Params: - is – the input stream with the certificates.
Throws: - CertificateException – on parsing errors.
Returns: a (possibly empty) collection view of X.509 certificate objects
initialized with the data from the input stream.
/**
* Returns a (possibly empty) collection view of X.509 certificates read
* from the given input stream <code>is</code>.
*
* @param is the input stream with the certificates.
*
* @return a (possibly empty) collection view of X.509 certificate objects
* initialized with the data from the input stream.
*
* @exception CertificateException on parsing errors.
*/
@Override
public Collection<? extends java.security.cert.Certificate>
engineGenerateCertificates(InputStream is)
throws CertificateException {
if (is == null) {
throw new CertificateException("Missing input stream");
}
try {
return parseX509orPKCS7Cert(is);
} catch (IOException ioe) {
throw new CertificateException(ioe);
}
}
Generates an X.509 certificate revocation list (CRL) object and
initializes it with the data read from the given input stream
is
.
Params: - is – an input stream with the CRL data.
Throws: - CRLException – on parsing errors.
Returns: an X.509 CRL object initialized with the data
from the input stream.
/**
* Generates an X.509 certificate revocation list (CRL) object and
* initializes it with the data read from the given input stream
* <code>is</code>.
*
* @param is an input stream with the CRL data.
*
* @return an X.509 CRL object initialized with the data
* from the input stream.
*
* @exception CRLException on parsing errors.
*/
@Override
public CRL engineGenerateCRL(InputStream is)
throws CRLException
{
if (is == null) {
// clear the cache (for debugging)
crlCache.clear();
throw new CRLException("Missing input stream");
}
try {
byte[] encoding = readOneBlock(is);
if (encoding != null) {
X509CRLImpl crl = getFromCache(crlCache, encoding);
if (crl != null) {
return crl;
}
crl = new X509CRLImpl(encoding);
addToCache(crlCache, crl.getEncodedInternal(), crl);
return crl;
} else {
throw new IOException("Empty input");
}
} catch (IOException ioe) {
throw new CRLException(ioe.getMessage());
}
}
Returns a (possibly empty) collection view of X.509 CRLs read
from the given input stream is
.
Params: - is – the input stream with the CRLs.
Throws: - CRLException – on parsing errors.
Returns: a (possibly empty) collection view of X.509 CRL objects
initialized with the data from the input stream.
/**
* Returns a (possibly empty) collection view of X.509 CRLs read
* from the given input stream <code>is</code>.
*
* @param is the input stream with the CRLs.
*
* @return a (possibly empty) collection view of X.509 CRL objects
* initialized with the data from the input stream.
*
* @exception CRLException on parsing errors.
*/
@Override
public Collection<? extends java.security.cert.CRL> engineGenerateCRLs(
InputStream is) throws CRLException
{
if (is == null) {
throw new CRLException("Missing input stream");
}
try {
return parseX509orPKCS7CRL(is);
} catch (IOException ioe) {
throw new CRLException(ioe.getMessage());
}
}
/*
* Parses the data in the given input stream as a sequence of DER
* encoded X.509 certificates (in binary or base 64 encoded format) OR
* as a single PKCS#7 encoded blob (in binary or base64 encoded format).
*/
private Collection<? extends java.security.cert.Certificate>
parseX509orPKCS7Cert(InputStream is)
throws CertificateException, IOException
{
int peekByte;
byte[] data;
PushbackInputStream pbis = new PushbackInputStream(is);
Collection<X509CertImpl> coll = new ArrayList<>();
// Test the InputStream for end-of-stream. If the stream's
// initial state is already at end-of-stream then return
// an empty collection. Otherwise, push the byte back into the
// stream and let readOneBlock look for the first certificate.
peekByte = pbis.read();
if (peekByte == -1) {
return new ArrayList<>(0);
} else {
pbis.unread(peekByte);
data = readOneBlock(pbis);
}
// If we end up with a null value after reading the first block
// then we know the end-of-stream has been reached and no certificate
// data has been found.
if (data == null) {
throw new CertificateException("No certificate data found");
}
try {
PKCS7 pkcs7 = new PKCS7(data);
X509Certificate[] certs = pkcs7.getCertificates();
// certs are optional in PKCS #7
if (certs != null) {
return Arrays.asList(certs);
} else {
// no certificates provided
return new ArrayList<>(0);
}
} catch (ParsingException e) {
while (data != null) {
coll.add(new X509CertImpl(data));
data = readOneBlock(pbis);
}
}
return coll;
}
/*
* Parses the data in the given input stream as a sequence of DER encoded
* X.509 CRLs (in binary or base 64 encoded format) OR as a single PKCS#7
* encoded blob (in binary or base 64 encoded format).
*/
private Collection<? extends java.security.cert.CRL>
parseX509orPKCS7CRL(InputStream is)
throws CRLException, IOException
{
int peekByte;
byte[] data;
PushbackInputStream pbis = new PushbackInputStream(is);
Collection<X509CRLImpl> coll = new ArrayList<>();
// Test the InputStream for end-of-stream. If the stream's
// initial state is already at end-of-stream then return
// an empty collection. Otherwise, push the byte back into the
// stream and let readOneBlock look for the first CRL.
peekByte = pbis.read();
if (peekByte == -1) {
return new ArrayList<>(0);
} else {
pbis.unread(peekByte);
data = readOneBlock(pbis);
}
// If we end up with a null value after reading the first block
// then we know the end-of-stream has been reached and no CRL
// data has been found.
if (data == null) {
throw new CRLException("No CRL data found");
}
try {
PKCS7 pkcs7 = new PKCS7(data);
X509CRL[] crls = pkcs7.getCRLs();
// CRLs are optional in PKCS #7
if (crls != null) {
return Arrays.asList(crls);
} else {
// no crls provided
return new ArrayList<>(0);
}
} catch (ParsingException e) {
while (data != null) {
coll.add(new X509CRLImpl(data));
data = readOneBlock(pbis);
}
}
return coll;
}
Returns an ASN.1 SEQUENCE from a stream, which might be a BER-encoded
binary block or a PEM-style BASE64-encoded ASCII data. In the latter
case, it's de-BASE64'ed before return.
After the reading, the input stream pointer is after the BER block, or
after the newline character after the -----END SOMETHING----- line.
Params: - is – the InputStream
Throws: - IOException – If any parsing error
Returns: byte block or null if end of stream
/**
* Returns an ASN.1 SEQUENCE from a stream, which might be a BER-encoded
* binary block or a PEM-style BASE64-encoded ASCII data. In the latter
* case, it's de-BASE64'ed before return.
*
* After the reading, the input stream pointer is after the BER block, or
* after the newline character after the -----END SOMETHING----- line.
*
* @param is the InputStream
* @return byte block or null if end of stream
* @throws IOException If any parsing error
*/
private static byte[] readOneBlock(InputStream is) throws IOException {
// The first character of a BLOCK.
int c = is.read();
if (c == -1) {
return null;
}
if (c == DerValue.tag_Sequence) {
ByteArrayOutputStream bout = new ByteArrayOutputStream(2048);
bout.write(c);
readBERInternal(is, bout, c);
return bout.toByteArray();
} else {
// Read BASE64 encoded data, might skip info at the beginning
ByteArrayOutputStream data = new ByteArrayOutputStream();
// Step 1: Read until header is found
int hyphen = (c=='-') ? 1: 0; // count of consequent hyphens
int last = (c=='-') ? -1: c; // the char before hyphen
while (true) {
int next = is.read();
if (next == -1) {
// We accept useless data after the last block,
// say, empty lines.
return null;
}
if (next == '-') {
hyphen++;
} else {
hyphen = 0;
last = next;
}
if (hyphen == 5 && (last == -1 || last == '\r' || last == '\n')) {
break;
}
}
// Step 2: Read the rest of header, determine the line end
int end;
StringBuilder header = new StringBuilder("-----");
while (true) {
int next = is.read();
if (next == -1) {
throw new IOException("Incomplete data");
}
if (next == '\n') {
end = '\n';
break;
}
if (next == '\r') {
next = is.read();
if (next == -1) {
throw new IOException("Incomplete data");
}
if (next == '\n') {
end = '\n';
} else {
end = '\r';
// Skip all white space chars
if (next != 9 && next != 10 && next != 13 && next != 32) {
data.write(next);
}
}
break;
}
header.append((char)next);
}
// Step 3: Read the data
while (true) {
int next = is.read();
if (next == -1) {
throw new IOException("Incomplete data");
}
if (next != '-') {
// Skip all white space chars
if (next != 9 && next != 10 && next != 13 && next != 32) {
data.write(next);
}
} else {
break;
}
}
// Step 4: Consume the footer
StringBuilder footer = new StringBuilder("-");
while (true) {
int next = is.read();
// Add next == '\n' for maximum safety, in case endline
// is not consistent.
if (next == -1 || next == end || next == '\n') {
break;
}
if (next != '\r') footer.append((char)next);
}
checkHeaderFooter(header.toString(), footer.toString());
try {
return Base64.getDecoder().decode(data.toByteArray());
} catch (IllegalArgumentException e) {
throw new IOException(e);
}
}
}
private static void checkHeaderFooter(String header,
String footer) throws IOException {
if (header.length() < 16 || !header.startsWith("-----BEGIN ") ||
!header.endsWith("-----")) {
throw new IOException("Illegal header: " + header);
}
if (footer.length() < 14 || !footer.startsWith("-----END ") ||
!footer.endsWith("-----")) {
throw new IOException("Illegal footer: " + footer);
}
String headerType = header.substring(11, header.length()-5);
String footerType = footer.substring(9, footer.length()-5);
if (!headerType.equals(footerType)) {
throw new IOException("Header and footer do not match: " +
header + " " + footer);
}
}
Read one BER data block. This method is aware of indefinite-length BER
encoding and will read all of the sub-sections in a recursive way
Params: - is – Read from this InputStream
- bout – Write into this OutputStream
- tag – Tag already read (-1 mean not read)
Throws: - IOException – Any parsing error
Returns: The current tag, used to check EOC in indefinite-length BER
/**
* Read one BER data block. This method is aware of indefinite-length BER
* encoding and will read all of the sub-sections in a recursive way
*
* @param is Read from this InputStream
* @param bout Write into this OutputStream
* @param tag Tag already read (-1 mean not read)
* @return The current tag, used to check EOC in indefinite-length BER
* @throws IOException Any parsing error
*/
private static int readBERInternal(InputStream is,
ByteArrayOutputStream bout, int tag) throws IOException {
if (tag == -1) { // Not read before the call, read now
tag = is.read();
if (tag == -1) {
throw new IOException("BER/DER tag info absent");
}
if ((tag & 0x1f) == 0x1f) {
throw new IOException("Multi octets tag not supported");
}
bout.write(tag);
}
int n = is.read();
if (n == -1) {
throw new IOException("BER/DER length info absent");
}
bout.write(n);
int length;
if (n == 0x80) { // Indefinite-length encoding
if ((tag & 0x20) != 0x20) {
throw new IOException(
"Non constructed encoding must have definite length");
}
while (true) {
int subTag = readBERInternal(is, bout, -1);
if (subTag == 0) { // EOC, end of indefinite-length section
break;
}
}
} else {
if (n < 0x80) {
length = n;
} else if (n == 0x81) {
length = is.read();
if (length == -1) {
throw new IOException("Incomplete BER/DER length info");
}
bout.write(length);
} else if (n == 0x82) {
int highByte = is.read();
int lowByte = is.read();
if (lowByte == -1) {
throw new IOException("Incomplete BER/DER length info");
}
bout.write(highByte);
bout.write(lowByte);
length = (highByte << 8) | lowByte;
} else if (n == 0x83) {
int highByte = is.read();
int midByte = is.read();
int lowByte = is.read();
if (lowByte == -1) {
throw new IOException("Incomplete BER/DER length info");
}
bout.write(highByte);
bout.write(midByte);
bout.write(lowByte);
length = (highByte << 16) | (midByte << 8) | lowByte;
} else if (n == 0x84) {
int highByte = is.read();
int nextByte = is.read();
int midByte = is.read();
int lowByte = is.read();
if (lowByte == -1) {
throw new IOException("Incomplete BER/DER length info");
}
if (highByte > 127) {
throw new IOException("Invalid BER/DER data (a little huge?)");
}
bout.write(highByte);
bout.write(nextByte);
bout.write(midByte);
bout.write(lowByte);
length = (highByte << 24 ) | (nextByte << 16) |
(midByte << 8) | lowByte;
} else { // ignore longer length forms
throw new IOException("Invalid BER/DER data (too huge?)");
}
if (readFully(is, bout, length) != length) {
throw new IOException("Incomplete BER/DER data");
}
}
return tag;
}
}