[ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * Pure-PHP X.509 Parser 5 * 6 * PHP version 5 7 * 8 * Encode and decode X.509 certificates. 9 * 10 * The extensions are from {@link http://tools.ietf.org/html/rfc5280 RFC5280} and 11 * {@link http://web.archive.org/web/19961027104704/http://www3.netscape.com/eng/security/cert-exts.html Netscape Certificate Extensions}. 12 * 13 * Note that loading an X.509 certificate and resaving it may invalidate the signature. The reason being that the signature is based on a 14 * portion of the certificate that contains optional parameters with default values. ie. if the parameter isn't there the default value is 15 * used. Problem is, if the parameter is there and it just so happens to have the default value there are two ways that that parameter can 16 * be encoded. It can be encoded explicitly or left out all together. This would effect the signature value and thus may invalidate the 17 * the certificate all together unless the certificate is re-signed. 18 * 19 * @category File 20 * @package X509 21 * @author Jim Wigginton <terrafrost@php.net> 22 * @copyright 2012 Jim Wigginton 23 * @license http://www.opensource.org/licenses/mit-license.html MIT License 24 * @link http://phpseclib.sourceforge.net 25 */ 26 27 namespace phpseclib\File; 28 29 use phpseclib\Crypt\Hash; 30 use phpseclib\Crypt\Random; 31 use phpseclib\Crypt\RSA; 32 use phpseclib\File\ASN1\Element; 33 use phpseclib\Math\BigInteger; 34 use DateTime; 35 use DateTimeZone; 36 37 /** 38 * Pure-PHP X.509 Parser 39 * 40 * @package X509 41 * @author Jim Wigginton <terrafrost@php.net> 42 * @access public 43 */ 44 class X509 45 { 46 /** 47 * Flag to only accept signatures signed by certificate authorities 48 * 49 * Not really used anymore but retained all the same to suppress E_NOTICEs from old installs 50 * 51 * @access public 52 */ 53 const VALIDATE_SIGNATURE_BY_CA = 1; 54 55 /**#@+ 56 * @access public 57 * @see \phpseclib\File\X509::getDN() 58 */ 59 /** 60 * Return internal array representation 61 */ 62 const DN_ARRAY = 0; 63 /** 64 * Return string 65 */ 66 const DN_STRING = 1; 67 /** 68 * Return ASN.1 name string 69 */ 70 const DN_ASN1 = 2; 71 /** 72 * Return OpenSSL compatible array 73 */ 74 const DN_OPENSSL = 3; 75 /** 76 * Return canonical ASN.1 RDNs string 77 */ 78 const DN_CANON = 4; 79 /** 80 * Return name hash for file indexing 81 */ 82 const DN_HASH = 5; 83 /**#@-*/ 84 85 /**#@+ 86 * @access public 87 * @see \phpseclib\File\X509::saveX509() 88 * @see \phpseclib\File\X509::saveCSR() 89 * @see \phpseclib\File\X509::saveCRL() 90 */ 91 /** 92 * Save as PEM 93 * 94 * ie. a base64-encoded PEM with a header and a footer 95 */ 96 const FORMAT_PEM = 0; 97 /** 98 * Save as DER 99 */ 100 const FORMAT_DER = 1; 101 /** 102 * Save as a SPKAC 103 * 104 * Only works on CSRs. Not currently supported. 105 */ 106 const FORMAT_SPKAC = 2; 107 /** 108 * Auto-detect the format 109 * 110 * Used only by the load*() functions 111 */ 112 const FORMAT_AUTO_DETECT = 3; 113 /**#@-*/ 114 115 /** 116 * Attribute value disposition. 117 * If disposition is >= 0, this is the index of the target value. 118 */ 119 const ATTR_ALL = -1; // All attribute values (array). 120 const ATTR_APPEND = -2; // Add a value. 121 const ATTR_REPLACE = -3; // Clear first, then add a value. 122 123 /** 124 * ASN.1 syntax for X.509 certificates 125 * 126 * @var array 127 * @access private 128 */ 129 var $Certificate; 130 131 /**#@+ 132 * ASN.1 syntax for various extensions 133 * 134 * @access private 135 */ 136 var $DirectoryString; 137 var $PKCS9String; 138 var $AttributeValue; 139 var $Extensions; 140 var $KeyUsage; 141 var $ExtKeyUsageSyntax; 142 var $BasicConstraints; 143 var $KeyIdentifier; 144 var $CRLDistributionPoints; 145 var $AuthorityKeyIdentifier; 146 var $CertificatePolicies; 147 var $AuthorityInfoAccessSyntax; 148 var $SubjectAltName; 149 var $SubjectDirectoryAttributes; 150 var $PrivateKeyUsagePeriod; 151 var $IssuerAltName; 152 var $PolicyMappings; 153 var $NameConstraints; 154 155 var $CPSuri; 156 var $UserNotice; 157 158 var $netscape_cert_type; 159 var $netscape_comment; 160 var $netscape_ca_policy_url; 161 162 var $Name; 163 var $RelativeDistinguishedName; 164 var $CRLNumber; 165 var $CRLReason; 166 var $IssuingDistributionPoint; 167 var $InvalidityDate; 168 var $CertificateIssuer; 169 var $HoldInstructionCode; 170 var $SignedPublicKeyAndChallenge; 171 /**#@-*/ 172 173 /**#@+ 174 * ASN.1 syntax for various DN attributes 175 * 176 * @access private 177 */ 178 var $PostalAddress; 179 /**#@-*/ 180 181 /** 182 * ASN.1 syntax for Certificate Signing Requests (RFC2986) 183 * 184 * @var array 185 * @access private 186 */ 187 var $CertificationRequest; 188 189 /** 190 * ASN.1 syntax for Certificate Revocation Lists (RFC5280) 191 * 192 * @var array 193 * @access private 194 */ 195 var $CertificateList; 196 197 /** 198 * Distinguished Name 199 * 200 * @var array 201 * @access private 202 */ 203 var $dn; 204 205 /** 206 * Public key 207 * 208 * @var string 209 * @access private 210 */ 211 var $publicKey; 212 213 /** 214 * Private key 215 * 216 * @var string 217 * @access private 218 */ 219 var $privateKey; 220 221 /** 222 * Object identifiers for X.509 certificates 223 * 224 * @var array 225 * @access private 226 * @link http://en.wikipedia.org/wiki/Object_identifier 227 */ 228 var $oids; 229 230 /** 231 * The certificate authorities 232 * 233 * @var array 234 * @access private 235 */ 236 var $CAs; 237 238 /** 239 * The currently loaded certificate 240 * 241 * @var array 242 * @access private 243 */ 244 var $currentCert; 245 246 /** 247 * The signature subject 248 * 249 * There's no guarantee \phpseclib\File\X509 is going to re-encode an X.509 cert in the same way it was originally 250 * encoded so we take save the portion of the original cert that the signature would have made for. 251 * 252 * @var string 253 * @access private 254 */ 255 var $signatureSubject; 256 257 /** 258 * Certificate Start Date 259 * 260 * @var string 261 * @access private 262 */ 263 var $startDate; 264 265 /** 266 * Certificate End Date 267 * 268 * @var string 269 * @access private 270 */ 271 var $endDate; 272 273 /** 274 * Serial Number 275 * 276 * @var string 277 * @access private 278 */ 279 var $serialNumber; 280 281 /** 282 * Key Identifier 283 * 284 * See {@link http://tools.ietf.org/html/rfc5280#section-4.2.1.1 RFC5280#section-4.2.1.1} and 285 * {@link http://tools.ietf.org/html/rfc5280#section-4.2.1.2 RFC5280#section-4.2.1.2}. 286 * 287 * @var string 288 * @access private 289 */ 290 var $currentKeyIdentifier; 291 292 /** 293 * CA Flag 294 * 295 * @var bool 296 * @access private 297 */ 298 var $caFlag = false; 299 300 /** 301 * SPKAC Challenge 302 * 303 * @var string 304 * @access private 305 */ 306 var $challenge; 307 308 /** 309 * Recursion Limit 310 * 311 * @var int 312 * @access private 313 */ 314 static $recur_limit = 5; 315 316 /** 317 * URL fetch flag 318 * 319 * @var bool 320 * @access private 321 */ 322 static $disable_url_fetch = false; 323 324 /** 325 * Default Constructor. 326 * 327 * @return \phpseclib\File\X509 328 * @access public 329 */ 330 function __construct() 331 { 332 // Explicitly Tagged Module, 1988 Syntax 333 // http://tools.ietf.org/html/rfc5280#appendix-A.1 334 335 $this->DirectoryString = array( 336 'type' => ASN1::TYPE_CHOICE, 337 'children' => array( 338 'teletexString' => array('type' => ASN1::TYPE_TELETEX_STRING), 339 'printableString' => array('type' => ASN1::TYPE_PRINTABLE_STRING), 340 'universalString' => array('type' => ASN1::TYPE_UNIVERSAL_STRING), 341 'utf8String' => array('type' => ASN1::TYPE_UTF8_STRING), 342 'bmpString' => array('type' => ASN1::TYPE_BMP_STRING) 343 ) 344 ); 345 346 $this->PKCS9String = array( 347 'type' => ASN1::TYPE_CHOICE, 348 'children' => array( 349 'ia5String' => array('type' => ASN1::TYPE_IA5_STRING), 350 'directoryString' => $this->DirectoryString 351 ) 352 ); 353 354 $this->AttributeValue = array('type' => ASN1::TYPE_ANY); 355 356 $AttributeType = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER); 357 358 $AttributeTypeAndValue = array( 359 'type' => ASN1::TYPE_SEQUENCE, 360 'children' => array( 361 'type' => $AttributeType, 362 'value'=> $this->AttributeValue 363 ) 364 ); 365 366 /* 367 In practice, RDNs containing multiple name-value pairs (called "multivalued RDNs") are rare, 368 but they can be useful at times when either there is no unique attribute in the entry or you 369 want to ensure that the entry's DN contains some useful identifying information. 370 371 - https://www.opends.org/wiki/page/DefinitionRelativeDistinguishedName 372 */ 373 $this->RelativeDistinguishedName = array( 374 'type' => ASN1::TYPE_SET, 375 'min' => 1, 376 'max' => -1, 377 'children' => $AttributeTypeAndValue 378 ); 379 380 // http://tools.ietf.org/html/rfc5280#section-4.1.2.4 381 $RDNSequence = array( 382 'type' => ASN1::TYPE_SEQUENCE, 383 // RDNSequence does not define a min or a max, which means it doesn't have one 384 'min' => 0, 385 'max' => -1, 386 'children' => $this->RelativeDistinguishedName 387 ); 388 389 $this->Name = array( 390 'type' => ASN1::TYPE_CHOICE, 391 'children' => array( 392 'rdnSequence' => $RDNSequence 393 ) 394 ); 395 396 // http://tools.ietf.org/html/rfc5280#section-4.1.1.2 397 $AlgorithmIdentifier = array( 398 'type' => ASN1::TYPE_SEQUENCE, 399 'children' => array( 400 'algorithm' => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER), 401 'parameters' => array( 402 'type' => ASN1::TYPE_ANY, 403 'optional' => true 404 ) 405 ) 406 ); 407 408 /* 409 A certificate using system MUST reject the certificate if it encounters 410 a critical extension it does not recognize; however, a non-critical 411 extension may be ignored if it is not recognized. 412 413 http://tools.ietf.org/html/rfc5280#section-4.2 414 */ 415 $Extension = array( 416 'type' => ASN1::TYPE_SEQUENCE, 417 'children' => array( 418 'extnId' => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER), 419 'critical' => array( 420 'type' => ASN1::TYPE_BOOLEAN, 421 'optional' => true, 422 'default' => false 423 ), 424 'extnValue' => array('type' => ASN1::TYPE_OCTET_STRING) 425 ) 426 ); 427 428 $this->Extensions = array( 429 'type' => ASN1::TYPE_SEQUENCE, 430 'min' => 1, 431 // technically, it's MAX, but we'll assume anything < 0 is MAX 432 'max' => -1, 433 // if 'children' isn't an array then 'min' and 'max' must be defined 434 'children' => $Extension 435 ); 436 437 $SubjectPublicKeyInfo = array( 438 'type' => ASN1::TYPE_SEQUENCE, 439 'children' => array( 440 'algorithm' => $AlgorithmIdentifier, 441 'subjectPublicKey' => array('type' => ASN1::TYPE_BIT_STRING) 442 ) 443 ); 444 445 $UniqueIdentifier = array('type' => ASN1::TYPE_BIT_STRING); 446 447 $Time = array( 448 'type' => ASN1::TYPE_CHOICE, 449 'children' => array( 450 'utcTime' => array('type' => ASN1::TYPE_UTC_TIME), 451 'generalTime' => array('type' => ASN1::TYPE_GENERALIZED_TIME) 452 ) 453 ); 454 455 // http://tools.ietf.org/html/rfc5280#section-4.1.2.5 456 $Validity = array( 457 'type' => ASN1::TYPE_SEQUENCE, 458 'children' => array( 459 'notBefore' => $Time, 460 'notAfter' => $Time 461 ) 462 ); 463 464 $CertificateSerialNumber = array('type' => ASN1::TYPE_INTEGER); 465 466 $Version = array( 467 'type' => ASN1::TYPE_INTEGER, 468 'mapping' => array('v1', 'v2', 'v3') 469 ); 470 471 // assert($TBSCertificate['children']['signature'] == $Certificate['children']['signatureAlgorithm']) 472 $TBSCertificate = array( 473 'type' => ASN1::TYPE_SEQUENCE, 474 'children' => array( 475 // technically, default implies optional, but we'll define it as being optional, none-the-less, just to 476 // reenforce that fact 477 'version' => array( 478 'constant' => 0, 479 'optional' => true, 480 'explicit' => true, 481 'default' => 'v1' 482 ) + $Version, 483 'serialNumber' => $CertificateSerialNumber, 484 'signature' => $AlgorithmIdentifier, 485 'issuer' => $this->Name, 486 'validity' => $Validity, 487 'subject' => $this->Name, 488 'subjectPublicKeyInfo' => $SubjectPublicKeyInfo, 489 // implicit means that the T in the TLV structure is to be rewritten, regardless of the type 490 'issuerUniqueID' => array( 491 'constant' => 1, 492 'optional' => true, 493 'implicit' => true 494 ) + $UniqueIdentifier, 495 'subjectUniqueID' => array( 496 'constant' => 2, 497 'optional' => true, 498 'implicit' => true 499 ) + $UniqueIdentifier, 500 // <http://tools.ietf.org/html/rfc2459#page-74> doesn't use the EXPLICIT keyword but if 501 // it's not IMPLICIT, it's EXPLICIT 502 'extensions' => array( 503 'constant' => 3, 504 'optional' => true, 505 'explicit' => true 506 ) + $this->Extensions 507 ) 508 ); 509 510 $this->Certificate = array( 511 'type' => ASN1::TYPE_SEQUENCE, 512 'children' => array( 513 'tbsCertificate' => $TBSCertificate, 514 'signatureAlgorithm' => $AlgorithmIdentifier, 515 'signature' => array('type' => ASN1::TYPE_BIT_STRING) 516 ) 517 ); 518 519 $this->KeyUsage = array( 520 'type' => ASN1::TYPE_BIT_STRING, 521 'mapping' => array( 522 'digitalSignature', 523 'nonRepudiation', 524 'keyEncipherment', 525 'dataEncipherment', 526 'keyAgreement', 527 'keyCertSign', 528 'cRLSign', 529 'encipherOnly', 530 'decipherOnly' 531 ) 532 ); 533 534 $this->BasicConstraints = array( 535 'type' => ASN1::TYPE_SEQUENCE, 536 'children' => array( 537 'cA' => array( 538 'type' => ASN1::TYPE_BOOLEAN, 539 'optional' => true, 540 'default' => false 541 ), 542 'pathLenConstraint' => array( 543 'type' => ASN1::TYPE_INTEGER, 544 'optional' => true 545 ) 546 ) 547 ); 548 549 $this->KeyIdentifier = array('type' => ASN1::TYPE_OCTET_STRING); 550 551 $OrganizationalUnitNames = array( 552 'type' => ASN1::TYPE_SEQUENCE, 553 'min' => 1, 554 'max' => 4, // ub-organizational-units 555 'children' => array('type' => ASN1::TYPE_PRINTABLE_STRING) 556 ); 557 558 $PersonalName = array( 559 'type' => ASN1::TYPE_SET, 560 'children' => array( 561 'surname' => array( 562 'type' => ASN1::TYPE_PRINTABLE_STRING, 563 'constant' => 0, 564 'optional' => true, 565 'implicit' => true 566 ), 567 'given-name' => array( 568 'type' => ASN1::TYPE_PRINTABLE_STRING, 569 'constant' => 1, 570 'optional' => true, 571 'implicit' => true 572 ), 573 'initials' => array( 574 'type' => ASN1::TYPE_PRINTABLE_STRING, 575 'constant' => 2, 576 'optional' => true, 577 'implicit' => true 578 ), 579 'generation-qualifier' => array( 580 'type' => ASN1::TYPE_PRINTABLE_STRING, 581 'constant' => 3, 582 'optional' => true, 583 'implicit' => true 584 ) 585 ) 586 ); 587 588 $NumericUserIdentifier = array('type' => ASN1::TYPE_NUMERIC_STRING); 589 590 $OrganizationName = array('type' => ASN1::TYPE_PRINTABLE_STRING); 591 592 $PrivateDomainName = array( 593 'type' => ASN1::TYPE_CHOICE, 594 'children' => array( 595 'numeric' => array('type' => ASN1::TYPE_NUMERIC_STRING), 596 'printable' => array('type' => ASN1::TYPE_PRINTABLE_STRING) 597 ) 598 ); 599 600 $TerminalIdentifier = array('type' => ASN1::TYPE_PRINTABLE_STRING); 601 602 $NetworkAddress = array('type' => ASN1::TYPE_NUMERIC_STRING); 603 604 $AdministrationDomainName = array( 605 'type' => ASN1::TYPE_CHOICE, 606 // if class isn't present it's assumed to be \phpseclib\File\ASN1::CLASS_UNIVERSAL or 607 // (if constant is present) \phpseclib\File\ASN1::CLASS_CONTEXT_SPECIFIC 608 'class' => ASN1::CLASS_APPLICATION, 609 'cast' => 2, 610 'children' => array( 611 'numeric' => array('type' => ASN1::TYPE_NUMERIC_STRING), 612 'printable' => array('type' => ASN1::TYPE_PRINTABLE_STRING) 613 ) 614 ); 615 616 $CountryName = array( 617 'type' => ASN1::TYPE_CHOICE, 618 // if class isn't present it's assumed to be \phpseclib\File\ASN1::CLASS_UNIVERSAL or 619 // (if constant is present) \phpseclib\File\ASN1::CLASS_CONTEXT_SPECIFIC 620 'class' => ASN1::CLASS_APPLICATION, 621 'cast' => 1, 622 'children' => array( 623 'x121-dcc-code' => array('type' => ASN1::TYPE_NUMERIC_STRING), 624 'iso-3166-alpha2-code' => array('type' => ASN1::TYPE_PRINTABLE_STRING) 625 ) 626 ); 627 628 $AnotherName = array( 629 'type' => ASN1::TYPE_SEQUENCE, 630 'children' => array( 631 'type-id' => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER), 632 'value' => array( 633 'type' => ASN1::TYPE_ANY, 634 'constant' => 0, 635 'optional' => true, 636 'explicit' => true 637 ) 638 ) 639 ); 640 641 $ExtensionAttribute = array( 642 'type' => ASN1::TYPE_SEQUENCE, 643 'children' => array( 644 'extension-attribute-type' => array( 645 'type' => ASN1::TYPE_PRINTABLE_STRING, 646 'constant' => 0, 647 'optional' => true, 648 'implicit' => true 649 ), 650 'extension-attribute-value' => array( 651 'type' => ASN1::TYPE_ANY, 652 'constant' => 1, 653 'optional' => true, 654 'explicit' => true 655 ) 656 ) 657 ); 658 659 $ExtensionAttributes = array( 660 'type' => ASN1::TYPE_SET, 661 'min' => 1, 662 'max' => 256, // ub-extension-attributes 663 'children' => $ExtensionAttribute 664 ); 665 666 $BuiltInDomainDefinedAttribute = array( 667 'type' => ASN1::TYPE_SEQUENCE, 668 'children' => array( 669 'type' => array('type' => ASN1::TYPE_PRINTABLE_STRING), 670 'value' => array('type' => ASN1::TYPE_PRINTABLE_STRING) 671 ) 672 ); 673 674 $BuiltInDomainDefinedAttributes = array( 675 'type' => ASN1::TYPE_SEQUENCE, 676 'min' => 1, 677 'max' => 4, // ub-domain-defined-attributes 678 'children' => $BuiltInDomainDefinedAttribute 679 ); 680 681 $BuiltInStandardAttributes = array( 682 'type' => ASN1::TYPE_SEQUENCE, 683 'children' => array( 684 'country-name' => array('optional' => true) + $CountryName, 685 'administration-domain-name' => array('optional' => true) + $AdministrationDomainName, 686 'network-address' => array( 687 'constant' => 0, 688 'optional' => true, 689 'implicit' => true 690 ) + $NetworkAddress, 691 'terminal-identifier' => array( 692 'constant' => 1, 693 'optional' => true, 694 'implicit' => true 695 ) + $TerminalIdentifier, 696 'private-domain-name' => array( 697 'constant' => 2, 698 'optional' => true, 699 'explicit' => true 700 ) + $PrivateDomainName, 701 'organization-name' => array( 702 'constant' => 3, 703 'optional' => true, 704 'implicit' => true 705 ) + $OrganizationName, 706 'numeric-user-identifier' => array( 707 'constant' => 4, 708 'optional' => true, 709 'implicit' => true 710 ) + $NumericUserIdentifier, 711 'personal-name' => array( 712 'constant' => 5, 713 'optional' => true, 714 'implicit' => true 715 ) + $PersonalName, 716 'organizational-unit-names' => array( 717 'constant' => 6, 718 'optional' => true, 719 'implicit' => true 720 ) + $OrganizationalUnitNames 721 ) 722 ); 723 724 $ORAddress = array( 725 'type' => ASN1::TYPE_SEQUENCE, 726 'children' => array( 727 'built-in-standard-attributes' => $BuiltInStandardAttributes, 728 'built-in-domain-defined-attributes' => array('optional' => true) + $BuiltInDomainDefinedAttributes, 729 'extension-attributes' => array('optional' => true) + $ExtensionAttributes 730 ) 731 ); 732 733 $EDIPartyName = array( 734 'type' => ASN1::TYPE_SEQUENCE, 735 'children' => array( 736 'nameAssigner' => array( 737 'constant' => 0, 738 'optional' => true, 739 'implicit' => true 740 ) + $this->DirectoryString, 741 // partyName is technically required but \phpseclib\File\ASN1 doesn't currently support non-optional constants and 742 // setting it to optional gets the job done in any event. 743 'partyName' => array( 744 'constant' => 1, 745 'optional' => true, 746 'implicit' => true 747 ) + $this->DirectoryString 748 ) 749 ); 750 751 $GeneralName = array( 752 'type' => ASN1::TYPE_CHOICE, 753 'children' => array( 754 'otherName' => array( 755 'constant' => 0, 756 'optional' => true, 757 'implicit' => true 758 ) + $AnotherName, 759 'rfc822Name' => array( 760 'type' => ASN1::TYPE_IA5_STRING, 761 'constant' => 1, 762 'optional' => true, 763 'implicit' => true 764 ), 765 'dNSName' => array( 766 'type' => ASN1::TYPE_IA5_STRING, 767 'constant' => 2, 768 'optional' => true, 769 'implicit' => true 770 ), 771 'x400Address' => array( 772 'constant' => 3, 773 'optional' => true, 774 'implicit' => true 775 ) + $ORAddress, 776 'directoryName' => array( 777 'constant' => 4, 778 'optional' => true, 779 'explicit' => true 780 ) + $this->Name, 781 'ediPartyName' => array( 782 'constant' => 5, 783 'optional' => true, 784 'implicit' => true 785 ) + $EDIPartyName, 786 'uniformResourceIdentifier' => array( 787 'type' => ASN1::TYPE_IA5_STRING, 788 'constant' => 6, 789 'optional' => true, 790 'implicit' => true 791 ), 792 'iPAddress' => array( 793 'type' => ASN1::TYPE_OCTET_STRING, 794 'constant' => 7, 795 'optional' => true, 796 'implicit' => true 797 ), 798 'registeredID' => array( 799 'type' => ASN1::TYPE_OBJECT_IDENTIFIER, 800 'constant' => 8, 801 'optional' => true, 802 'implicit' => true 803 ) 804 ) 805 ); 806 807 $GeneralNames = array( 808 'type' => ASN1::TYPE_SEQUENCE, 809 'min' => 1, 810 'max' => -1, 811 'children' => $GeneralName 812 ); 813 814 $this->IssuerAltName = $GeneralNames; 815 816 $ReasonFlags = array( 817 'type' => ASN1::TYPE_BIT_STRING, 818 'mapping' => array( 819 'unused', 820 'keyCompromise', 821 'cACompromise', 822 'affiliationChanged', 823 'superseded', 824 'cessationOfOperation', 825 'certificateHold', 826 'privilegeWithdrawn', 827 'aACompromise' 828 ) 829 ); 830 831 $DistributionPointName = array( 832 'type' => ASN1::TYPE_CHOICE, 833 'children' => array( 834 'fullName' => array( 835 'constant' => 0, 836 'optional' => true, 837 'implicit' => true 838 ) + $GeneralNames, 839 'nameRelativeToCRLIssuer' => array( 840 'constant' => 1, 841 'optional' => true, 842 'implicit' => true 843 ) + $this->RelativeDistinguishedName 844 ) 845 ); 846 847 $DistributionPoint = array( 848 'type' => ASN1::TYPE_SEQUENCE, 849 'children' => array( 850 'distributionPoint' => array( 851 'constant' => 0, 852 'optional' => true, 853 'explicit' => true 854 ) + $DistributionPointName, 855 'reasons' => array( 856 'constant' => 1, 857 'optional' => true, 858 'implicit' => true 859 ) + $ReasonFlags, 860 'cRLIssuer' => array( 861 'constant' => 2, 862 'optional' => true, 863 'implicit' => true 864 ) + $GeneralNames 865 ) 866 ); 867 868 $this->CRLDistributionPoints = array( 869 'type' => ASN1::TYPE_SEQUENCE, 870 'min' => 1, 871 'max' => -1, 872 'children' => $DistributionPoint 873 ); 874 875 $this->AuthorityKeyIdentifier = array( 876 'type' => ASN1::TYPE_SEQUENCE, 877 'children' => array( 878 'keyIdentifier' => array( 879 'constant' => 0, 880 'optional' => true, 881 'implicit' => true 882 ) + $this->KeyIdentifier, 883 'authorityCertIssuer' => array( 884 'constant' => 1, 885 'optional' => true, 886 'implicit' => true 887 ) + $GeneralNames, 888 'authorityCertSerialNumber' => array( 889 'constant' => 2, 890 'optional' => true, 891 'implicit' => true 892 ) + $CertificateSerialNumber 893 ) 894 ); 895 896 $PolicyQualifierId = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER); 897 898 $PolicyQualifierInfo = array( 899 'type' => ASN1::TYPE_SEQUENCE, 900 'children' => array( 901 'policyQualifierId' => $PolicyQualifierId, 902 'qualifier' => array('type' => ASN1::TYPE_ANY) 903 ) 904 ); 905 906 $CertPolicyId = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER); 907 908 $PolicyInformation = array( 909 'type' => ASN1::TYPE_SEQUENCE, 910 'children' => array( 911 'policyIdentifier' => $CertPolicyId, 912 'policyQualifiers' => array( 913 'type' => ASN1::TYPE_SEQUENCE, 914 'min' => 0, 915 'max' => -1, 916 'optional' => true, 917 'children' => $PolicyQualifierInfo 918 ) 919 ) 920 ); 921 922 $this->CertificatePolicies = array( 923 'type' => ASN1::TYPE_SEQUENCE, 924 'min' => 1, 925 'max' => -1, 926 'children' => $PolicyInformation 927 ); 928 929 $this->PolicyMappings = array( 930 'type' => ASN1::TYPE_SEQUENCE, 931 'min' => 1, 932 'max' => -1, 933 'children' => array( 934 'type' => ASN1::TYPE_SEQUENCE, 935 'children' => array( 936 'issuerDomainPolicy' => $CertPolicyId, 937 'subjectDomainPolicy' => $CertPolicyId 938 ) 939 ) 940 ); 941 942 $KeyPurposeId = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER); 943 944 $this->ExtKeyUsageSyntax = array( 945 'type' => ASN1::TYPE_SEQUENCE, 946 'min' => 1, 947 'max' => -1, 948 'children' => $KeyPurposeId 949 ); 950 951 $AccessDescription = array( 952 'type' => ASN1::TYPE_SEQUENCE, 953 'children' => array( 954 'accessMethod' => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER), 955 'accessLocation' => $GeneralName 956 ) 957 ); 958 959 $this->AuthorityInfoAccessSyntax = array( 960 'type' => ASN1::TYPE_SEQUENCE, 961 'min' => 1, 962 'max' => -1, 963 'children' => $AccessDescription 964 ); 965 966 $this->SubjectInfoAccessSyntax = array( 967 'type' => ASN1::TYPE_SEQUENCE, 968 'min' => 1, 969 'max' => -1, 970 'children' => $AccessDescription 971 ); 972 973 $this->SubjectAltName = $GeneralNames; 974 975 $this->PrivateKeyUsagePeriod = array( 976 'type' => ASN1::TYPE_SEQUENCE, 977 'children' => array( 978 'notBefore' => array( 979 'constant' => 0, 980 'optional' => true, 981 'implicit' => true, 982 'type' => ASN1::TYPE_GENERALIZED_TIME), 983 'notAfter' => array( 984 'constant' => 1, 985 'optional' => true, 986 'implicit' => true, 987 'type' => ASN1::TYPE_GENERALIZED_TIME) 988 ) 989 ); 990 991 $BaseDistance = array('type' => ASN1::TYPE_INTEGER); 992 993 $GeneralSubtree = array( 994 'type' => ASN1::TYPE_SEQUENCE, 995 'children' => array( 996 'base' => $GeneralName, 997 'minimum' => array( 998 'constant' => 0, 999 'optional' => true, 1000 'implicit' => true, 1001 'default' => new BigInteger(0) 1002 ) + $BaseDistance, 1003 'maximum' => array( 1004 'constant' => 1, 1005 'optional' => true, 1006 'implicit' => true, 1007 ) + $BaseDistance 1008 ) 1009 ); 1010 1011 $GeneralSubtrees = array( 1012 'type' => ASN1::TYPE_SEQUENCE, 1013 'min' => 1, 1014 'max' => -1, 1015 'children' => $GeneralSubtree 1016 ); 1017 1018 $this->NameConstraints = array( 1019 'type' => ASN1::TYPE_SEQUENCE, 1020 'children' => array( 1021 'permittedSubtrees' => array( 1022 'constant' => 0, 1023 'optional' => true, 1024 'implicit' => true 1025 ) + $GeneralSubtrees, 1026 'excludedSubtrees' => array( 1027 'constant' => 1, 1028 'optional' => true, 1029 'implicit' => true 1030 ) + $GeneralSubtrees 1031 ) 1032 ); 1033 1034 $this->CPSuri = array('type' => ASN1::TYPE_IA5_STRING); 1035 1036 $DisplayText = array( 1037 'type' => ASN1::TYPE_CHOICE, 1038 'children' => array( 1039 'ia5String' => array('type' => ASN1::TYPE_IA5_STRING), 1040 'visibleString' => array('type' => ASN1::TYPE_VISIBLE_STRING), 1041 'bmpString' => array('type' => ASN1::TYPE_BMP_STRING), 1042 'utf8String' => array('type' => ASN1::TYPE_UTF8_STRING) 1043 ) 1044 ); 1045 1046 $NoticeReference = array( 1047 'type' => ASN1::TYPE_SEQUENCE, 1048 'children' => array( 1049 'organization' => $DisplayText, 1050 'noticeNumbers' => array( 1051 'type' => ASN1::TYPE_SEQUENCE, 1052 'min' => 1, 1053 'max' => 200, 1054 'children' => array('type' => ASN1::TYPE_INTEGER) 1055 ) 1056 ) 1057 ); 1058 1059 $this->UserNotice = array( 1060 'type' => ASN1::TYPE_SEQUENCE, 1061 'children' => array( 1062 'noticeRef' => array( 1063 'optional' => true, 1064 'implicit' => true 1065 ) + $NoticeReference, 1066 'explicitText' => array( 1067 'optional' => true, 1068 'implicit' => true 1069 ) + $DisplayText 1070 ) 1071 ); 1072 1073 // mapping is from <http://www.mozilla.org/projects/security/pki/nss/tech-notes/tn3.html> 1074 $this->netscape_cert_type = array( 1075 'type' => ASN1::TYPE_BIT_STRING, 1076 'mapping' => array( 1077 'SSLClient', 1078 'SSLServer', 1079 'Email', 1080 'ObjectSigning', 1081 'Reserved', 1082 'SSLCA', 1083 'EmailCA', 1084 'ObjectSigningCA' 1085 ) 1086 ); 1087 1088 $this->netscape_comment = array('type' => ASN1::TYPE_IA5_STRING); 1089 $this->netscape_ca_policy_url = array('type' => ASN1::TYPE_IA5_STRING); 1090 1091 // attribute is used in RFC2986 but we're using the RFC5280 definition 1092 1093 $Attribute = array( 1094 'type' => ASN1::TYPE_SEQUENCE, 1095 'children' => array( 1096 'type' => $AttributeType, 1097 'value'=> array( 1098 'type' => ASN1::TYPE_SET, 1099 'min' => 1, 1100 'max' => -1, 1101 'children' => $this->AttributeValue 1102 ) 1103 ) 1104 ); 1105 1106 $this->SubjectDirectoryAttributes = array( 1107 'type' => ASN1::TYPE_SEQUENCE, 1108 'min' => 1, 1109 'max' => -1, 1110 'children' => $Attribute 1111 ); 1112 1113 // adapted from <http://tools.ietf.org/html/rfc2986> 1114 1115 $Attributes = array( 1116 'type' => ASN1::TYPE_SET, 1117 'min' => 1, 1118 'max' => -1, 1119 'children' => $Attribute 1120 ); 1121 1122 $CertificationRequestInfo = array( 1123 'type' => ASN1::TYPE_SEQUENCE, 1124 'children' => array( 1125 'version' => array( 1126 'type' => ASN1::TYPE_INTEGER, 1127 'mapping' => array('v1') 1128 ), 1129 'subject' => $this->Name, 1130 'subjectPKInfo' => $SubjectPublicKeyInfo, 1131 'attributes' => array( 1132 'constant' => 0, 1133 'optional' => true, 1134 'implicit' => true 1135 ) + $Attributes, 1136 ) 1137 ); 1138 1139 $this->CertificationRequest = array( 1140 'type' => ASN1::TYPE_SEQUENCE, 1141 'children' => array( 1142 'certificationRequestInfo' => $CertificationRequestInfo, 1143 'signatureAlgorithm' => $AlgorithmIdentifier, 1144 'signature' => array('type' => ASN1::TYPE_BIT_STRING) 1145 ) 1146 ); 1147 1148 $RevokedCertificate = array( 1149 'type' => ASN1::TYPE_SEQUENCE, 1150 'children' => array( 1151 'userCertificate' => $CertificateSerialNumber, 1152 'revocationDate' => $Time, 1153 'crlEntryExtensions' => array( 1154 'optional' => true 1155 ) + $this->Extensions 1156 ) 1157 ); 1158 1159 $TBSCertList = array( 1160 'type' => ASN1::TYPE_SEQUENCE, 1161 'children' => array( 1162 'version' => array( 1163 'optional' => true, 1164 'default' => 'v1' 1165 ) + $Version, 1166 'signature' => $AlgorithmIdentifier, 1167 'issuer' => $this->Name, 1168 'thisUpdate' => $Time, 1169 'nextUpdate' => array( 1170 'optional' => true 1171 ) + $Time, 1172 'revokedCertificates' => array( 1173 'type' => ASN1::TYPE_SEQUENCE, 1174 'optional' => true, 1175 'min' => 0, 1176 'max' => -1, 1177 'children' => $RevokedCertificate 1178 ), 1179 'crlExtensions' => array( 1180 'constant' => 0, 1181 'optional' => true, 1182 'explicit' => true 1183 ) + $this->Extensions 1184 ) 1185 ); 1186 1187 $this->CertificateList = array( 1188 'type' => ASN1::TYPE_SEQUENCE, 1189 'children' => array( 1190 'tbsCertList' => $TBSCertList, 1191 'signatureAlgorithm' => $AlgorithmIdentifier, 1192 'signature' => array('type' => ASN1::TYPE_BIT_STRING) 1193 ) 1194 ); 1195 1196 $this->CRLNumber = array('type' => ASN1::TYPE_INTEGER); 1197 1198 $this->CRLReason = array('type' => ASN1::TYPE_ENUMERATED, 1199 'mapping' => array( 1200 'unspecified', 1201 'keyCompromise', 1202 'cACompromise', 1203 'affiliationChanged', 1204 'superseded', 1205 'cessationOfOperation', 1206 'certificateHold', 1207 // Value 7 is not used. 1208 8 => 'removeFromCRL', 1209 'privilegeWithdrawn', 1210 'aACompromise' 1211 ) 1212 ); 1213 1214 $this->IssuingDistributionPoint = array('type' => ASN1::TYPE_SEQUENCE, 1215 'children' => array( 1216 'distributionPoint' => array( 1217 'constant' => 0, 1218 'optional' => true, 1219 'explicit' => true 1220 ) + $DistributionPointName, 1221 'onlyContainsUserCerts' => array( 1222 'type' => ASN1::TYPE_BOOLEAN, 1223 'constant' => 1, 1224 'optional' => true, 1225 'default' => false, 1226 'implicit' => true 1227 ), 1228 'onlyContainsCACerts' => array( 1229 'type' => ASN1::TYPE_BOOLEAN, 1230 'constant' => 2, 1231 'optional' => true, 1232 'default' => false, 1233 'implicit' => true 1234 ), 1235 'onlySomeReasons' => array( 1236 'constant' => 3, 1237 'optional' => true, 1238 'implicit' => true 1239 ) + $ReasonFlags, 1240 'indirectCRL' => array( 1241 'type' => ASN1::TYPE_BOOLEAN, 1242 'constant' => 4, 1243 'optional' => true, 1244 'default' => false, 1245 'implicit' => true 1246 ), 1247 'onlyContainsAttributeCerts' => array( 1248 'type' => ASN1::TYPE_BOOLEAN, 1249 'constant' => 5, 1250 'optional' => true, 1251 'default' => false, 1252 'implicit' => true 1253 ) 1254 ) 1255 ); 1256 1257 $this->InvalidityDate = array('type' => ASN1::TYPE_GENERALIZED_TIME); 1258 1259 $this->CertificateIssuer = $GeneralNames; 1260 1261 $this->HoldInstructionCode = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER); 1262 1263 $PublicKeyAndChallenge = array( 1264 'type' => ASN1::TYPE_SEQUENCE, 1265 'children' => array( 1266 'spki' => $SubjectPublicKeyInfo, 1267 'challenge' => array('type' => ASN1::TYPE_IA5_STRING) 1268 ) 1269 ); 1270 1271 $this->SignedPublicKeyAndChallenge = array( 1272 'type' => ASN1::TYPE_SEQUENCE, 1273 'children' => array( 1274 'publicKeyAndChallenge' => $PublicKeyAndChallenge, 1275 'signatureAlgorithm' => $AlgorithmIdentifier, 1276 'signature' => array('type' => ASN1::TYPE_BIT_STRING) 1277 ) 1278 ); 1279 1280 $this->PostalAddress = array( 1281 'type' => ASN1::TYPE_SEQUENCE, 1282 'optional' => true, 1283 'min' => 1, 1284 'max' => -1, 1285 'children' => $this->DirectoryString 1286 ); 1287 1288 // OIDs from RFC5280 and those RFCs mentioned in RFC5280#section-4.1.1.2 1289 $this->oids = array( 1290 '1.3.6.1.5.5.7' => 'id-pkix', 1291 '1.3.6.1.5.5.7.1' => 'id-pe', 1292 '1.3.6.1.5.5.7.2' => 'id-qt', 1293 '1.3.6.1.5.5.7.3' => 'id-kp', 1294 '1.3.6.1.5.5.7.48' => 'id-ad', 1295 '1.3.6.1.5.5.7.2.1' => 'id-qt-cps', 1296 '1.3.6.1.5.5.7.2.2' => 'id-qt-unotice', 1297 '1.3.6.1.5.5.7.48.1' =>'id-ad-ocsp', 1298 '1.3.6.1.5.5.7.48.2' => 'id-ad-caIssuers', 1299 '1.3.6.1.5.5.7.48.3' => 'id-ad-timeStamping', 1300 '1.3.6.1.5.5.7.48.5' => 'id-ad-caRepository', 1301 '2.5.4' => 'id-at', 1302 '2.5.4.41' => 'id-at-name', 1303 '2.5.4.4' => 'id-at-surname', 1304 '2.5.4.42' => 'id-at-givenName', 1305 '2.5.4.43' => 'id-at-initials', 1306 '2.5.4.44' => 'id-at-generationQualifier', 1307 '2.5.4.3' => 'id-at-commonName', 1308 '2.5.4.7' => 'id-at-localityName', 1309 '2.5.4.8' => 'id-at-stateOrProvinceName', 1310 '2.5.4.10' => 'id-at-organizationName', 1311 '2.5.4.11' => 'id-at-organizationalUnitName', 1312 '2.5.4.12' => 'id-at-title', 1313 '2.5.4.13' => 'id-at-description', 1314 '2.5.4.46' => 'id-at-dnQualifier', 1315 '2.5.4.6' => 'id-at-countryName', 1316 '2.5.4.5' => 'id-at-serialNumber', 1317 '2.5.4.65' => 'id-at-pseudonym', 1318 '2.5.4.17' => 'id-at-postalCode', 1319 '2.5.4.9' => 'id-at-streetAddress', 1320 '2.5.4.45' => 'id-at-uniqueIdentifier', 1321 '2.5.4.72' => 'id-at-role', 1322 '2.5.4.16' => 'id-at-postalAddress', 1323 1324 '0.9.2342.19200300.100.1.25' => 'id-domainComponent', 1325 '1.2.840.113549.1.9' => 'pkcs-9', 1326 '1.2.840.113549.1.9.1' => 'pkcs-9-at-emailAddress', 1327 '2.5.29' => 'id-ce', 1328 '2.5.29.35' => 'id-ce-authorityKeyIdentifier', 1329 '2.5.29.14' => 'id-ce-subjectKeyIdentifier', 1330 '2.5.29.15' => 'id-ce-keyUsage', 1331 '2.5.29.16' => 'id-ce-privateKeyUsagePeriod', 1332 '2.5.29.32' => 'id-ce-certificatePolicies', 1333 '2.5.29.32.0' => 'anyPolicy', 1334 1335 '2.5.29.33' => 'id-ce-policyMappings', 1336 '2.5.29.17' => 'id-ce-subjectAltName', 1337 '2.5.29.18' => 'id-ce-issuerAltName', 1338 '2.5.29.9' => 'id-ce-subjectDirectoryAttributes', 1339 '2.5.29.19' => 'id-ce-basicConstraints', 1340 '2.5.29.30' => 'id-ce-nameConstraints', 1341 '2.5.29.36' => 'id-ce-policyConstraints', 1342 '2.5.29.31' => 'id-ce-cRLDistributionPoints', 1343 '2.5.29.37' => 'id-ce-extKeyUsage', 1344 '2.5.29.37.0' => 'anyExtendedKeyUsage', 1345 '1.3.6.1.5.5.7.3.1' => 'id-kp-serverAuth', 1346 '1.3.6.1.5.5.7.3.2' => 'id-kp-clientAuth', 1347 '1.3.6.1.5.5.7.3.3' => 'id-kp-codeSigning', 1348 '1.3.6.1.5.5.7.3.4' => 'id-kp-emailProtection', 1349 '1.3.6.1.5.5.7.3.8' => 'id-kp-timeStamping', 1350 '1.3.6.1.5.5.7.3.9' => 'id-kp-OCSPSigning', 1351 '2.5.29.54' => 'id-ce-inhibitAnyPolicy', 1352 '2.5.29.46' => 'id-ce-freshestCRL', 1353 '1.3.6.1.5.5.7.1.1' => 'id-pe-authorityInfoAccess', 1354 '1.3.6.1.5.5.7.1.11' => 'id-pe-subjectInfoAccess', 1355 '2.5.29.20' => 'id-ce-cRLNumber', 1356 '2.5.29.28' => 'id-ce-issuingDistributionPoint', 1357 '2.5.29.27' => 'id-ce-deltaCRLIndicator', 1358 '2.5.29.21' => 'id-ce-cRLReasons', 1359 '2.5.29.29' => 'id-ce-certificateIssuer', 1360 '2.5.29.23' => 'id-ce-holdInstructionCode', 1361 '1.2.840.10040.2' => 'holdInstruction', 1362 '1.2.840.10040.2.1' => 'id-holdinstruction-none', 1363 '1.2.840.10040.2.2' => 'id-holdinstruction-callissuer', 1364 '1.2.840.10040.2.3' => 'id-holdinstruction-reject', 1365 '2.5.29.24' => 'id-ce-invalidityDate', 1366 1367 '1.2.840.113549.2.2' => 'md2', 1368 '1.2.840.113549.2.5' => 'md5', 1369 '1.3.14.3.2.26' => 'id-sha1', 1370 '1.2.840.10040.4.1' => 'id-dsa', 1371 '1.2.840.10040.4.3' => 'id-dsa-with-sha1', 1372 '1.2.840.113549.1.1' => 'pkcs-1', 1373 '1.2.840.113549.1.1.1' => 'rsaEncryption', 1374 '1.2.840.113549.1.1.2' => 'md2WithRSAEncryption', 1375 '1.2.840.113549.1.1.4' => 'md5WithRSAEncryption', 1376 '1.2.840.113549.1.1.5' => 'sha1WithRSAEncryption', 1377 '1.2.840.10046.2.1' => 'dhpublicnumber', 1378 '2.16.840.1.101.2.1.1.22' => 'id-keyExchangeAlgorithm', 1379 '1.2.840.10045' => 'ansi-X9-62', 1380 '1.2.840.10045.4' => 'id-ecSigType', 1381 '1.2.840.10045.4.1' => 'ecdsa-with-SHA1', 1382 '1.2.840.10045.1' => 'id-fieldType', 1383 '1.2.840.10045.1.1' => 'prime-field', 1384 '1.2.840.10045.1.2' => 'characteristic-two-field', 1385 '1.2.840.10045.1.2.3' => 'id-characteristic-two-basis', 1386 '1.2.840.10045.1.2.3.1' => 'gnBasis', 1387 '1.2.840.10045.1.2.3.2' => 'tpBasis', 1388 '1.2.840.10045.1.2.3.3' => 'ppBasis', 1389 '1.2.840.10045.2' => 'id-publicKeyType', 1390 '1.2.840.10045.2.1' => 'id-ecPublicKey', 1391 '1.2.840.10045.3' => 'ellipticCurve', 1392 '1.2.840.10045.3.0' => 'c-TwoCurve', 1393 '1.2.840.10045.3.0.1' => 'c2pnb163v1', 1394 '1.2.840.10045.3.0.2' => 'c2pnb163v2', 1395 '1.2.840.10045.3.0.3' => 'c2pnb163v3', 1396 '1.2.840.10045.3.0.4' => 'c2pnb176w1', 1397 '1.2.840.10045.3.0.5' => 'c2pnb191v1', 1398 '1.2.840.10045.3.0.6' => 'c2pnb191v2', 1399 '1.2.840.10045.3.0.7' => 'c2pnb191v3', 1400 '1.2.840.10045.3.0.8' => 'c2pnb191v4', 1401 '1.2.840.10045.3.0.9' => 'c2pnb191v5', 1402 '1.2.840.10045.3.0.10' => 'c2pnb208w1', 1403 '1.2.840.10045.3.0.11' => 'c2pnb239v1', 1404 '1.2.840.10045.3.0.12' => 'c2pnb239v2', 1405 '1.2.840.10045.3.0.13' => 'c2pnb239v3', 1406 '1.2.840.10045.3.0.14' => 'c2pnb239v4', 1407 '1.2.840.10045.3.0.15' => 'c2pnb239v5', 1408 '1.2.840.10045.3.0.16' => 'c2pnb272w1', 1409 '1.2.840.10045.3.0.17' => 'c2pnb304w1', 1410 '1.2.840.10045.3.0.18' => 'c2pnb359v1', 1411 '1.2.840.10045.3.0.19' => 'c2pnb368w1', 1412 '1.2.840.10045.3.0.20' => 'c2pnb431r1', 1413 '1.2.840.10045.3.1' => 'primeCurve', 1414 '1.2.840.10045.3.1.1' => 'prime192v1', 1415 '1.2.840.10045.3.1.2' => 'prime192v2', 1416 '1.2.840.10045.3.1.3' => 'prime192v3', 1417 '1.2.840.10045.3.1.4' => 'prime239v1', 1418 '1.2.840.10045.3.1.5' => 'prime239v2', 1419 '1.2.840.10045.3.1.6' => 'prime239v3', 1420 '1.2.840.10045.3.1.7' => 'prime256v1', 1421 '1.2.840.113549.1.1.7' => 'id-RSAES-OAEP', 1422 '1.2.840.113549.1.1.9' => 'id-pSpecified', 1423 '1.2.840.113549.1.1.10' => 'id-RSASSA-PSS', 1424 '1.2.840.113549.1.1.8' => 'id-mgf1', 1425 '1.2.840.113549.1.1.14' => 'sha224WithRSAEncryption', 1426 '1.2.840.113549.1.1.11' => 'sha256WithRSAEncryption', 1427 '1.2.840.113549.1.1.12' => 'sha384WithRSAEncryption', 1428 '1.2.840.113549.1.1.13' => 'sha512WithRSAEncryption', 1429 '2.16.840.1.101.3.4.2.4' => 'id-sha224', 1430 '2.16.840.1.101.3.4.2.1' => 'id-sha256', 1431 '2.16.840.1.101.3.4.2.2' => 'id-sha384', 1432 '2.16.840.1.101.3.4.2.3' => 'id-sha512', 1433 '1.2.643.2.2.4' => 'id-GostR3411-94-with-GostR3410-94', 1434 '1.2.643.2.2.3' => 'id-GostR3411-94-with-GostR3410-2001', 1435 '1.2.643.2.2.20' => 'id-GostR3410-2001', 1436 '1.2.643.2.2.19' => 'id-GostR3410-94', 1437 // Netscape Object Identifiers from "Netscape Certificate Extensions" 1438 '2.16.840.1.113730' => 'netscape', 1439 '2.16.840.1.113730.1' => 'netscape-cert-extension', 1440 '2.16.840.1.113730.1.1' => 'netscape-cert-type', 1441 '2.16.840.1.113730.1.13' => 'netscape-comment', 1442 '2.16.840.1.113730.1.8' => 'netscape-ca-policy-url', 1443 // the following are X.509 extensions not supported by phpseclib 1444 '1.3.6.1.5.5.7.1.12' => 'id-pe-logotype', 1445 '1.2.840.113533.7.65.0' => 'entrustVersInfo', 1446 '2.16.840.1.113733.1.6.9' => 'verisignPrivate', 1447 // for Certificate Signing Requests 1448 // see http://tools.ietf.org/html/rfc2985 1449 '1.2.840.113549.1.9.2' => 'pkcs-9-at-unstructuredName', // PKCS #9 unstructured name 1450 '1.2.840.113549.1.9.7' => 'pkcs-9-at-challengePassword', // Challenge password for certificate revocations 1451 '1.2.840.113549.1.9.14' => 'pkcs-9-at-extensionRequest' // Certificate extension request 1452 ); 1453 } 1454 1455 /** 1456 * Load X.509 certificate 1457 * 1458 * Returns an associative array describing the X.509 cert or a false if the cert failed to load 1459 * 1460 * @param string $cert 1461 * @param int $mode 1462 * @access public 1463 * @return mixed 1464 */ 1465 function loadX509($cert, $mode = self::FORMAT_AUTO_DETECT) 1466 { 1467 if (is_array($cert) && isset($cert['tbsCertificate'])) { 1468 unset($this->currentCert); 1469 unset($this->currentKeyIdentifier); 1470 $this->dn = $cert['tbsCertificate']['subject']; 1471 if (!isset($this->dn)) { 1472 return false; 1473 } 1474 $this->currentCert = $cert; 1475 1476 $currentKeyIdentifier = $this->getExtension('id-ce-subjectKeyIdentifier'); 1477 $this->currentKeyIdentifier = is_string($currentKeyIdentifier) ? $currentKeyIdentifier : null; 1478 1479 unset($this->signatureSubject); 1480 1481 return $cert; 1482 } 1483 1484 $asn1 = new ASN1(); 1485 1486 if ($mode != self::FORMAT_DER) { 1487 $newcert = $this->_extractBER($cert); 1488 if ($mode == self::FORMAT_PEM && $cert == $newcert) { 1489 return false; 1490 } 1491 $cert = $newcert; 1492 } 1493 1494 if ($cert === false) { 1495 $this->currentCert = false; 1496 return false; 1497 } 1498 1499 $asn1->loadOIDs($this->oids); 1500 $decoded = $asn1->decodeBER($cert); 1501 1502 if (!empty($decoded)) { 1503 $x509 = $asn1->asn1map($decoded[0], $this->Certificate); 1504 } 1505 if (!isset($x509) || $x509 === false) { 1506 $this->currentCert = false; 1507 return false; 1508 } 1509 1510 $this->signatureSubject = substr($cert, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']); 1511 1512 if ($this->_isSubArrayValid($x509, 'tbsCertificate/extensions')) { 1513 $this->_mapInExtensions($x509, 'tbsCertificate/extensions', $asn1); 1514 } 1515 $this->_mapInDNs($x509, 'tbsCertificate/issuer/rdnSequence', $asn1); 1516 $this->_mapInDNs($x509, 'tbsCertificate/subject/rdnSequence', $asn1); 1517 1518 $key = &$x509['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']; 1519 $key = $this->_reformatKey($x509['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'], $key); 1520 1521 $this->currentCert = $x509; 1522 $this->dn = $x509['tbsCertificate']['subject']; 1523 1524 $currentKeyIdentifier = $this->getExtension('id-ce-subjectKeyIdentifier'); 1525 $this->currentKeyIdentifier = is_string($currentKeyIdentifier) ? $currentKeyIdentifier : null; 1526 1527 return $x509; 1528 } 1529 1530 /** 1531 * Save X.509 certificate 1532 * 1533 * @param array $cert 1534 * @param int $format optional 1535 * @access public 1536 * @return string 1537 */ 1538 function saveX509($cert, $format = self::FORMAT_PEM) 1539 { 1540 if (!is_array($cert) || !isset($cert['tbsCertificate'])) { 1541 return false; 1542 } 1543 1544 switch (true) { 1545 // "case !$a: case !$b: break; default: whatever();" is the same thing as "if ($a && $b) whatever()" 1546 case !($algorithm = $this->_subArray($cert, 'tbsCertificate/subjectPublicKeyInfo/algorithm/algorithm')): 1547 case is_object($cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']): 1548 break; 1549 default: 1550 switch ($algorithm) { 1551 case 'rsaEncryption': 1552 $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'] 1553 = base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']))); 1554 /* "[For RSA keys] the parameters field MUST have ASN.1 type NULL for this algorithm identifier." 1555 -- https://tools.ietf.org/html/rfc3279#section-2.3.1 1556 1557 given that and the fact that RSA keys appear ot be the only key type for which the parameters field can be blank, 1558 it seems like perhaps the ASN.1 description ought not say the parameters field is OPTIONAL, but whatever. 1559 */ 1560 $cert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['parameters'] = null; 1561 // https://tools.ietf.org/html/rfc3279#section-2.2.1 1562 $cert['signatureAlgorithm']['parameters'] = null; 1563 $cert['tbsCertificate']['signature']['parameters'] = null; 1564 } 1565 } 1566 1567 $asn1 = new ASN1(); 1568 $asn1->loadOIDs($this->oids); 1569 1570 $filters = array(); 1571 $type_utf8_string = array('type' => ASN1::TYPE_UTF8_STRING); 1572 $filters['tbsCertificate']['signature']['parameters'] = $type_utf8_string; 1573 $filters['tbsCertificate']['signature']['issuer']['rdnSequence']['value'] = $type_utf8_string; 1574 $filters['tbsCertificate']['issuer']['rdnSequence']['value'] = $type_utf8_string; 1575 $filters['tbsCertificate']['subject']['rdnSequence']['value'] = $type_utf8_string; 1576 $filters['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['parameters'] = $type_utf8_string; 1577 $filters['signatureAlgorithm']['parameters'] = $type_utf8_string; 1578 $filters['authorityCertIssuer']['directoryName']['rdnSequence']['value'] = $type_utf8_string; 1579 //$filters['policyQualifiers']['qualifier'] = $type_utf8_string; 1580 $filters['distributionPoint']['fullName']['directoryName']['rdnSequence']['value'] = $type_utf8_string; 1581 $filters['directoryName']['rdnSequence']['value'] = $type_utf8_string; 1582 1583 /* in the case of policyQualifiers/qualifier, the type has to be \phpseclib\File\ASN1::TYPE_IA5_STRING. 1584 \phpseclib\File\ASN1::TYPE_PRINTABLE_STRING will cause OpenSSL's X.509 parser to spit out random 1585 characters. 1586 */ 1587 $filters['policyQualifiers']['qualifier'] 1588 = array('type' => ASN1::TYPE_IA5_STRING); 1589 1590 $asn1->loadFilters($filters); 1591 1592 $this->_mapOutExtensions($cert, 'tbsCertificate/extensions', $asn1); 1593 $this->_mapOutDNs($cert, 'tbsCertificate/issuer/rdnSequence', $asn1); 1594 $this->_mapOutDNs($cert, 'tbsCertificate/subject/rdnSequence', $asn1); 1595 1596 $cert = $asn1->encodeDER($cert, $this->Certificate); 1597 1598 switch ($format) { 1599 case self::FORMAT_DER: 1600 return $cert; 1601 // case self::FORMAT_PEM: 1602 default: 1603 return "-----BEGIN CERTIFICATE-----\r\n" . chunk_split(base64_encode($cert), 64) . '-----END CERTIFICATE-----'; 1604 } 1605 } 1606 1607 /** 1608 * Map extension values from octet string to extension-specific internal 1609 * format. 1610 * 1611 * @param array $root (by reference) 1612 * @param string $path 1613 * @param object $asn1 1614 * @access private 1615 */ 1616 function _mapInExtensions(&$root, $path, $asn1) 1617 { 1618 $extensions = &$this->_subArrayUnchecked($root, $path); 1619 1620 if ($extensions) { 1621 for ($i = 0; $i < count($extensions); $i++) { 1622 $id = $extensions[$i]['extnId']; 1623 $value = &$extensions[$i]['extnValue']; 1624 $value = base64_decode($value); 1625 $decoded = $asn1->decodeBER($value); 1626 /* [extnValue] contains the DER encoding of an ASN.1 value 1627 corresponding to the extension type identified by extnID */ 1628 $map = $this->_getMapping($id); 1629 if (!is_bool($map)) { 1630 $decoder = $id == 'id-ce-nameConstraints' ? 1631 array($this, '_decodeNameConstraintIP') : 1632 array($this, '_decodeIP'); 1633 $mapped = $asn1->asn1map($decoded[0], $map, array('iPAddress' => $decoder)); 1634 $value = $mapped === false ? $decoded[0] : $mapped; 1635 1636 if ($id == 'id-ce-certificatePolicies') { 1637 for ($j = 0; $j < count($value); $j++) { 1638 if (!isset($value[$j]['policyQualifiers'])) { 1639 continue; 1640 } 1641 for ($k = 0; $k < count($value[$j]['policyQualifiers']); $k++) { 1642 $subid = $value[$j]['policyQualifiers'][$k]['policyQualifierId']; 1643 $map = $this->_getMapping($subid); 1644 $subvalue = &$value[$j]['policyQualifiers'][$k]['qualifier']; 1645 if ($map !== false) { 1646 $decoded = $asn1->decodeBER($subvalue); 1647 $mapped = $asn1->asn1map($decoded[0], $map); 1648 $subvalue = $mapped === false ? $decoded[0] : $mapped; 1649 } 1650 } 1651 } 1652 } 1653 } else { 1654 $value = base64_encode($value); 1655 } 1656 } 1657 } 1658 } 1659 1660 /** 1661 * Map extension values from extension-specific internal format to 1662 * octet string. 1663 * 1664 * @param array $root (by reference) 1665 * @param string $path 1666 * @param object $asn1 1667 * @access private 1668 */ 1669 function _mapOutExtensions(&$root, $path, $asn1) 1670 { 1671 $extensions = &$this->_subArray($root, $path); 1672 1673 if (is_array($extensions)) { 1674 $size = count($extensions); 1675 for ($i = 0; $i < $size; $i++) { 1676 if ($extensions[$i] instanceof Element) { 1677 continue; 1678 } 1679 1680 $id = $extensions[$i]['extnId']; 1681 $value = &$extensions[$i]['extnValue']; 1682 1683 switch ($id) { 1684 case 'id-ce-certificatePolicies': 1685 for ($j = 0; $j < count($value); $j++) { 1686 if (!isset($value[$j]['policyQualifiers'])) { 1687 continue; 1688 } 1689 for ($k = 0; $k < count($value[$j]['policyQualifiers']); $k++) { 1690 $subid = $value[$j]['policyQualifiers'][$k]['policyQualifierId']; 1691 $map = $this->_getMapping($subid); 1692 $subvalue = &$value[$j]['policyQualifiers'][$k]['qualifier']; 1693 if ($map !== false) { 1694 // by default \phpseclib\File\ASN1 will try to render qualifier as a \phpseclib\File\ASN1::TYPE_IA5_STRING since it's 1695 // actual type is \phpseclib\File\ASN1::TYPE_ANY 1696 $subvalue = new Element($asn1->encodeDER($subvalue, $map)); 1697 } 1698 } 1699 } 1700 break; 1701 case 'id-ce-authorityKeyIdentifier': // use 00 as the serial number instead of an empty string 1702 if (isset($value['authorityCertSerialNumber'])) { 1703 if ($value['authorityCertSerialNumber']->toBytes() == '') { 1704 $temp = chr((ASN1::CLASS_CONTEXT_SPECIFIC << 6) | 2) . "\1\0"; 1705 $value['authorityCertSerialNumber'] = new Element($temp); 1706 } 1707 } 1708 } 1709 1710 /* [extnValue] contains the DER encoding of an ASN.1 value 1711 corresponding to the extension type identified by extnID */ 1712 $map = $this->_getMapping($id); 1713 if (is_bool($map)) { 1714 if (!$map) { 1715 user_error($id . ' is not a currently supported extension'); 1716 unset($extensions[$i]); 1717 } 1718 } else { 1719 $temp = $asn1->encodeDER($value, $map, array('iPAddress' => array($this, '_encodeIP'))); 1720 $value = base64_encode($temp); 1721 } 1722 } 1723 } 1724 } 1725 1726 /** 1727 * Map attribute values from ANY type to attribute-specific internal 1728 * format. 1729 * 1730 * @param array $root (by reference) 1731 * @param string $path 1732 * @param object $asn1 1733 * @access private 1734 */ 1735 function _mapInAttributes(&$root, $path, $asn1) 1736 { 1737 $attributes = &$this->_subArray($root, $path); 1738 1739 if (is_array($attributes)) { 1740 for ($i = 0; $i < count($attributes); $i++) { 1741 $id = $attributes[$i]['type']; 1742 /* $value contains the DER encoding of an ASN.1 value 1743 corresponding to the attribute type identified by type */ 1744 $map = $this->_getMapping($id); 1745 if (is_array($attributes[$i]['value'])) { 1746 $values = &$attributes[$i]['value']; 1747 for ($j = 0; $j < count($values); $j++) { 1748 $value = $asn1->encodeDER($values[$j], $this->AttributeValue); 1749 $decoded = $asn1->decodeBER($value); 1750 if (!is_bool($map)) { 1751 $mapped = $asn1->asn1map($decoded[0], $map); 1752 if ($mapped !== false) { 1753 $values[$j] = $mapped; 1754 } 1755 if ($id == 'pkcs-9-at-extensionRequest' && $this->_isSubArrayValid($values, $j)) { 1756 $this->_mapInExtensions($values, $j, $asn1); 1757 } 1758 } elseif ($map) { 1759 $values[$j] = base64_encode($value); 1760 } 1761 } 1762 } 1763 } 1764 } 1765 } 1766 1767 /** 1768 * Map attribute values from attribute-specific internal format to 1769 * ANY type. 1770 * 1771 * @param array $root (by reference) 1772 * @param string $path 1773 * @param object $asn1 1774 * @access private 1775 */ 1776 function _mapOutAttributes(&$root, $path, $asn1) 1777 { 1778 $attributes = &$this->_subArray($root, $path); 1779 1780 if (is_array($attributes)) { 1781 $size = count($attributes); 1782 for ($i = 0; $i < $size; $i++) { 1783 /* [value] contains the DER encoding of an ASN.1 value 1784 corresponding to the attribute type identified by type */ 1785 $id = $attributes[$i]['type']; 1786 $map = $this->_getMapping($id); 1787 if ($map === false) { 1788 user_error($id . ' is not a currently supported attribute', E_USER_NOTICE); 1789 unset($attributes[$i]); 1790 } elseif (is_array($attributes[$i]['value'])) { 1791 $values = &$attributes[$i]['value']; 1792 for ($j = 0; $j < count($values); $j++) { 1793 switch ($id) { 1794 case 'pkcs-9-at-extensionRequest': 1795 $this->_mapOutExtensions($values, $j, $asn1); 1796 break; 1797 } 1798 1799 if (!is_bool($map)) { 1800 $temp = $asn1->encodeDER($values[$j], $map); 1801 $decoded = $asn1->decodeBER($temp); 1802 $values[$j] = $asn1->asn1map($decoded[0], $this->AttributeValue); 1803 } 1804 } 1805 } 1806 } 1807 } 1808 } 1809 1810 /** 1811 * Map DN values from ANY type to DN-specific internal 1812 * format. 1813 * 1814 * @param array $root (by reference) 1815 * @param string $path 1816 * @param object $asn1 1817 * @access private 1818 */ 1819 function _mapInDNs(&$root, $path, $asn1) 1820 { 1821 $dns = &$this->_subArray($root, $path); 1822 1823 if (is_array($dns)) { 1824 for ($i = 0; $i < count($dns); $i++) { 1825 for ($j = 0; $j < count($dns[$i]); $j++) { 1826 $type = $dns[$i][$j]['type']; 1827 $value = &$dns[$i][$j]['value']; 1828 if (is_object($value) && $value instanceof Element) { 1829 $map = $this->_getMapping($type); 1830 if (!is_bool($map)) { 1831 $decoded = $asn1->decodeBER($value); 1832 $value = $asn1->asn1map($decoded[0], $map); 1833 } 1834 } 1835 } 1836 } 1837 } 1838 } 1839 1840 /** 1841 * Map DN values from DN-specific internal format to 1842 * ANY type. 1843 * 1844 * @param array $root (by reference) 1845 * @param string $path 1846 * @param object $asn1 1847 * @access private 1848 */ 1849 function _mapOutDNs(&$root, $path, $asn1) 1850 { 1851 $dns = &$this->_subArray($root, $path); 1852 1853 if (is_array($dns)) { 1854 $size = count($dns); 1855 for ($i = 0; $i < $size; $i++) { 1856 for ($j = 0; $j < count($dns[$i]); $j++) { 1857 $type = $dns[$i][$j]['type']; 1858 $value = &$dns[$i][$j]['value']; 1859 if (is_object($value) && $value instanceof Element) { 1860 continue; 1861 } 1862 1863 $map = $this->_getMapping($type); 1864 if (!is_bool($map)) { 1865 $value = new Element($asn1->encodeDER($value, $map)); 1866 } 1867 } 1868 } 1869 } 1870 } 1871 1872 /** 1873 * Associate an extension ID to an extension mapping 1874 * 1875 * @param string $extnId 1876 * @access private 1877 * @return mixed 1878 */ 1879 function _getMapping($extnId) 1880 { 1881 if (!is_string($extnId)) { // eg. if it's a \phpseclib\File\ASN1\Element object 1882 return true; 1883 } 1884 1885 switch ($extnId) { 1886 case 'id-ce-keyUsage': 1887 return $this->KeyUsage; 1888 case 'id-ce-basicConstraints': 1889 return $this->BasicConstraints; 1890 case 'id-ce-subjectKeyIdentifier': 1891 return $this->KeyIdentifier; 1892 case 'id-ce-cRLDistributionPoints': 1893 return $this->CRLDistributionPoints; 1894 case 'id-ce-authorityKeyIdentifier': 1895 return $this->AuthorityKeyIdentifier; 1896 case 'id-ce-certificatePolicies': 1897 return $this->CertificatePolicies; 1898 case 'id-ce-extKeyUsage': 1899 return $this->ExtKeyUsageSyntax; 1900 case 'id-pe-authorityInfoAccess': 1901 return $this->AuthorityInfoAccessSyntax; 1902 case 'id-pe-subjectInfoAccess': 1903 return $this->SubjectInfoAccessSyntax; 1904 case 'id-ce-subjectAltName': 1905 return $this->SubjectAltName; 1906 case 'id-ce-subjectDirectoryAttributes': 1907 return $this->SubjectDirectoryAttributes; 1908 case 'id-ce-privateKeyUsagePeriod': 1909 return $this->PrivateKeyUsagePeriod; 1910 case 'id-ce-issuerAltName': 1911 return $this->IssuerAltName; 1912 case 'id-ce-policyMappings': 1913 return $this->PolicyMappings; 1914 case 'id-ce-nameConstraints': 1915 return $this->NameConstraints; 1916 1917 case 'netscape-cert-type': 1918 return $this->netscape_cert_type; 1919 case 'netscape-comment': 1920 return $this->netscape_comment; 1921 case 'netscape-ca-policy-url': 1922 return $this->netscape_ca_policy_url; 1923 1924 // since id-qt-cps isn't a constructed type it will have already been decoded as a string by the time it gets 1925 // back around to asn1map() and we don't want it decoded again. 1926 //case 'id-qt-cps': 1927 // return $this->CPSuri; 1928 case 'id-qt-unotice': 1929 return $this->UserNotice; 1930 1931 // the following OIDs are unsupported but we don't want them to give notices when calling saveX509(). 1932 case 'id-pe-logotype': // http://www.ietf.org/rfc/rfc3709.txt 1933 case 'entrustVersInfo': 1934 // http://support.microsoft.com/kb/287547 1935 case '1.3.6.1.4.1.311.20.2': // szOID_ENROLL_CERTTYPE_EXTENSION 1936 case '1.3.6.1.4.1.311.21.1': // szOID_CERTSRV_CA_VERSION 1937 // "SET Secure Electronic Transaction Specification" 1938 // http://www.maithean.com/docs/set_bk3.pdf 1939 case '2.23.42.7.0': // id-set-hashedRootKey 1940 // "Certificate Transparency" 1941 // https://tools.ietf.org/html/rfc6962 1942 case '1.3.6.1.4.1.11129.2.4.2': 1943 // "Qualified Certificate statements" 1944 // https://tools.ietf.org/html/rfc3739#section-3.2.6 1945 case '1.3.6.1.5.5.7.1.3': 1946 return true; 1947 1948 // CSR attributes 1949 case 'pkcs-9-at-unstructuredName': 1950 return $this->PKCS9String; 1951 case 'pkcs-9-at-challengePassword': 1952 return $this->DirectoryString; 1953 case 'pkcs-9-at-extensionRequest': 1954 return $this->Extensions; 1955 1956 // CRL extensions. 1957 case 'id-ce-cRLNumber': 1958 return $this->CRLNumber; 1959 case 'id-ce-deltaCRLIndicator': 1960 return $this->CRLNumber; 1961 case 'id-ce-issuingDistributionPoint': 1962 return $this->IssuingDistributionPoint; 1963 case 'id-ce-freshestCRL': 1964 return $this->CRLDistributionPoints; 1965 case 'id-ce-cRLReasons': 1966 return $this->CRLReason; 1967 case 'id-ce-invalidityDate': 1968 return $this->InvalidityDate; 1969 case 'id-ce-certificateIssuer': 1970 return $this->CertificateIssuer; 1971 case 'id-ce-holdInstructionCode': 1972 return $this->HoldInstructionCode; 1973 case 'id-at-postalAddress': 1974 return $this->PostalAddress; 1975 } 1976 1977 return false; 1978 } 1979 1980 /** 1981 * Load an X.509 certificate as a certificate authority 1982 * 1983 * @param string $cert 1984 * @access public 1985 * @return bool 1986 */ 1987 function loadCA($cert) 1988 { 1989 $olddn = $this->dn; 1990 $oldcert = $this->currentCert; 1991 $oldsigsubj = $this->signatureSubject; 1992 $oldkeyid = $this->currentKeyIdentifier; 1993 1994 $cert = $this->loadX509($cert); 1995 if (!$cert) { 1996 $this->dn = $olddn; 1997 $this->currentCert = $oldcert; 1998 $this->signatureSubject = $oldsigsubj; 1999 $this->currentKeyIdentifier = $oldkeyid; 2000 2001 return false; 2002 } 2003 2004 /* From RFC5280 "PKIX Certificate and CRL Profile": 2005 2006 If the keyUsage extension is present, then the subject public key 2007 MUST NOT be used to verify signatures on certificates or CRLs unless 2008 the corresponding keyCertSign or cRLSign bit is set. */ 2009 //$keyUsage = $this->getExtension('id-ce-keyUsage'); 2010 //if ($keyUsage && !in_array('keyCertSign', $keyUsage)) { 2011 // return false; 2012 //} 2013 2014 /* From RFC5280 "PKIX Certificate and CRL Profile": 2015 2016 The cA boolean indicates whether the certified public key may be used 2017 to verify certificate signatures. If the cA boolean is not asserted, 2018 then the keyCertSign bit in the key usage extension MUST NOT be 2019 asserted. If the basic constraints extension is not present in a 2020 version 3 certificate, or the extension is present but the cA boolean 2021 is not asserted, then the certified public key MUST NOT be used to 2022 verify certificate signatures. */ 2023 //$basicConstraints = $this->getExtension('id-ce-basicConstraints'); 2024 //if (!$basicConstraints || !$basicConstraints['cA']) { 2025 // return false; 2026 //} 2027 2028 $this->CAs[] = $cert; 2029 2030 $this->dn = $olddn; 2031 $this->currentCert = $oldcert; 2032 $this->signatureSubject = $oldsigsubj; 2033 2034 return true; 2035 } 2036 2037 /** 2038 * Validate an X.509 certificate against a URL 2039 * 2040 * From RFC2818 "HTTP over TLS": 2041 * 2042 * Matching is performed using the matching rules specified by 2043 * [RFC2459]. If more than one identity of a given type is present in 2044 * the certificate (e.g., more than one dNSName name, a match in any one 2045 * of the set is considered acceptable.) Names may contain the wildcard 2046 * character * which is considered to match any single domain name 2047 * component or component fragment. E.g., *.a.com matches foo.a.com but 2048 * not bar.foo.a.com. f*.com matches foo.com but not bar.com. 2049 * 2050 * @param string $url 2051 * @access public 2052 * @return bool 2053 */ 2054 function validateURL($url) 2055 { 2056 if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) { 2057 return false; 2058 } 2059 2060 $components = parse_url($url); 2061 if (!isset($components['host'])) { 2062 return false; 2063 } 2064 2065 if ($names = $this->getExtension('id-ce-subjectAltName')) { 2066 foreach ($names as $name) { 2067 foreach ($name as $key => $value) { 2068 $value = str_replace(array('.', '*'), array('\.', '[^.]*'), $value); 2069 switch ($key) { 2070 case 'dNSName': 2071 /* From RFC2818 "HTTP over TLS": 2072 2073 If a subjectAltName extension of type dNSName is present, that MUST 2074 be used as the identity. Otherwise, the (most specific) Common Name 2075 field in the Subject field of the certificate MUST be used. Although 2076 the use of the Common Name is existing practice, it is deprecated and 2077 Certification Authorities are encouraged to use the dNSName instead. */ 2078 if (preg_match('#^' . $value . '$#', $components['host'])) { 2079 return true; 2080 } 2081 break; 2082 case 'iPAddress': 2083 /* From RFC2818 "HTTP over TLS": 2084 2085 In some cases, the URI is specified as an IP address rather than a 2086 hostname. In this case, the iPAddress subjectAltName must be present 2087 in the certificate and must exactly match the IP in the URI. */ 2088 if (preg_match('#(?:\d{1-3}\.){4}#', $components['host'] . '.') && preg_match('#^' . $value . '$#', $components['host'])) { 2089 return true; 2090 } 2091 } 2092 } 2093 } 2094 return false; 2095 } 2096 2097 if ($value = $this->getDNProp('id-at-commonName')) { 2098 $value = str_replace(array('.', '*'), array('\.', '[^.]*'), $value[0]); 2099 return preg_match('#^' . $value . '$#', $components['host']); 2100 } 2101 2102 return false; 2103 } 2104 2105 /** 2106 * Validate a date 2107 * 2108 * If $date isn't defined it is assumed to be the current date. 2109 * 2110 * @param \DateTime|string $date optional 2111 * @access public 2112 */ 2113 function validateDate($date = null) 2114 { 2115 if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) { 2116 return false; 2117 } 2118 2119 if (!isset($date)) { 2120 $date = new DateTime(null, new DateTimeZone(@date_default_timezone_get())); 2121 } 2122 2123 $notBefore = $this->currentCert['tbsCertificate']['validity']['notBefore']; 2124 $notBefore = isset($notBefore['generalTime']) ? $notBefore['generalTime'] : $notBefore['utcTime']; 2125 2126 $notAfter = $this->currentCert['tbsCertificate']['validity']['notAfter']; 2127 $notAfter = isset($notAfter['generalTime']) ? $notAfter['generalTime'] : $notAfter['utcTime']; 2128 2129 if (is_string($date)) { 2130 $date = new DateTime($date, new DateTimeZone(@date_default_timezone_get())); 2131 } 2132 2133 $notBefore = new DateTime($notBefore, new DateTimeZone(@date_default_timezone_get())); 2134 $notAfter = new DateTime($notAfter, new DateTimeZone(@date_default_timezone_get())); 2135 2136 switch (true) { 2137 case $date < $notBefore: 2138 case $date > $notAfter: 2139 return false; 2140 } 2141 2142 return true; 2143 } 2144 2145 /** 2146 * Fetches a URL 2147 * 2148 * @param string $url 2149 * @access private 2150 * @return bool|string 2151 */ 2152 static function _fetchURL($url) 2153 { 2154 if (self::$disable_url_fetch) { 2155 return false; 2156 } 2157 2158 $parts = parse_url($url); 2159 $data = ''; 2160 switch ($parts['scheme']) { 2161 case 'http': 2162 $fsock = @fsockopen($parts['host'], isset($parts['port']) ? $parts['port'] : 80); 2163 if (!$fsock) { 2164 return false; 2165 } 2166 fputs($fsock, "GET $parts[path] HTTP/1.0\r\n"); 2167 fputs($fsock, "Host: $parts[host]\r\n\r\n"); 2168 $line = fgets($fsock, 1024); 2169 if (strlen($line) < 3) { 2170 return false; 2171 } 2172 preg_match('#HTTP/1.\d (\d{3})#', $line, $temp); 2173 if ($temp[1] != '200') { 2174 return false; 2175 } 2176 2177 // skip the rest of the headers in the http response 2178 while (!feof($fsock) && fgets($fsock, 1024) != "\r\n") { 2179 } 2180 2181 while (!feof($fsock)) { 2182 $temp = fread($fsock, 1024); 2183 if ($temp === false) { 2184 return false; 2185 } 2186 $data.= $temp; 2187 } 2188 2189 break; 2190 //case 'ftp': 2191 //case 'ldap': 2192 //default: 2193 } 2194 2195 return $data; 2196 } 2197 2198 /** 2199 * Validates an intermediate cert as identified via authority info access extension 2200 * 2201 * See https://tools.ietf.org/html/rfc4325 for more info 2202 * 2203 * @param bool $caonly 2204 * @param int $count 2205 * @access private 2206 * @return bool 2207 */ 2208 function _testForIntermediate($caonly, $count) 2209 { 2210 $opts = $this->getExtension('id-pe-authorityInfoAccess'); 2211 if (!is_array($opts)) { 2212 return false; 2213 } 2214 foreach ($opts as $opt) { 2215 if ($opt['accessMethod'] == 'id-ad-caIssuers') { 2216 // accessLocation is a GeneralName. GeneralName fields support stuff like email addresses, IP addresses, LDAP, 2217 // etc, but we're only supporting URI's. URI's and LDAP are the only thing https://tools.ietf.org/html/rfc4325 2218 // discusses 2219 if (isset($opt['accessLocation']['uniformResourceIdentifier'])) { 2220 $url = $opt['accessLocation']['uniformResourceIdentifier']; 2221 break; 2222 } 2223 } 2224 } 2225 2226 if (!isset($url)) { 2227 return false; 2228 } 2229 2230 $cert = static::_fetchURL($url); 2231 if (!is_string($cert)) { 2232 return false; 2233 } 2234 2235 $parent = new static(); 2236 $parent->CAs = $this->CAs; 2237 /* 2238 "Conforming applications that support HTTP or FTP for accessing 2239 certificates MUST be able to accept .cer files and SHOULD be able 2240 to accept .p7c files." -- https://tools.ietf.org/html/rfc4325 2241 2242 A .p7c file is 'a "certs-only" CMS message as specified in RFC 2797" 2243 2244 These are currently unsupported 2245 */ 2246 if (!is_array($parent->loadX509($cert))) { 2247 return false; 2248 } 2249 2250 if (!$parent->_validateSignatureCountable($caonly, ++$count)) { 2251 return false; 2252 } 2253 2254 $this->CAs[] = $parent->currentCert; 2255 //$this->loadCA($cert); 2256 2257 return true; 2258 } 2259 2260 /** 2261 * Validate a signature 2262 * 2263 * Works on X.509 certs, CSR's and CRL's. 2264 * Returns true if the signature is verified, false if it is not correct or null on error 2265 * 2266 * By default returns false for self-signed certs. Call validateSignature(false) to make this support 2267 * self-signed. 2268 * 2269 * The behavior of this function is inspired by {@link http://php.net/openssl-verify openssl_verify}. 2270 * 2271 * @param bool $caonly optional 2272 * @access public 2273 * @return mixed 2274 */ 2275 function validateSignature($caonly = true) 2276 { 2277 return $this->_validateSignatureCountable($caonly, 0); 2278 } 2279 2280 /** 2281 * Validate a signature 2282 * 2283 * Performs said validation whilst keeping track of how many times validation method is called 2284 * 2285 * @param bool $caonly 2286 * @param int $count 2287 * @access private 2288 * @return mixed 2289 */ 2290 function _validateSignatureCountable($caonly, $count) 2291 { 2292 if (!is_array($this->currentCert) || !isset($this->signatureSubject)) { 2293 return null; 2294 } 2295 2296 if ($count == self::$recur_limit) { 2297 return false; 2298 } 2299 2300 /* TODO: 2301 "emailAddress attribute values are not case-sensitive (e.g., "subscriber@example.com" is the same as "SUBSCRIBER@EXAMPLE.COM")." 2302 -- http://tools.ietf.org/html/rfc5280#section-4.1.2.6 2303 2304 implement pathLenConstraint in the id-ce-basicConstraints extension */ 2305 2306 switch (true) { 2307 case isset($this->currentCert['tbsCertificate']): 2308 // self-signed cert 2309 switch (true) { 2310 case !defined('FILE_X509_IGNORE_TYPE') && $this->currentCert['tbsCertificate']['issuer'] === $this->currentCert['tbsCertificate']['subject']: 2311 case defined('FILE_X509_IGNORE_TYPE') && $this->getIssuerDN(self::DN_STRING) === $this->getDN(self::DN_STRING): 2312 $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier'); 2313 $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier'); 2314 switch (true) { 2315 case !is_array($authorityKey): 2316 case !$subjectKeyID: 2317 case isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID: 2318 $signingCert = $this->currentCert; // working cert 2319 } 2320 } 2321 2322 if (!empty($this->CAs)) { 2323 for ($i = 0; $i < count($this->CAs); $i++) { 2324 // even if the cert is a self-signed one we still want to see if it's a CA; 2325 // if not, we'll conditionally return an error 2326 $ca = $this->CAs[$i]; 2327 switch (true) { 2328 case !defined('FILE_X509_IGNORE_TYPE') && $this->currentCert['tbsCertificate']['issuer'] === $ca['tbsCertificate']['subject']: 2329 case defined('FILE_X509_IGNORE_TYPE') && $this->getDN(self::DN_STRING, $this->currentCert['tbsCertificate']['issuer']) === $this->getDN(self::DN_STRING, $ca['tbsCertificate']['subject']): 2330 $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier'); 2331 $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca); 2332 switch (true) { 2333 case !is_array($authorityKey): 2334 case !$subjectKeyID: 2335 case isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID: 2336 if (is_array($authorityKey) && isset($authorityKey['authorityCertSerialNumber']) && !$authorityKey['authorityCertSerialNumber']->equals($ca['tbsCertificate']['serialNumber'])) { 2337 break 2; // serial mismatch - check other ca 2338 } 2339 $signingCert = $ca; // working cert 2340 break 3; 2341 } 2342 } 2343 } 2344 if (count($this->CAs) == $i && $caonly) { 2345 return $this->_testForIntermediate($caonly, $count) && $this->validateSignature($caonly); 2346 } 2347 } elseif (!isset($signingCert) || $caonly) { 2348 return $this->_testForIntermediate($caonly, $count) && $this->validateSignature($caonly); 2349 } 2350 return $this->_validateSignature( 2351 $signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'], 2352 $signingCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'], 2353 $this->currentCert['signatureAlgorithm']['algorithm'], 2354 substr(base64_decode($this->currentCert['signature']), 1), 2355 $this->signatureSubject 2356 ); 2357 case isset($this->currentCert['certificationRequestInfo']): 2358 return $this->_validateSignature( 2359 $this->currentCert['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm'], 2360 $this->currentCert['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'], 2361 $this->currentCert['signatureAlgorithm']['algorithm'], 2362 substr(base64_decode($this->currentCert['signature']), 1), 2363 $this->signatureSubject 2364 ); 2365 case isset($this->currentCert['publicKeyAndChallenge']): 2366 return $this->_validateSignature( 2367 $this->currentCert['publicKeyAndChallenge']['spki']['algorithm']['algorithm'], 2368 $this->currentCert['publicKeyAndChallenge']['spki']['subjectPublicKey'], 2369 $this->currentCert['signatureAlgorithm']['algorithm'], 2370 substr(base64_decode($this->currentCert['signature']), 1), 2371 $this->signatureSubject 2372 ); 2373 case isset($this->currentCert['tbsCertList']): 2374 if (!empty($this->CAs)) { 2375 for ($i = 0; $i < count($this->CAs); $i++) { 2376 $ca = $this->CAs[$i]; 2377 switch (true) { 2378 case !defined('FILE_X509_IGNORE_TYPE') && $this->currentCert['tbsCertList']['issuer'] === $ca['tbsCertificate']['subject']: 2379 case defined('FILE_X509_IGNORE_TYPE') && $this->getDN(self::DN_STRING, $this->currentCert['tbsCertList']['issuer']) === $this->getDN(self::DN_STRING, $ca['tbsCertificate']['subject']): 2380 $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier'); 2381 $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca); 2382 switch (true) { 2383 case !is_array($authorityKey): 2384 case !$subjectKeyID: 2385 case isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID: 2386 if (is_array($authorityKey) && isset($authorityKey['authorityCertSerialNumber']) && !$authorityKey['authorityCertSerialNumber']->equals($ca['tbsCertificate']['serialNumber'])) { 2387 break 2; // serial mismatch - check other ca 2388 } 2389 $signingCert = $ca; // working cert 2390 break 3; 2391 } 2392 } 2393 } 2394 } 2395 if (!isset($signingCert)) { 2396 return false; 2397 } 2398 return $this->_validateSignature( 2399 $signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'], 2400 $signingCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'], 2401 $this->currentCert['signatureAlgorithm']['algorithm'], 2402 substr(base64_decode($this->currentCert['signature']), 1), 2403 $this->signatureSubject 2404 ); 2405 default: 2406 return false; 2407 } 2408 } 2409 2410 /** 2411 * Validates a signature 2412 * 2413 * Returns true if the signature is verified, false if it is not correct or null on error 2414 * 2415 * @param string $publicKeyAlgorithm 2416 * @param string $publicKey 2417 * @param string $signatureAlgorithm 2418 * @param string $signature 2419 * @param string $signatureSubject 2420 * @access private 2421 * @return int 2422 */ 2423 function _validateSignature($publicKeyAlgorithm, $publicKey, $signatureAlgorithm, $signature, $signatureSubject) 2424 { 2425 switch ($publicKeyAlgorithm) { 2426 case 'rsaEncryption': 2427 $rsa = new RSA(); 2428 $rsa->loadKey($publicKey); 2429 2430 switch ($signatureAlgorithm) { 2431 case 'md2WithRSAEncryption': 2432 case 'md5WithRSAEncryption': 2433 case 'sha1WithRSAEncryption': 2434 case 'sha224WithRSAEncryption': 2435 case 'sha256WithRSAEncryption': 2436 case 'sha384WithRSAEncryption': 2437 case 'sha512WithRSAEncryption': 2438 $rsa->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm)); 2439 $rsa->setSignatureMode(RSA::SIGNATURE_PKCS1); 2440 if (!@$rsa->verify($signatureSubject, $signature)) { 2441 return false; 2442 } 2443 break; 2444 default: 2445 return null; 2446 } 2447 break; 2448 default: 2449 return null; 2450 } 2451 2452 return true; 2453 } 2454 2455 /** 2456 * Sets the recursion limit 2457 * 2458 * When validating a signature it may be necessary to download intermediate certs from URI's. 2459 * An intermediate cert that linked to itself would result in an infinite loop so to prevent 2460 * that we set a recursion limit. A negative number means that there is no recursion limit. 2461 * 2462 * @param int $count 2463 * @access public 2464 */ 2465 static function setRecurLimit($count) 2466 { 2467 self::$recur_limit = $count; 2468 } 2469 2470 /** 2471 * Prevents URIs from being automatically retrieved 2472 * 2473 * @access public 2474 */ 2475 static function disableURLFetch() 2476 { 2477 self::$disable_url_fetch = true; 2478 } 2479 2480 /** 2481 * Allows URIs to be automatically retrieved 2482 * 2483 * @access public 2484 */ 2485 static function enableURLFetch() 2486 { 2487 self::$disable_url_fetch = false; 2488 } 2489 2490 /** 2491 * Reformat public keys 2492 * 2493 * Reformats a public key to a format supported by phpseclib (if applicable) 2494 * 2495 * @param string $algorithm 2496 * @param string $key 2497 * @access private 2498 * @return string 2499 */ 2500 function _reformatKey($algorithm, $key) 2501 { 2502 switch ($algorithm) { 2503 case 'rsaEncryption': 2504 return 2505 "-----BEGIN RSA PUBLIC KEY-----\r\n" . 2506 // subjectPublicKey is stored as a bit string in X.509 certs. the first byte of a bit string represents how many bits 2507 // in the last byte should be ignored. the following only supports non-zero stuff but as none of the X.509 certs Firefox 2508 // uses as a cert authority actually use a non-zero bit I think it's safe to assume that none do. 2509 chunk_split(base64_encode(substr(base64_decode($key), 1)), 64) . 2510 '-----END RSA PUBLIC KEY-----'; 2511 default: 2512 return $key; 2513 } 2514 } 2515 2516 /** 2517 * Decodes an IP address 2518 * 2519 * Takes in a base64 encoded "blob" and returns a human readable IP address 2520 * 2521 * @param string $ip 2522 * @access private 2523 * @return string 2524 */ 2525 function _decodeIP($ip) 2526 { 2527 return inet_ntop(base64_decode($ip)); 2528 } 2529 2530 /** 2531 * Decodes an IP address in a name constraints extension 2532 * 2533 * Takes in a base64 encoded "blob" and returns a human readable IP address / mask 2534 * 2535 * @param string $ip 2536 * @access private 2537 * @return array 2538 */ 2539 function _decodeNameConstraintIP($ip) 2540 { 2541 $ip = base64_decode($ip); 2542 $size = strlen($ip) >> 1; 2543 $mask = substr($ip, $size); 2544 $ip = substr($ip, 0, $size); 2545 return array(inet_ntop($ip), inet_ntop($mask)); 2546 } 2547 2548 /** 2549 * Encodes an IP address 2550 * 2551 * Takes a human readable IP address into a base64-encoded "blob" 2552 * 2553 * @param string|array $ip 2554 * @access private 2555 * @return string 2556 */ 2557 function _encodeIP($ip) 2558 { 2559 return is_string($ip) ? 2560 base64_encode(inet_pton($ip)) : 2561 base64_encode(inet_pton($ip[0]) . inet_pton($ip[1])); 2562 } 2563 2564 /** 2565 * "Normalizes" a Distinguished Name property 2566 * 2567 * @param string $propName 2568 * @access private 2569 * @return mixed 2570 */ 2571 function _translateDNProp($propName) 2572 { 2573 switch (strtolower($propName)) { 2574 case 'id-at-countryname': 2575 case 'countryname': 2576 case 'c': 2577 return 'id-at-countryName'; 2578 case 'id-at-organizationname': 2579 case 'organizationname': 2580 case 'o': 2581 return 'id-at-organizationName'; 2582 case 'id-at-dnqualifier': 2583 case 'dnqualifier': 2584 return 'id-at-dnQualifier'; 2585 case 'id-at-commonname': 2586 case 'commonname': 2587 case 'cn': 2588 return 'id-at-commonName'; 2589 case 'id-at-stateorprovincename': 2590 case 'stateorprovincename': 2591 case 'state': 2592 case 'province': 2593 case 'provincename': 2594 case 'st': 2595 return 'id-at-stateOrProvinceName'; 2596 case 'id-at-localityname': 2597 case 'localityname': 2598 case 'l': 2599 return 'id-at-localityName'; 2600 case 'id-emailaddress': 2601 case 'emailaddress': 2602 return 'pkcs-9-at-emailAddress'; 2603 case 'id-at-serialnumber': 2604 case 'serialnumber': 2605 return 'id-at-serialNumber'; 2606 case 'id-at-postalcode': 2607 case 'postalcode': 2608 return 'id-at-postalCode'; 2609 case 'id-at-streetaddress': 2610 case 'streetaddress': 2611 return 'id-at-streetAddress'; 2612 case 'id-at-name': 2613 case 'name': 2614 return 'id-at-name'; 2615 case 'id-at-givenname': 2616 case 'givenname': 2617 return 'id-at-givenName'; 2618 case 'id-at-surname': 2619 case 'surname': 2620 case 'sn': 2621 return 'id-at-surname'; 2622 case 'id-at-initials': 2623 case 'initials': 2624 return 'id-at-initials'; 2625 case 'id-at-generationqualifier': 2626 case 'generationqualifier': 2627 return 'id-at-generationQualifier'; 2628 case 'id-at-organizationalunitname': 2629 case 'organizationalunitname': 2630 case 'ou': 2631 return 'id-at-organizationalUnitName'; 2632 case 'id-at-pseudonym': 2633 case 'pseudonym': 2634 return 'id-at-pseudonym'; 2635 case 'id-at-title': 2636 case 'title': 2637 return 'id-at-title'; 2638 case 'id-at-description': 2639 case 'description': 2640 return 'id-at-description'; 2641 case 'id-at-role': 2642 case 'role': 2643 return 'id-at-role'; 2644 case 'id-at-uniqueidentifier': 2645 case 'uniqueidentifier': 2646 case 'x500uniqueidentifier': 2647 return 'id-at-uniqueIdentifier'; 2648 case 'postaladdress': 2649 case 'id-at-postaladdress': 2650 return 'id-at-postalAddress'; 2651 default: 2652 return false; 2653 } 2654 } 2655 2656 /** 2657 * Set a Distinguished Name property 2658 * 2659 * @param string $propName 2660 * @param mixed $propValue 2661 * @param string $type optional 2662 * @access public 2663 * @return bool 2664 */ 2665 function setDNProp($propName, $propValue, $type = 'utf8String') 2666 { 2667 if (empty($this->dn)) { 2668 $this->dn = array('rdnSequence' => array()); 2669 } 2670 2671 if (($propName = $this->_translateDNProp($propName)) === false) { 2672 return false; 2673 } 2674 2675 foreach ((array) $propValue as $v) { 2676 if (!is_array($v) && isset($type)) { 2677 $v = array($type => $v); 2678 } 2679 $this->dn['rdnSequence'][] = array( 2680 array( 2681 'type' => $propName, 2682 'value'=> $v 2683 ) 2684 ); 2685 } 2686 2687 return true; 2688 } 2689 2690 /** 2691 * Remove Distinguished Name properties 2692 * 2693 * @param string $propName 2694 * @access public 2695 */ 2696 function removeDNProp($propName) 2697 { 2698 if (empty($this->dn)) { 2699 return; 2700 } 2701 2702 if (($propName = $this->_translateDNProp($propName)) === false) { 2703 return; 2704 } 2705 2706 $dn = &$this->dn['rdnSequence']; 2707 $size = count($dn); 2708 for ($i = 0; $i < $size; $i++) { 2709 if ($dn[$i][0]['type'] == $propName) { 2710 unset($dn[$i]); 2711 } 2712 } 2713 2714 $dn = array_values($dn); 2715 // fix for https://bugs.php.net/75433 affecting PHP 7.2 2716 if (!isset($dn[0])) { 2717 $dn = array_splice($dn, 0, 0); 2718 } 2719 } 2720 2721 /** 2722 * Get Distinguished Name properties 2723 * 2724 * @param string $propName 2725 * @param array $dn optional 2726 * @param bool $withType optional 2727 * @return mixed 2728 * @access public 2729 */ 2730 function getDNProp($propName, $dn = null, $withType = false) 2731 { 2732 if (!isset($dn)) { 2733 $dn = $this->dn; 2734 } 2735 2736 if (empty($dn)) { 2737 return false; 2738 } 2739 2740 if (($propName = $this->_translateDNProp($propName)) === false) { 2741 return false; 2742 } 2743 2744 $asn1 = new ASN1(); 2745 $asn1->loadOIDs($this->oids); 2746 $filters = array(); 2747 $filters['value'] = array('type' => ASN1::TYPE_UTF8_STRING); 2748 $asn1->loadFilters($filters); 2749 $this->_mapOutDNs($dn, 'rdnSequence', $asn1); 2750 $dn = $dn['rdnSequence']; 2751 $result = array(); 2752 for ($i = 0; $i < count($dn); $i++) { 2753 if ($dn[$i][0]['type'] == $propName) { 2754 $v = $dn[$i][0]['value']; 2755 if (!$withType) { 2756 if (is_array($v)) { 2757 foreach ($v as $type => $s) { 2758 $type = array_search($type, $asn1->ANYmap, true); 2759 if ($type !== false && isset($asn1->stringTypeSize[$type])) { 2760 $s = $asn1->convert($s, $type); 2761 if ($s !== false) { 2762 $v = $s; 2763 break; 2764 } 2765 } 2766 } 2767 if (is_array($v)) { 2768 $v = array_pop($v); // Always strip data type. 2769 } 2770 } elseif (is_object($v) && $v instanceof Element) { 2771 $map = $this->_getMapping($propName); 2772 if (!is_bool($map)) { 2773 $decoded = $asn1->decodeBER($v); 2774 $v = $asn1->asn1map($decoded[0], $map); 2775 } 2776 } 2777 } 2778 $result[] = $v; 2779 } 2780 } 2781 2782 return $result; 2783 } 2784 2785 /** 2786 * Set a Distinguished Name 2787 * 2788 * @param mixed $dn 2789 * @param bool $merge optional 2790 * @param string $type optional 2791 * @access public 2792 * @return bool 2793 */ 2794 function setDN($dn, $merge = false, $type = 'utf8String') 2795 { 2796 if (!$merge) { 2797 $this->dn = null; 2798 } 2799 2800 if (is_array($dn)) { 2801 if (isset($dn['rdnSequence'])) { 2802 $this->dn = $dn; // No merge here. 2803 return true; 2804 } 2805 2806 // handles stuff generated by openssl_x509_parse() 2807 foreach ($dn as $prop => $value) { 2808 if (!$this->setDNProp($prop, $value, $type)) { 2809 return false; 2810 } 2811 } 2812 return true; 2813 } 2814 2815 // handles everything else 2816 $results = preg_split('#((?:^|, *|/)(?:C=|O=|OU=|CN=|L=|ST=|SN=|postalCode=|streetAddress=|emailAddress=|serialNumber=|organizationalUnitName=|title=|description=|role=|x500UniqueIdentifier=|postalAddress=))#', $dn, -1, PREG_SPLIT_DELIM_CAPTURE); 2817 for ($i = 1; $i < count($results); $i+=2) { 2818 $prop = trim($results[$i], ', =/'); 2819 $value = $results[$i + 1]; 2820 if (!$this->setDNProp($prop, $value, $type)) { 2821 return false; 2822 } 2823 } 2824 2825 return true; 2826 } 2827 2828 /** 2829 * Get the Distinguished Name for a certificates subject 2830 * 2831 * @param mixed $format optional 2832 * @param array $dn optional 2833 * @access public 2834 * @return bool 2835 */ 2836 function getDN($format = self::DN_ARRAY, $dn = null) 2837 { 2838 if (!isset($dn)) { 2839 $dn = isset($this->currentCert['tbsCertList']) ? $this->currentCert['tbsCertList']['issuer'] : $this->dn; 2840 } 2841 2842 switch ((int) $format) { 2843 case self::DN_ARRAY: 2844 return $dn; 2845 case self::DN_ASN1: 2846 $asn1 = new ASN1(); 2847 $asn1->loadOIDs($this->oids); 2848 $filters = array(); 2849 $filters['rdnSequence']['value'] = array('type' => ASN1::TYPE_UTF8_STRING); 2850 $asn1->loadFilters($filters); 2851 $this->_mapOutDNs($dn, 'rdnSequence', $asn1); 2852 return $asn1->encodeDER($dn, $this->Name); 2853 case self::DN_CANON: 2854 // No SEQUENCE around RDNs and all string values normalized as 2855 // trimmed lowercase UTF-8 with all spacing as one blank. 2856 // constructed RDNs will not be canonicalized 2857 $asn1 = new ASN1(); 2858 $asn1->loadOIDs($this->oids); 2859 $filters = array(); 2860 $filters['value'] = array('type' => ASN1::TYPE_UTF8_STRING); 2861 $asn1->loadFilters($filters); 2862 $result = ''; 2863 $this->_mapOutDNs($dn, 'rdnSequence', $asn1); 2864 foreach ($dn['rdnSequence'] as $rdn) { 2865 foreach ($rdn as $i => $attr) { 2866 $attr = &$rdn[$i]; 2867 if (is_array($attr['value'])) { 2868 foreach ($attr['value'] as $type => $v) { 2869 $type = array_search($type, $asn1->ANYmap, true); 2870 if ($type !== false && isset($asn1->stringTypeSize[$type])) { 2871 $v = $asn1->convert($v, $type); 2872 if ($v !== false) { 2873 $v = preg_replace('/\s+/', ' ', $v); 2874 $attr['value'] = strtolower(trim($v)); 2875 break; 2876 } 2877 } 2878 } 2879 } 2880 } 2881 $result .= $asn1->encodeDER($rdn, $this->RelativeDistinguishedName); 2882 } 2883 return $result; 2884 case self::DN_HASH: 2885 $dn = $this->getDN(self::DN_CANON, $dn); 2886 $hash = new Hash('sha1'); 2887 $hash = $hash->hash($dn); 2888 extract(unpack('Vhash', $hash)); 2889 return strtolower(bin2hex(pack('N', $hash))); 2890 } 2891 2892 // Default is to return a string. 2893 $start = true; 2894 $output = ''; 2895 2896 $result = array(); 2897 $asn1 = new ASN1(); 2898 $asn1->loadOIDs($this->oids); 2899 $filters = array(); 2900 $filters['rdnSequence']['value'] = array('type' => ASN1::TYPE_UTF8_STRING); 2901 $asn1->loadFilters($filters); 2902 $this->_mapOutDNs($dn, 'rdnSequence', $asn1); 2903 2904 foreach ($dn['rdnSequence'] as $field) { 2905 $prop = $field[0]['type']; 2906 $value = $field[0]['value']; 2907 2908 $delim = ', '; 2909 switch ($prop) { 2910 case 'id-at-countryName': 2911 $desc = 'C'; 2912 break; 2913 case 'id-at-stateOrProvinceName': 2914 $desc = 'ST'; 2915 break; 2916 case 'id-at-organizationName': 2917 $desc = 'O'; 2918 break; 2919 case 'id-at-organizationalUnitName': 2920 $desc = 'OU'; 2921 break; 2922 case 'id-at-commonName': 2923 $desc = 'CN'; 2924 break; 2925 case 'id-at-localityName': 2926 $desc = 'L'; 2927 break; 2928 case 'id-at-surname': 2929 $desc = 'SN'; 2930 break; 2931 case 'id-at-uniqueIdentifier': 2932 $delim = '/'; 2933 $desc = 'x500UniqueIdentifier'; 2934 break; 2935 case 'id-at-postalAddress': 2936 $delim = '/'; 2937 $desc = 'postalAddress'; 2938 break; 2939 default: 2940 $delim = '/'; 2941 $desc = preg_replace('#.+-([^-]+)$#', '$1', $prop); 2942 } 2943 2944 if (!$start) { 2945 $output.= $delim; 2946 } 2947 if (is_array($value)) { 2948 foreach ($value as $type => $v) { 2949 $type = array_search($type, $asn1->ANYmap, true); 2950 if ($type !== false && isset($asn1->stringTypeSize[$type])) { 2951 $v = $asn1->convert($v, $type); 2952 if ($v !== false) { 2953 $value = $v; 2954 break; 2955 } 2956 } 2957 } 2958 if (is_array($value)) { 2959 $value = array_pop($value); // Always strip data type. 2960 } 2961 } elseif (is_object($value) && $value instanceof Element) { 2962 $callback = function ($x) { 2963 return "\x" . bin2hex($x[0]); 2964 }; 2965 $value = strtoupper(preg_replace_callback('#[^\x20-\x7E]#', $callback, $value->element)); 2966 } 2967 $output.= $desc . '=' . $value; 2968 $result[$desc] = isset($result[$desc]) ? 2969 array_merge((array) $result[$desc], array($value)) : 2970 $value; 2971 $start = false; 2972 } 2973 2974 return $format == self::DN_OPENSSL ? $result : $output; 2975 } 2976 2977 /** 2978 * Get the Distinguished Name for a certificate/crl issuer 2979 * 2980 * @param int $format optional 2981 * @access public 2982 * @return mixed 2983 */ 2984 function getIssuerDN($format = self::DN_ARRAY) 2985 { 2986 switch (true) { 2987 case !isset($this->currentCert) || !is_array($this->currentCert): 2988 break; 2989 case isset($this->currentCert['tbsCertificate']): 2990 return $this->getDN($format, $this->currentCert['tbsCertificate']['issuer']); 2991 case isset($this->currentCert['tbsCertList']): 2992 return $this->getDN($format, $this->currentCert['tbsCertList']['issuer']); 2993 } 2994 2995 return false; 2996 } 2997 2998 /** 2999 * Get the Distinguished Name for a certificate/csr subject 3000 * Alias of getDN() 3001 * 3002 * @param int $format optional 3003 * @access public 3004 * @return mixed 3005 */ 3006 function getSubjectDN($format = self::DN_ARRAY) 3007 { 3008 switch (true) { 3009 case !empty($this->dn): 3010 return $this->getDN($format); 3011 case !isset($this->currentCert) || !is_array($this->currentCert): 3012 break; 3013 case isset($this->currentCert['tbsCertificate']): 3014 return $this->getDN($format, $this->currentCert['tbsCertificate']['subject']); 3015 case isset($this->currentCert['certificationRequestInfo']): 3016 return $this->getDN($format, $this->currentCert['certificationRequestInfo']['subject']); 3017 } 3018 3019 return false; 3020 } 3021 3022 /** 3023 * Get an individual Distinguished Name property for a certificate/crl issuer 3024 * 3025 * @param string $propName 3026 * @param bool $withType optional 3027 * @access public 3028 * @return mixed 3029 */ 3030 function getIssuerDNProp($propName, $withType = false) 3031 { 3032 switch (true) { 3033 case !isset($this->currentCert) || !is_array($this->currentCert): 3034 break; 3035 case isset($this->currentCert['tbsCertificate']): 3036 return $this->getDNProp($propName, $this->currentCert['tbsCertificate']['issuer'], $withType); 3037 case isset($this->currentCert['tbsCertList']): 3038 return $this->getDNProp($propName, $this->currentCert['tbsCertList']['issuer'], $withType); 3039 } 3040 3041 return false; 3042 } 3043 3044 /** 3045 * Get an individual Distinguished Name property for a certificate/csr subject 3046 * 3047 * @param string $propName 3048 * @param bool $withType optional 3049 * @access public 3050 * @return mixed 3051 */ 3052 function getSubjectDNProp($propName, $withType = false) 3053 { 3054 switch (true) { 3055 case !empty($this->dn): 3056 return $this->getDNProp($propName, null, $withType); 3057 case !isset($this->currentCert) || !is_array($this->currentCert): 3058 break; 3059 case isset($this->currentCert['tbsCertificate']): 3060 return $this->getDNProp($propName, $this->currentCert['tbsCertificate']['subject'], $withType); 3061 case isset($this->currentCert['certificationRequestInfo']): 3062 return $this->getDNProp($propName, $this->currentCert['certificationRequestInfo']['subject'], $withType); 3063 } 3064 3065 return false; 3066 } 3067 3068 /** 3069 * Get the certificate chain for the current cert 3070 * 3071 * @access public 3072 * @return mixed 3073 */ 3074 function getChain() 3075 { 3076 $chain = array($this->currentCert); 3077 3078 if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) { 3079 return false; 3080 } 3081 if (empty($this->CAs)) { 3082 return $chain; 3083 } 3084 while (true) { 3085 $currentCert = $chain[count($chain) - 1]; 3086 for ($i = 0; $i < count($this->CAs); $i++) { 3087 $ca = $this->CAs[$i]; 3088 if ($currentCert['tbsCertificate']['issuer'] === $ca['tbsCertificate']['subject']) { 3089 $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier', $currentCert); 3090 $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca); 3091 switch (true) { 3092 case !is_array($authorityKey): 3093 case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID: 3094 if ($currentCert === $ca) { 3095 break 3; 3096 } 3097 $chain[] = $ca; 3098 break 2; 3099 } 3100 } 3101 } 3102 if ($i == count($this->CAs)) { 3103 break; 3104 } 3105 } 3106 foreach ($chain as $key => $value) { 3107 $chain[$key] = new X509(); 3108 $chain[$key]->loadX509($value); 3109 } 3110 return $chain; 3111 } 3112 3113 /** 3114 * Set public key 3115 * 3116 * Key needs to be a \phpseclib\Crypt\RSA object 3117 * 3118 * @param object $key 3119 * @access public 3120 * @return bool 3121 */ 3122 function setPublicKey($key) 3123 { 3124 $key->setPublicKey(); 3125 $this->publicKey = $key; 3126 } 3127 3128 /** 3129 * Set private key 3130 * 3131 * Key needs to be a \phpseclib\Crypt\RSA object 3132 * 3133 * @param object $key 3134 * @access public 3135 */ 3136 function setPrivateKey($key) 3137 { 3138 $this->privateKey = $key; 3139 } 3140 3141 /** 3142 * Set challenge 3143 * 3144 * Used for SPKAC CSR's 3145 * 3146 * @param string $challenge 3147 * @access public 3148 */ 3149 function setChallenge($challenge) 3150 { 3151 $this->challenge = $challenge; 3152 } 3153 3154 /** 3155 * Gets the public key 3156 * 3157 * Returns a \phpseclib\Crypt\RSA object or a false. 3158 * 3159 * @access public 3160 * @return mixed 3161 */ 3162 function getPublicKey() 3163 { 3164 if (isset($this->publicKey)) { 3165 return $this->publicKey; 3166 } 3167 3168 if (isset($this->currentCert) && is_array($this->currentCert)) { 3169 foreach (array('tbsCertificate/subjectPublicKeyInfo', 'certificationRequestInfo/subjectPKInfo') as $path) { 3170 $keyinfo = $this->_subArray($this->currentCert, $path); 3171 if (!empty($keyinfo)) { 3172 break; 3173 } 3174 } 3175 } 3176 if (empty($keyinfo)) { 3177 return false; 3178 } 3179 3180 $key = $keyinfo['subjectPublicKey']; 3181 3182 switch ($keyinfo['algorithm']['algorithm']) { 3183 case 'rsaEncryption': 3184 $publicKey = new RSA(); 3185 $publicKey->loadKey($key); 3186 $publicKey->setPublicKey(); 3187 break; 3188 default: 3189 return false; 3190 } 3191 3192 return $publicKey; 3193 } 3194 3195 /** 3196 * Load a Certificate Signing Request 3197 * 3198 * @param string|array $csr 3199 * @param int $mode 3200 * @access public 3201 * @return mixed 3202 */ 3203 function loadCSR($csr, $mode = self::FORMAT_AUTO_DETECT) 3204 { 3205 if (is_array($csr) && isset($csr['certificationRequestInfo'])) { 3206 unset($this->currentCert); 3207 unset($this->currentKeyIdentifier); 3208 unset($this->signatureSubject); 3209 $this->dn = $csr['certificationRequestInfo']['subject']; 3210 if (!isset($this->dn)) { 3211 return false; 3212 } 3213 3214 $this->currentCert = $csr; 3215 return $csr; 3216 } 3217 3218 // see http://tools.ietf.org/html/rfc2986 3219 3220 $asn1 = new ASN1(); 3221 3222 if ($mode != self::FORMAT_DER) { 3223 $newcsr = $this->_extractBER($csr); 3224 if ($mode == self::FORMAT_PEM && $csr == $newcsr) { 3225 return false; 3226 } 3227 $csr = $newcsr; 3228 } 3229 $orig = $csr; 3230 3231 if ($csr === false) { 3232 $this->currentCert = false; 3233 return false; 3234 } 3235 3236 $asn1->loadOIDs($this->oids); 3237 $decoded = $asn1->decodeBER($csr); 3238 3239 if (empty($decoded)) { 3240 $this->currentCert = false; 3241 return false; 3242 } 3243 3244 $csr = $asn1->asn1map($decoded[0], $this->CertificationRequest); 3245 if (!isset($csr) || $csr === false) { 3246 $this->currentCert = false; 3247 return false; 3248 } 3249 3250 $this->_mapInAttributes($csr, 'certificationRequestInfo/attributes', $asn1); 3251 $this->_mapInDNs($csr, 'certificationRequestInfo/subject/rdnSequence', $asn1); 3252 3253 $this->dn = $csr['certificationRequestInfo']['subject']; 3254 3255 $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']); 3256 3257 $algorithm = &$csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm']; 3258 $key = &$csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']; 3259 $key = $this->_reformatKey($algorithm, $key); 3260 3261 switch ($algorithm) { 3262 case 'rsaEncryption': 3263 $this->publicKey = new RSA(); 3264 $this->publicKey->loadKey($key); 3265 $this->publicKey->setPublicKey(); 3266 break; 3267 default: 3268 $this->publicKey = null; 3269 } 3270 3271 $this->currentKeyIdentifier = null; 3272 $this->currentCert = $csr; 3273 3274 return $csr; 3275 } 3276 3277 /** 3278 * Save CSR request 3279 * 3280 * @param array $csr 3281 * @param int $format optional 3282 * @access public 3283 * @return string 3284 */ 3285 function saveCSR($csr, $format = self::FORMAT_PEM) 3286 { 3287 if (!is_array($csr) || !isset($csr['certificationRequestInfo'])) { 3288 return false; 3289 } 3290 3291 switch (true) { 3292 case !($algorithm = $this->_subArray($csr, 'certificationRequestInfo/subjectPKInfo/algorithm/algorithm')): 3293 case is_object($csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']): 3294 break; 3295 default: 3296 switch ($algorithm) { 3297 case 'rsaEncryption': 3298 $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'] 3299 = base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']))); 3300 $csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['parameters'] = null; 3301 $csr['signatureAlgorithm']['parameters'] = null; 3302 $csr['certificationRequestInfo']['signature']['parameters'] = null; 3303 } 3304 } 3305 3306 $asn1 = new ASN1(); 3307 3308 $asn1->loadOIDs($this->oids); 3309 3310 $filters = array(); 3311 $filters['certificationRequestInfo']['subject']['rdnSequence']['value'] 3312 = array('type' => ASN1::TYPE_UTF8_STRING); 3313 3314 $asn1->loadFilters($filters); 3315 3316 $this->_mapOutDNs($csr, 'certificationRequestInfo/subject/rdnSequence', $asn1); 3317 $this->_mapOutAttributes($csr, 'certificationRequestInfo/attributes', $asn1); 3318 $csr = $asn1->encodeDER($csr, $this->CertificationRequest); 3319 3320 switch ($format) { 3321 case self::FORMAT_DER: 3322 return $csr; 3323 // case self::FORMAT_PEM: 3324 default: 3325 return "-----BEGIN CERTIFICATE REQUEST-----\r\n" . chunk_split(base64_encode($csr), 64) . '-----END CERTIFICATE REQUEST-----'; 3326 } 3327 } 3328 3329 /** 3330 * Load a SPKAC CSR 3331 * 3332 * SPKAC's are produced by the HTML5 keygen element: 3333 * 3334 * https://developer.mozilla.org/en-US/docs/HTML/Element/keygen 3335 * 3336 * @param string|array $spkac 3337 * @access public 3338 * @return mixed 3339 */ 3340 function loadSPKAC($spkac) 3341 { 3342 if (is_array($spkac) && isset($spkac['publicKeyAndChallenge'])) { 3343 unset($this->currentCert); 3344 unset($this->currentKeyIdentifier); 3345 unset($this->signatureSubject); 3346 $this->currentCert = $spkac; 3347 return $spkac; 3348 } 3349 3350 // see http://www.w3.org/html/wg/drafts/html/master/forms.html#signedpublickeyandchallenge 3351 3352 $asn1 = new ASN1(); 3353 3354 // OpenSSL produces SPKAC's that are preceded by the string SPKAC= 3355 $temp = preg_replace('#(?:SPKAC=)|[ \r\n\\\]#', '', $spkac); 3356 $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false; 3357 if ($temp != false) { 3358 $spkac = $temp; 3359 } 3360 $orig = $spkac; 3361 3362 if ($spkac === false) { 3363 $this->currentCert = false; 3364 return false; 3365 } 3366 3367 $asn1->loadOIDs($this->oids); 3368 $decoded = $asn1->decodeBER($spkac); 3369 3370 if (empty($decoded)) { 3371 $this->currentCert = false; 3372 return false; 3373 } 3374 3375 $spkac = $asn1->asn1map($decoded[0], $this->SignedPublicKeyAndChallenge); 3376 3377 if (!isset($spkac) || $spkac === false) { 3378 $this->currentCert = false; 3379 return false; 3380 } 3381 3382 $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']); 3383 3384 $algorithm = &$spkac['publicKeyAndChallenge']['spki']['algorithm']['algorithm']; 3385 $key = &$spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']; 3386 $key = $this->_reformatKey($algorithm, $key); 3387 3388 switch ($algorithm) { 3389 case 'rsaEncryption': 3390 $this->publicKey = new RSA(); 3391 $this->publicKey->loadKey($key); 3392 $this->publicKey->setPublicKey(); 3393 break; 3394 default: 3395 $this->publicKey = null; 3396 } 3397 3398 $this->currentKeyIdentifier = null; 3399 $this->currentCert = $spkac; 3400 3401 return $spkac; 3402 } 3403 3404 /** 3405 * Save a SPKAC CSR request 3406 * 3407 * @param string|array $spkac 3408 * @param int $format optional 3409 * @access public 3410 * @return string 3411 */ 3412 function saveSPKAC($spkac, $format = self::FORMAT_PEM) 3413 { 3414 if (!is_array($spkac) || !isset($spkac['publicKeyAndChallenge'])) { 3415 return false; 3416 } 3417 3418 $algorithm = $this->_subArray($spkac, 'publicKeyAndChallenge/spki/algorithm/algorithm'); 3419 switch (true) { 3420 case !$algorithm: 3421 case is_object($spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']): 3422 break; 3423 default: 3424 switch ($algorithm) { 3425 case 'rsaEncryption': 3426 $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'] 3427 = base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']))); 3428 } 3429 } 3430 3431 $asn1 = new ASN1(); 3432 3433 $asn1->loadOIDs($this->oids); 3434 $spkac = $asn1->encodeDER($spkac, $this->SignedPublicKeyAndChallenge); 3435 3436 switch ($format) { 3437 case self::FORMAT_DER: 3438 return $spkac; 3439 // case self::FORMAT_PEM: 3440 default: 3441 // OpenSSL's implementation of SPKAC requires the SPKAC be preceded by SPKAC= and since there are pretty much 3442 // no other SPKAC decoders phpseclib will use that same format 3443 return 'SPKAC=' . base64_encode($spkac); 3444 } 3445 } 3446 3447 /** 3448 * Load a Certificate Revocation List 3449 * 3450 * @param string $crl 3451 * @param int $mode 3452 * @access public 3453 * @return mixed 3454 */ 3455 function loadCRL($crl, $mode = self::FORMAT_AUTO_DETECT) 3456 { 3457 if (is_array($crl) && isset($crl['tbsCertList'])) { 3458 $this->currentCert = $crl; 3459 unset($this->signatureSubject); 3460 return $crl; 3461 } 3462 3463 $asn1 = new ASN1(); 3464 3465 if ($mode != self::FORMAT_DER) { 3466 $newcrl = $this->_extractBER($crl); 3467 if ($mode == self::FORMAT_PEM && $crl == $newcrl) { 3468 return false; 3469 } 3470 $crl = $newcrl; 3471 } 3472 $orig = $crl; 3473 3474 if ($crl === false) { 3475 $this->currentCert = false; 3476 return false; 3477 } 3478 3479 $asn1->loadOIDs($this->oids); 3480 $decoded = $asn1->decodeBER($crl); 3481 3482 if (empty($decoded)) { 3483 $this->currentCert = false; 3484 return false; 3485 } 3486 3487 $crl = $asn1->asn1map($decoded[0], $this->CertificateList); 3488 if (!isset($crl) || $crl === false) { 3489 $this->currentCert = false; 3490 return false; 3491 } 3492 3493 $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']); 3494 3495 $this->_mapInDNs($crl, 'tbsCertList/issuer/rdnSequence', $asn1); 3496 if ($this->_isSubArrayValid($crl, 'tbsCertList/crlExtensions')) { 3497 $this->_mapInExtensions($crl, 'tbsCertList/crlExtensions', $asn1); 3498 } 3499 if ($this->_isSubArrayValid($crl, 'tbsCertList/revokedCertificates')) { 3500 $rclist_ref = &$this->_subArrayUnchecked($crl, 'tbsCertList/revokedCertificates'); 3501 if ($rclist_ref) { 3502 $rclist = $crl['tbsCertList']['revokedCertificates']; 3503 foreach ($rclist as $i => $extension) { 3504 if ($this->_isSubArrayValid($rclist, "$i/crlEntryExtensions", $asn1)) { 3505 $this->_mapInExtensions($rclist_ref, "$i/crlEntryExtensions", $asn1); 3506 } 3507 } 3508 } 3509 } 3510 3511 $this->currentKeyIdentifier = null; 3512 $this->currentCert = $crl; 3513 3514 return $crl; 3515 } 3516 3517 /** 3518 * Save Certificate Revocation List. 3519 * 3520 * @param array $crl 3521 * @param int $format optional 3522 * @access public 3523 * @return string 3524 */ 3525 function saveCRL($crl, $format = self::FORMAT_PEM) 3526 { 3527 if (!is_array($crl) || !isset($crl['tbsCertList'])) { 3528 return false; 3529 } 3530 3531 $asn1 = new ASN1(); 3532 3533 $asn1->loadOIDs($this->oids); 3534 3535 $filters = array(); 3536 $filters['tbsCertList']['issuer']['rdnSequence']['value'] 3537 = array('type' => ASN1::TYPE_UTF8_STRING); 3538 $filters['tbsCertList']['signature']['parameters'] 3539 = array('type' => ASN1::TYPE_UTF8_STRING); 3540 $filters['signatureAlgorithm']['parameters'] 3541 = array('type' => ASN1::TYPE_UTF8_STRING); 3542 3543 if (empty($crl['tbsCertList']['signature']['parameters'])) { 3544 $filters['tbsCertList']['signature']['parameters'] 3545 = array('type' => ASN1::TYPE_NULL); 3546 } 3547 3548 if (empty($crl['signatureAlgorithm']['parameters'])) { 3549 $filters['signatureAlgorithm']['parameters'] 3550 = array('type' => ASN1::TYPE_NULL); 3551 } 3552 3553 $asn1->loadFilters($filters); 3554 3555 $this->_mapOutDNs($crl, 'tbsCertList/issuer/rdnSequence', $asn1); 3556 $this->_mapOutExtensions($crl, 'tbsCertList/crlExtensions', $asn1); 3557 $rclist = &$this->_subArray($crl, 'tbsCertList/revokedCertificates'); 3558 if (is_array($rclist)) { 3559 foreach ($rclist as $i => $extension) { 3560 $this->_mapOutExtensions($rclist, "$i/crlEntryExtensions", $asn1); 3561 } 3562 } 3563 3564 $crl = $asn1->encodeDER($crl, $this->CertificateList); 3565 3566 switch ($format) { 3567 case self::FORMAT_DER: 3568 return $crl; 3569 // case self::FORMAT_PEM: 3570 default: 3571 return "-----BEGIN X509 CRL-----\r\n" . chunk_split(base64_encode($crl), 64) . '-----END X509 CRL-----'; 3572 } 3573 } 3574 3575 /** 3576 * Helper function to build a time field according to RFC 3280 section 3577 * - 4.1.2.5 Validity 3578 * - 5.1.2.4 This Update 3579 * - 5.1.2.5 Next Update 3580 * - 5.1.2.6 Revoked Certificates 3581 * by choosing utcTime iff year of date given is before 2050 and generalTime else. 3582 * 3583 * @param string $date in format date('D, d M Y H:i:s O') 3584 * @access private 3585 * @return array 3586 */ 3587 function _timeField($date) 3588 { 3589 if ($date instanceof Element) { 3590 return $date; 3591 } 3592 $dateObj = new DateTime($date, new DateTimeZone('GMT')); 3593 $year = $dateObj->format('Y'); // the same way ASN1.php parses this 3594 if ($year < 2050) { 3595 return array('utcTime' => $date); 3596 } else { 3597 return array('generalTime' => $date); 3598 } 3599 } 3600 3601 /** 3602 * Sign an X.509 certificate 3603 * 3604 * $issuer's private key needs to be loaded. 3605 * $subject can be either an existing X.509 cert (if you want to resign it), 3606 * a CSR or something with the DN and public key explicitly set. 3607 * 3608 * @param \phpseclib\File\X509 $issuer 3609 * @param \phpseclib\File\X509 $subject 3610 * @param string $signatureAlgorithm optional 3611 * @access public 3612 * @return mixed 3613 */ 3614 function sign($issuer, $subject, $signatureAlgorithm = 'sha1WithRSAEncryption') 3615 { 3616 if (!is_object($issuer->privateKey) || empty($issuer->dn)) { 3617 return false; 3618 } 3619 3620 if (isset($subject->publicKey) && !($subjectPublicKey = $subject->_formatSubjectPublicKey())) { 3621 return false; 3622 } 3623 3624 $currentCert = isset($this->currentCert) ? $this->currentCert : null; 3625 $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null; 3626 3627 if (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertificate'])) { 3628 $this->currentCert = $subject->currentCert; 3629 $this->currentCert['tbsCertificate']['signature']['algorithm'] = $signatureAlgorithm; 3630 $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm; 3631 3632 if (!empty($this->startDate)) { 3633 $this->currentCert['tbsCertificate']['validity']['notBefore'] = $this->_timeField($this->startDate); 3634 } 3635 if (!empty($this->endDate)) { 3636 $this->currentCert['tbsCertificate']['validity']['notAfter'] = $this->_timeField($this->endDate); 3637 } 3638 if (!empty($this->serialNumber)) { 3639 $this->currentCert['tbsCertificate']['serialNumber'] = $this->serialNumber; 3640 } 3641 if (!empty($subject->dn)) { 3642 $this->currentCert['tbsCertificate']['subject'] = $subject->dn; 3643 } 3644 if (!empty($subject->publicKey)) { 3645 $this->currentCert['tbsCertificate']['subjectPublicKeyInfo'] = $subjectPublicKey; 3646 } 3647 $this->removeExtension('id-ce-authorityKeyIdentifier'); 3648 if (isset($subject->domains)) { 3649 $this->removeExtension('id-ce-subjectAltName'); 3650 } 3651 } elseif (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertList'])) { 3652 return false; 3653 } else { 3654 if (!isset($subject->publicKey)) { 3655 return false; 3656 } 3657 3658 $startDate = new DateTime('now', new DateTimeZone(@date_default_timezone_get())); 3659 $startDate = !empty($this->startDate) ? $this->startDate : $startDate->format('D, d M Y H:i:s O'); 3660 3661 $endDate = new DateTime('+1 year', new DateTimeZone(@date_default_timezone_get())); 3662 $endDate = !empty($this->endDate) ? $this->endDate : $endDate->format('D, d M Y H:i:s O'); 3663 3664 /* "The serial number MUST be a positive integer" 3665 "Conforming CAs MUST NOT use serialNumber values longer than 20 octets." 3666 -- https://tools.ietf.org/html/rfc5280#section-4.1.2.2 3667 3668 for the integer to be positive the leading bit needs to be 0 hence the 3669 application of a bitmap 3670 */ 3671 $serialNumber = !empty($this->serialNumber) ? 3672 $this->serialNumber : 3673 new BigInteger(Random::string(20) & ("\x7F" . str_repeat("\xFF", 19)), 256); 3674 3675 $this->currentCert = array( 3676 'tbsCertificate' => 3677 array( 3678 'version' => 'v3', 3679 'serialNumber' => $serialNumber, // $this->setSerialNumber() 3680 'signature' => array('algorithm' => $signatureAlgorithm), 3681 'issuer' => false, // this is going to be overwritten later 3682 'validity' => array( 3683 'notBefore' => $this->_timeField($startDate), // $this->setStartDate() 3684 'notAfter' => $this->_timeField($endDate) // $this->setEndDate() 3685 ), 3686 'subject' => $subject->dn, 3687 'subjectPublicKeyInfo' => $subjectPublicKey 3688 ), 3689 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm), 3690 'signature' => false // this is going to be overwritten later 3691 ); 3692 3693 // Copy extensions from CSR. 3694 $csrexts = $subject->getAttribute('pkcs-9-at-extensionRequest', 0); 3695 3696 if (!empty($csrexts)) { 3697 $this->currentCert['tbsCertificate']['extensions'] = $csrexts; 3698 } 3699 } 3700 3701 $this->currentCert['tbsCertificate']['issuer'] = $issuer->dn; 3702 3703 if (isset($issuer->currentKeyIdentifier)) { 3704 $this->setExtension('id-ce-authorityKeyIdentifier', array( 3705 //'authorityCertIssuer' => array( 3706 // array( 3707 // 'directoryName' => $issuer->dn 3708 // ) 3709 //), 3710 'keyIdentifier' => $issuer->currentKeyIdentifier 3711 )); 3712 //$extensions = &$this->currentCert['tbsCertificate']['extensions']; 3713 //if (isset($issuer->serialNumber)) { 3714 // $extensions[count($extensions) - 1]['authorityCertSerialNumber'] = $issuer->serialNumber; 3715 //} 3716 //unset($extensions); 3717 } 3718 3719 if (isset($subject->currentKeyIdentifier)) { 3720 $this->setExtension('id-ce-subjectKeyIdentifier', $subject->currentKeyIdentifier); 3721 } 3722 3723 $altName = array(); 3724 3725 if (isset($subject->domains) && count($subject->domains)) { 3726 $altName = array_map(array('\phpseclib\File\X509', '_dnsName'), $subject->domains); 3727 } 3728 3729 if (isset($subject->ipAddresses) && count($subject->ipAddresses)) { 3730 // should an IP address appear as the CN if no domain name is specified? idk 3731 //$ips = count($subject->domains) ? $subject->ipAddresses : array_slice($subject->ipAddresses, 1); 3732 $ipAddresses = array(); 3733 foreach ($subject->ipAddresses as $ipAddress) { 3734 $encoded = $subject->_ipAddress($ipAddress); 3735 if ($encoded !== false) { 3736 $ipAddresses[] = $encoded; 3737 } 3738 } 3739 if (count($ipAddresses)) { 3740 $altName = array_merge($altName, $ipAddresses); 3741 } 3742 } 3743 3744 if (!empty($altName)) { 3745 $this->setExtension('id-ce-subjectAltName', $altName); 3746 } 3747 3748 if ($this->caFlag) { 3749 $keyUsage = $this->getExtension('id-ce-keyUsage'); 3750 if (!$keyUsage) { 3751 $keyUsage = array(); 3752 } 3753 3754 $this->setExtension( 3755 'id-ce-keyUsage', 3756 array_values(array_unique(array_merge($keyUsage, array('cRLSign', 'keyCertSign')))) 3757 ); 3758 3759 $basicConstraints = $this->getExtension('id-ce-basicConstraints'); 3760 if (!$basicConstraints) { 3761 $basicConstraints = array(); 3762 } 3763 3764 $this->setExtension( 3765 'id-ce-basicConstraints', 3766 array_unique(array_merge(array('cA' => true), $basicConstraints)), 3767 true 3768 ); 3769 3770 if (!isset($subject->currentKeyIdentifier)) { 3771 $this->setExtension('id-ce-subjectKeyIdentifier', base64_encode($this->computeKeyIdentifier($this->currentCert)), false, false); 3772 } 3773 } 3774 3775 // resync $this->signatureSubject 3776 // save $tbsCertificate in case there are any \phpseclib\File\ASN1\Element objects in it 3777 $tbsCertificate = $this->currentCert['tbsCertificate']; 3778 $this->loadX509($this->saveX509($this->currentCert)); 3779 3780 $result = $this->_sign($issuer->privateKey, $signatureAlgorithm); 3781 $result['tbsCertificate'] = $tbsCertificate; 3782 3783 $this->currentCert = $currentCert; 3784 $this->signatureSubject = $signatureSubject; 3785 3786 return $result; 3787 } 3788 3789 /** 3790 * Sign a CSR 3791 * 3792 * @access public 3793 * @return mixed 3794 */ 3795 function signCSR($signatureAlgorithm = 'sha1WithRSAEncryption') 3796 { 3797 if (!is_object($this->privateKey) || empty($this->dn)) { 3798 return false; 3799 } 3800 3801 $origPublicKey = $this->publicKey; 3802 $class = get_class($this->privateKey); 3803 $this->publicKey = new $class(); 3804 $this->publicKey->loadKey($this->privateKey->getPublicKey()); 3805 $this->publicKey->setPublicKey(); 3806 if (!($publicKey = $this->_formatSubjectPublicKey())) { 3807 return false; 3808 } 3809 $this->publicKey = $origPublicKey; 3810 3811 $currentCert = isset($this->currentCert) ? $this->currentCert : null; 3812 $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null; 3813 3814 if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['certificationRequestInfo'])) { 3815 $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm; 3816 if (!empty($this->dn)) { 3817 $this->currentCert['certificationRequestInfo']['subject'] = $this->dn; 3818 } 3819 $this->currentCert['certificationRequestInfo']['subjectPKInfo'] = $publicKey; 3820 } else { 3821 $this->currentCert = array( 3822 'certificationRequestInfo' => 3823 array( 3824 'version' => 'v1', 3825 'subject' => $this->dn, 3826 'subjectPKInfo' => $publicKey 3827 ), 3828 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm), 3829 'signature' => false // this is going to be overwritten later 3830 ); 3831 } 3832 3833 // resync $this->signatureSubject 3834 // save $certificationRequestInfo in case there are any \phpseclib\File\ASN1\Element objects in it 3835 $certificationRequestInfo = $this->currentCert['certificationRequestInfo']; 3836 $this->loadCSR($this->saveCSR($this->currentCert)); 3837 3838 $result = $this->_sign($this->privateKey, $signatureAlgorithm); 3839 $result['certificationRequestInfo'] = $certificationRequestInfo; 3840 3841 $this->currentCert = $currentCert; 3842 $this->signatureSubject = $signatureSubject; 3843 3844 return $result; 3845 } 3846 3847 /** 3848 * Sign a SPKAC 3849 * 3850 * @access public 3851 * @return mixed 3852 */ 3853 function signSPKAC($signatureAlgorithm = 'sha1WithRSAEncryption') 3854 { 3855 if (!is_object($this->privateKey)) { 3856 return false; 3857 } 3858 3859 $origPublicKey = $this->publicKey; 3860 $class = get_class($this->privateKey); 3861 $this->publicKey = new $class(); 3862 $this->publicKey->loadKey($this->privateKey->getPublicKey()); 3863 $this->publicKey->setPublicKey(); 3864 $publicKey = $this->_formatSubjectPublicKey(); 3865 if (!$publicKey) { 3866 return false; 3867 } 3868 $this->publicKey = $origPublicKey; 3869 3870 $currentCert = isset($this->currentCert) ? $this->currentCert : null; 3871 $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null; 3872 3873 // re-signing a SPKAC seems silly but since everything else supports re-signing why not? 3874 if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['publicKeyAndChallenge'])) { 3875 $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm; 3876 $this->currentCert['publicKeyAndChallenge']['spki'] = $publicKey; 3877 if (!empty($this->challenge)) { 3878 // the bitwise AND ensures that the output is a valid IA5String 3879 $this->currentCert['publicKeyAndChallenge']['challenge'] = $this->challenge & str_repeat("\x7F", strlen($this->challenge)); 3880 } 3881 } else { 3882 $this->currentCert = array( 3883 'publicKeyAndChallenge' => 3884 array( 3885 'spki' => $publicKey, 3886 // quoting <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/keygen>, 3887 // "A challenge string that is submitted along with the public key. Defaults to an empty string if not specified." 3888 // both Firefox and OpenSSL ("openssl spkac -key private.key") behave this way 3889 // we could alternatively do this instead if we ignored the specs: 3890 // Random::string(8) & str_repeat("\x7F", 8) 3891 'challenge' => !empty($this->challenge) ? $this->challenge : '' 3892 ), 3893 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm), 3894 'signature' => false // this is going to be overwritten later 3895 ); 3896 } 3897 3898 // resync $this->signatureSubject 3899 // save $publicKeyAndChallenge in case there are any \phpseclib\File\ASN1\Element objects in it 3900 $publicKeyAndChallenge = $this->currentCert['publicKeyAndChallenge']; 3901 $this->loadSPKAC($this->saveSPKAC($this->currentCert)); 3902 3903 $result = $this->_sign($this->privateKey, $signatureAlgorithm); 3904 $result['publicKeyAndChallenge'] = $publicKeyAndChallenge; 3905 3906 $this->currentCert = $currentCert; 3907 $this->signatureSubject = $signatureSubject; 3908 3909 return $result; 3910 } 3911 3912 /** 3913 * Sign a CRL 3914 * 3915 * $issuer's private key needs to be loaded. 3916 * 3917 * @param \phpseclib\File\X509 $issuer 3918 * @param \phpseclib\File\X509 $crl 3919 * @param string $signatureAlgorithm optional 3920 * @access public 3921 * @return mixed 3922 */ 3923 function signCRL($issuer, $crl, $signatureAlgorithm = 'sha1WithRSAEncryption') 3924 { 3925 if (!is_object($issuer->privateKey) || empty($issuer->dn)) { 3926 return false; 3927 } 3928 3929 $currentCert = isset($this->currentCert) ? $this->currentCert : null; 3930 $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null; 3931 3932 $thisUpdate = new DateTime('now', new DateTimeZone(@date_default_timezone_get())); 3933 $thisUpdate = !empty($this->startDate) ? $this->startDate : $thisUpdate->format('D, d M Y H:i:s O'); 3934 3935 if (isset($crl->currentCert) && is_array($crl->currentCert) && isset($crl->currentCert['tbsCertList'])) { 3936 $this->currentCert = $crl->currentCert; 3937 $this->currentCert['tbsCertList']['signature']['algorithm'] = $signatureAlgorithm; 3938 $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm; 3939 } else { 3940 $this->currentCert = array( 3941 'tbsCertList' => 3942 array( 3943 'version' => 'v2', 3944 'signature' => array('algorithm' => $signatureAlgorithm), 3945 'issuer' => false, // this is going to be overwritten later 3946 'thisUpdate' => $this->_timeField($thisUpdate) // $this->setStartDate() 3947 ), 3948 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm), 3949 'signature' => false // this is going to be overwritten later 3950 ); 3951 } 3952 3953 $tbsCertList = &$this->currentCert['tbsCertList']; 3954 $tbsCertList['issuer'] = $issuer->dn; 3955 $tbsCertList['thisUpdate'] = $this->_timeField($thisUpdate); 3956 3957 if (!empty($this->endDate)) { 3958 $tbsCertList['nextUpdate'] = $this->_timeField($this->endDate); // $this->setEndDate() 3959 } else { 3960 unset($tbsCertList['nextUpdate']); 3961 } 3962 3963 if (!empty($this->serialNumber)) { 3964 $crlNumber = $this->serialNumber; 3965 } else { 3966 $crlNumber = $this->getExtension('id-ce-cRLNumber'); 3967 // "The CRL number is a non-critical CRL extension that conveys a 3968 // monotonically increasing sequence number for a given CRL scope and 3969 // CRL issuer. This extension allows users to easily determine when a 3970 // particular CRL supersedes another CRL." 3971 // -- https://tools.ietf.org/html/rfc5280#section-5.2.3 3972 $crlNumber = $crlNumber !== false ? $crlNumber->add(new BigInteger(1)) : null; 3973 } 3974 3975 $this->removeExtension('id-ce-authorityKeyIdentifier'); 3976 $this->removeExtension('id-ce-issuerAltName'); 3977 3978 // Be sure version >= v2 if some extension found. 3979 $version = isset($tbsCertList['version']) ? $tbsCertList['version'] : 0; 3980 if (!$version) { 3981 if (!empty($tbsCertList['crlExtensions'])) { 3982 $version = 1; // v2. 3983 } elseif (!empty($tbsCertList['revokedCertificates'])) { 3984 foreach ($tbsCertList['revokedCertificates'] as $cert) { 3985 if (!empty($cert['crlEntryExtensions'])) { 3986 $version = 1; // v2. 3987 } 3988 } 3989 } 3990 3991 if ($version) { 3992 $tbsCertList['version'] = $version; 3993 } 3994 } 3995 3996 // Store additional extensions. 3997 if (!empty($tbsCertList['version'])) { // At least v2. 3998 if (!empty($crlNumber)) { 3999 $this->setExtension('id-ce-cRLNumber', $crlNumber); 4000 } 4001 4002 if (isset($issuer->currentKeyIdentifier)) { 4003 $this->setExtension('id-ce-authorityKeyIdentifier', array( 4004 //'authorityCertIssuer' => array( 4005 // array( 4006 // 'directoryName' => $issuer->dn 4007 // ) 4008 //), 4009 'keyIdentifier' => $issuer->currentKeyIdentifier 4010 )); 4011 //$extensions = &$tbsCertList['crlExtensions']; 4012 //if (isset($issuer->serialNumber)) { 4013 // $extensions[count($extensions) - 1]['authorityCertSerialNumber'] = $issuer->serialNumber; 4014 //} 4015 //unset($extensions); 4016 } 4017 4018 $issuerAltName = $this->getExtension('id-ce-subjectAltName', $issuer->currentCert); 4019 4020 if ($issuerAltName !== false) { 4021 $this->setExtension('id-ce-issuerAltName', $issuerAltName); 4022 } 4023 } 4024 4025 if (empty($tbsCertList['revokedCertificates'])) { 4026 unset($tbsCertList['revokedCertificates']); 4027 } 4028 4029 unset($tbsCertList); 4030 4031 // resync $this->signatureSubject 4032 // save $tbsCertList in case there are any \phpseclib\File\ASN1\Element objects in it 4033 $tbsCertList = $this->currentCert['tbsCertList']; 4034 $this->loadCRL($this->saveCRL($this->currentCert)); 4035 4036 $result = $this->_sign($issuer->privateKey, $signatureAlgorithm); 4037 $result['tbsCertList'] = $tbsCertList; 4038 4039 $this->currentCert = $currentCert; 4040 $this->signatureSubject = $signatureSubject; 4041 4042 return $result; 4043 } 4044 4045 /** 4046 * X.509 certificate signing helper function. 4047 * 4048 * @param \phpseclib\File\X509 $key 4049 * @param string $signatureAlgorithm 4050 * @access public 4051 * @return mixed 4052 */ 4053 function _sign($key, $signatureAlgorithm) 4054 { 4055 if ($key instanceof RSA) { 4056 switch ($signatureAlgorithm) { 4057 case 'md2WithRSAEncryption': 4058 case 'md5WithRSAEncryption': 4059 case 'sha1WithRSAEncryption': 4060 case 'sha224WithRSAEncryption': 4061 case 'sha256WithRSAEncryption': 4062 case 'sha384WithRSAEncryption': 4063 case 'sha512WithRSAEncryption': 4064 $key->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm)); 4065 $key->setSignatureMode(RSA::SIGNATURE_PKCS1); 4066 4067 $this->currentCert['signature'] = base64_encode("\0" . $key->sign($this->signatureSubject)); 4068 return $this->currentCert; 4069 } 4070 } 4071 4072 return false; 4073 } 4074 4075 /** 4076 * Set certificate start date 4077 * 4078 * @param string $date 4079 * @access public 4080 */ 4081 function setStartDate($date) 4082 { 4083 if (!is_object($date) || !is_a($date, 'DateTime')) { 4084 $date = new DateTime($date, new DateTimeZone(@date_default_timezone_get())); 4085 } 4086 4087 $this->startDate = $date->format('D, d M Y H:i:s O'); 4088 } 4089 4090 /** 4091 * Set certificate end date 4092 * 4093 * @param string $date 4094 * @access public 4095 */ 4096 function setEndDate($date) 4097 { 4098 /* 4099 To indicate that a certificate has no well-defined expiration date, 4100 the notAfter SHOULD be assigned the GeneralizedTime value of 4101 99991231235959Z. 4102 4103 -- http://tools.ietf.org/html/rfc5280#section-4.1.2.5 4104 */ 4105 if (strtolower($date) == 'lifetime') { 4106 $temp = '99991231235959Z'; 4107 $asn1 = new ASN1(); 4108 $temp = chr(ASN1::TYPE_GENERALIZED_TIME) . $asn1->_encodeLength(strlen($temp)) . $temp; 4109 $this->endDate = new Element($temp); 4110 } else { 4111 if (!is_object($date) || !is_a($date, 'DateTime')) { 4112 $date = new DateTime($date, new DateTimeZone(@date_default_timezone_get())); 4113 } 4114 4115 $this->endDate = $date->format('D, d M Y H:i:s O'); 4116 } 4117 } 4118 4119 /** 4120 * Set Serial Number 4121 * 4122 * @param string $serial 4123 * @param int $base optional 4124 * @access public 4125 */ 4126 function setSerialNumber($serial, $base = -256) 4127 { 4128 $this->serialNumber = new BigInteger($serial, $base); 4129 } 4130 4131 /** 4132 * Turns the certificate into a certificate authority 4133 * 4134 * @access public 4135 */ 4136 function makeCA() 4137 { 4138 $this->caFlag = true; 4139 } 4140 4141 /** 4142 * Check for validity of subarray 4143 * 4144 * This is intended for use in conjunction with _subArrayUnchecked(), 4145 * implementing the checks included in _subArray() but without copying 4146 * a potentially large array by passing its reference by-value to is_array(). 4147 * 4148 * @param array $root 4149 * @param string $path 4150 * @return boolean 4151 * @access private 4152 */ 4153 function _isSubArrayValid($root, $path) 4154 { 4155 if (!is_array($root)) { 4156 return false; 4157 } 4158 4159 foreach (explode('/', $path) as $i) { 4160 if (!is_array($root)) { 4161 return false; 4162 } 4163 4164 if (!isset($root[$i])) { 4165 return true; 4166 } 4167 4168 $root = $root[$i]; 4169 } 4170 4171 return true; 4172 } 4173 4174 /** 4175 * Get a reference to a subarray 4176 * 4177 * This variant of _subArray() does no is_array() checking, 4178 * so $root should be checked with _isSubArrayValid() first. 4179 * 4180 * This is here for performance reasons: 4181 * Passing a reference (i.e. $root) by-value (i.e. to is_array()) 4182 * creates a copy. If $root is an especially large array, this is expensive. 4183 * 4184 * @param array $root 4185 * @param string $path absolute path with / as component separator 4186 * @param bool $create optional 4187 * @access private 4188 * @return array|false 4189 */ 4190 function &_subArrayUnchecked(&$root, $path, $create = false) 4191 { 4192 $false = false; 4193 4194 foreach (explode('/', $path) as $i) { 4195 if (!isset($root[$i])) { 4196 if (!$create) { 4197 return $false; 4198 } 4199 4200 $root[$i] = array(); 4201 } 4202 4203 $root = &$root[$i]; 4204 } 4205 4206 return $root; 4207 } 4208 4209 /** 4210 * Get a reference to a subarray 4211 * 4212 * @param array $root 4213 * @param string $path absolute path with / as component separator 4214 * @param bool $create optional 4215 * @access private 4216 * @return array|false 4217 */ 4218 function &_subArray(&$root, $path, $create = false) 4219 { 4220 $false = false; 4221 4222 if (!is_array($root)) { 4223 return $false; 4224 } 4225 4226 foreach (explode('/', $path) as $i) { 4227 if (!is_array($root)) { 4228 return $false; 4229 } 4230 4231 if (!isset($root[$i])) { 4232 if (!$create) { 4233 return $false; 4234 } 4235 4236 $root[$i] = array(); 4237 } 4238 4239 $root = &$root[$i]; 4240 } 4241 4242 return $root; 4243 } 4244 4245 /** 4246 * Get a reference to an extension subarray 4247 * 4248 * @param array $root 4249 * @param string $path optional absolute path with / as component separator 4250 * @param bool $create optional 4251 * @access private 4252 * @return array|false 4253 */ 4254 function &_extensions(&$root, $path = null, $create = false) 4255 { 4256 if (!isset($root)) { 4257 $root = $this->currentCert; 4258 } 4259 4260 switch (true) { 4261 case !empty($path): 4262 case !is_array($root): 4263 break; 4264 case isset($root['tbsCertificate']): 4265 $path = 'tbsCertificate/extensions'; 4266 break; 4267 case isset($root['tbsCertList']): 4268 $path = 'tbsCertList/crlExtensions'; 4269 break; 4270 case isset($root['certificationRequestInfo']): 4271 $pth = 'certificationRequestInfo/attributes'; 4272 $attributes = &$this->_subArray($root, $pth, $create); 4273 4274 if (is_array($attributes)) { 4275 foreach ($attributes as $key => $value) { 4276 if ($value['type'] == 'pkcs-9-at-extensionRequest') { 4277 $path = "$pth/$key/value/0"; 4278 break 2; 4279 } 4280 } 4281 if ($create) { 4282 $key = count($attributes); 4283 $attributes[] = array('type' => 'pkcs-9-at-extensionRequest', 'value' => array()); 4284 $path = "$pth/$key/value/0"; 4285 } 4286 } 4287 break; 4288 } 4289 4290 $extensions = &$this->_subArray($root, $path, $create); 4291 4292 if (!is_array($extensions)) { 4293 $false = false; 4294 return $false; 4295 } 4296 4297 return $extensions; 4298 } 4299 4300 /** 4301 * Remove an Extension 4302 * 4303 * @param string $id 4304 * @param string $path optional 4305 * @access private 4306 * @return bool 4307 */ 4308 function _removeExtension($id, $path = null) 4309 { 4310 $extensions = &$this->_extensions($this->currentCert, $path); 4311 4312 if (!is_array($extensions)) { 4313 return false; 4314 } 4315 4316 $result = false; 4317 foreach ($extensions as $key => $value) { 4318 if ($value['extnId'] == $id) { 4319 unset($extensions[$key]); 4320 $result = true; 4321 } 4322 } 4323 4324 $extensions = array_values($extensions); 4325 // fix for https://bugs.php.net/75433 affecting PHP 7.2 4326 if (!isset($extensions[0])) { 4327 $extensions = array_splice($extensions, 0, 0); 4328 } 4329 return $result; 4330 } 4331 4332 /** 4333 * Get an Extension 4334 * 4335 * Returns the extension if it exists and false if not 4336 * 4337 * @param string $id 4338 * @param array $cert optional 4339 * @param string $path optional 4340 * @access private 4341 * @return mixed 4342 */ 4343 function _getExtension($id, $cert = null, $path = null) 4344 { 4345 $extensions = $this->_extensions($cert, $path); 4346 4347 if (!is_array($extensions)) { 4348 return false; 4349 } 4350 4351 foreach ($extensions as $key => $value) { 4352 if ($value['extnId'] == $id) { 4353 return $value['extnValue']; 4354 } 4355 } 4356 4357 return false; 4358 } 4359 4360 /** 4361 * Returns a list of all extensions in use 4362 * 4363 * @param array $cert optional 4364 * @param string $path optional 4365 * @access private 4366 * @return array 4367 */ 4368 function _getExtensions($cert = null, $path = null) 4369 { 4370 $exts = $this->_extensions($cert, $path); 4371 $extensions = array(); 4372 4373 if (is_array($exts)) { 4374 foreach ($exts as $extension) { 4375 $extensions[] = $extension['extnId']; 4376 } 4377 } 4378 4379 return $extensions; 4380 } 4381 4382 /** 4383 * Set an Extension 4384 * 4385 * @param string $id 4386 * @param mixed $value 4387 * @param bool $critical optional 4388 * @param bool $replace optional 4389 * @param string $path optional 4390 * @access private 4391 * @return bool 4392 */ 4393 function _setExtension($id, $value, $critical = false, $replace = true, $path = null) 4394 { 4395 $extensions = &$this->_extensions($this->currentCert, $path, true); 4396 4397 if (!is_array($extensions)) { 4398 return false; 4399 } 4400 4401 $newext = array('extnId' => $id, 'critical' => $critical, 'extnValue' => $value); 4402 4403 foreach ($extensions as $key => $value) { 4404 if ($value['extnId'] == $id) { 4405 if (!$replace) { 4406 return false; 4407 } 4408 4409 $extensions[$key] = $newext; 4410 return true; 4411 } 4412 } 4413 4414 $extensions[] = $newext; 4415 return true; 4416 } 4417 4418 /** 4419 * Remove a certificate, CSR or CRL Extension 4420 * 4421 * @param string $id 4422 * @access public 4423 * @return bool 4424 */ 4425 function removeExtension($id) 4426 { 4427 return $this->_removeExtension($id); 4428 } 4429 4430 /** 4431 * Get a certificate, CSR or CRL Extension 4432 * 4433 * Returns the extension if it exists and false if not 4434 * 4435 * @param string $id 4436 * @param array $cert optional 4437 * @access public 4438 * @return mixed 4439 */ 4440 function getExtension($id, $cert = null) 4441 { 4442 return $this->_getExtension($id, $cert); 4443 } 4444 4445 /** 4446 * Returns a list of all extensions in use in certificate, CSR or CRL 4447 * 4448 * @param array $cert optional 4449 * @access public 4450 * @return array 4451 */ 4452 function getExtensions($cert = null) 4453 { 4454 return $this->_getExtensions($cert); 4455 } 4456 4457 /** 4458 * Set a certificate, CSR or CRL Extension 4459 * 4460 * @param string $id 4461 * @param mixed $value 4462 * @param bool $critical optional 4463 * @param bool $replace optional 4464 * @access public 4465 * @return bool 4466 */ 4467 function setExtension($id, $value, $critical = false, $replace = true) 4468 { 4469 return $this->_setExtension($id, $value, $critical, $replace); 4470 } 4471 4472 /** 4473 * Remove a CSR attribute. 4474 * 4475 * @param string $id 4476 * @param int $disposition optional 4477 * @access public 4478 * @return bool 4479 */ 4480 function removeAttribute($id, $disposition = self::ATTR_ALL) 4481 { 4482 $attributes = &$this->_subArray($this->currentCert, 'certificationRequestInfo/attributes'); 4483 4484 if (!is_array($attributes)) { 4485 return false; 4486 } 4487 4488 $result = false; 4489 foreach ($attributes as $key => $attribute) { 4490 if ($attribute['type'] == $id) { 4491 $n = count($attribute['value']); 4492 switch (true) { 4493 case $disposition == self::ATTR_APPEND: 4494 case $disposition == self::ATTR_REPLACE: 4495 return false; 4496 case $disposition >= $n: 4497 $disposition -= $n; 4498 break; 4499 case $disposition == self::ATTR_ALL: 4500 case $n == 1: 4501 unset($attributes[$key]); 4502 $result = true; 4503 break; 4504 default: 4505 unset($attributes[$key]['value'][$disposition]); 4506 $attributes[$key]['value'] = array_values($attributes[$key]['value']); 4507 $result = true; 4508 break; 4509 } 4510 if ($result && $disposition != self::ATTR_ALL) { 4511 break; 4512 } 4513 } 4514 } 4515 4516 $attributes = array_values($attributes); 4517 return $result; 4518 } 4519 4520 /** 4521 * Get a CSR attribute 4522 * 4523 * Returns the attribute if it exists and false if not 4524 * 4525 * @param string $id 4526 * @param int $disposition optional 4527 * @param array $csr optional 4528 * @access public 4529 * @return mixed 4530 */ 4531 function getAttribute($id, $disposition = self::ATTR_ALL, $csr = null) 4532 { 4533 if (empty($csr)) { 4534 $csr = $this->currentCert; 4535 } 4536 4537 $attributes = $this->_subArray($csr, 'certificationRequestInfo/attributes'); 4538 4539 if (!is_array($attributes)) { 4540 return false; 4541 } 4542 4543 foreach ($attributes as $key => $attribute) { 4544 if ($attribute['type'] == $id) { 4545 $n = count($attribute['value']); 4546 switch (true) { 4547 case $disposition == self::ATTR_APPEND: 4548 case $disposition == self::ATTR_REPLACE: 4549 return false; 4550 case $disposition == self::ATTR_ALL: 4551 return $attribute['value']; 4552 case $disposition >= $n: 4553 $disposition -= $n; 4554 break; 4555 default: 4556 return $attribute['value'][$disposition]; 4557 } 4558 } 4559 } 4560 4561 return false; 4562 } 4563 4564 /** 4565 * Returns a list of all CSR attributes in use 4566 * 4567 * @param array $csr optional 4568 * @access public 4569 * @return array 4570 */ 4571 function getAttributes($csr = null) 4572 { 4573 if (empty($csr)) { 4574 $csr = $this->currentCert; 4575 } 4576 4577 $attributes = $this->_subArray($csr, 'certificationRequestInfo/attributes'); 4578 $attrs = array(); 4579 4580 if (is_array($attributes)) { 4581 foreach ($attributes as $attribute) { 4582 $attrs[] = $attribute['type']; 4583 } 4584 } 4585 4586 return $attrs; 4587 } 4588 4589 /** 4590 * Set a CSR attribute 4591 * 4592 * @param string $id 4593 * @param mixed $value 4594 * @param bool $disposition optional 4595 * @access public 4596 * @return bool 4597 */ 4598 function setAttribute($id, $value, $disposition = self::ATTR_ALL) 4599 { 4600 $attributes = &$this->_subArray($this->currentCert, 'certificationRequestInfo/attributes', true); 4601 4602 if (!is_array($attributes)) { 4603 return false; 4604 } 4605 4606 switch ($disposition) { 4607 case self::ATTR_REPLACE: 4608 $disposition = self::ATTR_APPEND; 4609 case self::ATTR_ALL: 4610 $this->removeAttribute($id); 4611 break; 4612 } 4613 4614 foreach ($attributes as $key => $attribute) { 4615 if ($attribute['type'] == $id) { 4616 $n = count($attribute['value']); 4617 switch (true) { 4618 case $disposition == self::ATTR_APPEND: 4619 $last = $key; 4620 break; 4621 case $disposition >= $n: 4622 $disposition -= $n; 4623 break; 4624 default: 4625 $attributes[$key]['value'][$disposition] = $value; 4626 return true; 4627 } 4628 } 4629 } 4630 4631 switch (true) { 4632 case $disposition >= 0: 4633 return false; 4634 case isset($last): 4635 $attributes[$last]['value'][] = $value; 4636 break; 4637 default: 4638 $attributes[] = array('type' => $id, 'value' => $disposition == self::ATTR_ALL ? $value: array($value)); 4639 break; 4640 } 4641 4642 return true; 4643 } 4644 4645 /** 4646 * Sets the subject key identifier 4647 * 4648 * This is used by the id-ce-authorityKeyIdentifier and the id-ce-subjectKeyIdentifier extensions. 4649 * 4650 * @param string $value 4651 * @access public 4652 */ 4653 function setKeyIdentifier($value) 4654 { 4655 if (empty($value)) { 4656 unset($this->currentKeyIdentifier); 4657 } else { 4658 $this->currentKeyIdentifier = base64_encode($value); 4659 } 4660 } 4661 4662 /** 4663 * Compute a public key identifier. 4664 * 4665 * Although key identifiers may be set to any unique value, this function 4666 * computes key identifiers from public key according to the two 4667 * recommended methods (4.2.1.2 RFC 3280). 4668 * Highly polymorphic: try to accept all possible forms of key: 4669 * - Key object 4670 * - \phpseclib\File\X509 object with public or private key defined 4671 * - Certificate or CSR array 4672 * - \phpseclib\File\ASN1\Element object 4673 * - PEM or DER string 4674 * 4675 * @param mixed $key optional 4676 * @param int $method optional 4677 * @access public 4678 * @return string binary key identifier 4679 */ 4680 function computeKeyIdentifier($key = null, $method = 1) 4681 { 4682 if (is_null($key)) { 4683 $key = $this; 4684 } 4685 4686 switch (true) { 4687 case is_string($key): 4688 break; 4689 case is_array($key) && isset($key['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']): 4690 return $this->computeKeyIdentifier($key['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'], $method); 4691 case is_array($key) && isset($key['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']): 4692 return $this->computeKeyIdentifier($key['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'], $method); 4693 case !is_object($key): 4694 return false; 4695 case $key instanceof Element: 4696 // Assume the element is a bitstring-packed key. 4697 $asn1 = new ASN1(); 4698 $decoded = $asn1->decodeBER($key->element); 4699 if (empty($decoded)) { 4700 return false; 4701 } 4702 $raw = $asn1->asn1map($decoded[0], array('type' => ASN1::TYPE_BIT_STRING)); 4703 if (empty($raw)) { 4704 return false; 4705 } 4706 $raw = base64_decode($raw); 4707 // If the key is private, compute identifier from its corresponding public key. 4708 $key = new RSA(); 4709 if (!$key->loadKey($raw)) { 4710 return false; // Not an unencrypted RSA key. 4711 } 4712 if ($key->getPrivateKey() !== false) { // If private. 4713 return $this->computeKeyIdentifier($key, $method); 4714 } 4715 $key = $raw; // Is a public key. 4716 break; 4717 case $key instanceof X509: 4718 if (isset($key->publicKey)) { 4719 return $this->computeKeyIdentifier($key->publicKey, $method); 4720 } 4721 if (isset($key->privateKey)) { 4722 return $this->computeKeyIdentifier($key->privateKey, $method); 4723 } 4724 if (isset($key->currentCert['tbsCertificate']) || isset($key->currentCert['certificationRequestInfo'])) { 4725 return $this->computeKeyIdentifier($key->currentCert, $method); 4726 } 4727 return false; 4728 default: // Should be a key object (i.e.: \phpseclib\Crypt\RSA). 4729 $key = $key->getPublicKey(RSA::PUBLIC_FORMAT_PKCS1); 4730 break; 4731 } 4732 4733 // If in PEM format, convert to binary. 4734 $key = $this->_extractBER($key); 4735 4736 // Now we have the key string: compute its sha-1 sum. 4737 $hash = new Hash('sha1'); 4738 $hash = $hash->hash($key); 4739 4740 if ($method == 2) { 4741 $hash = substr($hash, -8); 4742 $hash[0] = chr((ord($hash[0]) & 0x0F) | 0x40); 4743 } 4744 4745 return $hash; 4746 }