[ 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 ref $root
1612       * @param string $path
1613       * @param object $asn1
1614       * @access private
1615       */
1616      function _mapInExtensions(&$root, $path, $asn1)
1617      {
1618          $extensions = &$this->_subArrayUnchecked($root, $path);
1619  
1620          if ($extensions) {
1621              for ($i = 0; $i < count($extensions); $i++) {
1622                  $id = $extensions[$i]['extnId'];
1623                  $value = &$extensions[$i]['extnValue'];
1624                  $value = base64_decode($value);
1625                  $decoded = $asn1->decodeBER($value);
1626                  /* [extnValue] contains the DER encoding of an ASN.1 value
1627                     corresponding to the extension type identified by extnID */
1628                  $map = $this->_getMapping($id);
1629                  if (!is_bool($map)) {
1630                      $decoder = $id == 'id-ce-nameConstraints' ?
1631                          array($this, '_decodeNameConstraintIP') :
1632                          array($this, '_decodeIP');
1633                      $mapped = $asn1->asn1map($decoded[0], $map, array('iPAddress' => $decoder));
1634                      $value = $mapped === false ? $decoded[0] : $mapped;
1635  
1636                      if ($id == 'id-ce-certificatePolicies') {
1637                          for ($j = 0; $j < count($value); $j++) {
1638                              if (!isset($value[$j]['policyQualifiers'])) {
1639                                  continue;
1640                              }
1641                              for ($k = 0; $k < count($value[$j]['policyQualifiers']); $k++) {
1642                                  $subid = $value[$j]['policyQualifiers'][$k]['policyQualifierId'];
1643                                  $map = $this->_getMapping($subid);
1644                                  $subvalue = &$value[$j]['policyQualifiers'][$k]['qualifier'];
1645                                  if ($map !== false) {
1646                                      $decoded = $asn1->decodeBER($subvalue);
1647                                      $mapped = $asn1->asn1map($decoded[0], $map);
1648                                      $subvalue = $mapped === false ? $decoded[0] : $mapped;
1649                                  }
1650                              }
1651                          }
1652                      }
1653                  } else {
1654                      $value = base64_encode($value);
1655                  }
1656              }
1657          }
1658      }
1659  
1660      /**
1661       * Map extension values from extension-specific internal format to
1662       *   octet string.
1663       *
1664       * @param array ref $root
1665       * @param string $path
1666       * @param object $asn1
1667       * @access private
1668       */
1669      function _mapOutExtensions(&$root, $path, $asn1)
1670      {
1671          $extensions = &$this->_subArray($root, $path);
1672  
1673          if (is_array($extensions)) {
1674              $size = count($extensions);
1675              for ($i = 0; $i < $size; $i++) {
1676                  if ($extensions[$i] instanceof Element) {
1677                      continue;
1678                  }
1679  
1680                  $id = $extensions[$i]['extnId'];
1681                  $value = &$extensions[$i]['extnValue'];
1682  
1683                  switch ($id) {
1684                      case 'id-ce-certificatePolicies':
1685                          for ($j = 0; $j < count($value); $j++) {
1686                              if (!isset($value[$j]['policyQualifiers'])) {
1687                                  continue;
1688                              }
1689                              for ($k = 0; $k < count($value[$j]['policyQualifiers']); $k++) {
1690                                  $subid = $value[$j]['policyQualifiers'][$k]['policyQualifierId'];
1691                                  $map = $this->_getMapping($subid);
1692                                  $subvalue = &$value[$j]['policyQualifiers'][$k]['qualifier'];
1693                                  if ($map !== false) {
1694                                      // by default \phpseclib\File\ASN1 will try to render qualifier as a \phpseclib\File\ASN1::TYPE_IA5_STRING since it's
1695                                      // actual type is \phpseclib\File\ASN1::TYPE_ANY
1696                                      $subvalue = new Element($asn1->encodeDER($subvalue, $map));
1697                                  }
1698                              }
1699                          }
1700                          break;
1701                      case 'id-ce-authorityKeyIdentifier': // use 00 as the serial number instead of an empty string
1702                          if (isset($value['authorityCertSerialNumber'])) {
1703                              if ($value['authorityCertSerialNumber']->toBytes() == '') {
1704                                  $temp = chr((ASN1::CLASS_CONTEXT_SPECIFIC << 6) | 2) . "\1\0";
1705                                  $value['authorityCertSerialNumber'] = new Element($temp);
1706                              }
1707                          }
1708                  }
1709  
1710                  /* [extnValue] contains the DER encoding of an ASN.1 value
1711                     corresponding to the extension type identified by extnID */
1712                  $map = $this->_getMapping($id);
1713                  if (is_bool($map)) {
1714                      if (!$map) {
1715                          user_error($id . ' is not a currently supported extension');
1716                          unset($extensions[$i]);
1717                      }
1718                  } else {
1719                      $temp = $asn1->encodeDER($value, $map, array('iPAddress' => array($this, '_encodeIP')));
1720                      $value = base64_encode($temp);
1721                  }
1722              }
1723          }
1724      }
1725  
1726      /**
1727       * Map attribute values from ANY type to attribute-specific internal
1728       *   format.
1729       *
1730       * @param array ref $root
1731       * @param string $path
1732       * @param object $asn1
1733       * @access private
1734       */
1735      function _mapInAttributes(&$root, $path, $asn1)
1736      {
1737          $attributes = &$this->_subArray($root, $path);
1738  
1739          if (is_array($attributes)) {
1740              for ($i = 0; $i < count($attributes); $i++) {
1741                  $id = $attributes[$i]['type'];
1742                  /* $value contains the DER encoding of an ASN.1 value
1743                     corresponding to the attribute type identified by type */
1744                  $map = $this->_getMapping($id);
1745                  if (is_array($attributes[$i]['value'])) {
1746                      $values = &$attributes[$i]['value'];
1747                      for ($j = 0; $j < count($values); $j++) {
1748                          $value = $asn1->encodeDER($values[$j], $this->AttributeValue);
1749                          $decoded = $asn1->decodeBER($value);
1750                          if (!is_bool($map)) {
1751                              $mapped = $asn1->asn1map($decoded[0], $map);
1752                              if ($mapped !== false) {
1753                                  $values[$j] = $mapped;
1754                              }
1755                              if ($id == 'pkcs-9-at-extensionRequest' && $this->_isSubArrayValid($values, $j)) {
1756                                  $this->_mapInExtensions($values, $j, $asn1);
1757                              }
1758                          } elseif ($map) {
1759                              $values[$j] = base64_encode($value);
1760                          }
1761                      }
1762                  }
1763              }
1764          }
1765      }
1766  
1767      /**
1768       * Map attribute values from attribute-specific internal format to
1769       *   ANY type.
1770       *
1771       * @param array ref $root
1772       * @param string $path
1773       * @param object $asn1
1774       * @access private
1775       */
1776      function _mapOutAttributes(&$root, $path, $asn1)
1777      {
1778          $attributes = &$this->_subArray($root, $path);
1779  
1780          if (is_array($attributes)) {
1781              $size = count($attributes);
1782              for ($i = 0; $i < $size; $i++) {
1783                  /* [value] contains the DER encoding of an ASN.1 value
1784                     corresponding to the attribute type identified by type */
1785                  $id = $attributes[$i]['type'];
1786                  $map = $this->_getMapping($id);
1787                  if ($map === false) {
1788                      user_error($id . ' is not a currently supported attribute', E_USER_NOTICE);
1789                      unset($attributes[$i]);
1790                  } elseif (is_array($attributes[$i]['value'])) {
1791                      $values = &$attributes[$i]['value'];
1792                      for ($j = 0; $j < count($values); $j++) {
1793                          switch ($id) {
1794                              case 'pkcs-9-at-extensionRequest':
1795                                  $this->_mapOutExtensions($values, $j, $asn1);
1796                                  break;
1797                          }
1798  
1799                          if (!is_bool($map)) {
1800                              $temp = $asn1->encodeDER($values[$j], $map);
1801                              $decoded = $asn1->decodeBER($temp);
1802                              $values[$j] = $asn1->asn1map($decoded[0], $this->AttributeValue);
1803                          }
1804                      }
1805                  }
1806              }
1807          }
1808      }
1809  
1810      /**
1811       * Map DN values from ANY type to DN-specific internal
1812       *   format.
1813       *
1814       * @param array ref $root
1815       * @param string $path
1816       * @param object $asn1
1817       * @access private
1818       */
1819      function _mapInDNs(&$root, $path, $asn1)
1820      {
1821          $dns = &$this->_subArray($root, $path);
1822  
1823          if (is_array($dns)) {
1824              for ($i = 0; $i < count($dns); $i++) {
1825                  for ($j = 0; $j < count($dns[$i]); $j++) {
1826                      $type = $dns[$i][$j]['type'];
1827                      $value = &$dns[$i][$j]['value'];
1828                      if (is_object($value) && $value instanceof Element) {
1829                          $map = $this->_getMapping($type);
1830                          if (!is_bool($map)) {
1831                              $decoded = $asn1->decodeBER($value);
1832                              $value = $asn1->asn1map($decoded[0], $map);
1833                          }
1834                      }
1835                  }
1836              }
1837          }
1838      }
1839  
1840      /**
1841       * Map DN values from DN-specific internal format to
1842       *   ANY type.
1843       *
1844       * @param array ref $root
1845       * @param string $path
1846       * @param object $asn1
1847       * @access private
1848       */
1849      function _mapOutDNs(&$root, $path, $asn1)
1850      {
1851          $dns = &$this->_subArray($root, $path);
1852  
1853          if (is_array($dns)) {
1854              $size = count($dns);
1855              for ($i = 0; $i < $size; $i++) {
1856                  for ($j = 0; $j < count($dns[$i]); $j++) {
1857                      $type = $dns[$i][$j]['type'];
1858                      $value = &$dns[$i][$j]['value'];
1859                      if (is_object($value) && $value instanceof Element) {
1860                          continue;
1861                      }
1862  
1863                      $map = $this->_getMapping($type);
1864                      if (!is_bool($map)) {
1865                          $value = new Element($asn1->encodeDER($value, $map));
1866                      }
1867                  }
1868              }
1869          }
1870      }
1871  
1872      /**
1873       * Associate an extension ID to an extension mapping
1874       *
1875       * @param string $extnId
1876       * @access private
1877       * @return mixed
1878       */
1879      function _getMapping($extnId)
1880      {
1881          if (!is_string($extnId)) { // eg. if it's a \phpseclib\File\ASN1\Element object
1882              return true;
1883          }
1884  
1885          switch ($extnId) {
1886              case 'id-ce-keyUsage':
1887                  return $this->KeyUsage;
1888              case 'id-ce-basicConstraints':
1889                  return $this->BasicConstraints;
1890              case 'id-ce-subjectKeyIdentifier':
1891                  return $this->KeyIdentifier;
1892              case 'id-ce-cRLDistributionPoints':
1893                  return $this->CRLDistributionPoints;
1894              case 'id-ce-authorityKeyIdentifier':
1895                  return $this->AuthorityKeyIdentifier;
1896              case 'id-ce-certificatePolicies':
1897                  return $this->CertificatePolicies;
1898              case 'id-ce-extKeyUsage':
1899                  return $this->ExtKeyUsageSyntax;
1900              case 'id-pe-authorityInfoAccess':
1901                  return $this->AuthorityInfoAccessSyntax;
1902              case 'id-pe-subjectInfoAccess':
1903                  return $this->SubjectInfoAccessSyntax;
1904              case 'id-ce-subjectAltName':
1905                  return $this->SubjectAltName;
1906              case 'id-ce-subjectDirectoryAttributes':
1907                  return $this->SubjectDirectoryAttributes;
1908              case 'id-ce-privateKeyUsagePeriod':
1909                  return $this->PrivateKeyUsagePeriod;
1910              case 'id-ce-issuerAltName':
1911                  return $this->IssuerAltName;
1912              case 'id-ce-policyMappings':
1913                  return $this->PolicyMappings;
1914              case 'id-ce-nameConstraints':
1915                  return $this->NameConstraints;
1916  
1917              case 'netscape-cert-type':
1918                  return $this->netscape_cert_type;
1919              case 'netscape-comment':
1920                  return $this->netscape_comment;
1921              case 'netscape-ca-policy-url':
1922                  return $this->netscape_ca_policy_url;
1923  
1924              // since id-qt-cps isn't a constructed type it will have already been decoded as a string by the time it gets
1925              // back around to asn1map() and we don't want it decoded again.
1926              //case 'id-qt-cps':
1927              //    return $this->CPSuri;
1928              case 'id-qt-unotice':
1929                  return $this->UserNotice;
1930  
1931              // the following OIDs are unsupported but we don't want them to give notices when calling saveX509().
1932              case 'id-pe-logotype': // http://www.ietf.org/rfc/rfc3709.txt
1933              case 'entrustVersInfo':
1934              // http://support.microsoft.com/kb/287547
1935              case '1.3.6.1.4.1.311.20.2': // szOID_ENROLL_CERTTYPE_EXTENSION
1936              case '1.3.6.1.4.1.311.21.1': // szOID_CERTSRV_CA_VERSION
1937              // "SET Secure Electronic Transaction Specification"
1938              // http://www.maithean.com/docs/set_bk3.pdf
1939              case '2.23.42.7.0': // id-set-hashedRootKey
1940              // "Certificate Transparency"
1941              // https://tools.ietf.org/html/rfc6962
1942              case '1.3.6.1.4.1.11129.2.4.2':
1943              // "Qualified Certificate statements"
1944              // https://tools.ietf.org/html/rfc3739#section-3.2.6
1945              case '1.3.6.1.5.5.7.1.3':
1946                  return true;
1947  
1948              // CSR attributes
1949              case 'pkcs-9-at-unstructuredName':
1950                  return $this->PKCS9String;
1951              case 'pkcs-9-at-challengePassword':
1952                  return $this->DirectoryString;
1953              case 'pkcs-9-at-extensionRequest':
1954                  return $this->Extensions;
1955  
1956              // CRL extensions.
1957              case 'id-ce-cRLNumber':
1958                  return $this->CRLNumber;
1959              case 'id-ce-deltaCRLIndicator':
1960                  return $this->CRLNumber;
1961              case 'id-ce-issuingDistributionPoint':
1962                  return $this->IssuingDistributionPoint;
1963              case 'id-ce-freshestCRL':
1964                  return $this->CRLDistributionPoints;
1965              case 'id-ce-cRLReasons':
1966                  return $this->CRLReason;
1967              case 'id-ce-invalidityDate':
1968                  return $this->InvalidityDate;
1969              case 'id-ce-certificateIssuer':
1970                  return $this->CertificateIssuer;
1971              case 'id-ce-holdInstructionCode':
1972                  return $this->HoldInstructionCode;
1973              case 'id-at-postalAddress':
1974                  return $this->PostalAddress;
1975          }
1976  
1977          return false;
1978      }
1979  
1980      /**
1981       * Load an X.509 certificate as a certificate authority
1982       *
1983       * @param string $cert
1984       * @access public
1985       * @return bool
1986       */
1987      function loadCA($cert)
1988      {
1989          $olddn = $this->dn;
1990          $oldcert = $this->currentCert;
1991          $oldsigsubj = $this->signatureSubject;
1992          $oldkeyid = $this->currentKeyIdentifier;
1993  
1994          $cert = $this->loadX509($cert);
1995          if (!$cert) {
1996              $this->dn = $olddn;
1997              $this->currentCert = $oldcert;
1998              $this->signatureSubject = $oldsigsubj;
1999              $this->currentKeyIdentifier = $oldkeyid;
2000  
2001              return false;
2002          }
2003  
2004          /* From RFC5280 "PKIX Certificate and CRL Profile":
2005  
2006             If the keyUsage extension is present, then the subject public key
2007             MUST NOT be used to verify signatures on certificates or CRLs unless
2008             the corresponding keyCertSign or cRLSign bit is set. */
2009          //$keyUsage = $this->getExtension('id-ce-keyUsage');
2010          //if ($keyUsage && !in_array('keyCertSign', $keyUsage)) {
2011          //    return false;
2012          //}
2013  
2014          /* From RFC5280 "PKIX Certificate and CRL Profile":
2015  
2016             The cA boolean indicates whether the certified public key may be used
2017             to verify certificate signatures.  If the cA boolean is not asserted,
2018             then the keyCertSign bit in the key usage extension MUST NOT be
2019             asserted.  If the basic constraints extension is not present in a
2020             version 3 certificate, or the extension is present but the cA boolean
2021             is not asserted, then the certified public key MUST NOT be used to
2022             verify certificate signatures. */
2023          //$basicConstraints = $this->getExtension('id-ce-basicConstraints');
2024          //if (!$basicConstraints || !$basicConstraints['cA']) {
2025          //    return false;
2026          //}
2027  
2028          $this->CAs[] = $cert;
2029  
2030          $this->dn = $olddn;
2031          $this->currentCert = $oldcert;
2032          $this->signatureSubject = $oldsigsubj;
2033  
2034          return true;
2035      }
2036  
2037      /**
2038       * Validate an X.509 certificate against a URL
2039       *
2040       * From RFC2818 "HTTP over TLS":
2041       *
2042       * Matching is performed using the matching rules specified by
2043       * [RFC2459].  If more than one identity of a given type is present in
2044       * the certificate (e.g., more than one dNSName name, a match in any one
2045       * of the set is considered acceptable.) Names may contain the wildcard
2046       * character * which is considered to match any single domain name
2047       * component or component fragment. E.g., *.a.com matches foo.a.com but
2048       * not bar.foo.a.com. f*.com matches foo.com but not bar.com.
2049       *
2050       * @param string $url
2051       * @access public
2052       * @return bool
2053       */
2054      function validateURL($url)
2055      {
2056          if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
2057              return false;
2058          }
2059  
2060          $components = parse_url($url);
2061          if (!isset($components['host'])) {
2062              return false;
2063          }
2064  
2065          if ($names = $this->getExtension('id-ce-subjectAltName')) {
2066              foreach ($names as $name) {
2067                  foreach ($name as $key => $value) {
2068                      $value = str_replace(array('.', '*'), array('\.', '[^.]*'), $value);
2069                      switch ($key) {
2070                          case 'dNSName':
2071                              /* From RFC2818 "HTTP over TLS":
2072  
2073                                 If a subjectAltName extension of type dNSName is present, that MUST
2074                                 be used as the identity. Otherwise, the (most specific) Common Name
2075                                 field in the Subject field of the certificate MUST be used. Although
2076                                 the use of the Common Name is existing practice, it is deprecated and
2077                                 Certification Authorities are encouraged to use the dNSName instead. */
2078                              if (preg_match('#^' . $value . '$#', $components['host'])) {
2079                                  return true;
2080                              }
2081                              break;
2082                          case 'iPAddress':
2083                              /* From RFC2818 "HTTP over TLS":
2084  
2085                                 In some cases, the URI is specified as an IP address rather than a
2086                                 hostname. In this case, the iPAddress subjectAltName must be present
2087                                 in the certificate and must exactly match the IP in the URI. */
2088                              if (preg_match('#(?:\d{1-3}\.){4}#', $components['host'] . '.') && preg_match('#^' . $value . '$#', $components['host'])) {
2089                                  return true;
2090                              }
2091                      }
2092                  }
2093              }
2094              return false;
2095          }
2096  
2097          if ($value = $this->getDNProp('id-at-commonName')) {
2098              $value = str_replace(array('.', '*'), array('\.', '[^.]*'), $value[0]);
2099              return preg_match('#^' . $value . '$#', $components['host']);
2100          }
2101  
2102          return false;
2103      }
2104  
2105      /**
2106       * Validate a date
2107       *
2108       * If $date isn't defined it is assumed to be the current date.
2109       *
2110       * @param \DateTime|string $date optional
2111       * @access public
2112       */
2113      function validateDate($date = null)
2114      {
2115          if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
2116              return false;
2117          }
2118  
2119          if (!isset($date)) {
2120              $date = new DateTime(null, new DateTimeZone(@date_default_timezone_get()));
2121          }
2122  
2123          $notBefore = $this->currentCert['tbsCertificate']['validity']['notBefore'];
2124          $notBefore = isset($notBefore['generalTime']) ? $notBefore['generalTime'] : $notBefore['utcTime'];
2125  
2126          $notAfter = $this->currentCert['tbsCertificate']['validity']['notAfter'];
2127          $notAfter = isset($notAfter['generalTime']) ? $notAfter['generalTime'] : $notAfter['utcTime'];
2128  
2129          if (is_string($date)) {
2130              $date = new DateTime($date, new DateTimeZone(@date_default_timezone_get()));
2131          }
2132  
2133          $notBefore = new DateTime($notBefore, new DateTimeZone(@date_default_timezone_get()));
2134          $notAfter = new DateTime($notAfter, new DateTimeZone(@date_default_timezone_get()));
2135  
2136          switch (true) {
2137              case $date < $notBefore:
2138              case $date > $notAfter:
2139                  return false;
2140          }
2141  
2142          return true;
2143      }
2144  
2145      /**
2146       * Fetches a URL
2147       *
2148       * @param string $url
2149       * @access private
2150       * @return bool|string
2151       */
2152      static function _fetchURL($url)
2153      {
2154          if (self::$disable_url_fetch) {
2155              return false;
2156          }
2157  
2158          $parts = parse_url($url);
2159          $data = '';
2160          switch ($parts['scheme']) {
2161              case 'http':
2162                  $fsock = @fsockopen($parts['host'], isset($parts['port']) ? $parts['port'] : 80);
2163                  if (!$fsock) {
2164                      return false;
2165                  }
2166                  fputs($fsock, "GET $parts[path] HTTP/1.0\r\n");
2167                  fputs($fsock, "Host: $parts[host]\r\n\r\n");
2168                  $line = fgets($fsock, 1024);
2169                  if (strlen($line) < 3) {
2170                      return false;
2171                  }
2172                  preg_match('#HTTP/1.\d (\d{3})#', $line, $temp);
2173                  if ($temp[1] != '200') {
2174                      return false;
2175                  }
2176  
2177                  // skip the rest of the headers in the http response
2178                  while (!feof($fsock) && fgets($fsock, 1024) != "\r\n") {
2179                  }
2180  
2181                  while (!feof($fsock)) {
2182                      $data.= fread($fsock, 1024);
2183                  }
2184  
2185                  break;
2186              //case 'ftp':
2187              //case 'ldap':
2188              //default:
2189          }
2190  
2191          return $data;
2192      }
2193  
2194      /**
2195       * Validates an intermediate cert as identified via authority info access extension
2196       *
2197       * See https://tools.ietf.org/html/rfc4325 for more info
2198       *
2199       * @param bool $caonly
2200       * @param int $count
2201       * @access private
2202       * @return bool
2203       */
2204      function _testForIntermediate($caonly, $count)
2205      {
2206          $opts = $this->getExtension('id-pe-authorityInfoAccess');
2207          if (!is_array($opts)) {
2208              return false;
2209          }
2210          foreach ($opts as $opt) {
2211              if ($opt['accessMethod'] == 'id-ad-caIssuers') {
2212                  // accessLocation is a GeneralName. GeneralName fields support stuff like email addresses, IP addresses, LDAP,
2213                  // etc, but we're only supporting URI's. URI's and LDAP are the only thing https://tools.ietf.org/html/rfc4325
2214                  // discusses
2215                  if (isset($opt['accessLocation']['uniformResourceIdentifier'])) {
2216                      $url = $opt['accessLocation']['uniformResourceIdentifier'];
2217                      break;
2218                  }
2219              }
2220          }
2221  
2222          if (!isset($url)) {
2223              return false;
2224          }
2225  
2226          $cert = static::_fetchURL($url);
2227          if (!is_string($cert)) {
2228              return false;
2229          }
2230  
2231          $parent = new static();
2232          $parent->CAs = $this->CAs;
2233          /*
2234           "Conforming applications that support HTTP or FTP for accessing
2235            certificates MUST be able to accept .cer files and SHOULD be able
2236            to accept .p7c files." -- https://tools.ietf.org/html/rfc4325
2237  
2238           A .p7c file is 'a "certs-only" CMS message as specified in RFC 2797"
2239  
2240           These are currently unsupported
2241          */
2242          if (!is_array($parent->loadX509($cert))) {
2243              return false;
2244          }
2245  
2246          if (!$parent->_validateSignatureCountable($caonly, ++$count)) {
2247              return false;
2248          }
2249  
2250          $this->CAs[] = $parent->currentCert;
2251          //$this->loadCA($cert);
2252  
2253          return true;
2254      }
2255  
2256      /**
2257       * Validate a signature
2258       *
2259       * Works on X.509 certs, CSR's and CRL's.
2260       * Returns true if the signature is verified, false if it is not correct or null on error
2261       *
2262       * By default returns false for self-signed certs. Call validateSignature(false) to make this support
2263       * self-signed.
2264       *
2265       * The behavior of this function is inspired by {@link http://php.net/openssl-verify openssl_verify}.
2266       *
2267       * @param bool $caonly optional
2268       * @access public
2269       * @return mixed
2270       */
2271      function validateSignature($caonly = true)
2272      {
2273          return $this->_validateSignatureCountable($caonly, 0);
2274      }
2275  
2276      /**
2277       * Validate a signature
2278       *
2279       * Performs said validation whilst keeping track of how many times validation method is called
2280       *
2281       * @param bool $caonly
2282       * @param int $count
2283       * @access private
2284       * @return mixed
2285       */
2286      function _validateSignatureCountable($caonly, $count)
2287      {
2288          if (!is_array($this->currentCert) || !isset($this->signatureSubject)) {
2289              return null;
2290          }
2291  
2292          if ($count == self::$recur_limit) {
2293              return false;
2294          }
2295  
2296          /* TODO:
2297             "emailAddress attribute values are not case-sensitive (e.g., "subscriber@example.com" is the same as "SUBSCRIBER@EXAMPLE.COM")."
2298              -- http://tools.ietf.org/html/rfc5280#section-4.1.2.6
2299  
2300             implement pathLenConstraint in the id-ce-basicConstraints extension */
2301  
2302          switch (true) {
2303              case isset($this->currentCert['tbsCertificate']):
2304                  // self-signed cert
2305                  switch (true) {
2306                      case !defined('FILE_X509_IGNORE_TYPE') && $this->currentCert['tbsCertificate']['issuer'] === $this->currentCert['tbsCertificate']['subject']:
2307                      case defined('FILE_X509_IGNORE_TYPE') && $this->getIssuerDN(self::DN_STRING) === $this->getDN(self::DN_STRING):
2308                          $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier');
2309                          $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier');
2310                          switch (true) {
2311                              case !is_array($authorityKey):
2312                              case !$subjectKeyID:
2313                              case isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
2314                                  $signingCert = $this->currentCert; // working cert
2315                          }
2316                  }
2317  
2318                  if (!empty($this->CAs)) {
2319                      for ($i = 0; $i < count($this->CAs); $i++) {
2320                          // even if the cert is a self-signed one we still want to see if it's a CA;
2321                          // if not, we'll conditionally return an error
2322                          $ca = $this->CAs[$i];
2323                          switch (true) {
2324                              case !defined('FILE_X509_IGNORE_TYPE') && $this->currentCert['tbsCertificate']['issuer'] === $ca['tbsCertificate']['subject']:
2325                              case defined('FILE_X509_IGNORE_TYPE') && $this->getDN(self::DN_STRING, $this->currentCert['tbsCertificate']['issuer']) === $this->getDN(self::DN_STRING, $ca['tbsCertificate']['subject']):
2326                                  $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier');
2327                                  $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca);
2328                                  switch (true) {
2329                                      case !is_array($authorityKey):
2330                                      case !$subjectKeyID:
2331                                      case isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
2332                                          if (is_array($authorityKey) && isset($authorityKey['authorityCertSerialNumber']) && !$authorityKey['authorityCertSerialNumber']->equals($ca['tbsCertificate']['serialNumber'])) {
2333                                              break 2; // serial mismatch - check other ca
2334                                          }
2335                                          $signingCert = $ca; // working cert
2336                                          break 3;
2337                                  }
2338                          }
2339                      }
2340                      if (count($this->CAs) == $i && $caonly) {
2341                          return $this->_testForIntermediate($caonly, $count) && $this->validateSignature($caonly);
2342                      }
2343                  } elseif (!isset($signingCert) || $caonly) {
2344                      return $this->_testForIntermediate($caonly, $count) && $this->validateSignature($caonly);
2345                  }
2346                  return $this->_validateSignature(
2347                      $signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'],
2348                      $signingCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'],
2349                      $this->currentCert['signatureAlgorithm']['algorithm'],
2350                      substr(base64_decode($this->currentCert['signature']), 1),
2351                      $this->signatureSubject
2352                  );
2353              case isset($this->currentCert['certificationRequestInfo']):
2354                  return $this->_validateSignature(
2355                      $this->currentCert['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm'],
2356                      $this->currentCert['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'],
2357                      $this->currentCert['signatureAlgorithm']['algorithm'],
2358                      substr(base64_decode($this->currentCert['signature']), 1),
2359                      $this->signatureSubject
2360                  );
2361              case isset($this->currentCert['publicKeyAndChallenge']):
2362                  return $this->_validateSignature(
2363                      $this->currentCert['publicKeyAndChallenge']['spki']['algorithm']['algorithm'],
2364                      $this->currentCert['publicKeyAndChallenge']['spki']['subjectPublicKey'],
2365                      $this->currentCert['signatureAlgorithm']['algorithm'],
2366                      substr(base64_decode($this->currentCert['signature']), 1),
2367                      $this->signatureSubject
2368                  );
2369              case isset($this->currentCert['tbsCertList']):
2370                  if (!empty($this->CAs)) {
2371                      for ($i = 0; $i < count($this->CAs); $i++) {
2372                          $ca = $this->CAs[$i];
2373                          switch (true) {
2374                              case !defined('FILE_X509_IGNORE_TYPE') && $this->currentCert['tbsCertList']['issuer'] === $ca['tbsCertificate']['subject']:
2375                              case defined('FILE_X509_IGNORE_TYPE') && $this->getDN(self::DN_STRING, $this->currentCert['tbsCertList']['issuer']) === $this->getDN(self::DN_STRING, $ca['tbsCertificate']['subject']):
2376                                  $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier');
2377                                  $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca);
2378                                  switch (true) {
2379                                      case !is_array($authorityKey):
2380                                      case !$subjectKeyID:
2381                                      case isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
2382                                          if (is_array($authorityKey) && isset($authorityKey['authorityCertSerialNumber']) && !$authorityKey['authorityCertSerialNumber']->equals($ca['tbsCertificate']['serialNumber'])) {
2383                                              break 2; // serial mismatch - check other ca
2384                                          }
2385                                          $signingCert = $ca; // working cert
2386                                          break 3;
2387                                  }
2388                          }
2389                      }
2390                  }
2391                  if (!isset($signingCert)) {
2392                      return false;
2393                  }
2394                  return $this->_validateSignature(
2395                      $signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'],
2396                      $signingCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'],
2397                      $this->currentCert['signatureAlgorithm']['algorithm'],
2398                      substr(base64_decode($this->currentCert['signature']), 1),
2399                      $this->signatureSubject
2400                  );
2401              default:
2402                  return false;
2403          }
2404      }
2405  
2406      /**
2407       * Validates a signature
2408       *
2409       * Returns true if the signature is verified, false if it is not correct or null on error
2410       *
2411       * @param string $publicKeyAlgorithm
2412       * @param string $publicKey
2413       * @param string $signatureAlgorithm
2414       * @param string $signature
2415       * @param string $signatureSubject
2416       * @access private
2417       * @return int
2418       */
2419      function _validateSignature($publicKeyAlgorithm, $publicKey, $signatureAlgorithm, $signature, $signatureSubject)
2420      {
2421          switch ($publicKeyAlgorithm) {
2422              case 'rsaEncryption':
2423                  $rsa = new RSA();
2424                  $rsa->loadKey($publicKey);
2425  
2426                  switch ($signatureAlgorithm) {
2427                      case 'md2WithRSAEncryption':
2428                      case 'md5WithRSAEncryption':
2429                      case 'sha1WithRSAEncryption':
2430                      case 'sha224WithRSAEncryption':
2431                      case 'sha256WithRSAEncryption':
2432                      case 'sha384WithRSAEncryption':
2433                      case 'sha512WithRSAEncryption':
2434                          $rsa->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm));
2435                          $rsa->setSignatureMode(RSA::SIGNATURE_PKCS1);
2436                          if (!@$rsa->verify($signatureSubject, $signature)) {
2437                              return false;
2438                          }
2439                          break;
2440                      default:
2441                          return null;
2442                  }
2443                  break;
2444              default:
2445                  return null;
2446          }
2447  
2448          return true;
2449      }
2450  
2451      /**
2452       * Sets the recursion limit
2453       *
2454       * When validating a signature it may be necessary to download intermediate certs from URI's.
2455       * An intermediate cert that linked to itself would result in an infinite loop so to prevent
2456       * that we set a recursion limit. A negative number means that there is no recursion limit.
2457       *
2458       * @param int $count
2459       * @access public
2460       */
2461      static function setRecurLimit($count)
2462      {
2463          self::$recur_limit = $count;
2464      }
2465  
2466      /**
2467       * Prevents URIs from being automatically retrieved
2468       *
2469       * @access public
2470       */
2471      static function disableURLFetch()
2472      {
2473          self::$disable_url_fetch = true;
2474      }
2475  
2476      /**
2477       * Allows URIs to be automatically retrieved
2478       *
2479       * @access public
2480       */
2481      static function enableURLFetch()
2482      {
2483          self::$disable_url_fetch = false;
2484      }
2485  
2486      /**
2487       * Reformat public keys
2488       *
2489       * Reformats a public key to a format supported by phpseclib (if applicable)
2490       *
2491       * @param string $algorithm
2492       * @param string $key
2493       * @access private
2494       * @return string
2495       */
2496      function _reformatKey($algorithm, $key)
2497      {
2498          switch ($algorithm) {
2499              case 'rsaEncryption':
2500                  return
2501                      "-----BEGIN RSA PUBLIC KEY-----\r\n" .
2502                      // subjectPublicKey is stored as a bit string in X.509 certs.  the first byte of a bit string represents how many bits
2503                      // in the last byte should be ignored.  the following only supports non-zero stuff but as none of the X.509 certs Firefox
2504                      // uses as a cert authority actually use a non-zero bit I think it's safe to assume that none do.
2505                      chunk_split(base64_encode(substr(base64_decode($key), 1)), 64) .
2506                      '-----END RSA PUBLIC KEY-----';
2507              default:
2508                  return $key;
2509          }
2510      }
2511  
2512      /**
2513       * Decodes an IP address
2514       *
2515       * Takes in a base64 encoded "blob" and returns a human readable IP address
2516       *
2517       * @param string $ip
2518       * @access private
2519       * @return string
2520       */
2521      function _decodeIP($ip)
2522      {
2523          return inet_ntop(base64_decode($ip));
2524      }
2525  
2526      /**
2527       * Decodes an IP address in a name constraints extension
2528       *
2529       * Takes in a base64 encoded "blob" and returns a human readable IP address / mask
2530       *
2531       * @param string $ip
2532       * @access private
2533       * @return array
2534       */
2535      function _decodeNameConstraintIP($ip)
2536      {
2537          $ip = base64_decode($ip);
2538          $size = strlen($ip) >> 1;
2539          $mask = substr($ip, $size);
2540          $ip = substr($ip, 0, $size);
2541          return array(inet_ntop($ip), inet_ntop($mask));
2542      }
2543  
2544      /**
2545       * Encodes an IP address
2546       *
2547       * Takes a human readable IP address into a base64-encoded "blob"
2548       *
2549       * @param string|array $ip
2550       * @access private
2551       * @return string
2552       */
2553      function _encodeIP($ip)
2554      {
2555          return is_string($ip) ?
2556              base64_encode(inet_pton($ip)) :
2557              base64_encode(inet_pton($ip[0]) . inet_pton($ip[1]));
2558      }
2559  
2560      /**
2561       * "Normalizes" a Distinguished Name property
2562       *
2563       * @param string $propName
2564       * @access private
2565       * @return mixed
2566       */
2567      function _translateDNProp($propName)
2568      {
2569          switch (strtolower($propName)) {
2570              case 'id-at-countryname':
2571              case 'countryname':
2572              case 'c':
2573                  return 'id-at-countryName';
2574              case 'id-at-organizationname':
2575              case 'organizationname':
2576              case 'o':
2577                  return 'id-at-organizationName';
2578              case 'id-at-dnqualifier':
2579              case 'dnqualifier':
2580                  return 'id-at-dnQualifier';
2581              case 'id-at-commonname':
2582              case 'commonname':
2583              case 'cn':
2584                  return 'id-at-commonName';
2585              case 'id-at-stateorprovincename':
2586              case 'stateorprovincename':
2587              case 'state':
2588              case 'province':
2589              case 'provincename':
2590              case 'st':
2591                  return 'id-at-stateOrProvinceName';
2592              case 'id-at-localityname':
2593              case 'localityname':
2594              case 'l':
2595                  return 'id-at-localityName';
2596              case 'id-emailaddress':
2597              case 'emailaddress':
2598                  return 'pkcs-9-at-emailAddress';
2599              case 'id-at-serialnumber':
2600              case 'serialnumber':
2601                  return 'id-at-serialNumber';
2602              case 'id-at-postalcode':
2603              case 'postalcode':
2604                  return 'id-at-postalCode';
2605              case 'id-at-streetaddress':
2606              case 'streetaddress':
2607                  return 'id-at-streetAddress';
2608              case 'id-at-name':
2609              case 'name':
2610                  return 'id-at-name';
2611              case 'id-at-givenname':
2612              case 'givenname':
2613                  return 'id-at-givenName';
2614              case 'id-at-surname':
2615              case 'surname':
2616              case 'sn':
2617                  return 'id-at-surname';
2618              case 'id-at-initials':
2619              case 'initials':
2620                  return 'id-at-initials';
2621              case 'id-at-generationqualifier':
2622              case 'generationqualifier':
2623                  return 'id-at-generationQualifier';
2624              case 'id-at-organizationalunitname':
2625              case 'organizationalunitname':
2626              case 'ou':
2627                  return 'id-at-organizationalUnitName';
2628              case 'id-at-pseudonym':
2629              case 'pseudonym':
2630                  return 'id-at-pseudonym';
2631              case 'id-at-title':
2632              case 'title':
2633                  return 'id-at-title';
2634              case 'id-at-description':
2635              case 'description':
2636                  return 'id-at-description';
2637              case 'id-at-role':
2638              case 'role':
2639                  return 'id-at-role';
2640              case 'id-at-uniqueidentifier':
2641              case 'uniqueidentifier':
2642              case 'x500uniqueidentifier':
2643                  return 'id-at-uniqueIdentifier';
2644              case 'postaladdress':
2645              case 'id-at-postaladdress':
2646                  return 'id-at-postalAddress';
2647              default:
2648                  return false;
2649          }
2650      }
2651  
2652      /**
2653       * Set a Distinguished Name property
2654       *
2655       * @param string $propName
2656       * @param mixed $propValue
2657       * @param string $type optional
2658       * @access public
2659       * @return bool
2660       */
2661      function setDNProp($propName, $propValue, $type = 'utf8String')
2662      {
2663          if (empty($this->dn)) {
2664              $this->dn = array('rdnSequence' => array());
2665          }
2666  
2667          if (($propName = $this->_translateDNProp($propName)) === false) {
2668              return false;
2669          }
2670  
2671          foreach ((array) $propValue as $v) {
2672              if (!is_array($v) && isset($type)) {
2673                  $v = array($type => $v);
2674              }
2675              $this->dn['rdnSequence'][] = array(
2676                  array(
2677                      'type' => $propName,
2678                      'value'=> $v
2679                  )
2680              );
2681          }
2682  
2683          return true;
2684      }
2685  
2686      /**
2687       * Remove Distinguished Name properties
2688       *
2689       * @param string $propName
2690       * @access public
2691       */
2692      function removeDNProp($propName)
2693      {
2694          if (empty($this->dn)) {
2695              return;
2696          }
2697  
2698          if (($propName = $this->_translateDNProp($propName)) === false) {
2699              return;
2700          }
2701  
2702          $dn = &$this->dn['rdnSequence'];
2703          $size = count($dn);
2704          for ($i = 0; $i < $size; $i++) {
2705              if ($dn[$i][0]['type'] == $propName) {
2706                  unset($dn[$i]);
2707              }
2708          }
2709  
2710          $dn = array_values($dn);
2711          // fix for https://bugs.php.net/75433 affecting PHP 7.2
2712          if (!isset($dn[0])) {
2713              $dn = array_splice($dn, 0, 0);
2714          }
2715      }
2716  
2717      /**
2718       * Get Distinguished Name properties
2719       *
2720       * @param string $propName
2721       * @param array $dn optional
2722       * @param bool $withType optional
2723       * @return mixed
2724       * @access public
2725       */
2726      function getDNProp($propName, $dn = null, $withType = false)
2727      {
2728          if (!isset($dn)) {
2729              $dn = $this->dn;
2730          }
2731  
2732          if (empty($dn)) {
2733              return false;
2734          }
2735  
2736          if (($propName = $this->_translateDNProp($propName)) === false) {
2737              return false;
2738          }
2739  
2740          $asn1 = new ASN1();
2741          $asn1->loadOIDs($this->oids);
2742          $filters = array();
2743          $filters['value'] = array('type' => ASN1::TYPE_UTF8_STRING);
2744          $asn1->loadFilters($filters);
2745          $this->_mapOutDNs($dn, 'rdnSequence', $asn1);
2746          $dn = $dn['rdnSequence'];
2747          $result = array();
2748          for ($i = 0; $i < count($dn); $i++) {
2749              if ($dn[$i][0]['type'] == $propName) {
2750                  $v = $dn[$i][0]['value'];
2751                  if (!$withType) {
2752                      if (is_array($v)) {
2753                          foreach ($v as $type => $s) {
2754                              $type = array_search($type, $asn1->ANYmap, true);
2755                              if ($type !== false && isset($asn1->stringTypeSize[$type])) {
2756                                  $s = $asn1->convert($s, $type);
2757                                  if ($s !== false) {
2758                                      $v = $s;
2759                                      break;
2760                                  }
2761                              }
2762                          }
2763                          if (is_array($v)) {
2764                              $v = array_pop($v); // Always strip data type.
2765                          }
2766                      } elseif (is_object($v) && $v instanceof Element) {
2767                          $map = $this->_getMapping($propName);
2768                          if (!is_bool($map)) {
2769                              $decoded = $asn1->decodeBER($v);
2770                              $v = $asn1->asn1map($decoded[0], $map);
2771                          }
2772                      }
2773                  }
2774                  $result[] = $v;
2775              }
2776          }
2777  
2778          return $result;
2779      }
2780  
2781      /**
2782       * Set a Distinguished Name
2783       *
2784       * @param mixed $dn
2785       * @param bool $merge optional
2786       * @param string $type optional
2787       * @access public
2788       * @return bool
2789       */
2790      function setDN($dn, $merge = false, $type = 'utf8String')
2791      {
2792          if (!$merge) {
2793              $this->dn = null;
2794          }
2795  
2796          if (is_array($dn)) {
2797              if (isset($dn['rdnSequence'])) {
2798                  $this->dn = $dn; // No merge here.
2799                  return true;
2800              }
2801  
2802              // handles stuff generated by openssl_x509_parse()
2803              foreach ($dn as $prop => $value) {
2804                  if (!$this->setDNProp($prop, $value, $type)) {
2805                      return false;
2806                  }
2807              }
2808              return true;
2809          }
2810  
2811          // handles everything else
2812          $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);
2813          for ($i = 1; $i < count($results); $i+=2) {
2814              $prop = trim($results[$i], ', =/');
2815              $value = $results[$i + 1];
2816              if (!$this->setDNProp($prop, $value, $type)) {
2817                  return false;
2818              }
2819          }
2820  
2821          return true;
2822      }
2823  
2824      /**
2825       * Get the Distinguished Name for a certificates subject
2826       *
2827       * @param mixed $format optional
2828       * @param array $dn optional
2829       * @access public
2830       * @return bool
2831       */
2832      function getDN($format = self::DN_ARRAY, $dn = null)
2833      {
2834          if (!isset($dn)) {
2835              $dn = isset($this->currentCert['tbsCertList']) ? $this->currentCert['tbsCertList']['issuer'] : $this->dn;
2836          }
2837  
2838          switch ((int) $format) {
2839              case self::DN_ARRAY:
2840                  return $dn;
2841              case self::DN_ASN1:
2842                  $asn1 = new ASN1();
2843                  $asn1->loadOIDs($this->oids);
2844                  $filters = array();
2845                  $filters['rdnSequence']['value'] = array('type' => ASN1::TYPE_UTF8_STRING);
2846                  $asn1->loadFilters($filters);
2847                  $this->_mapOutDNs($dn, 'rdnSequence', $asn1);
2848                  return $asn1->encodeDER($dn, $this->Name);
2849              case self::DN_CANON:
2850                  //  No SEQUENCE around RDNs and all string values normalized as
2851                  // trimmed lowercase UTF-8 with all spacing as one blank.
2852                  // constructed RDNs will not be canonicalized
2853                  $asn1 = new ASN1();
2854                  $asn1->loadOIDs($this->oids);
2855                  $filters = array();
2856                  $filters['value'] = array('type' => ASN1::TYPE_UTF8_STRING);
2857                  $asn1->loadFilters($filters);
2858                  $result = '';
2859                  $this->_mapOutDNs($dn, 'rdnSequence', $asn1);
2860                  foreach ($dn['rdnSequence'] as $rdn) {
2861                      foreach ($rdn as $i => $attr) {
2862                          $attr = &$rdn[$i];
2863                          if (is_array($attr['value'])) {
2864                              foreach ($attr['value'] as $type => $v) {
2865                                  $type = array_search($type, $asn1->ANYmap, true);
2866                                  if ($type !== false && isset($asn1->stringTypeSize[$type])) {
2867                                      $v = $asn1->convert($v, $type);
2868                                      if ($v !== false) {
2869                                          $v = preg_replace('/\s+/', ' ', $v);
2870                                          $attr['value'] = strtolower(trim($v));
2871                                          break;
2872                                      }
2873                                  }
2874                              }
2875                          }
2876                      }
2877                      $result .= $asn1->encodeDER($rdn, $this->RelativeDistinguishedName);
2878                  }
2879                  return $result;
2880              case self::DN_HASH:
2881                  $dn = $this->getDN(self::DN_CANON, $dn);
2882                  $hash = new Hash('sha1');
2883                  $hash = $hash->hash($dn);
2884                  extract(unpack('Vhash', $hash));
2885                  return strtolower(bin2hex(pack('N', $hash)));
2886          }
2887  
2888          // Default is to return a string.
2889          $start = true;
2890          $output = '';
2891  
2892          $result = array();
2893          $asn1 = new ASN1();
2894          $asn1->loadOIDs($this->oids);
2895          $filters = array();
2896          $filters['rdnSequence']['value'] = array('type' => ASN1::TYPE_UTF8_STRING);
2897          $asn1->loadFilters($filters);
2898          $this->_mapOutDNs($dn, 'rdnSequence', $asn1);
2899  
2900          foreach ($dn['rdnSequence'] as $field) {
2901              $prop = $field[0]['type'];
2902              $value = $field[0]['value'];
2903  
2904              $delim = ', ';
2905              switch ($prop) {
2906                  case 'id-at-countryName':
2907                      $desc = 'C';
2908                      break;
2909                  case 'id-at-stateOrProvinceName':
2910                      $desc = 'ST';
2911                      break;
2912                  case 'id-at-organizationName':
2913                      $desc = 'O';
2914                      break;
2915                  case 'id-at-organizationalUnitName':
2916                      $desc = 'OU';
2917                      break;
2918                  case 'id-at-commonName':
2919                      $desc = 'CN';
2920                      break;
2921                  case 'id-at-localityName':
2922                      $desc = 'L';
2923                      break;
2924                  case 'id-at-surname':
2925                      $desc = 'SN';
2926                      break;
2927                  case 'id-at-uniqueIdentifier':
2928                      $delim = '/';
2929                      $desc = 'x500UniqueIdentifier';
2930                      break;
2931                  case 'id-at-postalAddress':
2932                      $delim = '/';
2933                      $desc = 'postalAddress';
2934                      break;
2935                  default:
2936                      $delim = '/';
2937                      $desc = preg_replace('#.+-([^-]+)$#', '$1', $prop);
2938              }
2939  
2940              if (!$start) {
2941                  $output.= $delim;
2942              }
2943              if (is_array($value)) {
2944                  foreach ($value as $type => $v) {
2945                      $type = array_search($type, $asn1->ANYmap, true);
2946                      if ($type !== false && isset($asn1->stringTypeSize[$type])) {
2947                          $v = $asn1->convert($v, $type);
2948                          if ($v !== false) {
2949                              $value = $v;
2950                              break;
2951                          }
2952                      }
2953                  }
2954                  if (is_array($value)) {
2955                      $value = array_pop($value); // Always strip data type.
2956                  }
2957              } elseif (is_object($value) && $value instanceof Element) {
2958                  $callback = function ($x) {
2959                      return "\x" . bin2hex($x[0]);
2960                  };
2961                  $value = strtoupper(preg_replace_callback('#[^\x20-\x7E]#', $callback, $value->element));
2962              }
2963              $output.= $desc . '=' . $value;
2964              $result[$desc] = isset($result[$desc]) ?
2965                  array_merge((array) $result[$desc], array($value)) :
2966                  $value;
2967              $start = false;
2968          }
2969  
2970          return $format == self::DN_OPENSSL ? $result : $output;
2971      }
2972  
2973      /**
2974       * Get the Distinguished Name for a certificate/crl issuer
2975       *
2976       * @param int $format optional
2977       * @access public
2978       * @return mixed
2979       */
2980      function getIssuerDN($format = self::DN_ARRAY)
2981      {
2982          switch (true) {
2983              case !isset($this->currentCert) || !is_array($this->currentCert):
2984                  break;
2985              case isset($this->currentCert['tbsCertificate']):
2986                  return $this->getDN($format, $this->currentCert['tbsCertificate']['issuer']);
2987              case isset($this->currentCert['tbsCertList']):
2988                  return $this->getDN($format, $this->currentCert['tbsCertList']['issuer']);
2989          }
2990  
2991          return false;
2992      }
2993  
2994      /**
2995       * Get the Distinguished Name for a certificate/csr subject
2996       * Alias of getDN()
2997       *
2998       * @param int $format optional
2999       * @access public
3000       * @return mixed
3001       */
3002      function getSubjectDN($format = self::DN_ARRAY)
3003      {
3004          switch (true) {
3005              case !empty($this->dn):
3006                  return $this->getDN($format);
3007              case !isset($this->currentCert) || !is_array($this->currentCert):
3008                  break;
3009              case isset($this->currentCert['tbsCertificate']):
3010                  return $this->getDN($format, $this->currentCert['tbsCertificate']['subject']);
3011              case isset($this->currentCert['certificationRequestInfo']):
3012                  return $this->getDN($format, $this->currentCert['certificationRequestInfo']['subject']);
3013          }
3014  
3015          return false;
3016      }
3017  
3018      /**
3019       * Get an individual Distinguished Name property for a certificate/crl issuer
3020       *
3021       * @param string $propName
3022       * @param bool $withType optional
3023       * @access public
3024       * @return mixed
3025       */
3026      function getIssuerDNProp($propName, $withType = false)
3027      {
3028          switch (true) {
3029              case !isset($this->currentCert) || !is_array($this->currentCert):
3030                  break;
3031              case isset($this->currentCert['tbsCertificate']):
3032                  return $this->getDNProp($propName, $this->currentCert['tbsCertificate']['issuer'], $withType);
3033              case isset($this->currentCert['tbsCertList']):
3034                  return $this->getDNProp($propName, $this->currentCert['tbsCertList']['issuer'], $withType);
3035          }
3036  
3037          return false;
3038      }
3039  
3040      /**
3041       * Get an individual Distinguished Name property for a certificate/csr subject
3042       *
3043       * @param string $propName
3044       * @param bool $withType optional
3045       * @access public
3046       * @return mixed
3047       */
3048      function getSubjectDNProp($propName, $withType = false)
3049      {
3050          switch (true) {
3051              case !empty($this->dn):
3052                  return $this->getDNProp($propName, null, $withType);
3053              case !isset($this->currentCert) || !is_array($this->currentCert):
3054                  break;
3055              case isset($this->currentCert['tbsCertificate']):
3056                  return $this->getDNProp($propName, $this->currentCert['tbsCertificate']['subject'], $withType);
3057              case isset($this->currentCert['certificationRequestInfo']):
3058                  return $this->getDNProp($propName, $this->currentCert['certificationRequestInfo']['subject'], $withType);
3059          }
3060  
3061          return false;
3062      }
3063  
3064      /**
3065       * Get the certificate chain for the current cert
3066       *
3067       * @access public
3068       * @return mixed
3069       */
3070      function getChain()
3071      {
3072          $chain = array($this->currentCert);
3073  
3074          if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
3075              return false;
3076          }
3077          if (empty($this->CAs)) {
3078              return $chain;
3079          }
3080          while (true) {
3081              $currentCert = $chain[count($chain) - 1];
3082              for ($i = 0; $i < count($this->CAs); $i++) {
3083                  $ca = $this->CAs[$i];
3084                  if ($currentCert['tbsCertificate']['issuer'] === $ca['tbsCertificate']['subject']) {
3085                      $authorityKey = $this->getExtension('id-ce-authorityKeyIdentifier', $currentCert);
3086                      $subjectKeyID = $this->getExtension('id-ce-subjectKeyIdentifier', $ca);
3087                      switch (true) {
3088                          case !is_array($authorityKey):
3089                          case is_array($authorityKey) && isset($authorityKey['keyIdentifier']) && $authorityKey['keyIdentifier'] === $subjectKeyID:
3090                              if ($currentCert === $ca) {
3091                                  break 3;
3092                              }
3093                              $chain[] = $ca;
3094                              break 2;
3095                      }
3096                  }
3097              }
3098              if ($i == count($this->CAs)) {
3099                  break;
3100              }
3101          }
3102          foreach ($chain as $key => $value) {
3103              $chain[$key] = new X509();
3104              $chain[$key]->loadX509($value);
3105          }
3106          return $chain;
3107      }
3108  
3109      /**
3110       * Set public key
3111       *
3112       * Key needs to be a \phpseclib\Crypt\RSA object
3113       *
3114       * @param object $key
3115       * @access public
3116       * @return bool
3117       */
3118      function setPublicKey($key)
3119      {
3120          $key->setPublicKey();
3121          $this->publicKey = $key;
3122      }
3123  
3124      /**
3125       * Set private key
3126       *
3127       * Key needs to be a \phpseclib\Crypt\RSA object
3128       *
3129       * @param object $key
3130       * @access public
3131       */
3132      function setPrivateKey($key)
3133      {
3134          $this->privateKey = $key;
3135      }
3136  
3137      /**
3138       * Set challenge
3139       *
3140       * Used for SPKAC CSR's
3141       *
3142       * @param string $challenge
3143       * @access public
3144       */
3145      function setChallenge($challenge)
3146      {
3147          $this->challenge = $challenge;
3148      }
3149  
3150      /**
3151       * Gets the public key
3152       *
3153       * Returns a \phpseclib\Crypt\RSA object or a false.
3154       *
3155       * @access public
3156       * @return mixed
3157       */
3158      function getPublicKey()
3159      {
3160          if (isset($this->publicKey)) {
3161              return $this->publicKey;
3162          }
3163  
3164          if (isset($this->currentCert) && is_array($this->currentCert)) {
3165              foreach (array('tbsCertificate/subjectPublicKeyInfo', 'certificationRequestInfo/subjectPKInfo') as $path) {
3166                  $keyinfo = $this->_subArray($this->currentCert, $path);
3167                  if (!empty($keyinfo)) {
3168                      break;
3169                  }
3170              }
3171          }
3172          if (empty($keyinfo)) {
3173              return false;
3174          }
3175  
3176          $key = $keyinfo['subjectPublicKey'];
3177  
3178          switch ($keyinfo['algorithm']['algorithm']) {
3179              case 'rsaEncryption':
3180                  $publicKey = new RSA();
3181                  $publicKey->loadKey($key);
3182                  $publicKey->setPublicKey();
3183                  break;
3184              default:
3185                  return false;
3186          }
3187  
3188          return $publicKey;
3189      }
3190  
3191      /**
3192       * Load a Certificate Signing Request
3193       *
3194       * @param string $csr
3195       * @access public
3196       * @return mixed
3197       */
3198      function loadCSR($csr, $mode = self::FORMAT_AUTO_DETECT)
3199      {
3200          if (is_array($csr) && isset($csr['certificationRequestInfo'])) {
3201              unset($this->currentCert);
3202              unset($this->currentKeyIdentifier);
3203              unset($this->signatureSubject);
3204              $this->dn = $csr['certificationRequestInfo']['subject'];
3205              if (!isset($this->dn)) {
3206                  return false;
3207              }
3208  
3209              $this->currentCert = $csr;
3210              return $csr;
3211          }
3212  
3213          // see http://tools.ietf.org/html/rfc2986
3214  
3215          $asn1 = new ASN1();
3216  
3217          if ($mode != self::FORMAT_DER) {
3218              $newcsr = $this->_extractBER($csr);
3219              if ($mode == self::FORMAT_PEM && $csr == $newcsr) {
3220                  return false;
3221              }
3222              $csr = $newcsr;
3223          }
3224          $orig = $csr;
3225  
3226          if ($csr === false) {
3227              $this->currentCert = false;
3228              return false;
3229          }
3230  
3231          $asn1->loadOIDs($this->oids);
3232          $decoded = $asn1->decodeBER($csr);
3233  
3234          if (empty($decoded)) {
3235              $this->currentCert = false;
3236              return false;
3237          }
3238  
3239          $csr = $asn1->asn1map($decoded[0], $this->CertificationRequest);
3240          if (!isset($csr) || $csr === false) {
3241              $this->currentCert = false;
3242              return false;
3243          }
3244  
3245          $this->_mapInAttributes($csr, 'certificationRequestInfo/attributes', $asn1);
3246          $this->_mapInDNs($csr, 'certificationRequestInfo/subject/rdnSequence', $asn1);
3247  
3248          $this->dn = $csr['certificationRequestInfo']['subject'];
3249  
3250          $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
3251  
3252          $algorithm = &$csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm'];
3253          $key = &$csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'];
3254          $key = $this->_reformatKey($algorithm, $key);
3255  
3256          switch ($algorithm) {
3257              case 'rsaEncryption':
3258                  $this->publicKey = new RSA();
3259                  $this->publicKey->loadKey($key);
3260                  $this->publicKey->setPublicKey();
3261                  break;
3262              default:
3263                  $this->publicKey = null;
3264          }
3265  
3266          $this->currentKeyIdentifier = null;
3267          $this->currentCert = $csr;
3268  
3269          return $csr;
3270      }
3271  
3272      /**
3273       * Save CSR request
3274       *
3275       * @param array $csr
3276       * @param int $format optional
3277       * @access public
3278       * @return string
3279       */
3280      function saveCSR($csr, $format = self::FORMAT_PEM)
3281      {
3282          if (!is_array($csr) || !isset($csr['certificationRequestInfo'])) {
3283              return false;
3284          }
3285  
3286          switch (true) {
3287              case !($algorithm = $this->_subArray($csr, 'certificationRequestInfo/subjectPKInfo/algorithm/algorithm')):
3288              case is_object($csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']):
3289                  break;
3290              default:
3291                  switch ($algorithm) {
3292                      case 'rsaEncryption':
3293                          $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']
3294                              = base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'])));
3295                          $csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['parameters'] = null;
3296                          $csr['signatureAlgorithm']['parameters'] = null;
3297                          $csr['certificationRequestInfo']['signature']['parameters'] = null;
3298                  }
3299          }
3300  
3301          $asn1 = new ASN1();
3302  
3303          $asn1->loadOIDs($this->oids);
3304  
3305          $filters = array();
3306          $filters['certificationRequestInfo']['subject']['rdnSequence']['value']
3307              = array('type' => ASN1::TYPE_UTF8_STRING);
3308  
3309          $asn1->loadFilters($filters);
3310  
3311          $this->_mapOutDNs($csr, 'certificationRequestInfo/subject/rdnSequence', $asn1);
3312          $this->_mapOutAttributes($csr, 'certificationRequestInfo/attributes', $asn1);
3313          $csr = $asn1->encodeDER($csr, $this->CertificationRequest);
3314  
3315          switch ($format) {
3316              case self::FORMAT_DER:
3317                  return $csr;
3318              // case self::FORMAT_PEM:
3319              default:
3320                  return "-----BEGIN CERTIFICATE REQUEST-----\r\n" . chunk_split(base64_encode($csr), 64) . '-----END CERTIFICATE REQUEST-----';
3321          }
3322      }
3323  
3324      /**
3325       * Load a SPKAC CSR
3326       *
3327       * SPKAC's are produced by the HTML5 keygen element:
3328       *
3329       * https://developer.mozilla.org/en-US/docs/HTML/Element/keygen
3330       *
3331       * @param string $csr
3332       * @access public
3333       * @return mixed
3334       */
3335      function loadSPKAC($spkac)
3336      {
3337          if (is_array($spkac) && isset($spkac['publicKeyAndChallenge'])) {
3338              unset($this->currentCert);
3339              unset($this->currentKeyIdentifier);
3340              unset($this->signatureSubject);
3341              $this->currentCert = $spkac;
3342              return $spkac;
3343          }
3344  
3345          // see http://www.w3.org/html/wg/drafts/html/master/forms.html#signedpublickeyandchallenge
3346  
3347          $asn1 = new ASN1();
3348  
3349          // OpenSSL produces SPKAC's that are preceded by the string SPKAC=
3350          $temp = preg_replace('#(?:SPKAC=)|[ \r\n\\\]#', '', $spkac);
3351          $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false;
3352          if ($temp != false) {
3353              $spkac = $temp;
3354          }
3355          $orig = $spkac;
3356  
3357          if ($spkac === false) {
3358              $this->currentCert = false;
3359              return false;
3360          }
3361  
3362          $asn1->loadOIDs($this->oids);
3363          $decoded = $asn1->decodeBER($spkac);
3364  
3365          if (empty($decoded)) {
3366              $this->currentCert = false;
3367              return false;
3368          }
3369  
3370          $spkac = $asn1->asn1map($decoded[0], $this->SignedPublicKeyAndChallenge);
3371  
3372          if (!isset($spkac) || $spkac === false) {
3373              $this->currentCert = false;
3374              return false;
3375          }
3376  
3377          $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
3378  
3379          $algorithm = &$spkac['publicKeyAndChallenge']['spki']['algorithm']['algorithm'];
3380          $key = &$spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'];
3381          $key = $this->_reformatKey($algorithm, $key);
3382  
3383          switch ($algorithm) {
3384              case 'rsaEncryption':
3385                  $this->publicKey = new RSA();
3386                  $this->publicKey->loadKey($key);
3387                  $this->publicKey->setPublicKey();
3388                  break;
3389              default:
3390                  $this->publicKey = null;
3391          }
3392  
3393          $this->currentKeyIdentifier = null;
3394          $this->currentCert = $spkac;
3395  
3396          return $spkac;
3397      }
3398  
3399      /**
3400       * Save a SPKAC CSR request
3401       *
3402       * @param array $csr
3403       * @param int $format optional
3404       * @access public
3405       * @return string
3406       */
3407      function saveSPKAC($spkac, $format = self::FORMAT_PEM)
3408      {
3409          if (!is_array($spkac) || !isset($spkac['publicKeyAndChallenge'])) {
3410              return false;
3411          }
3412  
3413          $algorithm = $this->_subArray($spkac, 'publicKeyAndChallenge/spki/algorithm/algorithm');
3414          switch (true) {
3415              case !$algorithm:
3416              case is_object($spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']):
3417                  break;
3418              default:
3419                  switch ($algorithm) {
3420                      case 'rsaEncryption':
3421                          $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']
3422                              = base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'])));
3423                  }
3424          }
3425  
3426          $asn1 = new ASN1();
3427  
3428          $asn1->loadOIDs($this->oids);
3429          $spkac = $asn1->encodeDER($spkac, $this->SignedPublicKeyAndChallenge);
3430  
3431          switch ($format) {
3432              case self::FORMAT_DER:
3433                  return $spkac;
3434              // case self::FORMAT_PEM:
3435              default:
3436                  // OpenSSL's implementation of SPKAC requires the SPKAC be preceded by SPKAC= and since there are pretty much
3437                  // no other SPKAC decoders phpseclib will use that same format
3438                  return 'SPKAC=' . base64_encode($spkac);
3439          }
3440      }
3441  
3442      /**
3443       * Load a Certificate Revocation List
3444       *
3445       * @param string $crl
3446       * @access public
3447       * @return mixed
3448       */
3449      function loadCRL($crl, $mode = self::FORMAT_AUTO_DETECT)
3450      {
3451          if (is_array($crl) && isset($crl['tbsCertList'])) {
3452              $this->currentCert = $crl;
3453              unset($this->signatureSubject);
3454              return $crl;
3455          }
3456  
3457          $asn1 = new ASN1();
3458  
3459          if ($mode != self::FORMAT_DER) {
3460              $newcrl = $this->_extractBER($crl);
3461              if ($mode == self::FORMAT_PEM && $crl == $newcrl) {
3462                  return false;
3463              }
3464              $crl = $newcrl;
3465          }
3466          $orig = $crl;
3467  
3468          if ($crl === false) {
3469              $this->currentCert = false;
3470              return false;
3471          }
3472  
3473          $asn1->loadOIDs($this->oids);
3474          $decoded = $asn1->decodeBER($crl);
3475  
3476          if (empty($decoded)) {
3477              $this->currentCert = false;
3478              return false;
3479          }
3480  
3481          $crl = $asn1->asn1map($decoded[0], $this->CertificateList);
3482          if (!isset($crl) || $crl === false) {
3483              $this->currentCert = false;
3484              return false;
3485          }
3486  
3487          $this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
3488  
3489          $this->_mapInDNs($crl, 'tbsCertList/issuer/rdnSequence', $asn1);
3490          if ($this->_isSubArrayValid($crl, 'tbsCertList/crlExtensions')) {
3491              $this->_mapInExtensions($crl, 'tbsCertList/crlExtensions', $asn1);
3492          }
3493          if ($this->_isSubArrayValid($crl, 'tbsCertList/revokedCertificates')) {
3494              $rclist_ref = &$this->_subArrayUnchecked($crl, 'tbsCertList/revokedCertificates');
3495              if ($rclist_ref) {
3496                  $rclist = $crl['tbsCertList']['revokedCertificates'];
3497                  foreach ($rclist as $i => $extension) {
3498                      if ($this->_isSubArrayValid($rclist, "$i/crlEntryExtensions", $asn1)) {
3499                          $this->_mapInExtensions($rclist_ref, "$i/crlEntryExtensions", $asn1);
3500                      }
3501                  }
3502              }
3503          }
3504  
3505          $this->currentKeyIdentifier = null;
3506          $this->currentCert = $crl;
3507  
3508          return $crl;
3509      }
3510  
3511      /**
3512       * Save Certificate Revocation List.
3513       *
3514       * @param array $crl
3515       * @param int $format optional
3516       * @access public
3517       * @return string
3518       */
3519      function saveCRL($crl, $format = self::FORMAT_PEM)
3520      {
3521          if (!is_array($crl) || !isset($crl['tbsCertList'])) {
3522              return false;
3523          }
3524  
3525          $asn1 = new ASN1();
3526  
3527          $asn1->loadOIDs($this->oids);
3528  
3529          $filters = array();
3530          $filters['tbsCertList']['issuer']['rdnSequence']['value']
3531              = array('type' => ASN1::TYPE_UTF8_STRING);
3532          $filters['tbsCertList']['signature']['parameters']
3533              = array('type' => ASN1::TYPE_UTF8_STRING);
3534          $filters['signatureAlgorithm']['parameters']
3535              = array('type' => ASN1::TYPE_UTF8_STRING);
3536  
3537          if (empty($crl['tbsCertList']['signature']['parameters'])) {
3538              $filters['tbsCertList']['signature']['parameters']
3539                  = array('type' => ASN1::TYPE_NULL);
3540          }
3541  
3542          if (empty($crl['signatureAlgorithm']['parameters'])) {
3543              $filters['signatureAlgorithm']['parameters']
3544                  = array('type' => ASN1::TYPE_NULL);
3545          }
3546  
3547          $asn1->loadFilters($filters);
3548  
3549          $this->_mapOutDNs($crl, 'tbsCertList/issuer/rdnSequence', $asn1);
3550          $this->_mapOutExtensions($crl, 'tbsCertList/crlExtensions', $asn1);
3551          $rclist = &$this->_subArray($crl, 'tbsCertList/revokedCertificates');
3552          if (is_array($rclist)) {
3553              foreach ($rclist as $i => $extension) {
3554                  $this->_mapOutExtensions($rclist, "$i/crlEntryExtensions", $asn1);
3555              }
3556          }
3557  
3558          $crl = $asn1->encodeDER($crl, $this->CertificateList);
3559  
3560          switch ($format) {
3561              case self::FORMAT_DER:
3562                  return $crl;
3563              // case self::FORMAT_PEM:
3564              default:
3565                  return "-----BEGIN X509 CRL-----\r\n" . chunk_split(base64_encode($crl), 64) . '-----END X509 CRL-----';
3566          }
3567      }
3568  
3569      /**
3570       * Helper function to build a time field according to RFC 3280 section
3571       *  - 4.1.2.5 Validity
3572       *  - 5.1.2.4 This Update
3573       *  - 5.1.2.5 Next Update
3574       *  - 5.1.2.6 Revoked Certificates
3575       * by choosing utcTime iff year of date given is before 2050 and generalTime else.
3576       *
3577       * @param string $date in format date('D, d M Y H:i:s O')
3578       * @access private
3579       * @return array
3580       */
3581      function _timeField($date)
3582      {
3583          if ($date instanceof Element) {
3584              return $date;
3585          }
3586          $dateObj = new DateTime($date, new DateTimeZone('GMT'));
3587          $year = $dateObj->format('Y'); // the same way ASN1.php parses this
3588          if ($year < 2050) {
3589              return array('utcTime' => $date);
3590          } else {
3591              return array('generalTime' => $date);
3592          }
3593      }
3594  
3595      /**
3596       * Sign an X.509 certificate
3597       *
3598       * $issuer's private key needs to be loaded.
3599       * $subject can be either an existing X.509 cert (if you want to resign it),
3600       * a CSR or something with the DN and public key explicitly set.
3601       *
3602       * @param \phpseclib\File\X509 $issuer
3603       * @param \phpseclib\File\X509 $subject
3604       * @param string $signatureAlgorithm optional
3605       * @access public
3606       * @return mixed
3607       */
3608      function sign($issuer, $subject, $signatureAlgorithm = 'sha1WithRSAEncryption')
3609      {
3610          if (!is_object($issuer->privateKey) || empty($issuer->dn)) {
3611              return false;
3612          }
3613  
3614          if (isset($subject->publicKey) && !($subjectPublicKey = $subject->_formatSubjectPublicKey())) {
3615              return false;
3616          }
3617  
3618          $currentCert = isset($this->currentCert) ? $this->currentCert : null;
3619          $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null;
3620  
3621          if (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertificate'])) {
3622              $this->currentCert = $subject->currentCert;
3623              $this->currentCert['tbsCertificate']['signature']['algorithm'] = $signatureAlgorithm;
3624              $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
3625  
3626              if (!empty($this->startDate)) {
3627                  $this->currentCert['tbsCertificate']['validity']['notBefore'] = $this->_timeField($this->startDate);
3628              }
3629              if (!empty($this->endDate)) {
3630                  $this->currentCert['tbsCertificate']['validity']['notAfter'] = $this->_timeField($this->endDate);
3631              }
3632              if (!empty($this->serialNumber)) {
3633                  $this->currentCert['tbsCertificate']['serialNumber'] = $this->serialNumber;
3634              }
3635              if (!empty($subject->dn)) {
3636                  $this->currentCert['tbsCertificate']['subject'] = $subject->dn;
3637              }
3638              if (!empty($subject->publicKey)) {
3639                  $this->currentCert['tbsCertificate']['subjectPublicKeyInfo'] = $subjectPublicKey;
3640              }
3641              $this->removeExtension('id-ce-authorityKeyIdentifier');
3642              if (isset($subject->domains)) {
3643                  $this->removeExtension('id-ce-subjectAltName');
3644              }
3645          } elseif (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertList'])) {
3646              return false;
3647          } else {
3648              if (!isset($subject->publicKey)) {
3649                  return false;
3650              }
3651  
3652              $startDate = new DateTime('now', new DateTimeZone(@date_default_timezone_get()));
3653              $startDate = !empty($this->startDate) ? $this->startDate : $startDate->format('D, d M Y H:i:s O');
3654  
3655              $endDate = new DateTime('+1 year', new DateTimeZone(@date_default_timezone_get()));
3656              $endDate = !empty($this->endDate) ? $this->endDate : $endDate->format('D, d M Y H:i:s O');
3657  
3658              /* "The serial number MUST be a positive integer"
3659                 "Conforming CAs MUST NOT use serialNumber values longer than 20 octets."
3660                  -- https://tools.ietf.org/html/rfc5280#section-4.1.2.2
3661  
3662                 for the integer to be positive the leading bit needs to be 0 hence the
3663                 application of a bitmap
3664              */
3665              $serialNumber = !empty($this->serialNumber) ?
3666                  $this->serialNumber :
3667                  new BigInteger(Random::string(20) & ("\x7F" . str_repeat("\xFF", 19)), 256);
3668  
3669              $this->currentCert = array(
3670                  'tbsCertificate' =>
3671                      array(
3672                          'version' => 'v3',
3673                          'serialNumber' => $serialNumber, // $this->setSerialNumber()
3674                          'signature' => array('algorithm' => $signatureAlgorithm),
3675                          'issuer' => false, // this is going to be overwritten later
3676                          'validity' => array(
3677                              'notBefore' => $this->_timeField($startDate), // $this->setStartDate()
3678                              'notAfter' => $this->_timeField($endDate)   // $this->setEndDate()
3679                          ),
3680                          'subject' => $subject->dn,
3681                          'subjectPublicKeyInfo' => $subjectPublicKey
3682                      ),
3683                      'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
3684                      'signature'          => false // this is going to be overwritten later
3685              );
3686  
3687              // Copy extensions from CSR.
3688              $csrexts = $subject->getAttribute('pkcs-9-at-extensionRequest', 0);
3689  
3690              if (!empty($csrexts)) {
3691                  $this->currentCert['tbsCertificate']['extensions'] = $csrexts;
3692              }
3693          }
3694  
3695          $this->currentCert['tbsCertificate']['issuer'] = $issuer->dn;
3696  
3697          if (isset($issuer->currentKeyIdentifier)) {
3698              $this->setExtension('id-ce-authorityKeyIdentifier', array(
3699                      //'authorityCertIssuer' => array(
3700                      //    array(
3701                      //        'directoryName' => $issuer->dn
3702                      //    )
3703                      //),
3704                      'keyIdentifier' => $issuer->currentKeyIdentifier
3705                  ));
3706              //$extensions = &$this->currentCert['tbsCertificate']['extensions'];
3707              //if (isset($issuer->serialNumber)) {
3708              //    $extensions[count($extensions) - 1]['authorityCertSerialNumber'] = $issuer->serialNumber;
3709              //}
3710              //unset($extensions);
3711          }
3712  
3713          if (isset($subject->currentKeyIdentifier)) {
3714              $this->setExtension('id-ce-subjectKeyIdentifier', $subject->currentKeyIdentifier);
3715          }
3716  
3717          $altName = array();
3718  
3719          if (isset($subject->domains) && count($subject->domains)) {
3720              $altName = array_map(array('\phpseclib\File\X509', '_dnsName'), $subject->domains);
3721          }
3722  
3723          if (isset($subject->ipAddresses) && count($subject->ipAddresses)) {
3724              // should an IP address appear as the CN if no domain name is specified? idk
3725              //$ips = count($subject->domains) ? $subject->ipAddresses : array_slice($subject->ipAddresses, 1);
3726              $ipAddresses = array();
3727              foreach ($subject->ipAddresses as $ipAddress) {
3728                  $encoded = $subject->_ipAddress($ipAddress);
3729                  if ($encoded !== false) {
3730                      $ipAddresses[] = $encoded;
3731                  }
3732              }
3733              if (count($ipAddresses)) {
3734                  $altName = array_merge($altName, $ipAddresses);
3735              }
3736          }
3737  
3738          if (!empty($altName)) {
3739              $this->setExtension('id-ce-subjectAltName', $altName);
3740          }
3741  
3742          if ($this->caFlag) {
3743              $keyUsage = $this->getExtension('id-ce-keyUsage');
3744              if (!$keyUsage) {
3745                  $keyUsage = array();
3746              }
3747  
3748              $this->setExtension(
3749                  'id-ce-keyUsage',
3750                  array_values(array_unique(array_merge($keyUsage, array('cRLSign', 'keyCertSign'))))
3751              );
3752  
3753              $basicConstraints = $this->getExtension('id-ce-basicConstraints');
3754              if (!$basicConstraints) {
3755                  $basicConstraints = array();
3756              }
3757  
3758              $this->setExtension(
3759                  'id-ce-basicConstraints',
3760                  array_unique(array_merge(array('cA' => true), $basicConstraints)),
3761                  true
3762              );
3763  
3764              if (!isset($subject->currentKeyIdentifier)) {
3765                  $this->setExtension('id-ce-subjectKeyIdentifier', base64_encode($this->computeKeyIdentifier($this->currentCert)), false, false);
3766              }
3767          }
3768  
3769          // resync $this->signatureSubject
3770          // save $tbsCertificate in case there are any \phpseclib\File\ASN1\Element objects in it
3771          $tbsCertificate = $this->currentCert['tbsCertificate'];
3772          $this->loadX509($this->saveX509($this->currentCert));
3773  
3774          $result = $this->_sign($issuer->privateKey, $signatureAlgorithm);
3775          $result['tbsCertificate'] = $tbsCertificate;
3776  
3777          $this->currentCert = $currentCert;
3778          $this->signatureSubject = $signatureSubject;
3779  
3780          return $result;
3781      }
3782  
3783      /**
3784       * Sign a CSR
3785       *
3786       * @access public
3787       * @return mixed
3788       */
3789      function signCSR($signatureAlgorithm = 'sha1WithRSAEncryption')
3790      {
3791          if (!is_object($this->privateKey) || empty($this->dn)) {
3792              return false;
3793          }
3794  
3795          $origPublicKey = $this->publicKey;
3796          $class = get_class($this->privateKey);
3797          $this->publicKey = new $class();
3798          $this->publicKey->loadKey($this->privateKey->getPublicKey());
3799          $this->publicKey->setPublicKey();
3800          if (!($publicKey = $this->_formatSubjectPublicKey())) {
3801              return false;
3802          }
3803          $this->publicKey = $origPublicKey;
3804  
3805          $currentCert = isset($this->currentCert) ? $this->currentCert : null;
3806          $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null;
3807  
3808          if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['certificationRequestInfo'])) {
3809              $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
3810              if (!empty($this->dn)) {
3811                  $this->currentCert['certificationRequestInfo']['subject'] = $this->dn;
3812              }
3813              $this->currentCert['certificationRequestInfo']['subjectPKInfo'] = $publicKey;
3814          } else {
3815              $this->currentCert = array(
3816                  'certificationRequestInfo' =>
3817                      array(
3818                          'version' => 'v1',
3819                          'subject' => $this->dn,
3820                          'subjectPKInfo' => $publicKey
3821                      ),
3822                      'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
3823                      'signature'          => false // this is going to be overwritten later
3824              );
3825          }
3826  
3827          // resync $this->signatureSubject
3828          // save $certificationRequestInfo in case there are any \phpseclib\File\ASN1\Element objects in it
3829          $certificationRequestInfo = $this->currentCert['certificationRequestInfo'];
3830          $this->loadCSR($this->saveCSR($this->currentCert));
3831  
3832          $result = $this->_sign($this->privateKey, $signatureAlgorithm);
3833          $result['certificationRequestInfo'] = $certificationRequestInfo;
3834  
3835          $this->currentCert = $currentCert;
3836          $this->signatureSubject = $signatureSubject;
3837  
3838          return $result;
3839      }
3840  
3841      /**
3842       * Sign a SPKAC
3843       *
3844       * @access public
3845       * @return mixed
3846       */
3847      function signSPKAC($signatureAlgorithm = 'sha1WithRSAEncryption')
3848      {
3849          if (!is_object($this->privateKey)) {
3850              return false;
3851          }
3852  
3853          $origPublicKey = $this->publicKey;
3854          $class = get_class($this->privateKey);
3855          $this->publicKey = new $class();
3856          $this->publicKey->loadKey($this->privateKey->getPublicKey());
3857          $this->publicKey->setPublicKey();
3858          $publicKey = $this->_formatSubjectPublicKey();
3859          if (!$publicKey) {
3860              return false;
3861          }
3862          $this->publicKey = $origPublicKey;
3863  
3864          $currentCert = isset($this->currentCert) ? $this->currentCert : null;
3865          $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null;
3866  
3867          // re-signing a SPKAC seems silly but since everything else supports re-signing why not?
3868          if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['publicKeyAndChallenge'])) {
3869              $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
3870              $this->currentCert['publicKeyAndChallenge']['spki'] = $publicKey;
3871              if (!empty($this->challenge)) {
3872                  // the bitwise AND ensures that the output is a valid IA5String
3873                  $this->currentCert['publicKeyAndChallenge']['challenge'] = $this->challenge & str_repeat("\x7F", strlen($this->challenge));
3874              }
3875          } else {
3876              $this->currentCert = array(
3877                  'publicKeyAndChallenge' =>
3878                      array(
3879                          'spki' => $publicKey,
3880                          // quoting <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/keygen>,
3881                          // "A challenge string that is submitted along with the public key. Defaults to an empty string if not specified."
3882                          // both Firefox and OpenSSL ("openssl spkac -key private.key") behave this way
3883                          // we could alternatively do this instead if we ignored the specs:
3884                          // Random::string(8) & str_repeat("\x7F", 8)
3885                          'challenge' => !empty($this->challenge) ? $this->challenge : ''
3886                      ),
3887                      'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
3888                      'signature'          => false // this is going to be overwritten later
3889              );
3890          }
3891  
3892          // resync $this->signatureSubject
3893          // save $publicKeyAndChallenge in case there are any \phpseclib\File\ASN1\Element objects in it
3894          $publicKeyAndChallenge = $this->currentCert['publicKeyAndChallenge'];
3895          $this->loadSPKAC($this->saveSPKAC($this->currentCert));
3896  
3897          $result = $this->_sign($this->privateKey, $signatureAlgorithm);
3898          $result['publicKeyAndChallenge'] = $publicKeyAndChallenge;
3899  
3900          $this->currentCert = $currentCert;
3901          $this->signatureSubject = $signatureSubject;
3902  
3903          return $result;
3904      }
3905  
3906      /**
3907       * Sign a CRL
3908       *
3909       * $issuer's private key needs to be loaded.
3910       *
3911       * @param \phpseclib\File\X509 $issuer
3912       * @param \phpseclib\File\X509 $crl
3913       * @param string $signatureAlgorithm optional
3914       * @access public
3915       * @return mixed
3916       */
3917      function signCRL($issuer, $crl, $signatureAlgorithm = 'sha1WithRSAEncryption')
3918      {
3919          if (!is_object($issuer->privateKey) || empty($issuer->dn)) {
3920              return false;
3921          }
3922  
3923          $currentCert = isset($this->currentCert) ? $this->currentCert : null;
3924          $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null;
3925  
3926          $thisUpdate = new DateTime('now', new DateTimeZone(@date_default_timezone_get()));
3927          $thisUpdate = !empty($this->startDate) ? $this->startDate : $thisUpdate->format('D, d M Y H:i:s O');
3928  
3929          if (isset($crl->currentCert) && is_array($crl->currentCert) && isset($crl->currentCert['tbsCertList'])) {
3930              $this->currentCert = $crl->currentCert;
3931              $this->currentCert['tbsCertList']['signature']['algorithm'] = $signatureAlgorithm;
3932              $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
3933          } else {
3934              $this->currentCert = array(
3935                  'tbsCertList' =>
3936                      array(
3937                          'version' => 'v2',
3938                          'signature' => array('algorithm' => $signatureAlgorithm),
3939                          'issuer' => false, // this is going to be overwritten later
3940                          'thisUpdate' => $this->_timeField($thisUpdate) // $this->setStartDate()
3941                      ),
3942                      'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
3943                      'signature'          => false // this is going to be overwritten later
3944              );
3945          }
3946  
3947          $tbsCertList = &$this->currentCert['tbsCertList'];
3948          $tbsCertList['issuer'] = $issuer->dn;
3949          $tbsCertList['thisUpdate'] = $this->_timeField($thisUpdate);
3950  
3951          if (!empty($this->endDate)) {
3952              $tbsCertList['nextUpdate'] = $this->_timeField($this->endDate); // $this->setEndDate()
3953          } else {
3954              unset($tbsCertList['nextUpdate']);
3955          }
3956  
3957          if (!empty($this->serialNumber)) {
3958              $crlNumber = $this->serialNumber;
3959          } else {
3960              $crlNumber = $this->getExtension('id-ce-cRLNumber');
3961              // "The CRL number is a non-critical CRL extension that conveys a
3962              //  monotonically increasing sequence number for a given CRL scope and
3963              //  CRL issuer.  This extension allows users to easily determine when a
3964              //  particular CRL supersedes another CRL."
3965              // -- https://tools.ietf.org/html/rfc5280#section-5.2.3
3966              $crlNumber = $crlNumber !== false ? $crlNumber->add(new BigInteger(1)) : null;
3967          }
3968  
3969          $this->removeExtension('id-ce-authorityKeyIdentifier');
3970          $this->removeExtension('id-ce-issuerAltName');
3971  
3972          // Be sure version >= v2 if some extension found.
3973          $version = isset($tbsCertList['version']) ? $tbsCertList['version'] : 0;
3974          if (!$version) {
3975              if (!empty($tbsCertList['crlExtensions'])) {
3976                  $version = 1; // v2.
3977              } elseif (!empty($tbsCertList['revokedCertificates'])) {
3978                  foreach ($tbsCertList['revokedCertificates'] as $cert) {
3979                      if (!empty($cert['crlEntryExtensions'])) {
3980                          $version = 1; // v2.
3981                      }
3982                  }
3983              }
3984  
3985              if ($version) {
3986                  $tbsCertList['version'] = $version;
3987              }
3988          }
3989  
3990          // Store additional extensions.
3991          if (!empty($tbsCertList['version'])) { // At least v2.
3992              if (!empty($crlNumber)) {
3993                  $this->setExtension('id-ce-cRLNumber', $crlNumber);
3994              }
3995  
3996              if (isset($issuer->currentKeyIdentifier)) {
3997                  $this->setExtension('id-ce-authorityKeyIdentifier', array(
3998                          //'authorityCertIssuer' => array(
3999                          //    array(
4000                          //        'directoryName' => $issuer->dn
4001                          //    )
4002                          //),
4003                          'keyIdentifier' => $issuer->currentKeyIdentifier
4004                      ));
4005                  //$extensions = &$tbsCertList['crlExtensions'];
4006                  //if (isset($issuer->serialNumber)) {
4007                  //    $extensions[count($extensions) - 1]['authorityCertSerialNumber'] = $issuer->serialNumber;
4008                  //}
4009                  //unset($extensions);
4010              }
4011  
4012              $issuerAltName = $this->getExtension('id-ce-subjectAltName', $issuer->currentCert);
4013  
4014              if ($issuerAltName !== false) {
4015                  $this->setExtension('id-ce-issuerAltName', $issuerAltName);
4016              }
4017          }
4018  
4019          if (empty($tbsCertList['revokedCertificates'])) {
4020              unset($tbsCertList['revokedCertificates']);
4021          }
4022  
4023          unset($tbsCertList);
4024  
4025          // resync $this->signatureSubject
4026          // save $tbsCertList in case there are any \phpseclib\File\ASN1\Element objects in it
4027          $tbsCertList = $this->currentCert['tbsCertList'];
4028          $this->loadCRL($this->saveCRL($this->currentCert));
4029  
4030          $result = $this->_sign($issuer->privateKey, $signatureAlgorithm);
4031          $result['tbsCertList'] = $tbsCertList;
4032  
4033          $this->currentCert = $currentCert;
4034          $this->signatureSubject = $signatureSubject;
4035  
4036          return $result;
4037      }
4038  
4039      /**
4040       * X.509 certificate signing helper function.
4041       *
4042       * @param object $key
4043       * @param \phpseclib\File\X509 $subject
4044       * @param string $signatureAlgorithm
4045       * @access public
4046       * @return mixed
4047       */
4048      function _sign($key, $signatureAlgorithm)
4049      {
4050          if ($key instanceof RSA) {
4051              switch ($signatureAlgorithm) {
4052                  case 'md2WithRSAEncryption':
4053                  case 'md5WithRSAEncryption':
4054                  case 'sha1WithRSAEncryption':
4055                  case 'sha224WithRSAEncryption':
4056                  case 'sha256WithRSAEncryption':
4057                  case 'sha384WithRSAEncryption':
4058                  case 'sha512WithRSAEncryption':
4059                      $key->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm));
4060                      $key->setSignatureMode(RSA::SIGNATURE_PKCS1);
4061  
4062                      $this->currentCert['signature'] = base64_encode("\0" . $key->sign($this->signatureSubject));
4063                      return $this->currentCert;
4064              }
4065          }
4066  
4067          return false;
4068      }
4069  
4070      /**
4071       * Set certificate start date
4072       *
4073       * @param string $date
4074       * @access public
4075       */
4076      function setStartDate($date)
4077      {
4078          if (!is_object($date) || !is_a($date, 'DateTime')) {
4079              $date = new DateTime($date, new DateTimeZone(@date_default_timezone_get()));
4080          }
4081  
4082          $this->startDate = $date->format('D, d M Y H:i:s O');
4083      }
4084  
4085      /**
4086       * Set certificate end date
4087       *
4088       * @param string $date
4089       * @access public
4090       */
4091      function setEndDate($date)
4092      {
4093          /*
4094            To indicate that a certificate has no well-defined expiration date,
4095            the notAfter SHOULD be assigned the GeneralizedTime value of
4096            99991231235959Z.
4097  
4098            -- http://tools.ietf.org/html/rfc5280#section-4.1.2.5
4099          */
4100          if (strtolower($date) == 'lifetime') {
4101              $temp = '99991231235959Z';
4102              $asn1 = new ASN1();
4103              $temp = chr(ASN1::TYPE_GENERALIZED_TIME) . $asn1->_encodeLength(strlen($temp)) . $temp;
4104              $this->endDate = new Element($temp);
4105          } else {
4106              if (!is_object($date) || !is_a($date, 'DateTime')) {
4107                  $date = new DateTime($date, new DateTimeZone(@date_default_timezone_get()));
4108              }
4109  
4110              $this->endDate = $date->format('D, d M Y H:i:s O');
4111          }
4112      }
4113  
4114      /**
4115       * Set Serial Number
4116       *
4117       * @param string $serial
4118       * @param $base optional
4119       * @access public
4120       */
4121      function setSerialNumber($serial, $base = -256)
4122      {
4123          $this->serialNumber = new BigInteger($serial, $base);
4124      }
4125  
4126      /**
4127       * Turns the certificate into a certificate authority
4128       *
4129       * @access public
4130       */
4131      function makeCA()
4132      {
4133          $this->caFlag = true;
4134      }
4135  
4136      /**
4137       * Check for validity of subarray
4138       *
4139       * This is intended for use in conjunction with _subArrayUnchecked(),
4140       * implementing the checks included in _subArray() but without copying
4141       * a potentially large array by passing its reference by-value to is_array().
4142       *
4143       * @param array $root
4144       * @param string $path
4145       * @return boolean
4146       * @access private
4147       */
4148      function _isSubArrayValid($root, $path)
4149      {
4150          if (!is_array($root)) {
4151              return false;
4152          }
4153  
4154          foreach (explode('/', $path) as $i) {
4155              if (!is_array($root)) {
4156                  return false;
4157              }
4158  
4159              if (!isset($root[$i])) {
4160                  return true;
4161              }
4162  
4163              $root = $root[$i];
4164          }
4165  
4166          return true;
4167      }
4168  
4169      /**
4170       * Get a reference to a subarray
4171       *
4172       * This variant of _subArray() does no is_array() checking,
4173       * so $root should be checked with _isSubArrayValid() first.
4174       *
4175       * This is here for performance reasons:
4176       * Passing a reference (i.e. $root) by-value (i.e. to is_array())
4177       * creates a copy. If $root is an especially large array, this is expensive.
4178       *
4179       * @param array $root
4180       * @param string $path  absolute path with / as component separator
4181       * @param bool $create optional
4182       * @access private
4183       * @return array|false
4184       */
4185      function &_subArrayUnchecked(&$root, $path, $create = false)
4186      {
4187          $false = false;
4188  
4189          foreach (explode('/', $path) as $i) {
4190              if (!isset($root[$i])) {
4191                  if (!$create) {
4192                      return $false;
4193                  }
4194  
4195                  $root[$i] = array();
4196              }
4197  
4198              $root = &$root[$i];
4199          }
4200  
4201          return $root;
4202      }
4203  
4204      /**
4205       * Get a reference to a subarray
4206       *
4207       * @param array $root
4208       * @param string $path  absolute path with / as component separator
4209       * @param bool $create optional
4210       * @access private
4211       * @return array|false
4212       */
4213      function &_subArray(&$root, $path, $create = false)
4214      {
4215          $false = false;
4216  
4217          if (!is_array($root)) {
4218              return $false;
4219          }
4220  
4221          foreach (explode('/', $path) as $i) {
4222              if (!is_array($root)) {
4223                  return $false;
4224              }
4225  
4226              if (!isset($root[$i])) {
4227                  if (!$create) {
4228                      return $false;
4229                  }
4230  
4231                  $root[$i] = array();
4232              }
4233  
4234              $root = &$root[$i];
4235          }
4236  
4237          return $root;
4238      }
4239  
4240      /**
4241       * Get a reference to an extension subarray
4242       *
4243       * @param array $root
4244       * @param string $path optional absolute path with / as component separator
4245       * @param bool $create optional
4246       * @access private
4247       * @return array|false
4248       */
4249      function &_extensions(&$root, $path = null, $create = false)
4250      {
4251          if (!isset($root)) {
4252              $root = $this->currentCert;
4253          }
4254  
4255          switch (true) {
4256              case !empty($path):
4257              case !is_array($root):
4258                  break;
4259              case isset($root['tbsCertificate']):
4260                  $path = 'tbsCertificate/extensions';
4261                  break;
4262              case isset($root['tbsCertList']):
4263                  $path = 'tbsCertList/crlExtensions';
4264                  break;
4265              case isset($root['certificationRequestInfo']):
4266                  $pth = 'certificationRequestInfo/attributes';
4267                  $attributes = &$this->_subArray($root, $pth, $create);
4268  
4269                  if (is_array($attributes)) {
4270                      foreach ($attributes as $key => $value) {
4271                          if ($value['type'] == 'pkcs-9-at-extensionRequest') {
4272                              $path = "$pth/$key/value/0";
4273                              break 2;
4274                          }
4275                      }
4276                      if ($create) {
4277                          $key = count($attributes);
4278                          $attributes[] = array('type' => 'pkcs-9-at-extensionRequest', 'value' => array());
4279                          $path = "$pth/$key/value/0";
4280                      }
4281                  }
4282                  break;
4283          }
4284  
4285          $extensions = &$this->_subArray($root, $path, $create);
4286  
4287          if (!is_array($extensions)) {
4288              $false = false;
4289              return $false;
4290          }
4291  
4292          return $extensions;
4293      }
4294  
4295      /**
4296       * Remove an Extension
4297       *
4298       * @param string $id
4299       * @param string $path optional
4300       * @access private
4301       * @return bool
4302       */
4303      function _removeExtension($id, $path = null)
4304      {
4305          $extensions = &$this->_extensions($this->currentCert, $path);
4306  
4307          if (!is_array($extensions)) {
4308              return false;
4309          }
4310  
4311          $result = false;
4312          foreach ($extensions as $key => $value) {
4313              if ($value['extnId'] == $id) {
4314                  unset($extensions[$key]);
4315                  $result = true;
4316              }
4317          }
4318  
4319          $extensions = array_values($extensions);
4320          // fix for https://bugs.php.net/75433 affecting PHP 7.2
4321          if (!isset($extensions[0])) {
4322              $extensions = array_splice($extensions, 0, 0);
4323          }
4324          return $result;
4325      }
4326  
4327      /**
4328       * Get an Extension
4329       *
4330       * Returns the extension if it exists and false if not
4331       *
4332       * @param string $id
4333       * @param array $cert optional
4334       * @param string $path optional
4335       * @access private
4336       * @return mixed
4337       */
4338      function _getExtension($id, $cert = null, $path = null)
4339      {
4340          $extensions = $this->_extensions($cert, $path);
4341  
4342          if (!is_array($extensions)) {
4343              return false;
4344          }
4345  
4346          foreach ($extensions as $key => $value) {
4347              if ($value['extnId'] == $id) {
4348                  return $value['extnValue'];
4349              }
4350          }
4351  
4352          return false;
4353      }
4354  
4355      /**
4356       * Returns a list of all extensions in use
4357       *
4358       * @param array $cert optional
4359       * @param string $path optional
4360       * @access private
4361       * @return array
4362       */
4363      function _getExtensions($cert = null, $path = null)
4364      {
4365          $exts = $this->_extensions($cert, $path);
4366          $extensions = array();
4367  
4368          if (is_array($exts)) {
4369              foreach ($exts as $extension) {
4370                  $extensions[] = $extension['extnId'];
4371              }
4372          }
4373  
4374          return $extensions;
4375      }
4376  
4377      /**
4378       * Set an Extension
4379       *
4380       * @param string $id
4381       * @param mixed $value
4382       * @param bool $critical optional
4383       * @param bool $replace optional
4384       * @param string $path optional
4385       * @access private
4386       * @return bool
4387       */
4388      function _setExtension($id, $value, $critical = false, $replace = true, $path = null)
4389      {
4390          $extensions = &$this->_extensions($this->currentCert, $path, true);
4391  
4392          if (!is_array($extensions)) {
4393              return false;
4394          }
4395  
4396          $newext = array('extnId'  => $id, 'critical' => $critical, 'extnValue' => $value);
4397  
4398          foreach ($extensions as $key => $value) {
4399              if ($value['extnId'] == $id) {
4400                  if (!$replace) {
4401                      return false;
4402                  }
4403  
4404                  $extensions[$key] = $newext;
4405                  return true;
4406              }
4407          }
4408  
4409          $extensions[] = $newext;
4410          return true;
4411      }
4412  
4413      /**
4414       * Remove a certificate, CSR or CRL Extension
4415       *
4416       * @param string $id
4417       * @access public
4418       * @return bool
4419       */
4420      function removeExtension($id)
4421      {
4422          return $this->_removeExtension($id);
4423      }
4424  
4425      /**
4426       * Get a certificate, CSR or CRL Extension
4427       *
4428       * Returns the extension if it exists and false if not
4429       *
4430       * @param string $id
4431       * @param array $cert optional
4432       * @access public
4433       * @return mixed
4434       */
4435      function getExtension($id, $cert = null)
4436      {
4437          return $this->_getExtension($id, $cert);
4438      }
4439  
4440      /**
4441       * Returns a list of all extensions in use in certificate, CSR or CRL
4442       *
4443       * @param array $cert optional
4444       * @access public
4445       * @return array
4446       */
4447      function getExtensions($cert = null)
4448      {
4449          return $this->_getExtensions($cert);
4450      }
4451  
4452      /**
4453       * Set a certificate, CSR or CRL Extension
4454       *
4455       * @param string $id
4456       * @param mixed $value
4457       * @param bool $critical optional
4458       * @param bool $replace optional
4459       * @access public
4460       * @return bool
4461       */
4462      function setExtension($id, $value, $critical = false, $replace = true)
4463      {
4464          return $this->_setExtension($id, $value, $critical, $replace);
4465      }
4466  
4467      /**
4468       * Remove a CSR attribute.
4469       *
4470       * @param string $id
4471       * @param int $disposition optional
4472       * @access public
4473       * @return bool
4474       */
4475      function removeAttribute($id, $disposition = self::ATTR_ALL)
4476      {
4477          $attributes = &$this->_subArray($this->currentCert, 'certificationRequestInfo/attributes');
4478  
4479          if (!is_array($attributes)) {
4480              return false;
4481          }
4482  
4483          $result = false;
4484          foreach ($attributes as $key => $attribute) {
4485              if ($attribute['type'] == $id) {
4486                  $n = count($attribute['value']);
4487                  switch (true) {
4488                      case $disposition == self::ATTR_APPEND:
4489                      case $disposition == self::ATTR_REPLACE:
4490                          return false;
4491                      case $disposition >= $n:
4492                          $disposition -= $n;
4493                          break;
4494                      case $disposition == self::ATTR_ALL:
4495                      case $n == 1:
4496                          unset($attributes[$key]);
4497                          $result = true;
4498                          break;
4499                      default:
4500                          unset($attributes[$key]['value'][$disposition]);
4501                          $attributes[$key]['value'] = array_values($attributes[$key]['value']);
4502                          $result = true;
4503                          break;
4504                  }
4505                  if ($result && $disposition != self::ATTR_ALL) {
4506                      break;
4507                  }
4508              }
4509          }
4510  
4511          $attributes = array_values($attributes);
4512          return $result;
4513      }
4514  
4515      /**
4516       * Get a CSR attribute
4517       *
4518       * Returns the attribute if it exists and false if not
4519       *
4520       * @param string $id
4521       * @param int $disposition optional
4522       * @param array $csr optional
4523       * @access public
4524       * @return mixed
4525       */
4526      function getAttribute($id, $disposition = self::ATTR_ALL, $csr = null)
4527      {
4528          if (empty($csr)) {
4529              $csr = $this->currentCert;
4530          }
4531  
4532          $attributes = $this->_subArray($csr, 'certificationRequestInfo/attributes');
4533  
4534          if (!is_array($attributes)) {
4535              return false;
4536          }
4537  
4538          foreach ($attributes as $key => $attribute) {
4539              if ($attribute['type'] == $id) {
4540                  $n = count($attribute['value']);
4541                  switch (true) {
4542                      case $disposition == self::ATTR_APPEND:
4543                      case $disposition == self::ATTR_REPLACE:
4544                          return false;
4545                      case $disposition == self::ATTR_ALL:
4546                          return $attribute['value'];
4547                      case $disposition >= $n:
4548                          $disposition -= $n;
4549                          break;
4550                      default:
4551                          return $attribute['value'][$disposition];
4552                  }
4553              }
4554          }
4555  
4556          return false;
4557      }
4558  
4559      /**
4560       * Returns a list of all CSR attributes in use
4561       *
4562       * @param array $csr optional
4563       * @access public
4564       * @return array
4565       */
4566      function getAttributes($csr = null)
4567      {
4568          if (empty($csr)) {
4569              $csr = $this->currentCert;
4570          }
4571  
4572          $attributes = $this->_subArray($csr, 'certificationRequestInfo/attributes');
4573          $attrs = array();
4574  
4575          if (is_array($attributes)) {
4576              foreach ($attributes as $attribute) {
4577                  $attrs[] = $attribute['type'];
4578              }
4579          }
4580  
4581          return $attrs;
4582      }
4583  
4584      /**
4585       * Set a CSR attribute
4586       *
4587       * @param string $id
4588       * @param mixed $value
4589       * @param bool $disposition optional
4590       * @access public
4591       * @return bool
4592       */
4593      function setAttribute($id, $value, $disposition = self::ATTR_ALL)
4594      {
4595          $attributes = &$this->_subArray($this->currentCert, 'certificationRequestInfo/attributes', true);
4596  
4597          if (!is_array($attributes)) {
4598              return false;
4599          }
4600  
4601          switch ($disposition) {
4602              case self::ATTR_REPLACE:
4603                  $disposition = self::ATTR_APPEND;
4604              case self::ATTR_ALL:
4605                  $this->removeAttribute($id);
4606                  break;
4607          }
4608  
4609          foreach ($attributes as $key => $attribute) {
4610              if ($attribute['type'] == $id) {
4611                  $n = count($attribute['value']);
4612                  switch (true) {
4613                      case $disposition == self::ATTR_APPEND:
4614                          $last = $key;
4615                          break;
4616                      case $disposition >= $n:
4617                          $disposition -= $n;
4618                          break;
4619                      default:
4620                          $attributes[$key]['value'][$disposition] = $value;
4621                          return true;
4622                  }
4623              }
4624          }
4625  
4626          switch (true) {
4627              case $disposition >= 0:
4628                  return false;
4629              case isset($last):
4630                  $attributes[$last]['value'][] = $value;
4631                  break;
4632              default:
4633                  $attributes[] = array('type' => $id, 'value' => $disposition == self::ATTR_ALL ? $value: array($value));
4634                  break;
4635          }
4636  
4637          return true;
4638      }
4639  
4640      /**
4641       * Sets the subject key identifier
4642       *
4643       * This is used by the id-ce-authorityKeyIdentifier and the id-ce-subjectKeyIdentifier extensions.
4644       *
4645       * @param string $value
4646       * @access public
4647       */
4648      function setKeyIdentifier($value)
4649      {
4650          if (empty($value)) {
4651              unset($this->currentKeyIdentifier);
4652          } else {
4653              $this->currentKeyIdentifier = base64_encode($value);
4654          }
4655      }
4656  
4657      /**
4658       * Compute a public key identifier.
4659       *
4660       * Although key identifiers may be set to any unique value, this function
4661       * computes key identifiers from public key according to the two
4662       * recommended methods (4.2.1.2 RFC 3280).
4663       * Highly polymorphic: try to accept all possible forms of key:
4664       * - Key object
4665       * - \phpseclib\File\X509 object with public or private key defined
4666       * - Certificate or CSR array
4667       * - \phpseclib\File\ASN1\Element object
4668       * - PEM or DER string
4669       *
4670       * @param mixed $key optional
4671       * @param int $method optional
4672       * @access public
4673       * @return string binary key identifier
4674       */
4675      function computeKeyIdentifier($key = null, $method = 1)
4676      {
4677          if (is_null($key)) {
4678              $key = $this;
4679          }
4680  
4681          switch (true) {
4682              case is_string($key):
4683                  break;
4684              case is_array($key) && isset($key['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']):
4685                  return $this->computeKeyIdentifier($key['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'], $method);
4686              case is_array($key) && isset($key['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']):
4687                  return $this->computeKeyIdentifier($key['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'], $method);
4688              case !is_object($key):
4689                  return false;
4690              case $key instanceof Element:
4691                  // Assume the element is a bitstring-packed key.
4692                  $asn1 = new ASN1();
4693                  $decoded = $asn1->decodeBER($key->element);
4694                  if (empty($decoded)) {
4695                      return false;
4696                  }
4697                  $raw = $asn1->asn1map($decoded[0], array('type' => ASN1::TYPE_BIT_STRING));
4698                  if (empty($raw)) {
4699                      return false;
4700                  }
4701                  $raw = base64_decode($raw);
4702                  // If the key is private, compute identifier from its corresponding public key.
4703                  $key = new RSA();
4704                  if (!$key->loadKey($raw)) {
4705                      return false;   // Not an unencrypted RSA key.
4706                  }
4707                  if ($key->getPrivateKey() !== false) {  // If private.
4708                      return $this->computeKeyIdentifier($key, $method);
4709                  }
4710                  $key = $raw;    // Is a public key.
4711                  break;
4712              case $key instanceof X509:
4713                  if (isset($key->publicKey)) {
4714                      return $this->computeKeyIdentifier($key->publicKey, $method);
4715                  }
4716                  if (isset($key->privateKey)) {
4717                      return $this->computeKeyIdentifier($key->privateKey, $method);
4718                  }
4719                  if (isset($key->currentCert['tbsCertificate']) || isset($key->currentCert['certificationRequestInfo'])) {
4720                      return $this->computeKeyIdentifier($key->currentCert, $method);
4721                  }
4722                  return false;
4723              default: // Should be a key object (i.e.: \phpseclib\Crypt\RSA).
4724                  $key = $key->getPublicKey(RSA::PUBLIC_FORMAT_PKCS1);
4725                  break;
4726          }
4727  
4728          // If in PEM format, convert to binary.
4729          $key = $this->_extractBER($key);
4730  
4731          // Now we have the key string: compute its sha-1 sum.
4732          $hash = new Hash('sha1');
4733          $hash = $hash->hash($key);
4734  
4735          if ($method == 2) {
4736              $hash = substr($hash, -8);
4737              $hash[0] = chr((ord($hash[0]) & 0x0F) | 0x40);
4738          }
4739  
4740          return $hash;
4741      }
4742  
4743      /**
4744