asn1.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. import { invalidKeyInput } from './invalid_key_input.js';
  2. import { encodeBase64, decodeBase64 } from '../lib/base64.js';
  3. import { JOSENotSupported } from '../util/errors.js';
  4. import { isCryptoKey, isKeyObject } from './is_key_like.js';
  5. const formatPEM = (b64, descriptor) => {
  6. const newlined = (b64.match(/.{1,64}/g) || []).join('\n');
  7. return `-----BEGIN ${descriptor}-----\n${newlined}\n-----END ${descriptor}-----`;
  8. };
  9. const genericExport = async (keyType, keyFormat, key) => {
  10. if (isKeyObject(key)) {
  11. if (key.type !== keyType) {
  12. throw new TypeError(`key is not a ${keyType} key`);
  13. }
  14. return key.export({ format: 'pem', type: keyFormat });
  15. }
  16. if (!isCryptoKey(key)) {
  17. throw new TypeError(invalidKeyInput(key, 'CryptoKey', 'KeyObject'));
  18. }
  19. if (!key.extractable) {
  20. throw new TypeError('CryptoKey is not extractable');
  21. }
  22. if (key.type !== keyType) {
  23. throw new TypeError(`key is not a ${keyType} key`);
  24. }
  25. return formatPEM(encodeBase64(new Uint8Array(await crypto.subtle.exportKey(keyFormat, key))), `${keyType.toUpperCase()} KEY`);
  26. };
  27. export const toSPKI = (key) => genericExport('public', 'spki', key);
  28. export const toPKCS8 = (key) => genericExport('private', 'pkcs8', key);
  29. const bytesEqual = (a, b) => {
  30. if (a.byteLength !== b.length)
  31. return false;
  32. for (let i = 0; i < a.byteLength; i++) {
  33. if (a[i] !== b[i])
  34. return false;
  35. }
  36. return true;
  37. };
  38. const createASN1State = (data) => ({ data, pos: 0 });
  39. const parseLength = (state) => {
  40. const first = state.data[state.pos++];
  41. if (first & 0x80) {
  42. const lengthOfLen = first & 0x7f;
  43. let length = 0;
  44. for (let i = 0; i < lengthOfLen; i++) {
  45. length = (length << 8) | state.data[state.pos++];
  46. }
  47. return length;
  48. }
  49. return first;
  50. };
  51. const skipElement = (state, count = 1) => {
  52. if (count <= 0)
  53. return;
  54. state.pos++;
  55. const length = parseLength(state);
  56. state.pos += length;
  57. if (count > 1) {
  58. skipElement(state, count - 1);
  59. }
  60. };
  61. const expectTag = (state, expectedTag, errorMessage) => {
  62. if (state.data[state.pos++] !== expectedTag) {
  63. throw new Error(errorMessage);
  64. }
  65. };
  66. const getSubarray = (state, length) => {
  67. const result = state.data.subarray(state.pos, state.pos + length);
  68. state.pos += length;
  69. return result;
  70. };
  71. const parseAlgorithmOID = (state) => {
  72. expectTag(state, 0x06, 'Expected algorithm OID');
  73. const oidLen = parseLength(state);
  74. return getSubarray(state, oidLen);
  75. };
  76. function parsePKCS8Header(state) {
  77. expectTag(state, 0x30, 'Invalid PKCS#8 structure');
  78. parseLength(state);
  79. expectTag(state, 0x02, 'Expected version field');
  80. const verLen = parseLength(state);
  81. state.pos += verLen;
  82. expectTag(state, 0x30, 'Expected algorithm identifier');
  83. const algIdLen = parseLength(state);
  84. const algIdStart = state.pos;
  85. return { algIdStart, algIdLength: algIdLen };
  86. }
  87. function parseSPKIHeader(state) {
  88. expectTag(state, 0x30, 'Invalid SPKI structure');
  89. parseLength(state);
  90. expectTag(state, 0x30, 'Expected algorithm identifier');
  91. const algIdLen = parseLength(state);
  92. const algIdStart = state.pos;
  93. return { algIdStart, algIdLength: algIdLen };
  94. }
  95. const parseECAlgorithmIdentifier = (state) => {
  96. const algOid = parseAlgorithmOID(state);
  97. if (bytesEqual(algOid, [0x2b, 0x65, 0x6e])) {
  98. return 'X25519';
  99. }
  100. if (!bytesEqual(algOid, [0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01])) {
  101. throw new Error('Unsupported key algorithm');
  102. }
  103. expectTag(state, 0x06, 'Expected curve OID');
  104. const curveOidLen = parseLength(state);
  105. const curveOid = getSubarray(state, curveOidLen);
  106. for (const { name, oid } of [
  107. { name: 'P-256', oid: [0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07] },
  108. { name: 'P-384', oid: [0x2b, 0x81, 0x04, 0x00, 0x22] },
  109. { name: 'P-521', oid: [0x2b, 0x81, 0x04, 0x00, 0x23] },
  110. ]) {
  111. if (bytesEqual(curveOid, oid)) {
  112. return name;
  113. }
  114. }
  115. throw new Error('Unsupported named curve');
  116. };
  117. const genericImport = async (keyFormat, keyData, alg, options) => {
  118. let algorithm;
  119. let keyUsages;
  120. const isPublic = keyFormat === 'spki';
  121. const getSigUsages = () => (isPublic ? ['verify'] : ['sign']);
  122. const getEncUsages = () => isPublic ? ['encrypt', 'wrapKey'] : ['decrypt', 'unwrapKey'];
  123. switch (alg) {
  124. case 'PS256':
  125. case 'PS384':
  126. case 'PS512':
  127. algorithm = { name: 'RSA-PSS', hash: `SHA-${alg.slice(-3)}` };
  128. keyUsages = getSigUsages();
  129. break;
  130. case 'RS256':
  131. case 'RS384':
  132. case 'RS512':
  133. algorithm = { name: 'RSASSA-PKCS1-v1_5', hash: `SHA-${alg.slice(-3)}` };
  134. keyUsages = getSigUsages();
  135. break;
  136. case 'RSA-OAEP':
  137. case 'RSA-OAEP-256':
  138. case 'RSA-OAEP-384':
  139. case 'RSA-OAEP-512':
  140. algorithm = {
  141. name: 'RSA-OAEP',
  142. hash: `SHA-${parseInt(alg.slice(-3), 10) || 1}`,
  143. };
  144. keyUsages = getEncUsages();
  145. break;
  146. case 'ES256':
  147. case 'ES384':
  148. case 'ES512': {
  149. const curveMap = { ES256: 'P-256', ES384: 'P-384', ES512: 'P-521' };
  150. algorithm = { name: 'ECDSA', namedCurve: curveMap[alg] };
  151. keyUsages = getSigUsages();
  152. break;
  153. }
  154. case 'ECDH-ES':
  155. case 'ECDH-ES+A128KW':
  156. case 'ECDH-ES+A192KW':
  157. case 'ECDH-ES+A256KW': {
  158. try {
  159. const namedCurve = options.getNamedCurve(keyData);
  160. algorithm = namedCurve === 'X25519' ? { name: 'X25519' } : { name: 'ECDH', namedCurve };
  161. }
  162. catch (cause) {
  163. throw new JOSENotSupported('Invalid or unsupported key format');
  164. }
  165. keyUsages = isPublic ? [] : ['deriveBits'];
  166. break;
  167. }
  168. case 'Ed25519':
  169. case 'EdDSA':
  170. algorithm = { name: 'Ed25519' };
  171. keyUsages = getSigUsages();
  172. break;
  173. case 'ML-DSA-44':
  174. case 'ML-DSA-65':
  175. case 'ML-DSA-87':
  176. algorithm = { name: alg };
  177. keyUsages = getSigUsages();
  178. break;
  179. default:
  180. throw new JOSENotSupported('Invalid or unsupported "alg" (Algorithm) value');
  181. }
  182. return crypto.subtle.importKey(keyFormat, keyData, algorithm, options?.extractable ?? (isPublic ? true : false), keyUsages);
  183. };
  184. const processPEMData = (pem, pattern) => {
  185. return decodeBase64(pem.replace(pattern, ''));
  186. };
  187. export const fromPKCS8 = (pem, alg, options) => {
  188. const keyData = processPEMData(pem, /(?:-----(?:BEGIN|END) PRIVATE KEY-----|\s)/g);
  189. let opts = options;
  190. if (alg?.startsWith?.('ECDH-ES')) {
  191. opts ||= {};
  192. opts.getNamedCurve = (keyData) => {
  193. const state = createASN1State(keyData);
  194. parsePKCS8Header(state);
  195. return parseECAlgorithmIdentifier(state);
  196. };
  197. }
  198. return genericImport('pkcs8', keyData, alg, opts);
  199. };
  200. export const fromSPKI = (pem, alg, options) => {
  201. const keyData = processPEMData(pem, /(?:-----(?:BEGIN|END) PUBLIC KEY-----|\s)/g);
  202. let opts = options;
  203. if (alg?.startsWith?.('ECDH-ES')) {
  204. opts ||= {};
  205. opts.getNamedCurve = (keyData) => {
  206. const state = createASN1State(keyData);
  207. parseSPKIHeader(state);
  208. return parseECAlgorithmIdentifier(state);
  209. };
  210. }
  211. return genericImport('spki', keyData, alg, opts);
  212. };
  213. function spkiFromX509(buf) {
  214. const state = createASN1State(buf);
  215. expectTag(state, 0x30, 'Invalid certificate structure');
  216. parseLength(state);
  217. expectTag(state, 0x30, 'Invalid tbsCertificate structure');
  218. parseLength(state);
  219. if (buf[state.pos] === 0xa0) {
  220. skipElement(state, 6);
  221. }
  222. else {
  223. skipElement(state, 5);
  224. }
  225. const spkiStart = state.pos;
  226. expectTag(state, 0x30, 'Invalid SPKI structure');
  227. const spkiContentLen = parseLength(state);
  228. return buf.subarray(spkiStart, spkiStart + spkiContentLen + (state.pos - spkiStart));
  229. }
  230. function extractX509SPKI(x509) {
  231. const derBytes = processPEMData(x509, /(?:-----(?:BEGIN|END) CERTIFICATE-----|\s)/g);
  232. return spkiFromX509(derBytes);
  233. }
  234. export const fromX509 = (pem, alg, options) => {
  235. let spki;
  236. try {
  237. spki = extractX509SPKI(pem);
  238. }
  239. catch (cause) {
  240. throw new TypeError('Failed to parse the X.509 certificate', { cause });
  241. }
  242. return fromSPKI(formatPEM(encodeBase64(spki), 'PUBLIC KEY'), alg, options);
  243. };