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.io.*;
29 import java.util.*;
30 import java.security.cert.*;
31 
32 import sun.security.util.Pem;
33 import sun.security.x509.X509CertImpl;
34 import sun.security.x509.X509CRLImpl;
35 import sun.security.pkcs.PKCS7;
36 import sun.security.provider.certpath.X509CertPath;
37 import sun.security.provider.certpath.X509CertificatePair;
38 import sun.security.util.DerValue;
39 import sun.security.util.Cache;
40 import java.util.Base64;
41 import sun.security.pkcs.ParsingException;
42 
43 /**
44  * This class defines a certificate factory for X.509 v3 certificates &
45  * certification paths, and X.509 v2 certificate revocation lists (CRLs).
46  *
47  * @author Jan Luehe
48  * @author Hemma Prafullchandra
49  * @author Sean Mullan
50  *
51  *
52  * @see java.security.cert.CertificateFactorySpi
53  * @see java.security.cert.Certificate
54  * @see java.security.cert.CertPath
55  * @see java.security.cert.CRL
56  * @see java.security.cert.X509Certificate
57  * @see java.security.cert.X509CRL
58  * @see sun.security.x509.X509CertImpl
59  * @see sun.security.x509.X509CRLImpl
60  */
61 
62 public class X509Factory extends CertificateFactorySpi {
63 
64     public static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----";
65     public static final String END_CERT = "-----END CERTIFICATE-----";
66 
67     private static final int ENC_MAX_LENGTH = 4096 * 1024; // 4 MB MAX
68 
69     private static final Cache<Object, X509CertImpl> certCache
70         = Cache.newSoftMemoryCache(750);
71     private static final Cache<Object, X509CRLImpl> crlCache
72         = Cache.newSoftMemoryCache(750);
73 
74     /**
75      * Generates an X.509 certificate object and initializes it with
76      * the data read from the input stream <code>is</code>.
77      *
78      * @param is an input stream with the certificate data.
79      *
80      * @return an X.509 certificate object initialized with the data
81      * from the input stream.
82      *
83      * @exception CertificateException on parsing errors.
84      */
85     @Override
engineGenerateCertificate(InputStream is)86     public Certificate engineGenerateCertificate(InputStream is)
87         throws CertificateException
88     {
89         if (is == null) {
90             // clear the caches (for debugging)
91             certCache.clear();
92             X509CertificatePair.clearCache();
93             throw new CertificateException("Missing input stream");
94         }
95         try {
96             byte[] encoding = readOneBlock(is);
97             if (encoding != null) {
98                 X509CertImpl cert = getFromCache(certCache, encoding);
99                 if (cert != null) {
100                     return cert;
101                 }
102                 cert = new X509CertImpl(encoding);
103                 addToCache(certCache, cert.getEncodedInternal(), cert);
104                 return cert;
105             } else {
106                 throw new IOException("Empty input");
107             }
108         } catch (IOException ioe) {
109             throw new CertificateException("Could not parse certificate: " +
110                     ioe.toString(), ioe);
111         }
112     }
113 
114     /**
115      * Read from the stream until length bytes have been read or EOF has
116      * been reached. Return the number of bytes actually read.
117      */
readFully(InputStream in, ByteArrayOutputStream bout, int length)118     private static int readFully(InputStream in, ByteArrayOutputStream bout,
119             int length) throws IOException {
120         int read = 0;
121         byte[] buffer = new byte[2048];
122         while (length > 0) {
123             int n = in.read(buffer, 0, length<2048?length:2048);
124             if (n <= 0) {
125                 break;
126             }
127             bout.write(buffer, 0, n);
128             read += n;
129             length -= n;
130         }
131         return read;
132     }
133 
134     /**
135      * Return an interned X509CertImpl for the given certificate.
136      * If the given X509Certificate or X509CertImpl is already present
137      * in the cert cache, the cached object is returned. Otherwise,
138      * if it is a X509Certificate, it is first converted to a X509CertImpl.
139      * Then the X509CertImpl is added to the cache and returned.
140      *
141      * Note that all certificates created via generateCertificate(InputStream)
142      * are already interned and this method does not need to be called.
143      * It is useful for certificates that cannot be created via
144      * generateCertificate() and for converting other X509Certificate
145      * implementations to an X509CertImpl.
146      *
147      * @param c The source X509Certificate
148      * @return An X509CertImpl object that is either a cached certificate or a
149      *      newly built X509CertImpl from the provided X509Certificate
150      * @throws CertificateException if failures occur while obtaining the DER
151      *      encoding for certificate data.
152      */
intern(X509Certificate c)153     public static synchronized X509CertImpl intern(X509Certificate c)
154             throws CertificateException {
155         if (c == null) {
156             return null;
157         }
158         boolean isImpl = c instanceof X509CertImpl;
159         byte[] encoding;
160         if (isImpl) {
161             encoding = ((X509CertImpl)c).getEncodedInternal();
162         } else {
163             encoding = c.getEncoded();
164         }
165         X509CertImpl newC = getFromCache(certCache, encoding);
166         if (newC != null) {
167             return newC;
168         }
169         if (isImpl) {
170             newC = (X509CertImpl)c;
171         } else {
172             newC = new X509CertImpl(encoding);
173             encoding = newC.getEncodedInternal();
174         }
175         addToCache(certCache, encoding, newC);
176         return newC;
177     }
178 
179     /**
180      * Return an interned X509CRLImpl for the given certificate.
181      * For more information, see intern(X509Certificate).
182      *
183      * @param c The source X509CRL
184      * @return An X509CRLImpl object that is either a cached CRL or a
185      *      newly built X509CRLImpl from the provided X509CRL
186      * @throws CRLException if failures occur while obtaining the DER
187      *      encoding for CRL data.
188      */
intern(X509CRL c)189     public static synchronized X509CRLImpl intern(X509CRL c)
190             throws CRLException {
191         if (c == null) {
192             return null;
193         }
194         boolean isImpl = c instanceof X509CRLImpl;
195         byte[] encoding;
196         if (isImpl) {
197             encoding = ((X509CRLImpl)c).getEncodedInternal();
198         } else {
199             encoding = c.getEncoded();
200         }
201         X509CRLImpl newC = getFromCache(crlCache, encoding);
202         if (newC != null) {
203             return newC;
204         }
205         if (isImpl) {
206             newC = (X509CRLImpl)c;
207         } else {
208             newC = new X509CRLImpl(encoding);
209             encoding = newC.getEncodedInternal();
210         }
211         addToCache(crlCache, encoding, newC);
212         return newC;
213     }
214 
215     /**
216      * Get the X509CertImpl or X509CRLImpl from the cache.
217      */
getFromCache(Cache<K,V> cache, byte[] encoding)218     private static synchronized <K,V> V getFromCache(Cache<K,V> cache,
219             byte[] encoding) {
220         Object key = new Cache.EqualByteArray(encoding);
221         return cache.get(key);
222     }
223 
224     /**
225      * Add the X509CertImpl or X509CRLImpl to the cache.
226      */
addToCache(Cache<Object, V> cache, byte[] encoding, V value)227     private static synchronized <V> void addToCache(Cache<Object, V> cache,
228             byte[] encoding, V value) {
229         if (encoding.length > ENC_MAX_LENGTH) {
230             return;
231         }
232         Object key = new Cache.EqualByteArray(encoding);
233         cache.put(key, value);
234     }
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
engineGenerateCertPath(InputStream inStream)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
engineGenerateCertPath(InputStream inStream, String encoding)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
engineGenerateCertPath(List<? extends Certificate> certificates)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
engineGetCertPathEncodings()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>
engineGenerateCertificates(InputStream is)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
engineGenerateCRL(InputStream is)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
engineGenerateCRLs( InputStream is)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>
parseX509orPKCS7Cert(InputStream is)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>
parseX509orPKCS7CRL(InputStream is)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      */
readOneBlock(InputStream is)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 Pem.decode(new String(data, 0, pos));
639         }
640     }
641 
checkHeaderFooter(String header, String footer)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      */
readBERInternal(InputStream is, ByteArrayOutputStream bout, int tag)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