PageRenderTime 175ms CodeModel.GetById 32ms RepoModel.GetById 3ms app.codeStats 2ms

/protected/vendors/phpseclib/File/X509.php

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