PageRenderTime 38ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/phpseclib/phpseclib/phpseclib/File/X509.php

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