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      */
intern(X509Certificate c)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      */
intern(X509CRL c)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      */
getFromCache(Cache<K,V> cache, byte[] encoding)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      */
addToCache(Cache<Object, V> cache, byte[] encoding, V value)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