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