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