[ 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 ref $root 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 ref $root 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 ref $root 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 ref $root 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 ref $root 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 ref $root 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 $csr 3199 * @access public 3200 * @return mixed 3201 */ 3202 function loadCSR($csr, $mode = self::FORMAT_AUTO_DETECT) 3203 { 3204 if (is_array($csr) && isset($csr['certificationRequestInfo'])) { 3205 unset($this->currentCert); 3206 unset($this->currentKeyIdentifier); 3207 unset($this->signatureSubject); 3208 $this->dn = $csr['certificationRequestInfo']['subject']; 3209 if (!isset($this->dn)) { 3210 return false; 3211 } 3212 3213 $this->currentCert = $csr; 3214 return $csr; 3215 } 3216 3217 // see http://tools.ietf.org/html/rfc2986 3218 3219 $asn1 = new ASN1(); 3220 3221 if ($mode != self::FORMAT_DER) { 3222 $newcsr = $this->_extractBER($csr); 3223 if ($mode == self::FORMAT_PEM && $csr == $newcsr) { 3224 return false; 3225 } 3226 $csr = $newcsr; 3227 } 3228 $orig = $csr; 3229 3230 if ($csr === false) { 3231 $this->currentCert = false; 3232 return false; 3233 } 3234 3235 $asn1->loadOIDs($this->oids); 3236 $decoded = $asn1->decodeBER($csr); 3237 3238 if (empty($decoded)) { 3239 $this->currentCert = false; 3240 return false; 3241 } 3242 3243 $csr = $asn1->asn1map($decoded[0], $this->CertificationRequest); 3244 if (!isset($csr) || $csr === false) { 3245 $this->currentCert = false; 3246 return false; 3247 } 3248 3249 $this->_mapInAttributes($csr, 'certificationRequestInfo/attributes', $asn1); 3250 $this->_mapInDNs($csr, 'certificationRequestInfo/subject/rdnSequence', $asn1); 3251 3252 $this->dn = $csr['certificationRequestInfo']['subject']; 3253 3254 $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']); 3255 3256 $algorithm = &$csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm']; 3257 $key = &$csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']; 3258 $key = $this->_reformatKey($algorithm, $key); 3259 3260 switch ($algorithm) { 3261 case 'rsaEncryption': 3262 $this->publicKey = new RSA(); 3263 $this->publicKey->loadKey($key); 3264 $this->publicKey->setPublicKey(); 3265 break; 3266 default: 3267 $this->publicKey = null; 3268 } 3269 3270 $this->currentKeyIdentifier = null; 3271 $this->currentCert = $csr; 3272 3273 return $csr; 3274 } 3275 3276 /** 3277 * Save CSR request 3278 * 3279 * @param array $csr 3280 * @param int $format optional 3281 * @access public 3282 * @return string 3283 */ 3284 function saveCSR($csr, $format = self::FORMAT_PEM) 3285 { 3286 if (!is_array($csr) || !isset($csr['certificationRequestInfo'])) { 3287 return false; 3288 } 3289 3290 switch (true) { 3291 case !($algorithm = $this->_subArray($csr, 'certificationRequestInfo/subjectPKInfo/algorithm/algorithm')): 3292 case is_object($csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']): 3293 break; 3294 default: 3295 switch ($algorithm) { 3296 case 'rsaEncryption': 3297 $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'] 3298 = base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']))); 3299 $csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['parameters'] = null; 3300 $csr['signatureAlgorithm']['parameters'] = null; 3301 $csr['certificationRequestInfo']['signature']['parameters'] = null; 3302 } 3303 } 3304 3305 $asn1 = new ASN1(); 3306 3307 $asn1->loadOIDs($this->oids); 3308 3309 $filters = array(); 3310 $filters['certificationRequestInfo']['subject']['rdnSequence']['value'] 3311 = array('type' => ASN1::TYPE_UTF8_STRING); 3312 3313 $asn1->loadFilters($filters); 3314 3315 $this->_mapOutDNs($csr, 'certificationRequestInfo/subject/rdnSequence', $asn1); 3316 $this->_mapOutAttributes($csr, 'certificationRequestInfo/attributes', $asn1); 3317 $csr = $asn1->encodeDER($csr, $this->CertificationRequest); 3318 3319 switch ($format) { 3320 case self::FORMAT_DER: 3321 return $csr; 3322 // case self::FORMAT_PEM: 3323 default: 3324 return "-----BEGIN CERTIFICATE REQUEST-----\r\n" . chunk_split(base64_encode($csr), 64) . '-----END CERTIFICATE REQUEST-----'; 3325 } 3326 } 3327 3328 /** 3329 * Load a SPKAC CSR 3330 * 3331 * SPKAC's are produced by the HTML5 keygen element: 3332 * 3333 * https://developer.mozilla.org/en-US/docs/HTML/Element/keygen 3334 * 3335 * @param string $csr 3336 * @access public 3337 * @return mixed 3338 */ 3339 function loadSPKAC($spkac) 3340 { 3341 if (is_array($spkac) && isset($spkac['publicKeyAndChallenge'])) { 3342 unset($this->currentCert); 3343 unset($this->currentKeyIdentifier); 3344 unset($this->signatureSubject); 3345 $this->currentCert = $spkac; 3346 return $spkac; 3347 } 3348 3349 // see http://www.w3.org/html/wg/drafts/html/master/forms.html#signedpublickeyandchallenge 3350 3351 $asn1 = new ASN1(); 3352 3353 // OpenSSL produces SPKAC's that are preceded by the string SPKAC= 3354 $temp = preg_replace('#(?:SPKAC=)|[ \r\n\\\]#', '', $spkac); 3355 $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false; 3356 if ($temp != false) { 3357 $spkac = $temp; 3358 } 3359 $orig = $spkac; 3360 3361 if ($spkac === false) { 3362 $this->currentCert = false; 3363 return false; 3364 } 3365 3366 $asn1->loadOIDs($this->oids); 3367 $decoded = $asn1->decodeBER($spkac); 3368 3369 if (empty($decoded)) { 3370 $this->currentCert = false; 3371 return false; 3372 } 3373 3374 $spkac = $asn1->asn1map($decoded[0], $this->SignedPublicKeyAndChallenge); 3375 3376 if (!isset($spkac) || $spkac === false) { 3377 $this->currentCert = false; 3378 return false; 3379 } 3380 3381 $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']); 3382 3383 $algorithm = &$spkac['publicKeyAndChallenge']['spki']['algorithm']['algorithm']; 3384 $key = &$spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']; 3385 $key = $this->_reformatKey($algorithm, $key); 3386 3387 switch ($algorithm) { 3388 case 'rsaEncryption': 3389 $this->publicKey = new RSA(); 3390 $this->publicKey->loadKey($key); 3391 $this->publicKey->setPublicKey(); 3392 break; 3393 default: 3394 $this->publicKey = null; 3395 } 3396 3397 $this->currentKeyIdentifier = null; 3398 $this->currentCert = $spkac; 3399 3400 return $spkac; 3401 } 3402 3403 /** 3404 * Save a SPKAC CSR request 3405 * 3406 * @param array $csr 3407 * @param int $format optional 3408 * @access public 3409 * @return string 3410 */ 3411 function saveSPKAC($spkac, $format = self::FORMAT_PEM) 3412 { 3413 if (!is_array($spkac) || !isset($spkac['publicKeyAndChallenge'])) { 3414 return false; 3415 } 3416 3417 $algorithm = $this->_subArray($spkac, 'publicKeyAndChallenge/spki/algorithm/algorithm'); 3418 switch (true) { 3419 case !$algorithm: 3420 case is_object($spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']): 3421 break; 3422 default: 3423 switch ($algorithm) { 3424 case 'rsaEncryption': 3425 $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'] 3426 = base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']))); 3427 } 3428 } 3429 3430 $asn1 = new ASN1(); 3431 3432 $asn1->loadOIDs($this->oids); 3433 $spkac = $asn1->encodeDER($spkac, $this->SignedPublicKeyAndChallenge); 3434 3435 switch ($format) { 3436 case self::FORMAT_DER: 3437 return $spkac; 3438 // case self::FORMAT_PEM: 3439 default: 3440 // OpenSSL's implementation of SPKAC requires the SPKAC be preceded by SPKAC= and since there are pretty much 3441 // no other SPKAC decoders phpseclib will use that same format 3442 return 'SPKAC=' . base64_encode($spkac); 3443 } 3444 } 3445 3446 /** 3447 * Load a Certificate Revocation List 3448 * 3449 * @param string $crl 3450 * @access public 3451 * @return mixed 3452 */ 3453 function loadCRL($crl, $mode = self::FORMAT_AUTO_DETECT) 3454 { 3455 if (is_array($crl) && isset($crl['tbsCertList'])) { 3456 $this->currentCert = $crl; 3457 unset($this->signatureSubject); 3458 return $crl; 3459 } 3460 3461 $asn1 = new ASN1(); 3462 3463 if ($mode != self::FORMAT_DER) { 3464 $newcrl = $this->_extractBER($crl); 3465 if ($mode == self::FORMAT_PEM && $crl == $newcrl) { 3466 return false; 3467 } 3468 $crl = $newcrl; 3469 } 3470 $orig = $crl; 3471 3472 if ($crl === false) { 3473 $this->currentCert = false; 3474 return false; 3475 } 3476 3477 $asn1->loadOIDs($this->oids); 3478 $decoded = $asn1->decodeBER($crl); 3479 3480 if (empty($decoded)) { 3481 $this->currentCert = false; 3482 return false; 3483 } 3484 3485 $crl = $asn1->asn1map($decoded[0], $this->CertificateList); 3486 if (!isset($crl) || $crl === false) { 3487 $this->currentCert = false; 3488 return false; 3489 } 3490 3491 $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']); 3492 3493 $this->_mapInDNs($crl, 'tbsCertList/issuer/rdnSequence', $asn1); 3494 if ($this->_isSubArrayValid($crl, 'tbsCertList/crlExtensions')) { 3495 $this->_mapInExtensions($crl, 'tbsCertList/crlExtensions', $asn1); 3496 } 3497 if ($this->_isSubArrayValid($crl, 'tbsCertList/revokedCertificates')) { 3498 $rclist_ref = &$this->_subArrayUnchecked($crl, 'tbsCertList/revokedCertificates'); 3499 if ($rclist_ref) { 3500 $rclist = $crl['tbsCertList']['revokedCertificates']; 3501 foreach ($rclist as $i => $extension) { 3502 if ($this->_isSubArrayValid($rclist, "$i/crlEntryExtensions", $asn1)) { 3503 $this->_mapInExtensions($rclist_ref, "$i/crlEntryExtensions", $asn1); 3504 } 3505 } 3506 } 3507 } 3508 3509 $this->currentKeyIdentifier = null; 3510 $this->currentCert = $crl; 3511 3512 return $crl; 3513 } 3514 3515 /** 3516 * Save Certificate Revocation List. 3517 * 3518 * @param array $crl 3519 * @param int $format optional 3520 * @access public 3521 * @return string 3522 */ 3523 function saveCRL($crl, $format = self::FORMAT_PEM) 3524 { 3525 if (!is_array($crl) || !isset($crl['tbsCertList'])) { 3526 return false; 3527 } 3528 3529 $asn1 = new ASN1(); 3530 3531 $asn1->loadOIDs($this->oids); 3532 3533 $filters = array(); 3534 $filters['tbsCertList']['issuer']['rdnSequence']['value'] 3535 = array('type' => ASN1::TYPE_UTF8_STRING); 3536 $filters['tbsCertList']['signature']['parameters'] 3537 = array('type' => ASN1::TYPE_UTF8_STRING); 3538 $filters['signatureAlgorithm']['parameters'] 3539 = array('type' => ASN1::TYPE_UTF8_STRING); 3540 3541 if (empty($crl['tbsCertList']['signature']['parameters'])) { 3542 $filters['tbsCertList']['signature']['parameters'] 3543 = array('type' => ASN1::TYPE_NULL); 3544 } 3545 3546 if (empty($crl['signatureAlgorithm']['parameters'])) { 3547 $filters['signatureAlgorithm']['parameters'] 3548 = array('type' => ASN1::TYPE_NULL); 3549 } 3550 3551 $asn1->loadFilters($filters); 3552 3553 $this->_mapOutDNs($crl, 'tbsCertList/issuer/rdnSequence', $asn1); 3554 $this->_mapOutExtensions($crl, 'tbsCertList/crlExtensions', $asn1); 3555 $rclist = &$this->_subArray($crl, 'tbsCertList/revokedCertificates'); 3556 if (is_array($rclist)) { 3557 foreach ($rclist as $i => $extension) { 3558 $this->_mapOutExtensions($rclist, "$i/crlEntryExtensions", $asn1); 3559 } 3560 } 3561 3562 $crl = $asn1->encodeDER($crl, $this->CertificateList); 3563 3564 switch ($format) { 3565 case self::FORMAT_DER: 3566 return $crl; 3567 // case self::FORMAT_PEM: 3568 default: 3569 return "-----BEGIN X509 CRL-----\r\n" . chunk_split(base64_encode($crl), 64) . '-----END X509 CRL-----'; 3570 } 3571 } 3572 3573 /** 3574 * Helper function to build a time field according to RFC 3280 section 3575 * - 4.1.2.5 Validity 3576 * - 5.1.2.4 This Update 3577 * - 5.1.2.5 Next Update 3578 * - 5.1.2.6 Revoked Certificates 3579 * by choosing utcTime iff year of date given is before 2050 and generalTime else. 3580 * 3581 * @param string $date in format date('D, d M Y H:i:s O') 3582 * @access private 3583 * @return array 3584 */ 3585 function _timeField($date) 3586 { 3587 if ($date instanceof Element) { 3588 return $date; 3589 } 3590 $dateObj = new DateTime($date, new DateTimeZone('GMT')); 3591 $year = $dateObj->format('Y'); // the same way ASN1.php parses this 3592 if ($year < 2050) { 3593 return array('utcTime' => $date); 3594 } else { 3595 return array('generalTime' => $date); 3596 } 3597 } 3598 3599 /** 3600 * Sign an X.509 certificate 3601 * 3602 * $issuer's private key needs to be loaded. 3603 * $subject can be either an existing X.509 cert (if you want to resign it), 3604 * a CSR or something with the DN and public key explicitly set. 3605 * 3606 * @param \phpseclib\File\X509 $issuer 3607 * @param \phpseclib\File\X509 $subject 3608 * @param string $signatureAlgorithm optional 3609 * @access public 3610 * @return mixed 3611 */ 3612 function sign($issuer, $subject, $signatureAlgorithm = 'sha1WithRSAEncryption') 3613 { 3614 if (!is_object($issuer->privateKey) || empty($issuer->dn)) { 3615 return false; 3616 } 3617 3618 if (isset($subject->publicKey) && !($subjectPublicKey = $subject->_formatSubjectPublicKey())) { 3619 return false; 3620 } 3621 3622 $currentCert = isset($this->currentCert) ? $this->currentCert : null; 3623 $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null; 3624 3625 if (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertificate'])) { 3626 $this->currentCert = $subject->currentCert; 3627 $this->currentCert['tbsCertificate']['signature']['algorithm'] = $signatureAlgorithm; 3628 $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm; 3629 3630 if (!empty($this->startDate)) { 3631 $this->currentCert['tbsCertificate']['validity']['notBefore'] = $this->_timeField($this->startDate); 3632 } 3633 if (!empty($this->endDate)) { 3634 $this->currentCert['tbsCertificate']['validity']['notAfter'] = $this->_timeField($this->endDate); 3635 } 3636 if (!empty($this->serialNumber)) { 3637 $this->currentCert['tbsCertificate']['serialNumber'] = $this->serialNumber; 3638 } 3639 if (!empty($subject->dn)) { 3640 $this->currentCert['tbsCertificate']['subject'] = $subject->dn; 3641 } 3642 if (!empty($subject->publicKey)) { 3643 $this->currentCert['tbsCertificate']['subjectPublicKeyInfo'] = $subjectPublicKey; 3644 } 3645 $this->removeExtension('id-ce-authorityKeyIdentifier'); 3646 if (isset($subject->domains)) { 3647 $this->removeExtension('id-ce-subjectAltName'); 3648 } 3649 } elseif (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertList'])) { 3650 return false; 3651 } else { 3652 if (!isset($subject->publicKey)) { 3653 return false; 3654 } 3655 3656 $startDate = new DateTime('now', new DateTimeZone(@date_default_timezone_get())); 3657 $startDate = !empty($this->startDate) ? $this->startDate : $startDate->format('D, d M Y H:i:s O'); 3658 3659 $endDate = new DateTime('+1 year', new DateTimeZone(@date_default_timezone_get())); 3660 $endDate = !empty($this->endDate) ? $this->endDate : $endDate->format('D, d M Y H:i:s O'); 3661 3662 /* "The serial number MUST be a positive integer" 3663 "Conforming CAs MUST NOT use serialNumber values longer than 20 octets." 3664 -- https://tools.ietf.org/html/rfc5280#section-4.1.2.2 3665 3666 for the integer to be positive the leading bit needs to be 0 hence the 3667 application of a bitmap 3668 */ 3669 $serialNumber = !empty($this->serialNumber) ? 3670 $this->serialNumber : 3671 new BigInteger(Random::string(20) & ("\x7F" . str_repeat("\xFF", 19)), 256); 3672 3673 $this->currentCert = array( 3674 'tbsCertificate' => 3675 array( 3676 'version' => 'v3', 3677 'serialNumber' => $serialNumber, // $this->setSerialNumber() 3678 'signature' => array('algorithm' => $signatureAlgorithm), 3679 'issuer' => false, // this is going to be overwritten later 3680 'validity' => array( 3681 'notBefore' => $this->_timeField($startDate), // $this->setStartDate() 3682 'notAfter' => $this->_timeField($endDate) // $this->setEndDate() 3683 ), 3684 'subject' => $subject->dn, 3685 'subjectPublicKeyInfo' => $subjectPublicKey 3686 ), 3687 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm), 3688 'signature' => false // this is going to be overwritten later 3689 ); 3690 3691 // Copy extensions from CSR. 3692 $csrexts = $subject->getAttribute('pkcs-9-at-extensionRequest', 0); 3693 3694 if (!empty($csrexts)) { 3695 $this->currentCert['tbsCertificate']['extensions'] = $csrexts; 3696 } 3697 } 3698 3699 $this->currentCert['tbsCertificate']['issuer'] = $issuer->dn; 3700 3701 if (isset($issuer->currentKeyIdentifier)) { 3702 $this->setExtension('id-ce-authorityKeyIdentifier', array( 3703 //'authorityCertIssuer' => array( 3704 // array( 3705 // 'directoryName' => $issuer->dn 3706 // ) 3707 //), 3708 'keyIdentifier' => $issuer->currentKeyIdentifier 3709 )); 3710 //$extensions = &$this->currentCert['tbsCertificate']['extensions']; 3711 //if (isset($issuer->serialNumber)) { 3712 // $extensions[count($extensions) - 1]['authorityCertSerialNumber'] = $issuer->serialNumber; 3713 //} 3714 //unset($extensions); 3715 } 3716 3717 if (isset($subject->currentKeyIdentifier)) { 3718 $this->setExtension('id-ce-subjectKeyIdentifier', $subject->currentKeyIdentifier); 3719 } 3720 3721 $altName = array(); 3722 3723 if (isset($subject->domains) && count($subject->domains)) { 3724 $altName = array_map(array('\phpseclib\File\X509', '_dnsName'), $subject->domains); 3725 } 3726 3727 if (isset($subject->ipAddresses) && count($subject->ipAddresses)) { 3728 // should an IP address appear as the CN if no domain name is specified? idk 3729 //$ips = count($subject->domains) ? $subject->ipAddresses : array_slice($subject->ipAddresses, 1); 3730 $ipAddresses = array(); 3731 foreach ($subject->ipAddresses as $ipAddress) { 3732 $encoded = $subject->_ipAddress($ipAddress); 3733 if ($encoded !== false) { 3734 $ipAddresses[] = $encoded; 3735 } 3736 } 3737 if (count($ipAddresses)) { 3738 $altName = array_merge($altName, $ipAddresses); 3739 } 3740 } 3741 3742 if (!empty($altName)) { 3743 $this->setExtension('id-ce-subjectAltName', $altName); 3744 } 3745 3746 if ($this->caFlag) { 3747 $keyUsage = $this->getExtension('id-ce-keyUsage'); 3748 if (!$keyUsage) { 3749 $keyUsage = array(); 3750 } 3751 3752 $this->setExtension( 3753 'id-ce-keyUsage', 3754 array_values(array_unique(array_merge($keyUsage, array('cRLSign', 'keyCertSign')))) 3755 ); 3756 3757 $basicConstraints = $this->getExtension('id-ce-basicConstraints'); 3758 if (!$basicConstraints) { 3759 $basicConstraints = array(); 3760 } 3761 3762 $this->setExtension( 3763 'id-ce-basicConstraints', 3764 array_unique(array_merge(array('cA' => true), $basicConstraints)), 3765 true 3766 ); 3767 3768 if (!isset($subject->currentKeyIdentifier)) { 3769 $this->setExtension('id-ce-subjectKeyIdentifier', base64_encode($this->computeKeyIdentifier($this->currentCert)), false, false); 3770 } 3771 } 3772 3773 // resync $this->signatureSubject 3774 // save $tbsCertificate in case there are any \phpseclib\File\ASN1\Element objects in it 3775 $tbsCertificate = $this->currentCert['tbsCertificate']; 3776 $this->loadX509($this->saveX509($this->currentCert)); 3777 3778 $result = $this->_sign($issuer->privateKey, $signatureAlgorithm); 3779 $result['tbsCertificate'] = $tbsCertificate; 3780 3781 $this->currentCert = $currentCert; 3782 $this->signatureSubject = $signatureSubject; 3783 3784 return $result; 3785 } 3786 3787 /** 3788 * Sign a CSR 3789 * 3790 * @access public 3791 * @return mixed 3792 */ 3793 function signCSR($signatureAlgorithm = 'sha1WithRSAEncryption') 3794 { 3795 if (!is_object($this->privateKey) || empty($this->dn)) { 3796 return false; 3797 } 3798 3799 $origPublicKey = $this->publicKey; 3800 $class = get_class($this->privateKey); 3801 $this->publicKey = new $class(); 3802 $this->publicKey->loadKey($this->privateKey->getPublicKey()); 3803 $this->publicKey->setPublicKey(); 3804 if (!($publicKey = $this->_formatSubjectPublicKey())) { 3805 return false; 3806 } 3807 $this->publicKey = $origPublicKey; 3808 3809 $currentCert = isset($this->currentCert) ? $this->currentCert : null; 3810 $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null; 3811 3812 if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['certificationRequestInfo'])) { 3813 $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm; 3814 if (!empty($this->dn)) { 3815 $this->currentCert['certificationRequestInfo']['subject'] = $this->dn; 3816 } 3817 $this->currentCert['certificationRequestInfo']['subjectPKInfo'] = $publicKey; 3818 } else { 3819 $this->currentCert = array( 3820 'certificationRequestInfo' => 3821 array( 3822 'version' => 'v1', 3823 'subject' => $this->dn, 3824 'subjectPKInfo' => $publicKey 3825 ), 3826 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm), 3827 'signature' => false // this is going to be overwritten later 3828 ); 3829 } 3830 3831 // resync $this->signatureSubject 3832 // save $certificationRequestInfo in case there are any \phpseclib\File\ASN1\Element objects in it 3833 $certificationRequestInfo = $this->currentCert['certificationRequestInfo']; 3834 $this->loadCSR($this->saveCSR($this->currentCert)); 3835 3836 $result = $this->_sign($this->privateKey, $signatureAlgorithm); 3837 $result['certificationRequestInfo'] = $certificationRequestInfo; 3838 3839 $this->currentCert = $currentCert; 3840 $this->signatureSubject = $signatureSubject; 3841 3842 return $result; 3843 } 3844 3845 /** 3846 * Sign a SPKAC 3847 * 3848 * @access public 3849 * @return mixed 3850 */ 3851 function signSPKAC($signatureAlgorithm = 'sha1WithRSAEncryption') 3852 { 3853 if (!is_object($this->privateKey)) { 3854 return false; 3855 } 3856 3857 $origPublicKey = $this->publicKey; 3858 $class = get_class($this->privateKey); 3859 $this->publicKey = new $class(); 3860 $this->publicKey->loadKey($this->privateKey->getPublicKey()); 3861 $this->publicKey->setPublicKey(); 3862 $publicKey = $this->_formatSubjectPublicKey(); 3863 if (!$publicKey) { 3864 return false; 3865 } 3866 $this->publicKey = $origPublicKey; 3867 3868 $currentCert = isset($this->currentCert) ? $this->currentCert : null; 3869 $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null; 3870 3871 // re-signing a SPKAC seems silly but since everything else supports re-signing why not? 3872 if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['publicKeyAndChallenge'])) { 3873 $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm; 3874 $this->currentCert['publicKeyAndChallenge']['spki'] = $publicKey; 3875 if (!empty($this->challenge)) { 3876 // the bitwise AND ensures that the output is a valid IA5String 3877 $this->currentCert['publicKeyAndChallenge']['challenge'] = $this->challenge & str_repeat("\x7F", strlen($this->challenge)); 3878 } 3879 } else { 3880 $this->currentCert = array( 3881 'publicKeyAndChallenge' => 3882 array( 3883 'spki' => $publicKey, 3884 // quoting <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/keygen>, 3885 // "A challenge string that is submitted along with the public key. Defaults to an empty string if not specified." 3886 // both Firefox and OpenSSL ("openssl spkac -key private.key") behave this way 3887 // we could alternatively do this instead if we ignored the specs: 3888 // Random::string(8) & str_repeat("\x7F", 8) 3889 'challenge' => !empty($this->challenge) ? $this->challenge : '' 3890 ), 3891 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm), 3892 'signature' => false // this is going to be overwritten later 3893 ); 3894 } 3895 3896 // resync $this->signatureSubject 3897 // save $publicKeyAndChallenge in case there are any \phpseclib\File\ASN1\Element objects in it 3898 $publicKeyAndChallenge = $this->currentCert['publicKeyAndChallenge']; 3899 $this->loadSPKAC($this->saveSPKAC($this->currentCert)); 3900 3901 $result = $this->_sign($this->privateKey, $signatureAlgorithm); 3902 $result['publicKeyAndChallenge'] = $publicKeyAndChallenge; 3903 3904 $this->currentCert = $currentCert; 3905 $this->signatureSubject = $signatureSubject; 3906 3907 return $result; 3908 } 3909 3910 /** 3911 * Sign a CRL 3912 * 3913 * $issuer's private key needs to be loaded. 3914 * 3915 * @param \phpseclib\File\X509 $issuer 3916 * @param \phpseclib\File\X509 $crl 3917 * @param string $signatureAlgorithm optional 3918 * @access public 3919 * @return mixed 3920 */ 3921 function signCRL($issuer, $crl, $signatureAlgorithm = 'sha1WithRSAEncryption') 3922 { 3923 if (!is_object($issuer->privateKey) || empty($issuer->dn)) { 3924 return false; 3925 } 3926 3927 $currentCert = isset($this->currentCert) ? $this->currentCert : null; 3928 $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null; 3929 3930 $thisUpdate = new DateTime('now', new DateTimeZone(@date_default_timezone_get())); 3931 $thisUpdate = !empty($this->startDate) ? $this->startDate : $thisUpdate->format('D, d M Y H:i:s O'); 3932 3933 if (isset($crl->currentCert) && is_array($crl->currentCert) && isset($crl->currentCert['tbsCertList'])) { 3934 $this->currentCert = $crl->currentCert; 3935 $this->currentCert['tbsCertList']['signature']['algorithm'] = $signatureAlgorithm; 3936 $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm; 3937 } else { 3938 $this->currentCert = array( 3939 'tbsCertList' => 3940 array( 3941 'version' => 'v2', 3942 'signature' => array('algorithm' => $signatureAlgorithm), 3943 'issuer' => false, // this is going to be overwritten later 3944 'thisUpdate' => $this->_timeField($thisUpdate) // $this->setStartDate() 3945 ), 3946 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm), 3947 'signature' => false // this is going to be overwritten later 3948 ); 3949 } 3950 3951 $tbsCertList = &$this->currentCert['tbsCertList']; 3952 $tbsCertList['issuer'] = $issuer->dn; 3953 $tbsCertList['thisUpdate'] = $this->_timeField($thisUpdate); 3954 3955 if (!empty($this->endDate)) { 3956 $tbsCertList['nextUpdate'] = $this->_timeField($this->endDate); // $this->setEndDate() 3957 } else { 3958 unset($tbsCertList['nextUpdate']); 3959 } 3960 3961 if (!empty($this->serialNumber)) { 3962 $crlNumber = $this->serialNumber; 3963 } else { 3964 $crlNumber = $this->getExtension('id-ce-cRLNumber'); 3965 // "The CRL number is a non-critical CRL extension that conveys a 3966 // monotonically increasing sequence number for a given CRL scope and 3967 // CRL issuer. This extension allows users to easily determine when a 3968 // particular CRL supersedes another CRL." 3969 // -- https://tools.ietf.org/html/rfc5280#section-5.2.3 3970 $crlNumber = $crlNumber !== false ? $crlNumber->add(new BigInteger(1)) : null; 3971 } 3972 3973 $this->removeExtension('id-ce-authorityKeyIdentifier'); 3974 $this->removeExtension('id-ce-issuerAltName'); 3975 3976 // Be sure version >= v2 if some extension found. 3977 $version = isset($tbsCertList['version']) ? $tbsCertList['version'] : 0; 3978 if (!$version) { 3979 if (!empty($tbsCertList['crlExtensions'])) { 3980 $version = 1; // v2. 3981 } elseif (!empty($tbsCertList['revokedCertificates'])) { 3982 foreach ($tbsCertList['revokedCertificates'] as $cert) { 3983 if (!empty($cert['crlEntryExtensions'])) { 3984 $version = 1; // v2. 3985 } 3986 } 3987 } 3988 3989 if ($version) { 3990 $tbsCertList['version'] = $version; 3991 } 3992 } 3993 3994 // Store additional extensions. 3995 if (!empty($tbsCertList['version'])) { // At least v2. 3996 if (!empty($crlNumber)) { 3997 $this->setExtension('id-ce-cRLNumber', $crlNumber); 3998 } 3999 4000 if (isset($issuer->currentKeyIdentifier)) { 4001 $this->setExtension('id-ce-authorityKeyIdentifier', array( 4002 //'authorityCertIssuer' => array( 4003 // array( 4004 // 'directoryName' => $issuer->dn 4005 // ) 4006 //), 4007 'keyIdentifier' => $issuer->currentKeyIdentifier 4008 )); 4009 //$extensions = &$tbsCertList['crlExtensions']; 4010 //if (isset($issuer->serialNumber)) { 4011 // $extensions[count($extensions) - 1]['authorityCertSerialNumber'] = $issuer->serialNumber; 4012 //} 4013 //unset($extensions); 4014 } 4015 4016 $issuerAltName = $this->getExtension('id-ce-subjectAltName', $issuer->currentCert); 4017 4018 if ($issuerAltName !== false) { 4019 $this->setExtension('id-ce-issuerAltName', $issuerAltName); 4020 } 4021 } 4022 4023 if (empty($tbsCertList['revokedCertificates'])) { 4024 unset($tbsCertList['revokedCertificates']); 4025 } 4026 4027 unset($tbsCertList); 4028 4029 // resync $this->signatureSubject 4030 // save $tbsCertList in case there are any \phpseclib\File\ASN1\Element objects in it 4031 $tbsCertList = $this->currentCert['tbsCertList']; 4032 $this->loadCRL($this->saveCRL($this->currentCert)); 4033 4034 $result = $this->_sign($issuer->privateKey, $signatureAlgorithm); 4035 $result['tbsCertList'] = $tbsCertList; 4036 4037 $this->currentCert = $currentCert; 4038 $this->signatureSubject = $signatureSubject; 4039 4040 return $result; 4041 } 4042 4043 /** 4044 * X.509 certificate signing helper function. 4045 * 4046 * @param object $key 4047 * @param \phpseclib\File\X509 $subject 4048 * @param string $signatureAlgorithm 4049 * @access public 4050 * @return mixed 4051 */ 4052 function _sign($key, $signatureAlgorithm) 4053 { 4054 if ($key instanceof RSA) { 4055 switch ($signatureAlgorithm) { 4056 case 'md2WithRSAEncryption': 4057 case 'md5WithRSAEncryption': 4058 case 'sha1WithRSAEncryption': 4059 case 'sha224WithRSAEncryption': 4060 case 'sha256WithRSAEncryption': 4061 case 'sha384WithRSAEncryption': 4062 case 'sha512WithRSAEncryption': 4063 $key->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm)); 4064 $key->setSignatureMode(RSA::SIGNATURE_PKCS1); 4065 4066 $this->currentCert['signature'] = base64_encode("\0" . $key->sign($this->signatureSubject)); 4067 return $this->currentCert; 4068 } 4069 } 4070 4071 return false; 4072 } 4073 4074 /** 4075 * Set certificate start date 4076 * 4077 * @param string $date 4078 * @access public 4079 */ 4080 function setStartDate($date) 4081 { 4082 if (!is_object($date) || !is_a($date, 'DateTime')) { 4083 $date = new DateTime($date, new DateTimeZone(@date_default_timezone_get())); 4084 } 4085 4086 $this->startDate = $date->format('D, d M Y H:i:s O'); 4087 } 4088 4089 /** 4090 * Set certificate end date 4091 * 4092 * @param string $date 4093 * @access public 4094 */ 4095 function setEndDate($date) 4096 { 4097 /* 4098 To indicate that a certificate has no well-defined expiration date, 4099 the notAfter SHOULD be assigned the GeneralizedTime value of 4100 99991231235959Z. 4101 4102 -- http://tools.ietf.org/html/rfc5280#section-4.1.2.5 4103 */ 4104 if (strtolower($date) == 'lifetime') { 4105 $temp = '99991231235959Z'; 4106 $asn1 = new ASN1(); 4107 $temp = chr(ASN1::TYPE_GENERALIZED_TIME) . $asn1->_encodeLength(strlen($temp)) . $temp; 4108 $this->endDate = new Element($temp); 4109 } else { 4110 if (!is_object($date) || !is_a($date, 'DateTime')) { 4111 $date = new DateTime($date, new DateTimeZone(@date_default_timezone_get())); 4112 } 4113 4114 $this->endDate = $date->format('D, d M Y H:i:s O'); 4115 } 4116 } 4117 4118 /** 4119 * Set Serial Number 4120 * 4121 * @param string $serial 4122 * @param $base optional 4123 * @access public 4124 */ 4125 function setSerialNumber($serial, $base = -256) 4126 { 4127 $this->serialNumber = new BigInteger($serial, $base); 4128 } 4129 4130 /** 4131 * Turns the certificate into a certificate authority 4132 * 4133 * @access public 4134 */ 4135 function makeCA() 4136 { 4137 $this->caFlag = true; 4138 } 4139 4140 /** 4141 * Check for validity of subarray 4142 * 4143 * This is intended for use in conjunction with _subArrayUnchecked(), 4144 * implementing the checks included in _subArray() but without copying 4145 * a potentially large array by passing its reference by-value to is_array(). 4146 * 4147 * @param array $root 4148 * @param string $path 4149 * @return boolean 4150 * @access private 4151 */ 4152 function _isSubArrayValid($root, $path) 4153 { 4154 if (!is_array($root)) { 4155 return false; 4156 } 4157 4158 foreach (explode('/', $path) as $i) { 4159 if (!is_array($root)) { 4160 return false; 4161 } 4162 4163 if (!isset($root[$i])) { 4164 return true; 4165 } 4166 4167 $root = $root[$i]; 4168 } 4169 4170 return true; 4171 } 4172 4173 /** 4174 * Get a reference to a subarray 4175 * 4176 * This variant of _subArray() does no is_array() checking, 4177 * so $root should be checked with _isSubArrayValid() first. 4178 * 4179 * This is here for performance reasons: 4180 * Passing a reference (i.e. $root) by-value (i.e. to is_array()) 4181 * creates a copy. If $root is an especially large array, this is expensive. 4182 * 4183 * @param array $root 4184 * @param string $path absolute path with / as component separator 4185 * @param bool $create optional 4186 * @access private 4187 * @return array|false 4188 */ 4189 function &_subArrayUnchecked(&$root, $path, $create = false) 4190 { 4191 $false = false; 4192 4193 foreach (explode('/', $path) as $i) { 4194 if (!isset($root[$i])) { 4195 if (!$create) { 4196 return $false; 4197 } 4198 4199 $root[$i] = array(); 4200 } 4201 4202 $root = &$root[$i]; 4203 } 4204 4205 return $root; 4206 } 4207 4208 /** 4209 * Get a reference to a subarray 4210 * 4211 * @param array $root 4212 * @param string $path absolute path with / as component separator 4213 * @param bool $create optional 4214 * @access private 4215 * @return array|false 4216 */ 4217 function &_subArray(&$root, $path, $create = false) 4218 { 4219 $false = false; 4220 4221 if (!is_array($root)) { 4222 return $false; 4223 } 4224 4225 foreach (explode('/', $path) as $i) { 4226 if (!is_array($root)) { 4227 return $false; 4228 } 4229 4230 if (!isset($root[$i])) { 4231 if (!$create) { 4232 return $false; 4233 } 4234 4235 $root[$i] = array(); 4236 } 4237 4238 $root = &$root[$i]; 4239 } 4240 4241 return $root; 4242 } 4243 4244 /** 4245 * Get a reference to an extension subarray 4246 * 4247 * @param array $root 4248 * @param string $path optional absolute path with / as component separator 4249 * @param bool $create optional 4250 * @access private 4251 * @return array|false 4252 */ 4253 function &_extensions(&$root, $path = null, $create = false) 4254 { 4255 if (!isset($root)) { 4256 $root = $this->currentCert; 4257 } 4258 4259 switch (true) { 4260 case !empty($path): 4261 case !is_array($root): 4262 break; 4263 case isset($root['tbsCertificate']): 4264 $path = 'tbsCertificate/extensions'; 4265 break; 4266 case isset($root['tbsCertList']): 4267 $path = 'tbsCertList/crlExtensions'; 4268 break; 4269 case isset($root['certificationRequestInfo']): 4270 $pth = 'certificationRequestInfo/attributes'; 4271 $attributes = &$this->_subArray($root, $pth, $create); 4272 4273 if (is_array($attributes)) { 4274 foreach ($attributes as $key => $value) { 4275 if ($value['type'] == 'pkcs-9-at-extensionRequest') { 4276 $path = "$pth/$key/value/0"; 4277 break 2; 4278 } 4279 } 4280 if ($create) { 4281 $key = count($attributes); 4282 $attributes[] = array('type' => 'pkcs-9-at-extensionRequest', 'value' => array()); 4283 $path = "$pth/$key/value/0"; 4284 } 4285 } 4286 break; 4287 } 4288 4289 $extensions = &$this->_subArray($root, $path, $create); 4290 4291 if (!is_array($extensions)) { 4292 $false = false; 4293 return $false; 4294 } 4295 4296 return $extensions; 4297 } 4298 4299 /** 4300 * Remove an Extension 4301 * 4302 * @param string $id 4303 * @param string $path optional 4304 * @access private 4305 * @return bool 4306 */ 4307 function _removeExtension($id, $path = null) 4308 { 4309 $extensions = &$this->_extensions($this->currentCert, $path); 4310 4311 if (!is_array($extensions)) { 4312 return false; 4313 } 4314 4315 $result = false; 4316 foreach ($extensions as $key => $value) { 4317 if ($value['extnId'] == $id) { 4318 unset($extensions[$key]); 4319 $result = true; 4320 } 4321 } 4322 4323 $extensions = array_values($extensions); 4324 // fix for https://bugs.php.net/75433 affecting PHP 7.2 4325 if (!isset($extensions[0])) { 4326 $extensions = array_splice($extensions, 0, 0); 4327 } 4328 return $result; 4329 } 4330 4331 /** 4332 * Get an Extension 4333 * 4334 * Returns the extension if it exists and false if not 4335 * 4336 * @param string $id 4337 * @param array $cert optional 4338 * @param string $path optional 4339 * @access private 4340 * @return mixed 4341 */ 4342 function _getExtension($id, $cert = null, $path = null) 4343 { 4344 $extensions = $this->_extensions($cert, $path); 4345 4346 if (!is_array($extensions)) { 4347 return false; 4348 } 4349 4350 foreach ($extensions as $key => $value) { 4351 if ($value['extnId'] == $id) { 4352 return $value['extnValue']; 4353 } 4354 } 4355 4356 return false; 4357 } 4358 4359 /** 4360 * Returns a list of all extensions in use 4361 * 4362 * @param array $cert optional 4363 * @param string $path optional 4364 * @access private 4365 * @return array 4366 */ 4367 function _getExtensions($cert = null, $path = null) 4368 { 4369 $exts = $this->_extensions($cert, $path); 4370 $extensions = array(); 4371 4372 if (is_array($exts)) { 4373 foreach ($exts as $extension) { 4374 $extensions[] = $extension['extnId']; 4375 } 4376 } 4377 4378 return $extensions; 4379 } 4380 4381 /** 4382 * Set an Extension 4383 * 4384 * @param string $id 4385 * @param mixed $value 4386 * @param bool $critical optional 4387 * @param bool $replace optional 4388 * @param string $path optional 4389 * @access private 4390 * @return bool 4391 */ 4392 function _setExtension($id, $value, $critical = false, $replace = true, $path = null) 4393 { 4394 $extensions = &$this->_extensions($this->currentCert, $path, true); 4395 4396 if (!is_array($extensions)) { 4397 return false; 4398 } 4399 4400 $newext = array('extnId' => $id, 'critical' => $critical, 'extnValue' => $value); 4401 4402 foreach ($extensions as $key => $value) { 4403 if ($value['extnId'] == $id) { 4404 if (!$replace) { 4405 return false; 4406 } 4407 4408 $extensions[$key] = $newext; 4409 return true; 4410 } 4411 } 4412 4413 $extensions[] = $newext; 4414 return true; 4415 } 4416 4417 /** 4418 * Remove a certificate, CSR or CRL Extension 4419 * 4420 * @param string $id 4421 * @access public 4422 * @return bool 4423 */ 4424 function removeExtension($id) 4425 { 4426 return $this->_removeExtension($id); 4427 } 4428 4429 /** 4430 * Get a certificate, CSR or CRL Extension 4431 * 4432 * Returns the extension if it exists and false if not 4433 * 4434 * @param string $id 4435 * @param array $cert optional 4436 * @access public 4437 * @return mixed 4438 */ 4439 function getExtension($id, $cert = null) 4440 { 4441 return $this->_getExtension($id, $cert); 4442 } 4443 4444 /** 4445 * Returns a list of all extensions in use in certificate, CSR or CRL 4446 * 4447 * @param array $cert optional 4448 * @access public 4449 * @return array 4450 */ 4451 function getExtensions($cert = null) 4452 { 4453 return $this->_getExtensions($cert); 4454 } 4455 4456 /** 4457 * Set a certificate, CSR or CRL Extension 4458 * 4459 * @param string $id 4460 * @param mixed $value 4461 * @param bool $critical optional 4462 * @param bool $replace optional 4463 * @access public 4464 * @return bool 4465 */ 4466 function setExtension($id, $value, $critical = false, $replace = true) 4467 { 4468 return $this->_setExtension($id, $value, $critical, $replace); 4469 } 4470 4471 /** 4472 * Remove a CSR attribute. 4473 * 4474 * @param string $id 4475 * @param int $disposition optional 4476 * @access public 4477 * @return bool 4478 */ 4479 function removeAttribute($id, $disposition = self::ATTR_ALL) 4480 { 4481 $attributes = &$this->_subArray($this->currentCert, 'certificationRequestInfo/attributes'); 4482 4483 if (!is_array($attributes)) { 4484 return false; 4485 } 4486 4487 $result = false; 4488 foreach ($attributes as $key => $attribute) { 4489 if ($attribute['type'] == $id) { 4490 $n = count($attribute['value']); 4491 switch (true) { 4492 case $disposition == self::ATTR_APPEND: 4493 case $disposition == self::ATTR_REPLACE: 4494 return false; 4495 case $disposition >= $n: 4496 $disposition -= $n; 4497 break; 4498 case $disposition == self::ATTR_ALL: 4499 case $n == 1: 4500 unset($attributes[$key]); 4501 $result = true; 4502 break; 4503 default: 4504 unset($attributes[$key]['value'][$disposition]); 4505 $attributes[$key]['value'] = array_values($attributes[$key]['value']); 4506 $result = true; 4507 break; 4508 } 4509 if ($result && $disposition != self::ATTR_ALL) { 4510 break; 4511 } 4512 } 4513 } 4514 4515 $attributes = array_values($attributes); 4516 return $result; 4517 } 4518 4519 /** 4520 * Get a CSR attribute 4521 * 4522 * Returns the attribute if it exists and false if not 4523 * 4524 * @param string $id 4525 * @param int $disposition optional 4526 * @param array $csr optional 4527 * @access public 4528 * @return mixed 4529 */ 4530 function getAttribute($id, $disposition = self::ATTR_ALL, $csr = null) 4531 { 4532 if (empty($csr)) { 4533 $csr = $this->currentCert; 4534 } 4535 4536 $attributes = $this->_subArray($csr, 'certificationRequestInfo/attributes'); 4537 4538 if (!is_array($attributes)) { 4539 return false; 4540 } 4541 4542 foreach ($attributes as $key => $attribute) { 4543 if ($attribute['type'] == $id) { 4544 $n = count($attribute['value']); 4545 switch (true) { 4546 case $disposition == self::ATTR_APPEND: 4547 case $disposition == self::ATTR_REPLACE: 4548 return false; 4549 case $disposition == self::ATTR_ALL: 4550 return $attribute['value']; 4551 case $disposition >= $n: 4552 $disposition -= $n; 4553 break; 4554 default: 4555 return $attribute['value'][$disposition]; 4556 } 4557 } 4558 } 4559 4560 return false; 4561 } 4562 4563 /** 4564 * Returns a list of all CSR attributes in use 4565 * 4566 * @param array $csr optional 4567 * @access public 4568 * @return array 4569 */ 4570 function getAttributes($csr = null) 4571 { 4572 if (empty($csr)) { 4573 $csr = $this->currentCert; 4574 } 4575 4576 $attributes = $this->_subArray($csr, 'certificationRequestInfo/attributes'); 4577 $attrs = array(); 4578 4579 if (is_array($attributes)) { 4580 foreach ($attributes as $attribute) { 4581 $attrs[] = $attribute['type']; 4582 } 4583 } 4584 4585 return $attrs; 4586 } 4587 4588 /** 4589 * Set a CSR attribute 4590 * 4591 * @param string $id 4592 * @param mixed $value 4593 * @param bool $disposition optional 4594 * @access public 4595 * @return bool 4596 */ 4597 function setAttribute($id, $value, $disposition = self::ATTR_ALL) 4598 { 4599 $attributes = &$this->_subArray($this->currentCert, 'certificationRequestInfo/attributes', true); 4600 4601 if (!is_array($attributes)) { 4602 return false; 4603 } 4604 4605 switch ($disposition) { 4606 case self::ATTR_REPLACE: 4607 $disposition = self::ATTR_APPEND; 4608 case self::ATTR_ALL: 4609 $this->removeAttribute($id); 4610 break; 4611 } 4612 4613 foreach ($attributes as $key => $attribute) { 4614 if ($attribute['type'] == $id) { 4615 $n = count($attribute['value']); 4616 switch (true) { 4617 case $disposition == self::ATTR_APPEND: 4618 $last = $key; 4619 break; 4620 case $disposition >= $n: 4621 $disposition -= $n; 4622 break; 4623 default: 4624 $attributes[$key]['value'][$disposition] = $value; 4625 return true; 4626 } 4627 } 4628 } 4629 4630 switch (true) { 4631 case $disposition >= 0: 4632 return false; 4633 case isset($last): 4634 $attributes[$last]['value'][] = $value; 4635 break; 4636 default: 4637 $attributes[] = array('type' => $id, 'value' => $disposition == self::ATTR_ALL ? $value: array($value)); 4638 break; 4639 } 4640 4641 return true; 4642 } 4643 4644 /** 4645 * Sets the subject key identifier 4646 * 4647 * This is used by the id-ce-authorityKeyIdentifier and the id-ce-subjectKeyIdentifier extensions. 4648 * 4649 * @param string $value 4650 * @access public 4651 */ 4652 function setKeyIdentifier($value) 4653 { 4654 if (empty($value)) { 4655 unset($this->currentKeyIdentifier); 4656 } else { 4657 $this->currentKeyIdentifier = base64_encode($value); 4658 } 4659 } 4660 4661 /** 4662 * Compute a public key identifier. 4663 * 4664 * Although key identifiers may be set to any unique value, this function 4665 * computes key identifiers from public key according to the two 4666 * recommended methods (4.2.1.2 RFC 3280). 4667 * Highly polymorphic: try to accept all possible forms of key: 4668 * - Key object 4669 * - \phpseclib\File\X509 object with public or private key defined 4670 * - Certificate or CSR array 4671 * - \phpseclib\File\ASN1\Element object 4672 * - PEM or DER string 4673 * 4674 * @param mixed $key optional 4675 * @param int $method optional 4676 * @access public 4677 * @return string binary key identifier 4678 */ 4679 function computeKeyIdentifier($key = null, $method = 1) 4680 { 4681 if (is_null($key)) { 4682 $key = $this; 4683 } 4684 4685 switch (true) { 4686 case is_string($key): 4687 break; 4688 case is_array($key) && isset($key['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']): 4689 return $this->computeKeyIdentifier($key['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'], $method); 4690 case is_array($key) && isset($key['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']): 4691 return $this->computeKeyIdentifier($key['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'], $method); 4692 case !is_object($key): 4693 return false; 4694 case $key instanceof Element: 4695 // Assume the element is a bitstring-packed key. 4696 $asn1 = new ASN1(); 4697 $decoded = $asn1->decodeBER($key->element); 4698 if (empty($decoded)) { 4699 return false; 4700 } 4701 $raw = $asn1->asn1map($decoded[0], array('type' => ASN1::TYPE_BIT_STRING)); 4702 if (empty($raw)) { 4703 return false; 4704 } 4705 $raw = base64_decode($raw); 4706 // If the key is private, compute identifier from its corresponding public key. 4707 $key = new RSA(); 4708 if (!$key->loadKey($raw)) { 4709 return false; // Not an unencrypted RSA key. 4710 } 4711 if ($key->getPrivateKey() !== false) { // If private. 4712 return $this->computeKeyIdentifier($key, $method); 4713 } 4714 $key = $raw; // Is a public key. 4715 break; 4716 case $key instanceof X509: 4717 if (isset($key->publicKey)) { 4718 return $this->computeKeyIdentifier($key->publicKey, $method); 4719 } 4720 if (isset($key->privateKey)) { 4721 return $this->computeKeyIdentifier($key->privateKey, $method); 4722 } 4723 if (isset($key->currentCert['tbsCertificate']) || isset($key->currentCert['certificationRequestInfo'])) { 4724 return $this->computeKeyIdentifier($key->currentCert, $method); 4725 } 4726 return false; 4727 default: // Should be a key object (i.e.: \phpseclib\Crypt\RSA). 4728 $key = $key->getPublicKey(RSA::PUBLIC_FORMAT_PKCS1); 4729 break; 4730 } 4731 4732 // If in PEM format, convert to binary. 4733 $key = $this->_extractBER($key); 4734 4735 // Now we have the key string: compute its sha-1 sum. 4736 $hash = new Hash('sha1'); 4737 $hash = $hash->hash($key); 4738 4739 if ($method == 2) { 4740 $hash = substr($hash, -8); 4741 $hash[0] = chr((ord($hash[0]) & 0x0F) | 0x40); 4742 } 4743 4744 return $hash; 4745 } 4746 4747 /** 4748 * Format a public key as appropriate 4749 * 4750 * @access private 4751 * @return array 4752 */ 4753 function