asn1.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. import crypto, { isCryptoKey } from './webcrypto.js';
  2. import invalidKeyInput from '../lib/invalid_key_input.js';
  3. import { encodeBase64, decodeBase64 } from './base64url.js';
  4. import formatPEM from '../lib/format_pem.js';
  5. import { JOSENotSupported } from '../util/errors.js';
  6. import { types } from './is_key_like.js';
  7. const genericExport = async (keyType, keyFormat, key) => {
  8. if (!isCryptoKey(key)) {
  9. throw new TypeError(invalidKeyInput(key, ...types));
  10. }
  11. if (!key.extractable) {
  12. throw new TypeError('CryptoKey is not extractable');
  13. }
  14. if (key.type !== keyType) {
  15. throw new TypeError(`key is not a ${keyType} key`);
  16. }
  17. return formatPEM(encodeBase64(new Uint8Array(await crypto.subtle.exportKey(keyFormat, key))), `${keyType.toUpperCase()} KEY`);
  18. };
  19. export const toSPKI = (key) => {
  20. return genericExport('public', 'spki', key);
  21. };
  22. export const toPKCS8 = (key) => {
  23. return genericExport('private', 'pkcs8', key);
  24. };
  25. const findOid = (keyData, oid, from = 0) => {
  26. if (from === 0) {
  27. oid.unshift(oid.length);
  28. oid.unshift(0x06);
  29. }
  30. let i = keyData.indexOf(oid[0], from);
  31. if (i === -1)
  32. return false;
  33. const sub = keyData.subarray(i, i + oid.length);
  34. if (sub.length !== oid.length)
  35. return false;
  36. return sub.every((value, index) => value === oid[index]) || findOid(keyData, oid, i + 1);
  37. };
  38. const getNamedCurve = (keyData) => {
  39. switch (true) {
  40. case findOid(keyData, [0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07]):
  41. return 'P-256';
  42. case findOid(keyData, [0x2b, 0x81, 0x04, 0x00, 0x22]):
  43. return 'P-384';
  44. case findOid(keyData, [0x2b, 0x81, 0x04, 0x00, 0x23]):
  45. return 'P-521';
  46. case findOid(keyData, [0x2b, 0x65, 0x6e]):
  47. return 'X25519';
  48. case findOid(keyData, [0x2b, 0x65, 0x6f]):
  49. return 'X448';
  50. case findOid(keyData, [0x2b, 0x65, 0x70]):
  51. return 'Ed25519';
  52. case findOid(keyData, [0x2b, 0x65, 0x71]):
  53. return 'Ed448';
  54. default:
  55. throw new JOSENotSupported('Invalid or unsupported EC Key Curve or OKP Key Sub Type');
  56. }
  57. };
  58. const genericImport = async (replace, keyFormat, pem, alg, options) => {
  59. var _a;
  60. let algorithm;
  61. let keyUsages;
  62. const keyData = new Uint8Array(atob(pem.replace(replace, ''))
  63. .split('')
  64. .map((c) => c.charCodeAt(0)));
  65. const isPublic = keyFormat === 'spki';
  66. switch (alg) {
  67. case 'PS256':
  68. case 'PS384':
  69. case 'PS512':
  70. algorithm = { name: 'RSA-PSS', hash: `SHA-${alg.slice(-3)}` };
  71. keyUsages = isPublic ? ['verify'] : ['sign'];
  72. break;
  73. case 'RS256':
  74. case 'RS384':
  75. case 'RS512':
  76. algorithm = { name: 'RSASSA-PKCS1-v1_5', hash: `SHA-${alg.slice(-3)}` };
  77. keyUsages = isPublic ? ['verify'] : ['sign'];
  78. break;
  79. case 'RSA-OAEP':
  80. case 'RSA-OAEP-256':
  81. case 'RSA-OAEP-384':
  82. case 'RSA-OAEP-512':
  83. algorithm = {
  84. name: 'RSA-OAEP',
  85. hash: `SHA-${parseInt(alg.slice(-3), 10) || 1}`,
  86. };
  87. keyUsages = isPublic ? ['encrypt', 'wrapKey'] : ['decrypt', 'unwrapKey'];
  88. break;
  89. case 'ES256':
  90. algorithm = { name: 'ECDSA', namedCurve: 'P-256' };
  91. keyUsages = isPublic ? ['verify'] : ['sign'];
  92. break;
  93. case 'ES384':
  94. algorithm = { name: 'ECDSA', namedCurve: 'P-384' };
  95. keyUsages = isPublic ? ['verify'] : ['sign'];
  96. break;
  97. case 'ES512':
  98. algorithm = { name: 'ECDSA', namedCurve: 'P-521' };
  99. keyUsages = isPublic ? ['verify'] : ['sign'];
  100. break;
  101. case 'ECDH-ES':
  102. case 'ECDH-ES+A128KW':
  103. case 'ECDH-ES+A192KW':
  104. case 'ECDH-ES+A256KW': {
  105. const namedCurve = getNamedCurve(keyData);
  106. algorithm = namedCurve.startsWith('P-') ? { name: 'ECDH', namedCurve } : { name: namedCurve };
  107. keyUsages = isPublic ? [] : ['deriveBits'];
  108. break;
  109. }
  110. case 'EdDSA':
  111. algorithm = { name: getNamedCurve(keyData) };
  112. keyUsages = isPublic ? ['verify'] : ['sign'];
  113. break;
  114. default:
  115. throw new JOSENotSupported('Invalid or unsupported "alg" (Algorithm) value');
  116. }
  117. return crypto.subtle.importKey(keyFormat, keyData, algorithm, (_a = options === null || options === void 0 ? void 0 : options.extractable) !== null && _a !== void 0 ? _a : false, keyUsages);
  118. };
  119. export const fromPKCS8 = (pem, alg, options) => {
  120. return genericImport(/(?:-----(?:BEGIN|END) PRIVATE KEY-----|\s)/g, 'pkcs8', pem, alg, options);
  121. };
  122. export const fromSPKI = (pem, alg, options) => {
  123. return genericImport(/(?:-----(?:BEGIN|END) PUBLIC KEY-----|\s)/g, 'spki', pem, alg, options);
  124. };
  125. function getElement(seq) {
  126. let result = [];
  127. let next = 0;
  128. while (next < seq.length) {
  129. let nextPart = parseElement(seq.subarray(next));
  130. result.push(nextPart);
  131. next += nextPart.byteLength;
  132. }
  133. return result;
  134. }
  135. function parseElement(bytes) {
  136. let position = 0;
  137. let tag = bytes[0] & 0x1f;
  138. position++;
  139. if (tag === 0x1f) {
  140. tag = 0;
  141. while (bytes[position] >= 0x80) {
  142. tag = tag * 128 + bytes[position] - 0x80;
  143. position++;
  144. }
  145. tag = tag * 128 + bytes[position] - 0x80;
  146. position++;
  147. }
  148. let length = 0;
  149. if (bytes[position] < 0x80) {
  150. length = bytes[position];
  151. position++;
  152. }
  153. else if (length === 0x80) {
  154. length = 0;
  155. while (bytes[position + length] !== 0 || bytes[position + length + 1] !== 0) {
  156. if (length > bytes.byteLength) {
  157. throw new TypeError('invalid indefinite form length');
  158. }
  159. length++;
  160. }
  161. const byteLength = position + length + 2;
  162. return {
  163. byteLength,
  164. contents: bytes.subarray(position, position + length),
  165. raw: bytes.subarray(0, byteLength),
  166. };
  167. }
  168. else {
  169. let numberOfDigits = bytes[position] & 0x7f;
  170. position++;
  171. length = 0;
  172. for (let i = 0; i < numberOfDigits; i++) {
  173. length = length * 256 + bytes[position];
  174. position++;
  175. }
  176. }
  177. const byteLength = position + length;
  178. return {
  179. byteLength,
  180. contents: bytes.subarray(position, byteLength),
  181. raw: bytes.subarray(0, byteLength),
  182. };
  183. }
  184. function spkiFromX509(buf) {
  185. const tbsCertificate = getElement(getElement(parseElement(buf).contents)[0].contents);
  186. return encodeBase64(tbsCertificate[tbsCertificate[0].raw[0] === 0xa0 ? 6 : 5].raw);
  187. }
  188. function getSPKI(x509) {
  189. const pem = x509.replace(/(?:-----(?:BEGIN|END) CERTIFICATE-----|\s)/g, '');
  190. const raw = decodeBase64(pem);
  191. return formatPEM(spkiFromX509(raw), 'PUBLIC KEY');
  192. }
  193. export const fromX509 = (pem, alg, options) => {
  194. let spki;
  195. try {
  196. spki = getSPKI(pem);
  197. }
  198. catch (cause) {
  199. throw new TypeError('Failed to parse the X.509 certificate', { cause });
  200. }
  201. return fromSPKI(spki, alg, options);
  202. };