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