Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/openjdk-multiarch-jdk8u
Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/sun/security/provider/X509Factory.java
38830 views
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
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
*/
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
*/
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
*/
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
*/
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
*/
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
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 Pem.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
760