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