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