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