[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/vendor/phpseclib/phpseclib/phpseclib/File/ -> X509.php (source)

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