UNPKG

@fedify/fedify

Version:

An ActivityPub server framework

153 lines (152 loc) • 5.77 kB
import * as dntShim from "../_dnt.shims.js"; import { concat } from "../deps/jsr.io/@std/bytes/1.0.5/concat.js"; import { decodeBase64, encodeBase64 } from "../deps/jsr.io/@std/encoding/1.0.7/base64.js"; import { decodeBase64Url } from "../deps/jsr.io/@std/encoding/1.0.7/base64url.js"; import { decodeHex } from "../deps/jsr.io/@std/encoding/1.0.7/hex.js"; import { Integer, Sequence } from "asn1js"; import { decode, encode } from "./multibase/index.js"; import { addPrefix, getCodeFromData, rmPrefix } from "multicodec"; import { createPublicKey } from "node:crypto"; import { PublicKeyInfo } from "pkijs"; import { validateCryptoKey } from "../sig/key.js"; const algorithms = { "1.2.840.113549.1.1.1": { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, "1.3.101.112": "Ed25519", }; /** * Imports a PEM-SPKI formatted public key. * @param pem The PEM-SPKI formatted public key. * @returns The imported public key. * @throws {TypeError} If the key is invalid or unsupported. * @since 0.5.0 */ export async function importSpki(pem) { pem = pem.replace(/(?:-----(?:BEGIN|END) PUBLIC KEY-----|\s)/g, ""); let spki; try { spki = decodeBase64(pem); } catch (_) { throw new TypeError("Invalid PEM-SPKI format."); } const pki = PublicKeyInfo.fromBER(spki); const oid = pki.algorithm.algorithmId; const algorithm = algorithms[oid]; if (algorithm == null) { throw new TypeError("Unsupported algorithm: " + oid); } return await dntShim.crypto.subtle.importKey("spki", spki, algorithm, true, ["verify"]); } /** * Exports a public key in PEM-SPKI format. * @param key The public key to export. * @returns The exported public key in PEM-SPKI format. * @throws {TypeError} If the key is invalid or unsupported. * @since 0.5.0 */ export async function exportSpki(key) { validateCryptoKey(key); const spki = await dntShim.crypto.subtle.exportKey("spki", key); let pem = encodeBase64(spki); pem = (pem.match(/.{1,64}/g) || []).join("\n"); return `-----BEGIN PUBLIC KEY-----\n${pem}\n-----END PUBLIC KEY-----\n`; } /** * Imports a PEM-PKCS#1 formatted public key. * @param pem The PEM-PKCS#1 formatted public key. * @returns The imported public key. * @throws {TypeError} If the key is invalid or unsupported. * @since 1.5.0 */ export function importPkcs1(pem) { const key = createPublicKey({ key: pem, format: "pem", type: "pkcs1" }); const spki = key.export({ type: "spki", format: "pem" }); return importSpki(spki); } const PKCS1_HEADER = /^\s*-----BEGIN\s+RSA\s+PUBLIC\s+KEY-----\s*\n/; /** * Imports a PEM formatted public key (SPKI or PKCS#1). * @param pem The PEM formatted public key to import (SPKI or PKCS#1). * @returns The imported public key. * @throws {TypeError} If the key is invalid or unsupported. * @since 1.5.0 */ export function importPem(pem) { return PKCS1_HEADER.test(pem) ? importPkcs1(pem) : importSpki(pem); } /** * Imports a [Multibase]-encoded public key. * * [Multibase]: https://www.w3.org/TR/vc-data-integrity/#multibase-0 * @param key The Multibase-encoded public key. * @returns The imported public key. * @throws {TypeError} If the key is invalid or unsupported. * @since 0.10.0 */ export async function importMultibaseKey(key) { const decoded = decode(key); const code = getCodeFromData(decoded); const content = rmPrefix(decoded); if (code === 0x1205) { // rsa-pub const keyObject = createPublicKey({ // deno-lint-ignore no-explicit-any key: content, format: "der", type: "pkcs1", }); const spki = keyObject.export({ type: "spki", format: "der" }).buffer; return await dntShim.crypto.subtle.importKey("spki", new Uint8Array(spki), { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, true, ["verify"]); } else if (code === 0xed) { // ed25519-pub return await dntShim.crypto.subtle.importKey("raw", content, "Ed25519", true, ["verify"]); } else { throw new TypeError("Unsupported key type: 0x" + code.toString(16)); } } /** * Exports a public key in [Multibase] format. * * [Multibase]: https://www.w3.org/TR/vc-data-integrity/#multibase-0 * @param key The public key to export. * @returns The exported public key in Multibase format. * @throws {TypeError} If the key is invalid or unsupported. * @since 0.10.0 */ export async function exportMultibaseKey(key) { let content; let code; if (key.algorithm.name === "Ed25519") { content = await dntShim.crypto.subtle.exportKey("raw", key); code = 0xed; // ed25519-pub } else if (key.algorithm.name === "RSASSA-PKCS1-v1_5" && key.algorithm.hash.name === "SHA-256") { const jwk = await dntShim.crypto.subtle.exportKey("jwk", key); const n = concat([new Uint8Array([0]), decodeBase64Url(jwk.n)]); const sequence = new Sequence({ value: [ new Integer({ isHexOnly: true, valueHex: n, }), new Integer({ isHexOnly: true, valueHex: decodeBase64Url(jwk.e), }), ], }); content = sequence.toBER(false); code = 0x1205; // rsa-pub } else { throw new TypeError("Unsupported key type: " + JSON.stringify(key.algorithm)); } const codeHex = code.toString(16); const codeBytes = decodeHex(codeHex.length % 2 < 1 ? codeHex : "0" + codeHex); const prefixed = addPrefix(codeBytes, new Uint8Array(content)); const encoded = encode("base58btc", prefixed); return new TextDecoder().decode(encoded); } // cSpell: ignore multicodec pkijs