Home | History | Annotate | Download | only in provider
      1 /*
      2  * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved.
      3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      4  *
      5  * This code is free software; you can redistribute it and/or modify it
      6  * under the terms of the GNU General Public License version 2 only, as
      7  * published by the Free Software Foundation.  Oracle designates this
      8  * particular file as subject to the "Classpath" exception as provided
      9  * by Oracle in the LICENSE file that accompanied this code.
     10  *
     11  * This code is distributed in the hope that it will be useful, but WITHOUT
     12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     14  * version 2 for more details (a copy is included in the LICENSE file that
     15  * accompanied this code).
     16  *
     17  * You should have received a copy of the GNU General Public License version
     18  * 2 along with this work; if not, write to the Free Software Foundation,
     19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
     20  *
     21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
     22  * or visit www.oracle.com if you need additional information or have any
     23  * questions.
     24  */
     25 
     26 package sun.security.provider;
     27 
     28 import java.security.cert.*;
     29 import sun.security.x509.X509CertImpl;
     30 import sun.security.x509.X509CRLImpl;
     31 import sun.security.util.Cache;
     32 
     33 /**
     34  * This class defines a certificate factory for X.509 v3 certificates &
     35  * certification paths, and X.509 v2 certificate revocation lists (CRLs).
     36  *
     37  * @author Jan Luehe
     38  * @author Hemma Prafullchandra
     39  * @author Sean Mullan
     40  *
     41  *
     42  * @see java.security.cert.CertificateFactorySpi
     43  * @see java.security.cert.Certificate
     44  * @see java.security.cert.CertPath
     45  * @see java.security.cert.CRL
     46  * @see java.security.cert.X509Certificate
     47  * @see java.security.cert.X509CRL
     48  * @see sun.security.x509.X509CertImpl
     49  * @see sun.security.x509.X509CRLImpl
     50  */
     51 
     52 // Android-changed
     53 // public class X509Factory extends CertificateFactorySpi {
     54 public class X509Factory {
     55 
     56     // BEGIN Android-removed
     57     // public static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----";
     58     // public static final String END_CERT = "-----END CERTIFICATE-----";
     59     // END Android-removed
     60 
     61     private static final int ENC_MAX_LENGTH = 4096 * 1024; // 4 MB MAX
     62 
     63     private static final Cache<Object, X509CertImpl> certCache
     64         = Cache.newSoftMemoryCache(750);
     65     private static final Cache<Object, X509CRLImpl> crlCache
     66         = Cache.newSoftMemoryCache(750);
     67 
     68     // BEGIN Android-removed
     69     /*
     70     /**
     71      * Generates an X.509 certificate object and initializes it with
     72      * the data read from the input stream <code>is</code>.
     73      *
     74      * @param is an input stream with the certificate data.
     75      *
     76      * @return an X.509 certificate object initialized with the data
     77      * from the input stream.
     78      *
     79      * @exception CertificateException on parsing errors.
     80      *
     81     @Override
     82     public Certificate engineGenerateCertificate(InputStream is)
     83         throws CertificateException
     84     {
     85         if (is == null) {
     86             // clear the caches (for debugging)
     87             certCache.clear();
     88             X509CertificatePair.clearCache();
     89             throw new CertificateException("Missing input stream");
     90         }
     91         try {
     92             byte[] encoding = readOneBlock(is);
     93             if (encoding != null) {
     94                 X509CertImpl cert = getFromCache(certCache, encoding);
     95                 if (cert != null) {
     96                     return cert;
     97                 }
     98                 cert = new X509CertImpl(encoding);
     99                 addToCache(certCache, cert.getEncodedInternal(), cert);
    100                 return cert;
    101             } else {
    102                 throw new IOException("Empty input");
    103             }
    104         } catch (IOException ioe) {
    105             throw new CertificateException("Could not parse certificate: " +
    106                     ioe.toString(), ioe);
    107         }
    108     }
    109 
    110     /**
    111      * Read from the stream until length bytes have been read or EOF has
    112      * been reached. Return the number of bytes actually read.
    113      *
    114     private static int readFully(InputStream in, ByteArrayOutputStream bout,
    115             int length) throws IOException {
    116         int read = 0;
    117         byte[] buffer = new byte[2048];
    118         while (length > 0) {
    119             int n = in.read(buffer, 0, length<2048?length:2048);
    120             if (n <= 0) {
    121                 break;
    122             }
    123             bout.write(buffer, 0, n);
    124             read += n;
    125             length -= n;
    126         }
    127         return read;
    128     }
    129     */
    130     // END Android-removed
    131 
    132     /**
    133      * Return an interned X509CertImpl for the given certificate.
    134      * If the given X509Certificate or X509CertImpl is already present
    135      * in the cert cache, the cached object is returned. Otherwise,
    136      * if it is a X509Certificate, it is first converted to a X509CertImpl.
    137      * Then the X509CertImpl is added to the cache and returned.
    138      *
    139      * Note that all certificates created via generateCertificate(InputStream)
    140      * are already interned and this method does not need to be called.
    141      * It is useful for certificates that cannot be created via
    142      * generateCertificate() and for converting other X509Certificate
    143      * implementations to an X509CertImpl.
    144      *
    145      * @param c The source X509Certificate
    146      * @return An X509CertImpl object that is either a cached certificate or a
    147      *      newly built X509CertImpl from the provided X509Certificate
    148      * @throws CertificateException if failures occur while obtaining the DER
    149      *      encoding for certificate data.
    150      */
    151     public static synchronized X509CertImpl intern(X509Certificate c)
    152             throws CertificateException {
    153         if (c == null) {
    154             return null;
    155         }
    156         boolean isImpl = c instanceof X509CertImpl;
    157         byte[] encoding;
    158         if (isImpl) {
    159             encoding = ((X509CertImpl)c).getEncodedInternal();
    160         } else {
    161             encoding = c.getEncoded();
    162         }
    163         X509CertImpl newC = getFromCache(certCache, encoding);
    164         if (newC != null) {
    165             return newC;
    166         }
    167         if (isImpl) {
    168             newC = (X509CertImpl)c;
    169         } else {
    170             newC = new X509CertImpl(encoding);
    171             encoding = newC.getEncodedInternal();
    172         }
    173         addToCache(certCache, encoding, newC);
    174         return newC;
    175     }
    176 
    177     /**
    178      * Return an interned X509CRLImpl for the given certificate.
    179      * For more information, see intern(X509Certificate).
    180      *
    181      * @param c The source X509CRL
    182      * @return An X509CRLImpl object that is either a cached CRL or a
    183      *      newly built X509CRLImpl from the provided X509CRL
    184      * @throws CRLException if failures occur while obtaining the DER
    185      *      encoding for CRL data.
    186      */
    187     public static synchronized X509CRLImpl intern(X509CRL c)
    188             throws CRLException {
    189         if (c == null) {
    190             return null;
    191         }
    192         boolean isImpl = c instanceof X509CRLImpl;
    193         byte[] encoding;
    194         if (isImpl) {
    195             encoding = ((X509CRLImpl)c).getEncodedInternal();
    196         } else {
    197             encoding = c.getEncoded();
    198         }
    199         X509CRLImpl newC = getFromCache(crlCache, encoding);
    200         if (newC != null) {
    201             return newC;
    202         }
    203         if (isImpl) {
    204             newC = (X509CRLImpl)c;
    205         } else {
    206             newC = new X509CRLImpl(encoding);
    207             encoding = newC.getEncodedInternal();
    208         }
    209         addToCache(crlCache, encoding, newC);
    210         return newC;
    211     }
    212 
    213     /**
    214      * Get the X509CertImpl or X509CRLImpl from the cache.
    215      */
    216     private static synchronized <K,V> V getFromCache(Cache<K,V> cache,
    217             byte[] encoding) {
    218         Object key = new Cache.EqualByteArray(encoding);
    219         return cache.get(key);
    220     }
    221 
    222     /**
    223      * Add the X509CertImpl or X509CRLImpl to the cache.
    224      */
    225     private static synchronized <V> void addToCache(Cache<Object, V> cache,
    226             byte[] encoding, V value) {
    227         if (encoding.length > ENC_MAX_LENGTH) {
    228             return;
    229         }
    230         Object key = new Cache.EqualByteArray(encoding);
    231         cache.put(key, value);
    232     }
    233 
    234     // BEGIN Android-removed
    235     /*
    236     /**
    237      * Generates a <code>CertPath</code> object and initializes it with
    238      * the data read from the <code>InputStream</code> inStream. The data
    239      * is assumed to be in the default encoding.
    240      *
    241      * @param inStream an <code>InputStream</code> containing the data
    242      * @return a <code>CertPath</code> initialized with the data from the
    243      *   <code>InputStream</code>
    244      * @exception CertificateException if an exception occurs while decoding
    245      * @since 1.4
    246      *
    247     @Override
    248     public CertPath engineGenerateCertPath(InputStream inStream)
    249         throws CertificateException
    250     {
    251         if (inStream == null) {
    252             throw new CertificateException("Missing input stream");
    253         }
    254         try {
    255             byte[] encoding = readOneBlock(inStream);
    256             if (encoding != null) {
    257                 return new X509CertPath(new ByteArrayInputStream(encoding));
    258             } else {
    259                 throw new IOException("Empty input");
    260             }
    261         } catch (IOException ioe) {
    262             throw new CertificateException(ioe.getMessage());
    263         }
    264     }
    265 
    266     /**
    267      * Generates a <code>CertPath</code> object and initializes it with
    268      * the data read from the <code>InputStream</code> inStream. The data
    269      * is assumed to be in the specified encoding.
    270      *
    271      * @param inStream an <code>InputStream</code> containing the data
    272      * @param encoding the encoding used for the data
    273      * @return a <code>CertPath</code> initialized with the data from the
    274      *   <code>InputStream</code>
    275      * @exception CertificateException if an exception occurs while decoding or
    276      *   the encoding requested is not supported
    277      * @since 1.4
    278      *
    279     @Override
    280     public CertPath engineGenerateCertPath(InputStream inStream,
    281         String encoding) throws CertificateException
    282     {
    283         if (inStream == null) {
    284             throw new CertificateException("Missing input stream");
    285         }
    286         try {
    287             byte[] data = readOneBlock(inStream);
    288             if (data != null) {
    289                 return new X509CertPath(new ByteArrayInputStream(data), encoding);
    290             } else {
    291                 throw new IOException("Empty input");
    292             }
    293         } catch (IOException ioe) {
    294             throw new CertificateException(ioe.getMessage());
    295         }
    296     }
    297 
    298     /**
    299      * Generates a <code>CertPath</code> object and initializes it with
    300      * a <code>List</code> of <code>Certificate</code>s.
    301      * <p>
    302      * The certificates supplied must be of a type supported by the
    303      * <code>CertificateFactory</code>. They will be copied out of the supplied
    304      * <code>List</code> object.
    305      *
    306      * @param certificates a <code>List</code> of <code>Certificate</code>s
    307      * @return a <code>CertPath</code> initialized with the supplied list of
    308      *   certificates
    309      * @exception CertificateException if an exception occurs
    310      * @since 1.4
    311      *
    312     @Override
    313     public CertPath
    314         engineGenerateCertPath(List<? extends Certificate> certificates)
    315         throws CertificateException
    316     {
    317         return(new X509CertPath(certificates));
    318     }
    319 
    320     /**
    321      * Returns an iteration of the <code>CertPath</code> encodings supported
    322      * by this certificate factory, with the default encoding first.
    323      * <p>
    324      * Attempts to modify the returned <code>Iterator</code> via its
    325      * <code>remove</code> method result in an
    326      * <code>UnsupportedOperationException</code>.
    327      *
    328      * @return an <code>Iterator</code> over the names of the supported
    329      *         <code>CertPath</code> encodings (as <code>String</code>s)
    330      * @since 1.4
    331      *
    332     @Override
    333     public Iterator<String> engineGetCertPathEncodings() {
    334         return(X509CertPath.getEncodingsStatic());
    335     }
    336 
    337     /**
    338      * Returns a (possibly empty) collection view of X.509 certificates read
    339      * from the given input stream <code>is</code>.
    340      *
    341      * @param is the input stream with the certificates.
    342      *
    343      * @return a (possibly empty) collection view of X.509 certificate objects
    344      * initialized with the data from the input stream.
    345      *
    346      * @exception CertificateException on parsing errors.
    347      *
    348     @Override
    349     public Collection<? extends java.security.cert.Certificate>
    350             engineGenerateCertificates(InputStream is)
    351             throws CertificateException {
    352         if (is == null) {
    353             throw new CertificateException("Missing input stream");
    354         }
    355         try {
    356             return parseX509orPKCS7Cert(is);
    357         } catch (IOException ioe) {
    358             throw new CertificateException(ioe);
    359         }
    360     }
    361 
    362     /**
    363      * Generates an X.509 certificate revocation list (CRL) object and
    364      * initializes it with the data read from the given input stream
    365      * <code>is</code>.
    366      *
    367      * @param is an input stream with the CRL data.
    368      *
    369      * @return an X.509 CRL object initialized with the data
    370      * from the input stream.
    371      *
    372      * @exception CRLException on parsing errors.
    373      *
    374     @Override
    375     public CRL engineGenerateCRL(InputStream is)
    376         throws CRLException
    377     {
    378         if (is == null) {
    379             // clear the cache (for debugging)
    380             crlCache.clear();
    381             throw new CRLException("Missing input stream");
    382         }
    383         try {
    384             byte[] encoding = readOneBlock(is);
    385             if (encoding != null) {
    386                 X509CRLImpl crl = getFromCache(crlCache, encoding);
    387                 if (crl != null) {
    388                     return crl;
    389                 }
    390                 crl = new X509CRLImpl(encoding);
    391                 addToCache(crlCache, crl.getEncodedInternal(), crl);
    392                 return crl;
    393             } else {
    394                 throw new IOException("Empty input");
    395             }
    396         } catch (IOException ioe) {
    397             throw new CRLException(ioe.getMessage());
    398         }
    399     }
    400 
    401     /**
    402      * Returns a (possibly empty) collection view of X.509 CRLs read
    403      * from the given input stream <code>is</code>.
    404      *
    405      * @param is the input stream with the CRLs.
    406      *
    407      * @return a (possibly empty) collection view of X.509 CRL objects
    408      * initialized with the data from the input stream.
    409      *
    410      * @exception CRLException on parsing errors.
    411      *
    412     @Override
    413     public Collection<? extends java.security.cert.CRL> engineGenerateCRLs(
    414             InputStream is) throws CRLException
    415     {
    416         if (is == null) {
    417             throw new CRLException("Missing input stream");
    418         }
    419         try {
    420             return parseX509orPKCS7CRL(is);
    421         } catch (IOException ioe) {
    422             throw new CRLException(ioe.getMessage());
    423         }
    424     }
    425 
    426     /*
    427      * Parses the data in the given input stream as a sequence of DER
    428      * encoded X.509 certificates (in binary or base 64 encoded format) OR
    429      * as a single PKCS#7 encoded blob (in binary or base64 encoded format).
    430      *
    431     private Collection<? extends java.security.cert.Certificate>
    432         parseX509orPKCS7Cert(InputStream is)
    433         throws CertificateException, IOException
    434     {
    435         int peekByte;
    436         byte[] data;
    437         PushbackInputStream pbis = new PushbackInputStream(is);
    438         Collection<X509CertImpl> coll = new ArrayList<>();
    439 
    440         // Test the InputStream for end-of-stream.  If the stream's
    441         // initial state is already at end-of-stream then return
    442         // an empty collection.  Otherwise, push the byte back into the
    443         // stream and let readOneBlock look for the first certificate.
    444         peekByte = pbis.read();
    445         if (peekByte == -1) {
    446             return new ArrayList<>(0);
    447         } else {
    448             pbis.unread(peekByte);
    449             data = readOneBlock(pbis);
    450         }
    451 
    452         // If we end up with a null value after reading the first block
    453         // then we know the end-of-stream has been reached and no certificate
    454         // data has been found.
    455         if (data == null) {
    456             throw new CertificateException("No certificate data found");
    457         }
    458 
    459         try {
    460             PKCS7 pkcs7 = new PKCS7(data);
    461             X509Certificate[] certs = pkcs7.getCertificates();
    462             // certs are optional in PKCS #7
    463             if (certs != null) {
    464                 return Arrays.asList(certs);
    465             } else {
    466                 // no certificates provided
    467                 return new ArrayList<>(0);
    468             }
    469         } catch (ParsingException e) {
    470             while (data != null) {
    471                 coll.add(new X509CertImpl(data));
    472                 data = readOneBlock(pbis);
    473             }
    474         }
    475         return coll;
    476     }
    477 
    478     /*
    479      * Parses the data in the given input stream as a sequence of DER encoded
    480      * X.509 CRLs (in binary or base 64 encoded format) OR as a single PKCS#7
    481      * encoded blob (in binary or base 64 encoded format).
    482      *
    483     private Collection<? extends java.security.cert.CRL>
    484         parseX509orPKCS7CRL(InputStream is)
    485         throws CRLException, IOException
    486     {
    487         int peekByte;
    488         byte[] data;
    489         PushbackInputStream pbis = new PushbackInputStream(is);
    490         Collection<X509CRLImpl> coll = new ArrayList<>();
    491 
    492         // Test the InputStream for end-of-stream.  If the stream's
    493         // initial state is already at end-of-stream then return
    494         // an empty collection.  Otherwise, push the byte back into the
    495         // stream and let readOneBlock look for the first CRL.
    496         peekByte = pbis.read();
    497         if (peekByte == -1) {
    498             return new ArrayList<>(0);
    499         } else {
    500             pbis.unread(peekByte);
    501             data = readOneBlock(pbis);
    502         }
    503 
    504         // If we end up with a null value after reading the first block
    505         // then we know the end-of-stream has been reached and no CRL
    506         // data has been found.
    507         if (data == null) {
    508             throw new CRLException("No CRL data found");
    509         }
    510 
    511         try {
    512             PKCS7 pkcs7 = new PKCS7(data);
    513             X509CRL[] crls = pkcs7.getCRLs();
    514             // CRLs are optional in PKCS #7
    515             if (crls != null) {
    516                 return Arrays.asList(crls);
    517             } else {
    518                 // no crls provided
    519                 return new ArrayList<>(0);
    520             }
    521         } catch (ParsingException e) {
    522             while (data != null) {
    523                 coll.add(new X509CRLImpl(data));
    524                 data = readOneBlock(pbis);
    525             }
    526         }
    527         return coll;
    528     }
    529 
    530     /**
    531      * Returns an ASN.1 SEQUENCE from a stream, which might be a BER-encoded
    532      * binary block or a PEM-style BASE64-encoded ASCII data. In the latter
    533      * case, it's de-BASE64'ed before return.
    534      *
    535      * After the reading, the input stream pointer is after the BER block, or
    536      * after the newline character after the -----END SOMETHING----- line.
    537      *
    538      * @param is the InputStream
    539      * @returns byte block or null if end of stream
    540      * @throws IOException If any parsing error
    541      *
    542     private static byte[] readOneBlock(InputStream is) throws IOException {
    543 
    544         // The first character of a BLOCK.
    545         int c = is.read();
    546         if (c == -1) {
    547             return null;
    548         }
    549         if (c == DerValue.tag_Sequence) {
    550             ByteArrayOutputStream bout = new ByteArrayOutputStream(2048);
    551             bout.write(c);
    552             readBERInternal(is, bout, c);
    553             return bout.toByteArray();
    554         } else {
    555             // Read BASE64 encoded data, might skip info at the beginning
    556             char[] data = new char[2048];
    557             int pos = 0;
    558 
    559             // Step 1: Read until header is found
    560             int hyphen = (c=='-') ? 1: 0;   // count of consequent hyphens
    561             int last = (c=='-') ? -1: c;    // the char before hyphen
    562             while (true) {
    563                 int next = is.read();
    564                 if (next == -1) {
    565                     // We accept useless data after the last block,
    566                     // say, empty lines.
    567                     return null;
    568                 }
    569                 if (next == '-') {
    570                     hyphen++;
    571                 } else {
    572                     hyphen = 0;
    573                     last = next;
    574                 }
    575                 if (hyphen == 5 && (last == -1 || last == '\r' || last == '\n')) {
    576                     break;
    577                 }
    578             }
    579 
    580             // Step 2: Read the rest of header, determine the line end
    581             int end;
    582             StringBuilder header = new StringBuilder("-----");
    583             while (true) {
    584                 int next = is.read();
    585                 if (next == -1) {
    586                     throw new IOException("Incomplete data");
    587                 }
    588                 if (next == '\n') {
    589                     end = '\n';
    590                     break;
    591                 }
    592                 if (next == '\r') {
    593                     next = is.read();
    594                     if (next == -1) {
    595                         throw new IOException("Incomplete data");
    596                     }
    597                     if (next == '\n') {
    598                         end = '\n';
    599                     } else {
    600                         end = '\r';
    601                         data[pos++] = (char)next;
    602                     }
    603                     break;
    604                 }
    605                 header.append((char)next);
    606             }
    607 
    608             // Step 3: Read the data
    609             while (true) {
    610                 int next = is.read();
    611                 if (next == -1) {
    612                     throw new IOException("Incomplete data");
    613                 }
    614                 if (next != '-') {
    615                     data[pos++] = (char)next;
    616                     if (pos >= data.length) {
    617                         data = Arrays.copyOf(data, data.length+1024);
    618                     }
    619                 } else {
    620                     break;
    621                 }
    622             }
    623 
    624             // Step 4: Consume the footer
    625             StringBuilder footer = new StringBuilder("-");
    626             while (true) {
    627                 int next = is.read();
    628                 // Add next == '\n' for maximum safety, in case endline
    629                 // is not consistent.
    630                 if (next == -1 || next == end || next == '\n') {
    631                     break;
    632                 }
    633                 if (next != '\r') footer.append((char)next);
    634             }
    635 
    636             checkHeaderFooter(header.toString(), footer.toString());
    637 
    638             return Base64.getMimeDecoder().decode(new String(data, 0, pos));
    639         }
    640     }
    641 
    642     private static void checkHeaderFooter(String header,
    643             String footer) throws IOException {
    644         if (header.length() < 16 || !header.startsWith("-----BEGIN ") ||
    645                 !header.endsWith("-----")) {
    646             throw new IOException("Illegal header: " + header);
    647         }
    648         if (footer.length() < 14 || !footer.startsWith("-----END ") ||
    649                 !footer.endsWith("-----")) {
    650             throw new IOException("Illegal footer: " + footer);
    651         }
    652         String headerType = header.substring(11, header.length()-5);
    653         String footerType = footer.substring(9, footer.length()-5);
    654         if (!headerType.equals(footerType)) {
    655             throw new IOException("Header and footer do not match: " +
    656                     header + " " + footer);
    657         }
    658     }
    659 
    660     /**
    661      * Read one BER data block. This method is aware of indefinite-length BER
    662      * encoding and will read all of the sub-sections in a recursive way
    663      *
    664      * @param is    Read from this InputStream
    665      * @param bout  Write into this OutputStream
    666      * @param tag   Tag already read (-1 mean not read)
    667      * @returns     The current tag, used to check EOC in indefinite-length BER
    668      * @throws IOException Any parsing error
    669      *
    670     private static int readBERInternal(InputStream is,
    671             ByteArrayOutputStream bout, int tag) throws IOException {
    672 
    673         if (tag == -1) {        // Not read before the call, read now
    674             tag = is.read();
    675             if (tag == -1) {
    676                 throw new IOException("BER/DER tag info absent");
    677             }
    678             if ((tag & 0x1f) == 0x1f) {
    679                 throw new IOException("Multi octets tag not supported");
    680             }
    681             bout.write(tag);
    682         }
    683 
    684         int n = is.read();
    685         if (n == -1) {
    686             throw new IOException("BER/DER length info absent");
    687         }
    688         bout.write(n);
    689 
    690         int length;
    691 
    692         if (n == 0x80) {        // Indefinite-length encoding
    693             if ((tag & 0x20) != 0x20) {
    694                 throw new IOException(
    695                         "Non constructed encoding must have definite length");
    696             }
    697             while (true) {
    698                 int subTag = readBERInternal(is, bout, -1);
    699                 if (subTag == 0) {   // EOC, end of indefinite-length section
    700                     break;
    701                 }
    702             }
    703         } else {
    704             if (n < 0x80) {
    705                 length = n;
    706             } else if (n == 0x81) {
    707                 length = is.read();
    708                 if (length == -1) {
    709                     throw new IOException("Incomplete BER/DER length info");
    710                 }
    711                 bout.write(length);
    712             } else if (n == 0x82) {
    713                 int highByte = is.read();
    714                 int lowByte = is.read();
    715                 if (lowByte == -1) {
    716                     throw new IOException("Incomplete BER/DER length info");
    717                 }
    718                 bout.write(highByte);
    719                 bout.write(lowByte);
    720                 length = (highByte << 8) | lowByte;
    721             } else if (n == 0x83) {
    722                 int highByte = is.read();
    723                 int midByte = is.read();
    724                 int lowByte = is.read();
    725                 if (lowByte == -1) {
    726                     throw new IOException("Incomplete BER/DER length info");
    727                 }
    728                 bout.write(highByte);
    729                 bout.write(midByte);
    730                 bout.write(lowByte);
    731                 length = (highByte << 16) | (midByte << 8) | lowByte;
    732             } else if (n == 0x84) {
    733                 int highByte = is.read();
    734                 int nextByte = is.read();
    735                 int midByte = is.read();
    736                 int lowByte = is.read();
    737                 if (lowByte == -1) {
    738                     throw new IOException("Incomplete BER/DER length info");
    739                 }
    740                 if (highByte > 127) {
    741                     throw new IOException("Invalid BER/DER data (a little huge?)");
    742                 }
    743                 bout.write(highByte);
    744                 bout.write(nextByte);
    745                 bout.write(midByte);
    746                 bout.write(lowByte);
    747                 length = (highByte << 24 ) | (nextByte << 16) |
    748                         (midByte << 8) | lowByte;
    749             } else { // ignore longer length forms
    750                 throw new IOException("Invalid BER/DER data (too huge?)");
    751             }
    752             if (readFully(is, bout, length) != length) {
    753                 throw new IOException("Incomplete BER/DER data");
    754             }
    755         }
    756         return tag;
    757     }
    758     */
    759     // END Android-removed
    760 }
    761