PageRenderTime 43ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/apps/files_external/3rdparty/phpseclib/phpseclib/File/X509.php

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