108 lines
3.0 KiB
TypeScript
108 lines
3.0 KiB
TypeScript
import { Integer, Null, ObjectIdentifier, OctetString, Sequence } from "asn1js";
|
|
|
|
function buffer(bytes: Uint8Array): ArrayBuffer {
|
|
return bytes.buffer.slice(
|
|
bytes.byteOffset,
|
|
bytes.byteOffset + bytes.byteLength,
|
|
) as ArrayBuffer;
|
|
}
|
|
|
|
export function encryptedPkcs8Pem(bytes: Uint8Array): string {
|
|
const binary = Array.from(bytes, (value) => String.fromCharCode(value)).join(
|
|
"",
|
|
);
|
|
const body =
|
|
btoa(binary)
|
|
.match(/.{1,64}/gu)
|
|
?.join("\n") ?? "";
|
|
return `-----BEGIN ENCRYPTED PRIVATE KEY-----\n${body}\n-----END ENCRYPTED PRIVATE KEY-----`;
|
|
}
|
|
|
|
export async function encryptedPkcs8(password: string): Promise<Uint8Array> {
|
|
const pair = await crypto.subtle.generateKey(
|
|
{ name: "ECDSA", namedCurve: "P-256" },
|
|
true,
|
|
["sign", "verify"],
|
|
);
|
|
const privateKey = new Uint8Array(
|
|
await crypto.subtle.exportKey("pkcs8", pair.privateKey),
|
|
);
|
|
try {
|
|
return await encryptedPbes2Payload(password, privateKey);
|
|
} finally {
|
|
privateKey.fill(0);
|
|
}
|
|
}
|
|
|
|
export async function encryptedPbes2Payload(
|
|
password: string,
|
|
plaintext: Uint8Array,
|
|
): Promise<Uint8Array> {
|
|
const salt = Uint8Array.from({ length: 16 }, (_, index) => index + 1);
|
|
const iv = Uint8Array.from({ length: 16 }, (_, index) => 32 - index);
|
|
const material = await crypto.subtle.importKey(
|
|
"raw",
|
|
new TextEncoder().encode(password),
|
|
"PBKDF2",
|
|
false,
|
|
["deriveKey"],
|
|
);
|
|
const key = await crypto.subtle.deriveKey(
|
|
{
|
|
name: "PBKDF2",
|
|
hash: "SHA-256",
|
|
salt: buffer(salt),
|
|
iterations: 12_000,
|
|
},
|
|
material,
|
|
{ name: "AES-CBC", length: 256 },
|
|
false,
|
|
["encrypt"],
|
|
);
|
|
const ciphertext = new Uint8Array(
|
|
await crypto.subtle.encrypt(
|
|
{ name: "AES-CBC", iv: buffer(iv) },
|
|
key,
|
|
buffer(plaintext),
|
|
),
|
|
);
|
|
const encoded = new Sequence({
|
|
value: [
|
|
new Sequence({
|
|
value: [
|
|
new ObjectIdentifier({ value: "1.2.840.113549.1.5.13" }),
|
|
new Sequence({
|
|
value: [
|
|
new Sequence({
|
|
value: [
|
|
new ObjectIdentifier({ value: "1.2.840.113549.1.5.12" }),
|
|
new Sequence({
|
|
value: [
|
|
new OctetString({ valueHex: buffer(salt) }),
|
|
new Integer({ value: 12_000 }),
|
|
new Sequence({
|
|
value: [
|
|
new ObjectIdentifier({ value: "1.2.840.113549.2.9" }),
|
|
new Null(),
|
|
],
|
|
}),
|
|
],
|
|
}),
|
|
],
|
|
}),
|
|
new Sequence({
|
|
value: [
|
|
new ObjectIdentifier({ value: "2.16.840.1.101.3.4.1.42" }),
|
|
new OctetString({ valueHex: buffer(iv) }),
|
|
],
|
|
}),
|
|
],
|
|
}),
|
|
],
|
|
}),
|
|
new OctetString({ valueHex: buffer(ciphertext) }),
|
|
],
|
|
});
|
|
return new Uint8Array(encoded.toBER(false));
|
|
}
|