Vendored
+107
@@ -0,0 +1,107 @@
|
||||
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));
|
||||
}
|
||||
Reference in New Issue
Block a user