1package org.bouncycastle.jce.provider; 2 3import java.io.ByteArrayOutputStream; 4import java.io.IOException; 5import java.math.BigInteger; 6import java.security.InvalidKeyException; 7import java.security.NoSuchAlgorithmException; 8import java.security.NoSuchProviderException; 9import java.security.Principal; 10import java.security.Provider; 11import java.security.PublicKey; 12import java.security.Security; 13import java.security.Signature; 14import java.security.SignatureException; 15import java.security.cert.Certificate; 16import java.security.cert.CertificateEncodingException; 17import java.security.cert.CertificateException; 18import java.security.cert.CertificateExpiredException; 19import java.security.cert.CertificateNotYetValidException; 20import java.security.cert.CertificateParsingException; 21import java.security.cert.X509Certificate; 22import java.util.ArrayList; 23import java.util.Collections; 24import java.util.Date; 25import java.util.Enumeration; 26import java.util.HashSet; 27import java.util.List; 28import java.util.Set; 29 30import javax.security.auth.x500.X500Principal; 31 32import org.bouncycastle.asn1.ASN1Encodable; 33import org.bouncycastle.asn1.ASN1Encoding; 34import org.bouncycastle.asn1.ASN1InputStream; 35import org.bouncycastle.asn1.ASN1ObjectIdentifier; 36import org.bouncycastle.asn1.ASN1OutputStream; 37import org.bouncycastle.asn1.ASN1Primitive; 38import org.bouncycastle.asn1.ASN1Sequence; 39import org.bouncycastle.asn1.DERBitString; 40import org.bouncycastle.asn1.DERIA5String; 41import org.bouncycastle.asn1.DERNull; 42import org.bouncycastle.asn1.DERObjectIdentifier; 43import org.bouncycastle.asn1.misc.MiscObjectIdentifiers; 44import org.bouncycastle.asn1.misc.NetscapeCertType; 45import org.bouncycastle.asn1.misc.NetscapeRevocationURL; 46import org.bouncycastle.asn1.misc.VerisignCzagExtension; 47import org.bouncycastle.asn1.util.ASN1Dump; 48import org.bouncycastle.asn1.x500.X500Name; 49import org.bouncycastle.asn1.x509.AlgorithmIdentifier; 50import org.bouncycastle.asn1.x509.BasicConstraints; 51import org.bouncycastle.asn1.x509.KeyUsage; 52import org.bouncycastle.asn1.x509.X509CertificateStructure; 53import org.bouncycastle.asn1.x509.X509Extension; 54import org.bouncycastle.asn1.x509.X509Extensions; 55import org.bouncycastle.jcajce.provider.asymmetric.util.PKCS12BagAttributeCarrierImpl; 56import org.bouncycastle.jce.X509Principal; 57import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier; 58import org.bouncycastle.util.Arrays; 59import org.bouncycastle.util.encoders.Hex; 60 61public class X509CertificateObject 62 extends X509Certificate 63 implements PKCS12BagAttributeCarrier 64{ 65 private X509CertificateStructure c; 66 private BasicConstraints basicConstraints; 67 private boolean[] keyUsage; 68 private boolean hashValueSet; 69 private int hashValue; 70 71 private PKCS12BagAttributeCarrier attrCarrier = new PKCS12BagAttributeCarrierImpl(); 72 73 public X509CertificateObject( 74 X509CertificateStructure c) 75 throws CertificateParsingException 76 { 77 this.c = c; 78 79 try 80 { 81 byte[] bytes = this.getExtensionBytes("2.5.29.19"); 82 83 if (bytes != null) 84 { 85 basicConstraints = BasicConstraints.getInstance(ASN1Primitive.fromByteArray(bytes)); 86 } 87 } 88 catch (Exception e) 89 { 90 throw new CertificateParsingException("cannot construct BasicConstraints: " + e); 91 } 92 93 try 94 { 95 byte[] bytes = this.getExtensionBytes("2.5.29.15"); 96 if (bytes != null) 97 { 98 DERBitString bits = DERBitString.getInstance(ASN1Primitive.fromByteArray(bytes)); 99 100 bytes = bits.getBytes(); 101 int length = (bytes.length * 8) - bits.getPadBits(); 102 103 keyUsage = new boolean[(length < 9) ? 9 : length]; 104 105 for (int i = 0; i != length; i++) 106 { 107 keyUsage[i] = (bytes[i / 8] & (0x80 >>> (i % 8))) != 0; 108 } 109 } 110 else 111 { 112 keyUsage = null; 113 } 114 } 115 catch (Exception e) 116 { 117 throw new CertificateParsingException("cannot construct KeyUsage: " + e); 118 } 119 } 120 121 public void checkValidity() 122 throws CertificateExpiredException, CertificateNotYetValidException 123 { 124 this.checkValidity(new Date()); 125 } 126 127 public void checkValidity( 128 Date date) 129 throws CertificateExpiredException, CertificateNotYetValidException 130 { 131 if (date.getTime() > this.getNotAfter().getTime()) // for other VM compatibility 132 { 133 throw new CertificateExpiredException("certificate expired on " + c.getEndDate().getTime()); 134 } 135 136 if (date.getTime() < this.getNotBefore().getTime()) 137 { 138 throw new CertificateNotYetValidException("certificate not valid till " + c.getStartDate().getTime()); 139 } 140 } 141 142 public int getVersion() 143 { 144 return c.getVersion(); 145 } 146 147 public BigInteger getSerialNumber() 148 { 149 return c.getSerialNumber().getValue(); 150 } 151 152 public Principal getIssuerDN() 153 { 154 try 155 { 156 return new X509Principal(X500Name.getInstance(c.getIssuer().getEncoded())); 157 } 158 catch (IOException e) 159 { 160 return null; 161 } 162 } 163 164 public X500Principal getIssuerX500Principal() 165 { 166 try 167 { 168 ByteArrayOutputStream bOut = new ByteArrayOutputStream(); 169 ASN1OutputStream aOut = new ASN1OutputStream(bOut); 170 171 aOut.writeObject(c.getIssuer()); 172 173 return new X500Principal(bOut.toByteArray()); 174 } 175 catch (IOException e) 176 { 177 throw new IllegalStateException("can't encode issuer DN"); 178 } 179 } 180 181 public Principal getSubjectDN() 182 { 183 return new X509Principal(X500Name.getInstance(c.getSubject().toASN1Primitive())); 184 } 185 186 public X500Principal getSubjectX500Principal() 187 { 188 try 189 { 190 ByteArrayOutputStream bOut = new ByteArrayOutputStream(); 191 ASN1OutputStream aOut = new ASN1OutputStream(bOut); 192 193 aOut.writeObject(c.getSubject()); 194 195 return new X500Principal(bOut.toByteArray()); 196 } 197 catch (IOException e) 198 { 199 throw new IllegalStateException("can't encode issuer DN"); 200 } 201 } 202 203 public Date getNotBefore() 204 { 205 return c.getStartDate().getDate(); 206 } 207 208 public Date getNotAfter() 209 { 210 return c.getEndDate().getDate(); 211 } 212 213 public byte[] getTBSCertificate() 214 throws CertificateEncodingException 215 { 216 try 217 { 218 return c.getTBSCertificate().getEncoded(ASN1Encoding.DER); 219 } 220 catch (IOException e) 221 { 222 throw new CertificateEncodingException(e.toString()); 223 } 224 } 225 226 public byte[] getSignature() 227 { 228 return c.getSignature().getBytes(); 229 } 230 231 /** 232 * return a more "meaningful" representation for the signature algorithm used in 233 * the certficate. 234 */ 235 public String getSigAlgName() 236 { 237 Provider prov = Security.getProvider(BouncyCastleProvider.PROVIDER_NAME); 238 239 if (prov != null) 240 { 241 String algName = prov.getProperty("Alg.Alias.Signature." + this.getSigAlgOID()); 242 243 if (algName != null) 244 { 245 return algName; 246 } 247 } 248 249 Provider[] provs = Security.getProviders(); 250 251 // 252 // search every provider looking for a real algorithm 253 // 254 for (int i = 0; i != provs.length; i++) 255 { 256 String algName = provs[i].getProperty("Alg.Alias.Signature." + this.getSigAlgOID()); 257 if (algName != null) 258 { 259 return algName; 260 } 261 } 262 263 return this.getSigAlgOID(); 264 } 265 266 /** 267 * return the object identifier for the signature. 268 */ 269 public String getSigAlgOID() 270 { 271 return c.getSignatureAlgorithm().getObjectId().getId(); 272 } 273 274 /** 275 * return the signature parameters, or null if there aren't any. 276 */ 277 public byte[] getSigAlgParams() 278 { 279 if (c.getSignatureAlgorithm().getParameters() != null) 280 { 281 try 282 { 283 return c.getSignatureAlgorithm().getParameters().toASN1Primitive().getEncoded(ASN1Encoding.DER); 284 } 285 catch (IOException e) 286 { 287 return null; 288 } 289 } 290 else 291 { 292 return null; 293 } 294 } 295 296 public boolean[] getIssuerUniqueID() 297 { 298 DERBitString id = c.getTBSCertificate().getIssuerUniqueId(); 299 300 if (id != null) 301 { 302 byte[] bytes = id.getBytes(); 303 boolean[] boolId = new boolean[bytes.length * 8 - id.getPadBits()]; 304 305 for (int i = 0; i != boolId.length; i++) 306 { 307 boolId[i] = (bytes[i / 8] & (0x80 >>> (i % 8))) != 0; 308 } 309 310 return boolId; 311 } 312 313 return null; 314 } 315 316 public boolean[] getSubjectUniqueID() 317 { 318 DERBitString id = c.getTBSCertificate().getSubjectUniqueId(); 319 320 if (id != null) 321 { 322 byte[] bytes = id.getBytes(); 323 boolean[] boolId = new boolean[bytes.length * 8 - id.getPadBits()]; 324 325 for (int i = 0; i != boolId.length; i++) 326 { 327 boolId[i] = (bytes[i / 8] & (0x80 >>> (i % 8))) != 0; 328 } 329 330 return boolId; 331 } 332 333 return null; 334 } 335 336 public boolean[] getKeyUsage() 337 { 338 return keyUsage; 339 } 340 341 public List getExtendedKeyUsage() 342 throws CertificateParsingException 343 { 344 byte[] bytes = this.getExtensionBytes("2.5.29.37"); 345 346 if (bytes != null) 347 { 348 try 349 { 350 ASN1InputStream dIn = new ASN1InputStream(bytes); 351 ASN1Sequence seq = (ASN1Sequence)dIn.readObject(); 352 List list = new ArrayList(); 353 354 for (int i = 0; i != seq.size(); i++) 355 { 356 list.add(((DERObjectIdentifier)seq.getObjectAt(i)).getId()); 357 } 358 359 return Collections.unmodifiableList(list); 360 } 361 catch (Exception e) 362 { 363 throw new CertificateParsingException("error processing extended key usage extension"); 364 } 365 } 366 367 return null; 368 } 369 370 public int getBasicConstraints() 371 { 372 if (basicConstraints != null) 373 { 374 if (basicConstraints.isCA()) 375 { 376 if (basicConstraints.getPathLenConstraint() == null) 377 { 378 return Integer.MAX_VALUE; 379 } 380 else 381 { 382 return basicConstraints.getPathLenConstraint().intValue(); 383 } 384 } 385 else 386 { 387 return -1; 388 } 389 } 390 391 return -1; 392 } 393 394 public Set getCriticalExtensionOIDs() 395 { 396 if (this.getVersion() == 3) 397 { 398 Set set = new HashSet(); 399 X509Extensions extensions = c.getTBSCertificate().getExtensions(); 400 401 if (extensions != null) 402 { 403 Enumeration e = extensions.oids(); 404 405 while (e.hasMoreElements()) 406 { 407 DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement(); 408 X509Extension ext = extensions.getExtension(oid); 409 410 if (ext.isCritical()) 411 { 412 set.add(oid.getId()); 413 } 414 } 415 416 return set; 417 } 418 } 419 420 return null; 421 } 422 423 private byte[] getExtensionBytes(String oid) 424 { 425 X509Extensions exts = c.getTBSCertificate().getExtensions(); 426 427 if (exts != null) 428 { 429 X509Extension ext = exts.getExtension(new DERObjectIdentifier(oid)); 430 if (ext != null) 431 { 432 return ext.getValue().getOctets(); 433 } 434 } 435 436 return null; 437 } 438 439 public byte[] getExtensionValue(String oid) 440 { 441 X509Extensions exts = c.getTBSCertificate().getExtensions(); 442 443 if (exts != null) 444 { 445 X509Extension ext = exts.getExtension(new DERObjectIdentifier(oid)); 446 447 if (ext != null) 448 { 449 try 450 { 451 return ext.getValue().getEncoded(); 452 } 453 catch (Exception e) 454 { 455 throw new IllegalStateException("error parsing " + e.toString()); 456 } 457 } 458 } 459 460 return null; 461 } 462 463 public Set getNonCriticalExtensionOIDs() 464 { 465 if (this.getVersion() == 3) 466 { 467 Set set = new HashSet(); 468 X509Extensions extensions = c.getTBSCertificate().getExtensions(); 469 470 if (extensions != null) 471 { 472 Enumeration e = extensions.oids(); 473 474 while (e.hasMoreElements()) 475 { 476 DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement(); 477 X509Extension ext = extensions.getExtension(oid); 478 479 if (!ext.isCritical()) 480 { 481 set.add(oid.getId()); 482 } 483 } 484 485 return set; 486 } 487 } 488 489 return null; 490 } 491 492 public boolean hasUnsupportedCriticalExtension() 493 { 494 if (this.getVersion() == 3) 495 { 496 X509Extensions extensions = c.getTBSCertificate().getExtensions(); 497 498 if (extensions != null) 499 { 500 Enumeration e = extensions.oids(); 501 502 while (e.hasMoreElements()) 503 { 504 DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement(); 505 String oidId = oid.getId(); 506 507 if (oidId.equals(RFC3280CertPathUtilities.KEY_USAGE) 508 || oidId.equals(RFC3280CertPathUtilities.CERTIFICATE_POLICIES) 509 || oidId.equals(RFC3280CertPathUtilities.POLICY_MAPPINGS) 510 || oidId.equals(RFC3280CertPathUtilities.INHIBIT_ANY_POLICY) 511 || oidId.equals(RFC3280CertPathUtilities.CRL_DISTRIBUTION_POINTS) 512 || oidId.equals(RFC3280CertPathUtilities.ISSUING_DISTRIBUTION_POINT) 513 || oidId.equals(RFC3280CertPathUtilities.DELTA_CRL_INDICATOR) 514 || oidId.equals(RFC3280CertPathUtilities.POLICY_CONSTRAINTS) 515 || oidId.equals(RFC3280CertPathUtilities.BASIC_CONSTRAINTS) 516 || oidId.equals(RFC3280CertPathUtilities.SUBJECT_ALTERNATIVE_NAME) 517 || oidId.equals(RFC3280CertPathUtilities.NAME_CONSTRAINTS)) 518 { 519 continue; 520 } 521 522 X509Extension ext = extensions.getExtension(oid); 523 524 if (ext.isCritical()) 525 { 526 return true; 527 } 528 } 529 } 530 } 531 532 return false; 533 } 534 535 public PublicKey getPublicKey() 536 { 537 try 538 { 539 return BouncyCastleProvider.getPublicKey(c.getSubjectPublicKeyInfo()); 540 } 541 catch (IOException e) 542 { 543 return null; // should never happen... 544 } 545 } 546 547 // BEGIN android-changed 548 private byte[] encoded; 549 // END android-changed 550 public byte[] getEncoded() 551 throws CertificateEncodingException 552 { 553 try 554 { 555 // BEGIN android-changed 556 if (encoded == null) { 557 encoded = c.getEncoded(ASN1Encoding.DER); 558 } 559 return encoded; 560 // END android-changed 561 } 562 catch (IOException e) 563 { 564 throw new CertificateEncodingException(e.toString()); 565 } 566 } 567 568 public boolean equals( 569 Object o) 570 { 571 if (o == this) 572 { 573 return true; 574 } 575 576 if (!(o instanceof Certificate)) 577 { 578 return false; 579 } 580 581 Certificate other = (Certificate)o; 582 583 try 584 { 585 byte[] b1 = this.getEncoded(); 586 byte[] b2 = other.getEncoded(); 587 588 return Arrays.areEqual(b1, b2); 589 } 590 catch (CertificateEncodingException e) 591 { 592 return false; 593 } 594 } 595 596 public synchronized int hashCode() 597 { 598 if (!hashValueSet) 599 { 600 hashValue = calculateHashCode(); 601 hashValueSet = true; 602 } 603 604 return hashValue; 605 } 606 607 private int calculateHashCode() 608 { 609 try 610 { 611 int hashCode = 0; 612 byte[] certData = this.getEncoded(); 613 for (int i = 1; i < certData.length; i++) 614 { 615 hashCode += certData[i] * i; 616 } 617 return hashCode; 618 } 619 catch (CertificateEncodingException e) 620 { 621 return 0; 622 } 623 } 624 625 public void setBagAttribute( 626 ASN1ObjectIdentifier oid, 627 ASN1Encodable attribute) 628 { 629 attrCarrier.setBagAttribute(oid, attribute); 630 } 631 632 public ASN1Encodable getBagAttribute( 633 DERObjectIdentifier oid) 634 { 635 return attrCarrier.getBagAttribute(oid); 636 } 637 638 public Enumeration getBagAttributeKeys() 639 { 640 return attrCarrier.getBagAttributeKeys(); 641 } 642 643 public String toString() 644 { 645 StringBuffer buf = new StringBuffer(); 646 String nl = System.getProperty("line.separator"); 647 648 buf.append(" [0] Version: ").append(this.getVersion()).append(nl); 649 buf.append(" SerialNumber: ").append(this.getSerialNumber()).append(nl); 650 buf.append(" IssuerDN: ").append(this.getIssuerDN()).append(nl); 651 buf.append(" Start Date: ").append(this.getNotBefore()).append(nl); 652 buf.append(" Final Date: ").append(this.getNotAfter()).append(nl); 653 buf.append(" SubjectDN: ").append(this.getSubjectDN()).append(nl); 654 buf.append(" Public Key: ").append(this.getPublicKey()).append(nl); 655 buf.append(" Signature Algorithm: ").append(this.getSigAlgName()).append(nl); 656 657 byte[] sig = this.getSignature(); 658 659 buf.append(" Signature: ").append(new String(Hex.encode(sig, 0, 20))).append(nl); 660 for (int i = 20; i < sig.length; i += 20) 661 { 662 if (i < sig.length - 20) 663 { 664 buf.append(" ").append(new String(Hex.encode(sig, i, 20))).append(nl); 665 } 666 else 667 { 668 buf.append(" ").append(new String(Hex.encode(sig, i, sig.length - i))).append(nl); 669 } 670 } 671 672 X509Extensions extensions = c.getTBSCertificate().getExtensions(); 673 674 if (extensions != null) 675 { 676 Enumeration e = extensions.oids(); 677 678 if (e.hasMoreElements()) 679 { 680 buf.append(" Extensions: \n"); 681 } 682 683 while (e.hasMoreElements()) 684 { 685 DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement(); 686 X509Extension ext = extensions.getExtension(oid); 687 688 if (ext.getValue() != null) 689 { 690 byte[] octs = ext.getValue().getOctets(); 691 ASN1InputStream dIn = new ASN1InputStream(octs); 692 buf.append(" critical(").append(ext.isCritical()).append(") "); 693 try 694 { 695 if (oid.equals(X509Extension.basicConstraints)) 696 { 697 buf.append(BasicConstraints.getInstance(dIn.readObject())).append(nl); 698 } 699 else if (oid.equals(X509Extension.keyUsage)) 700 { 701 buf.append(new KeyUsage((DERBitString)dIn.readObject())).append(nl); 702 } 703 else if (oid.equals(MiscObjectIdentifiers.netscapeCertType)) 704 { 705 buf.append(new NetscapeCertType((DERBitString)dIn.readObject())).append(nl); 706 } 707 else if (oid.equals(MiscObjectIdentifiers.netscapeRevocationURL)) 708 { 709 buf.append(new NetscapeRevocationURL((DERIA5String)dIn.readObject())).append(nl); 710 } 711 else if (oid.equals(MiscObjectIdentifiers.verisignCzagExtension)) 712 { 713 buf.append(new VerisignCzagExtension((DERIA5String)dIn.readObject())).append(nl); 714 } 715 else 716 { 717 buf.append(oid.getId()); 718 buf.append(" value = ").append(ASN1Dump.dumpAsString(dIn.readObject())).append(nl); 719 //buf.append(" value = ").append("*****").append(nl); 720 } 721 } 722 catch (Exception ex) 723 { 724 buf.append(oid.getId()); 725 // buf.append(" value = ").append(new String(Hex.encode(ext.getValue().getOctets()))).append(nl); 726 buf.append(" value = ").append("*****").append(nl); 727 } 728 } 729 else 730 { 731 buf.append(nl); 732 } 733 } 734 } 735 736 return buf.toString(); 737 } 738 739 public final void verify( 740 PublicKey key) 741 throws CertificateException, NoSuchAlgorithmException, 742 InvalidKeyException, NoSuchProviderException, SignatureException 743 { 744 Signature signature; 745 String sigName = X509SignatureUtil.getSignatureName(c.getSignatureAlgorithm()); 746 747 try 748 { 749 signature = Signature.getInstance(sigName, BouncyCastleProvider.PROVIDER_NAME); 750 } 751 catch (Exception e) 752 { 753 signature = Signature.getInstance(sigName); 754 } 755 756 checkSignature(key, signature); 757 } 758 759 public final void verify( 760 PublicKey key, 761 String sigProvider) 762 throws CertificateException, NoSuchAlgorithmException, 763 InvalidKeyException, NoSuchProviderException, SignatureException 764 { 765 String sigName = X509SignatureUtil.getSignatureName(c.getSignatureAlgorithm()); 766 Signature signature = Signature.getInstance(sigName, sigProvider); 767 768 checkSignature(key, signature); 769 } 770 771 private void checkSignature( 772 PublicKey key, 773 Signature signature) 774 throws CertificateException, NoSuchAlgorithmException, 775 SignatureException, InvalidKeyException 776 { 777 if (!isAlgIdEqual(c.getSignatureAlgorithm(), c.getTBSCertificate().getSignature())) 778 { 779 throw new CertificateException("signature algorithm in TBS cert not same as outer cert"); 780 } 781 782 ASN1Encodable params = c.getSignatureAlgorithm().getParameters(); 783 784 // TODO This should go after the initVerify? 785 X509SignatureUtil.setSignatureParameters(signature, params); 786 787 signature.initVerify(key); 788 789 signature.update(this.getTBSCertificate()); 790 791 if (!signature.verify(this.getSignature())) 792 { 793 throw new SignatureException("certificate does not verify with supplied key"); 794 } 795 } 796 797 private boolean isAlgIdEqual(AlgorithmIdentifier id1, AlgorithmIdentifier id2) 798 { 799 if (!id1.getObjectId().equals(id2.getObjectId())) 800 { 801 return false; 802 } 803 804 if (id1.getParameters() == null) 805 { 806 if (id2.getParameters() != null && !id2.getParameters().equals(DERNull.INSTANCE)) 807 { 808 return false; 809 } 810 811 return true; 812 } 813 814 if (id2.getParameters() == null) 815 { 816 if (id1.getParameters() != null && !id1.getParameters().equals(DERNull.INSTANCE)) 817 { 818 return false; 819 } 820 821 return true; 822 } 823 824 return id1.getParameters().equals(id2.getParameters()); 825 } 826} 827