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