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