ASN1.php 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. <?php
  2. /******************************************************************************
  3. * This file is part of the Phactor PHP project. You can always find the latest
  4. * version of this class and project at: https://github.com/ionux/phactor
  5. *
  6. * Copyright (c) 2015-2018 Rich Morgan, rich@richmorgan.me
  7. *
  8. * The MIT License (MIT)
  9. *
  10. * Permission is hereby granted, free of charge, to any person obtaining a copy of
  11. * this software and associated documentation files (the "Software"), to deal in
  12. * the Software without restriction, including without limitation the rights to
  13. * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  14. * the Software, and to permit persons to whom the Software is furnished to do so,
  15. * subject to the following conditions:
  16. *
  17. * The above copyright notice and this permission notice shall be included in all
  18. * copies or substantial portions of the Software.
  19. *
  20. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  21. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  22. * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  23. * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  24. * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  25. * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  26. ******************************************************************************/
  27. namespace Phactor;
  28. /**
  29. * Abstract Syntax Notation One (ASN.1) encoding trait for keys and signatures.
  30. *
  31. * @author Rich Morgan <rich@richmorgan.me>
  32. */
  33. trait ASN1
  34. {
  35. /**
  36. * Encodes keypair data to PEM format.
  37. *
  38. * @param array $keypair The keypair info.
  39. * @return string The data to decode.
  40. */
  41. public function encodePEM($keypair)
  42. {
  43. $this->pemInitialDataCheck($keypair);
  44. $ecpemstruct = array(
  45. 'sequence_beg' => '30',
  46. 'total_len' => '74',
  47. 'int_sec_beg' => '02',
  48. 'int_sec_len' => '01',
  49. 'int_sec_val' => '01',
  50. 'oct_sec_beg' => '04',
  51. 'oct_sec_len' => '20',
  52. 'oct_sec_val' => $keypair[0],
  53. 'a0_ele_beg' => 'a0',
  54. 'a0_ele_len' => '07',
  55. 'obj_id_beg' => '06',
  56. 'obj_id_len' => '05',
  57. 'obj_id_val' => '2b8104000a',
  58. 'a1_ele_beg' => 'a1',
  59. 'a1_ele_len' => '44',
  60. 'bit_str_beg' => '03',
  61. 'bit_str_len' => '42',
  62. 'bit_str_val' => '00' . $keypair[1],
  63. );
  64. $dec = trim(implode($ecpemstruct));
  65. $this->pemDataLenCheck($dec);
  66. return '-----BEGIN EC PRIVATE KEY-----' . "\r\n" . chunk_split(base64_encode($this->binConv($dec)), 64) . '-----END EC PRIVATE KEY-----';
  67. }
  68. /**
  69. * Decodes PEM data to retrieve the keypair.
  70. *
  71. * @param string $pem_data The data to decode.
  72. * @return array The keypair info.
  73. */
  74. public function decodePEM($pem_data)
  75. {
  76. $pem_data = $this->pemDataClean($pem_data);
  77. $decoded = bin2hex(base64_decode($pem_data));
  78. $this->pemDataLenCheck($decoded);
  79. $ecpemstruct = array(
  80. 'oct_sec_val' => substr($decoded, 14, 64),
  81. 'obj_id_val' => substr($decoded, 86, 10),
  82. 'bit_str_val' => substr($decoded, 106),
  83. );
  84. $this->pemOidCheck($ecpemstruct['obj_id_val']);
  85. $private_key = $ecpemstruct['oct_sec_val'];
  86. $public_key = '04' . $ecpemstruct['bit_str_val'];
  87. $this->pemKeyLenCheck(array($private_key, $public_key));
  88. return array(
  89. 'private_key' => $private_key,
  90. 'public_key' => $public_key
  91. );
  92. }
  93. /**
  94. * Ensures the data we want to PEM encode is acceptable.
  95. *
  96. * @param array $keypair The values to check.
  97. * @throws \Exception
  98. */
  99. private function pemInitialDataCheck($keypair)
  100. {
  101. if (true === empty($keypair) || false === is_array($keypair) || strlen($keypair[0]) < 62 || strlen($keypair[1]) < 126) {
  102. throw new \Exception('Invalid or corrupt secp256k1 keypair provided. Cannot encode the keys to PEM format. Value checked was "' . var_export($keypair, true) . '".');
  103. }
  104. }
  105. /**
  106. * Ensures the decoded PEM data length is acceptable.
  107. *
  108. * @param string $value The value to check.
  109. * @throws \Exception
  110. */
  111. private function pemDataLenCheck($value)
  112. {
  113. if (strlen($value) < 220) {
  114. throw new \Exception('Invalid or corrupt secp256k1 key provided. Cannot decode the supplied PEM data. Length < 220. Value received was "' . var_export($value, true) . '".');
  115. }
  116. }
  117. /**
  118. * Ensures the decoded PEM key lengths are acceptable.
  119. *
  120. * @param array $values The key values to check.
  121. * @throws \Exception
  122. */
  123. private function pemKeyLenCheck($values)
  124. {
  125. if (!is_array($values) || strlen($values[0]) < 62 || strlen($values[1]) < 126) {
  126. throw new \Exception('Invalid or corrupt secp256k1 key provided. Cannot decode the supplied PEM data. Key lengths too short. Values checked were "' . var_export($values[0], true) . '" and "' . var_export($values[1], true) . '".');
  127. }
  128. }
  129. /**
  130. * Ensures the decoded PEM data is for an EC Key.
  131. *
  132. * @param string $value The value to check.
  133. * @throws \Exception
  134. */
  135. private function pemOidCheck($value)
  136. {
  137. if ($value != '2b8104000a') {
  138. throw new \Exception('Invalid or corrupt secp256k1 key provided. Cannot decode the supplied PEM data. OID is not for EC key. Value checked was "' . var_export($value, true) . '".');
  139. }
  140. }
  141. /**
  142. * Cleans the PEM data of unwanted data.
  143. *
  144. * @param string $value The value to clean.
  145. * @return string $value The cleaned value.
  146. */
  147. private function pemDataClean($value)
  148. {
  149. $value = str_ireplace('-----BEGIN EC PRIVATE KEY-----', '', $value);
  150. $value = str_ireplace('-----END EC PRIVATE KEY-----', '', $value);
  151. $value = str_ireplace("\r", '', trim($value));
  152. $value = str_ireplace("\n", '', trim($value));
  153. $value = str_ireplace(' ', '', trim($value));
  154. return $value;
  155. }
  156. /**
  157. * Decodes an OID hex value obtained from
  158. * a PEM file or other encoded key file.
  159. *
  160. * @param string $data The hex OID string to decode.
  161. * @return string The decoded OID string.
  162. */
  163. public function decodeOID($data)
  164. {
  165. $bin_data = array();
  166. $index = 0;
  167. $binary_string = '';
  168. $oid = array();
  169. // first two numbers are: (40*x)+y
  170. // all next chars are 7-bit numbers
  171. foreach ($data as $key => $value) {
  172. $bin_data[$index] = bitConv($value);
  173. $binary_string .= $bin_data[$index];
  174. $index++;
  175. }
  176. $oid[0] = (int)($data[0] / 0x28);
  177. $oid[1] = $data[0] % 0x28;
  178. $elements = count($bin_data);
  179. $temp_number = '00000000';
  180. for ($x = 1; $x < $elements; $x++) {
  181. $and_temp = $bin_data[$x] & '01111111';
  182. $temp_number = bitAdd($temp_number, $and_temp);
  183. if (substr($bin_data[$x], 0, 1) == '0') {
  184. // This is a final value without
  185. // a value preceeding it.
  186. $oid[$x + 1] = decConv($temp_number);
  187. $temp_number = '';
  188. } else {
  189. $temp_number = shiftLeft($temp_number, 7);
  190. }
  191. }
  192. $oid_string = '';
  193. foreach ($oid as $key => $value) {
  194. $oid_string .= $value . ".";
  195. }
  196. $oid_string = substr($oid_string, 0, -1);
  197. return $oid_string;
  198. }
  199. }