1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package org.conscrypt; 18 19 import java.io.ByteArrayOutputStream; 20 import java.io.InputStream; 21 import java.math.BigInteger; 22 import java.security.InvalidKeyException; 23 import java.security.KeyFactory; 24 import java.security.NoSuchAlgorithmException; 25 import java.security.NoSuchProviderException; 26 import java.security.Principal; 27 import java.security.Provider; 28 import java.security.PublicKey; 29 import java.security.Signature; 30 import java.security.SignatureException; 31 import java.security.cert.Certificate; 32 import java.security.cert.CertificateEncodingException; 33 import java.security.cert.CertificateException; 34 import java.security.cert.CertificateExpiredException; 35 import java.security.cert.CertificateNotYetValidException; 36 import java.security.cert.CertificateParsingException; 37 import java.security.cert.X509Certificate; 38 import java.security.spec.InvalidKeySpecException; 39 import java.security.spec.X509EncodedKeySpec; 40 import java.util.ArrayList; 41 import java.util.Arrays; 42 import java.util.Calendar; 43 import java.util.Collection; 44 import java.util.Collections; 45 import java.util.Date; 46 import java.util.HashSet; 47 import java.util.List; 48 import java.util.Set; 49 import java.util.TimeZone; 50 import javax.crypto.BadPaddingException; 51 import javax.security.auth.x500.X500Principal; 52 import org.conscrypt.OpenSSLX509CertificateFactory.ParsingException; 53 54 /** 55 * An implementation of {@link X509Certificate} based on BoringSSL. 56 * 57 * @hide 58 */ 59 @Internal 60 public class OpenSSLX509Certificate extends X509Certificate { 61 private static final long serialVersionUID = 1992239142393372128L; 62 63 private transient final long mContext; 64 private transient Integer mHashCode; 65 66 OpenSSLX509Certificate(long ctx) { 67 mContext = ctx; 68 } 69 70 public static OpenSSLX509Certificate fromX509DerInputStream(InputStream is) 71 throws ParsingException { 72 @SuppressWarnings("resource") 73 final OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true); 74 75 try { 76 final long certCtx = NativeCrypto.d2i_X509_bio(bis.getBioContext()); 77 if (certCtx == 0) { 78 return null; 79 } 80 return new OpenSSLX509Certificate(certCtx); 81 } catch (Exception e) { 82 throw new ParsingException(e); 83 } finally { 84 bis.release(); 85 } 86 } 87 88 public static OpenSSLX509Certificate fromX509Der(byte[] encoded) 89 throws CertificateEncodingException { 90 try { 91 return new OpenSSLX509Certificate(NativeCrypto.d2i_X509(encoded)); 92 } catch (ParsingException e) { 93 throw new CertificateEncodingException(e); 94 } 95 } 96 97 public static List<OpenSSLX509Certificate> fromPkcs7DerInputStream(InputStream is) 98 throws ParsingException { 99 @SuppressWarnings("resource") 100 OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true); 101 102 final long[] certRefs; 103 try { 104 certRefs = NativeCrypto.d2i_PKCS7_bio(bis.getBioContext(), NativeCrypto.PKCS7_CERTS); 105 } catch (Exception e) { 106 throw new ParsingException(e); 107 } finally { 108 bis.release(); 109 } 110 111 if (certRefs == null) { 112 return Collections.emptyList(); 113 } 114 115 final List<OpenSSLX509Certificate> certs = new ArrayList<OpenSSLX509Certificate>( 116 certRefs.length); 117 for (int i = 0; i < certRefs.length; i++) { 118 if (certRefs[i] == 0) { 119 continue; 120 } 121 certs.add(new OpenSSLX509Certificate(certRefs[i])); 122 } 123 return certs; 124 } 125 126 public static OpenSSLX509Certificate fromX509PemInputStream(InputStream is) 127 throws ParsingException { 128 @SuppressWarnings("resource") 129 final OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true); 130 131 try { 132 final long certCtx = NativeCrypto.PEM_read_bio_X509(bis.getBioContext()); 133 if (certCtx == 0L) { 134 return null; 135 } 136 return new OpenSSLX509Certificate(certCtx); 137 } catch (Exception e) { 138 throw new ParsingException(e); 139 } finally { 140 bis.release(); 141 } 142 } 143 144 public static List<OpenSSLX509Certificate> fromPkcs7PemInputStream(InputStream is) 145 throws ParsingException { 146 @SuppressWarnings("resource") 147 OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true); 148 149 final long[] certRefs; 150 try { 151 certRefs = NativeCrypto.PEM_read_bio_PKCS7(bis.getBioContext(), 152 NativeCrypto.PKCS7_CERTS); 153 } catch (Exception e) { 154 throw new ParsingException(e); 155 } finally { 156 bis.release(); 157 } 158 159 final List<OpenSSLX509Certificate> certs = new ArrayList<OpenSSLX509Certificate>( 160 certRefs.length); 161 for (int i = 0; i < certRefs.length; i++) { 162 if (certRefs[i] == 0) { 163 continue; 164 } 165 certs.add(new OpenSSLX509Certificate(certRefs[i])); 166 } 167 return certs; 168 } 169 170 public static OpenSSLX509Certificate fromCertificate(Certificate cert) 171 throws CertificateEncodingException { 172 if (cert instanceof OpenSSLX509Certificate) { 173 return (OpenSSLX509Certificate) cert; 174 } else if (cert instanceof X509Certificate) { 175 return fromX509Der(cert.getEncoded()); 176 } else { 177 throw new CertificateEncodingException("Only X.509 certificates are supported"); 178 } 179 } 180 181 @Override 182 public Set<String> getCriticalExtensionOIDs() { 183 String[] critOids = 184 NativeCrypto.get_X509_ext_oids(mContext, NativeCrypto.EXTENSION_TYPE_CRITICAL); 185 186 /* 187 * This API has a special case that if there are no extensions, we 188 * should return null. So if we have no critical extensions, we'll check 189 * non-critical extensions. 190 */ 191 if ((critOids.length == 0) 192 && (NativeCrypto.get_X509_ext_oids(mContext, 193 NativeCrypto.EXTENSION_TYPE_NON_CRITICAL).length == 0)) { 194 return null; 195 } 196 197 return new HashSet<String>(Arrays.asList(critOids)); 198 } 199 200 @Override 201 public byte[] getExtensionValue(String oid) { 202 return NativeCrypto.X509_get_ext_oid(mContext, oid); 203 } 204 205 @Override 206 public Set<String> getNonCriticalExtensionOIDs() { 207 String[] nonCritOids = 208 NativeCrypto.get_X509_ext_oids(mContext, NativeCrypto.EXTENSION_TYPE_NON_CRITICAL); 209 210 /* 211 * This API has a special case that if there are no extensions, we 212 * should return null. So if we have no non-critical extensions, we'll 213 * check critical extensions. 214 */ 215 if ((nonCritOids.length == 0) 216 && (NativeCrypto.get_X509_ext_oids(mContext, 217 NativeCrypto.EXTENSION_TYPE_CRITICAL).length == 0)) { 218 return null; 219 } 220 221 return new HashSet<String>(Arrays.asList(nonCritOids)); 222 } 223 224 @Override 225 public boolean hasUnsupportedCriticalExtension() { 226 return (NativeCrypto.get_X509_ex_flags(mContext) & NativeConstants.EXFLAG_CRITICAL) != 0; 227 } 228 229 @Override 230 public void checkValidity() throws CertificateExpiredException, 231 CertificateNotYetValidException { 232 checkValidity(new Date()); 233 } 234 235 @Override 236 public void checkValidity(Date date) throws CertificateExpiredException, 237 CertificateNotYetValidException { 238 if (getNotBefore().compareTo(date) > 0) { 239 throw new CertificateNotYetValidException("Certificate not valid until " 240 + getNotBefore().toString() + " (compared to " + date.toString() + ")"); 241 } 242 243 if (getNotAfter().compareTo(date) < 0) { 244 throw new CertificateExpiredException("Certificate expired at " 245 + getNotAfter().toString() + " (compared to " + date.toString() + ")"); 246 } 247 } 248 249 @Override 250 public int getVersion() { 251 return (int) NativeCrypto.X509_get_version(mContext) + 1; 252 } 253 254 @Override 255 public BigInteger getSerialNumber() { 256 return new BigInteger(NativeCrypto.X509_get_serialNumber(mContext)); 257 } 258 259 @Override 260 public Principal getIssuerDN() { 261 return getIssuerX500Principal(); 262 } 263 264 @Override 265 public Principal getSubjectDN() { 266 return getSubjectX500Principal(); 267 } 268 269 @Override 270 public Date getNotBefore() { 271 Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); 272 calendar.set(Calendar.MILLISECOND, 0); 273 NativeCrypto.ASN1_TIME_to_Calendar(NativeCrypto.X509_get_notBefore(mContext), calendar); 274 return calendar.getTime(); 275 } 276 277 @Override 278 public Date getNotAfter() { 279 Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); 280 calendar.set(Calendar.MILLISECOND, 0); 281 NativeCrypto.ASN1_TIME_to_Calendar(NativeCrypto.X509_get_notAfter(mContext), calendar); 282 return calendar.getTime(); 283 } 284 285 @Override 286 public byte[] getTBSCertificate() throws CertificateEncodingException { 287 return NativeCrypto.get_X509_cert_info_enc(mContext); 288 } 289 290 @Override 291 public byte[] getSignature() { 292 return NativeCrypto.get_X509_signature(mContext); 293 } 294 295 @Override 296 public String getSigAlgName() { 297 String oid = getSigAlgOID(); 298 String algName = Platform.oidToAlgorithmName(oid); 299 if (algName != null) { 300 return algName; 301 } 302 return oid; 303 } 304 305 @Override 306 public String getSigAlgOID() { 307 return NativeCrypto.get_X509_sig_alg_oid(mContext); 308 } 309 310 @Override 311 public byte[] getSigAlgParams() { 312 return NativeCrypto.get_X509_sig_alg_parameter(mContext); 313 } 314 315 @Override 316 public boolean[] getIssuerUniqueID() { 317 return NativeCrypto.get_X509_issuerUID(mContext); 318 } 319 320 @Override 321 public boolean[] getSubjectUniqueID() { 322 return NativeCrypto.get_X509_subjectUID(mContext); 323 } 324 325 @Override 326 public boolean[] getKeyUsage() { 327 final boolean[] kusage = NativeCrypto.get_X509_ex_kusage(mContext); 328 if (kusage == null) { 329 return null; 330 } 331 332 if (kusage.length >= 9) { 333 return kusage; 334 } 335 336 final boolean[] resized = new boolean[9]; 337 System.arraycopy(kusage, 0, resized, 0, kusage.length); 338 return resized; 339 } 340 341 @Override 342 public int getBasicConstraints() { 343 if ((NativeCrypto.get_X509_ex_flags(mContext) & NativeConstants.EXFLAG_CA) == 0) { 344 return -1; 345 } 346 347 final int pathLen = NativeCrypto.get_X509_ex_pathlen(mContext); 348 if (pathLen == -1) { 349 return Integer.MAX_VALUE; 350 } 351 352 return pathLen; 353 } 354 355 @Override 356 public byte[] getEncoded() throws CertificateEncodingException { 357 return NativeCrypto.i2d_X509(mContext); 358 } 359 360 private void verifyOpenSSL(OpenSSLKey pkey) throws CertificateException, 361 NoSuchAlgorithmException, 362 InvalidKeyException, SignatureException { 363 try { 364 NativeCrypto.X509_verify(mContext, pkey.getNativeRef()); 365 } catch (RuntimeException e) { 366 throw new CertificateException(e); 367 } catch (BadPaddingException e) { 368 throw new SignatureException(); 369 } 370 } 371 372 private void verifyInternal(PublicKey key, String sigProvider) throws CertificateException, 373 NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, 374 SignatureException { 375 final Signature sig; 376 if (sigProvider == null) { 377 sig = Signature.getInstance(getSigAlgName()); 378 } else { 379 sig = Signature.getInstance(getSigAlgName(), sigProvider); 380 } 381 382 sig.initVerify(key); 383 sig.update(getTBSCertificate()); 384 if (!sig.verify(getSignature())) { 385 throw new SignatureException("signature did not verify"); 386 } 387 } 388 389 @Override 390 public void verify(PublicKey key) throws CertificateException, NoSuchAlgorithmException, 391 InvalidKeyException, NoSuchProviderException, SignatureException { 392 if (key instanceof OpenSSLKeyHolder) { 393 OpenSSLKey pkey = ((OpenSSLKeyHolder) key).getOpenSSLKey(); 394 verifyOpenSSL(pkey); 395 return; 396 } 397 398 verifyInternal(key, (String) null); 399 } 400 401 @Override 402 public void verify(PublicKey key, String sigProvider) throws CertificateException, 403 NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, 404 SignatureException { 405 verifyInternal(key, sigProvider); 406 } 407 408 /* @Override */ 409 @SuppressWarnings("MissingOverride") // For compilation with Java 7. 410 public void verify(PublicKey key, Provider sigProvider) 411 throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, 412 SignatureException { 413 if (key instanceof OpenSSLKeyHolder && sigProvider instanceof OpenSSLProvider) { 414 OpenSSLKey pkey = ((OpenSSLKeyHolder) key).getOpenSSLKey(); 415 verifyOpenSSL(pkey); 416 return; 417 } 418 419 final Signature sig; 420 if (sigProvider == null) { 421 sig = Signature.getInstance(getSigAlgName()); 422 } else { 423 sig = Signature.getInstance(getSigAlgName(), sigProvider); 424 } 425 426 sig.initVerify(key); 427 sig.update(getTBSCertificate()); 428 if (!sig.verify(getSignature())) { 429 throw new SignatureException("signature did not verify"); 430 } 431 } 432 433 @Override 434 public String toString() { 435 ByteArrayOutputStream os = new ByteArrayOutputStream(); 436 long bioCtx = NativeCrypto.create_BIO_OutputStream(os); 437 try { 438 NativeCrypto.X509_print_ex(bioCtx, mContext, 0, 0); 439 return os.toString(); 440 } finally { 441 NativeCrypto.BIO_free_all(bioCtx); 442 } 443 } 444 445 @Override 446 public PublicKey getPublicKey() { 447 /* First try to generate the key from supported OpenSSL key types. */ 448 try { 449 OpenSSLKey pkey = new OpenSSLKey(NativeCrypto.X509_get_pubkey(mContext)); 450 return pkey.getPublicKey(); 451 } catch (NoSuchAlgorithmException | InvalidKeyException ignored) { 452 } 453 454 /* Try generating the key using other Java providers. */ 455 String oid = NativeCrypto.get_X509_pubkey_oid(mContext); 456 byte[] encoded = NativeCrypto.i2d_X509_PUBKEY(mContext); 457 try { 458 KeyFactory kf = KeyFactory.getInstance(oid); 459 return kf.generatePublic(new X509EncodedKeySpec(encoded)); 460 } catch (NoSuchAlgorithmException | InvalidKeySpecException ignored) { 461 } 462 463 /* 464 * We couldn't find anything else, so just return a nearly-unusable 465 * X.509-encoded key. 466 */ 467 return new X509PublicKey(oid, encoded); 468 } 469 470 @Override 471 public X500Principal getIssuerX500Principal() { 472 final byte[] issuer = NativeCrypto.X509_get_issuer_name(mContext); 473 return new X500Principal(issuer); 474 } 475 476 @Override 477 public X500Principal getSubjectX500Principal() { 478 final byte[] subject = NativeCrypto.X509_get_subject_name(mContext); 479 return new X500Principal(subject); 480 } 481 482 @Override 483 public List<String> getExtendedKeyUsage() throws CertificateParsingException { 484 String[] extUsage = NativeCrypto.get_X509_ex_xkusage(mContext); 485 if (extUsage == null) { 486 return null; 487 } 488 489 return Arrays.asList(extUsage); 490 } 491 492 private static Collection<List<?>> alternativeNameArrayToList(Object[][] altNameArray) { 493 if (altNameArray == null) { 494 return null; 495 } 496 497 Collection<List<?>> coll = new ArrayList<List<?>>(altNameArray.length); 498 for (int i = 0; i < altNameArray.length; i++) { 499 coll.add(Collections.unmodifiableList(Arrays.asList(altNameArray[i]))); 500 } 501 502 return Collections.unmodifiableCollection(coll); 503 } 504 505 @Override 506 public Collection<List<?>> getSubjectAlternativeNames() throws CertificateParsingException { 507 return alternativeNameArrayToList(NativeCrypto.get_X509_GENERAL_NAME_stack(mContext, 508 NativeCrypto.GN_STACK_SUBJECT_ALT_NAME)); 509 } 510 511 @Override 512 public Collection<List<?>> getIssuerAlternativeNames() throws CertificateParsingException { 513 return alternativeNameArrayToList(NativeCrypto.get_X509_GENERAL_NAME_stack(mContext, 514 NativeCrypto.GN_STACK_ISSUER_ALT_NAME)); 515 } 516 517 @Override 518 public boolean equals(Object other) { 519 if (other instanceof OpenSSLX509Certificate) { 520 OpenSSLX509Certificate o = (OpenSSLX509Certificate) other; 521 522 return NativeCrypto.X509_cmp(mContext, o.mContext) == 0; 523 } 524 525 return super.equals(other); 526 } 527 528 @Override 529 public int hashCode() { 530 if (mHashCode != null) { 531 return mHashCode; 532 } 533 mHashCode = super.hashCode(); 534 return mHashCode; 535 } 536 537 /** 538 * Returns the raw pointer to the X509 context for use in JNI calls. The 539 * life cycle of this native pointer is managed by the 540 * {@code OpenSSLX509Certificate} instance and must not be destroyed or 541 * freed by users of this API. 542 */ 543 public long getContext() { 544 return mContext; 545 } 546 547 /** 548 * Delete an extension. 549 * 550 * A modified copy of the certificate is returned. The original object 551 * is unchanged. 552 * If the extension is not present, an unmodified copy is returned. 553 */ 554 public OpenSSLX509Certificate withDeletedExtension(String oid) { 555 OpenSSLX509Certificate copy = new OpenSSLX509Certificate(NativeCrypto.X509_dup(mContext)); 556 NativeCrypto.X509_delete_ext(copy.getContext(), oid); 557 return copy; 558 } 559 560 @Override 561 protected void finalize() throws Throwable { 562 try { 563 if (mContext != 0) { 564 NativeCrypto.X509_free(mContext); 565 } 566 } finally { 567 super.finalize(); 568 } 569 } 570 571 /** 572 * Return a possibly null array of X509Certificates given the possibly null 573 * array of DER encoded bytes. 574 */ 575 static OpenSSLX509Certificate[] createCertChain(long[] certificateRefs) { 576 if (certificateRefs == null) { 577 return null; 578 } 579 OpenSSLX509Certificate[] certificates = new OpenSSLX509Certificate[certificateRefs.length]; 580 for (int i = 0; i < certificateRefs.length; i++) { 581 certificates[i] = new OpenSSLX509Certificate(certificateRefs[i]); 582 } 583 return certificates; 584 } 585 } 586