chore: sync local changes to Gitea
This commit is contained in:
68
worker/crypto.ts
Normal file
68
worker/crypto.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
/** AES-GCM encrypt/decrypt for API keys at rest. */
|
||||
|
||||
function encEncoder(): TextEncoder {
|
||||
return new TextEncoder();
|
||||
}
|
||||
|
||||
async function getCryptoKey(env: Env): Promise<CryptoKey> {
|
||||
const raw = await resolveRawKey(env);
|
||||
return crypto.subtle.importKey("raw", raw, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
|
||||
}
|
||||
|
||||
async function resolveRawKey(env: Env): Promise<ArrayBuffer> {
|
||||
if (env.ENCRYPTION_KEY?.trim()) {
|
||||
const s = env.ENCRYPTION_KEY.trim();
|
||||
try {
|
||||
const buf = Uint8Array.from(atob(s), (c) => c.charCodeAt(0));
|
||||
if (buf.byteLength === 32) return buf.buffer;
|
||||
} catch {
|
||||
/* fall through */
|
||||
}
|
||||
const digest = await crypto.subtle.digest("SHA-256", encEncoder().encode(s));
|
||||
return digest;
|
||||
}
|
||||
const digest = await crypto.subtle.digest("SHA-256", encEncoder().encode(env.ADMIN_TOKEN));
|
||||
return digest;
|
||||
}
|
||||
|
||||
export async function encryptSecret(plain: string, env: Env): Promise<{ ciphertext: ArrayBuffer; nonce: Uint8Array }> {
|
||||
const key = await getCryptoKey(env);
|
||||
const nonce = crypto.getRandomValues(new Uint8Array(12));
|
||||
const ciphertext = await crypto.subtle.encrypt({ name: "AES-GCM", iv: nonce }, key, encEncoder().encode(plain));
|
||||
return { ciphertext, nonce };
|
||||
}
|
||||
|
||||
/** Normalize D1 BLOB columns: cloud/local may return ArrayBuffer, Uint8Array, or number[]. */
|
||||
export function d1BlobToUint8Array(v: unknown): Uint8Array {
|
||||
if (v == null) throw new TypeError("blob is null or undefined");
|
||||
if (v instanceof Uint8Array) return v;
|
||||
if (v instanceof ArrayBuffer) return new Uint8Array(v);
|
||||
if (Array.isArray(v)) return new Uint8Array(v as number[]);
|
||||
if (typeof v === "object" && "buffer" in v && (v as ArrayBufferView).buffer instanceof ArrayBuffer) {
|
||||
const t = v as ArrayBufferView;
|
||||
return new Uint8Array(t.buffer.slice(t.byteOffset, t.byteOffset + t.byteLength));
|
||||
}
|
||||
throw new TypeError("unsupported BLOB shape from D1");
|
||||
}
|
||||
|
||||
export async function decryptSecret(ciphertext: unknown, nonce: unknown, env: Env): Promise<string> {
|
||||
const ct = d1BlobToUint8Array(ciphertext);
|
||||
const iv = d1BlobToUint8Array(nonce);
|
||||
const key = await getCryptoKey(env);
|
||||
const plain = await crypto.subtle.decrypt({ name: "AES-GCM", iv: iv }, key, ct);
|
||||
return new TextDecoder().decode(plain);
|
||||
}
|
||||
|
||||
export function toB64(buf: ArrayBuffer): string {
|
||||
const u8 = new Uint8Array(buf);
|
||||
let s = "";
|
||||
for (let i = 0; i < u8.length; i++) s += String.fromCharCode(u8[i]!);
|
||||
return btoa(s);
|
||||
}
|
||||
|
||||
export function fromB64(s: string): ArrayBuffer {
|
||||
const bin = atob(s);
|
||||
const u8 = new Uint8Array(bin.length);
|
||||
for (let i = 0; i < bin.length; i++) u8[i] = bin.charCodeAt(i);
|
||||
return u8.buffer;
|
||||
}
|
||||
Reference in New Issue
Block a user