Files
ctms/frontend/src/utils/loginCrypto.ts
T
Cheng Zhou 74feca4467 feat: harden auth and study workflows
- replace plaintext login and unlock requests with RSA-OAEP/AES-GCM encrypted payloads

- add login challenge replay protection, production RSA key validation, and auth tests

- wire compose to environment-driven dev/prod settings without committing local secrets

- update setup-config smoke scripts and Postman docs for encrypted login

- add visit schedule migrations/tests and update study/subject setup workflows
2026-05-08 22:16:43 +08:00

57 lines
1.9 KiB
TypeScript

const pemToArrayBuffer = (pem: string): ArrayBuffer => {
const base64 = pem
.replace(/-----BEGIN PUBLIC KEY-----/g, "")
.replace(/-----END PUBLIC KEY-----/g, "")
.replace(/\s/g, "");
const binary = atob(base64);
const bytes = new Uint8Array(binary.length);
for (let index = 0; index < binary.length; index += 1) {
bytes[index] = binary.charCodeAt(index);
}
return bytes.buffer;
};
const arrayBufferToBase64 = (buffer: ArrayBuffer): string => {
const bytes = new Uint8Array(buffer);
let binary = "";
bytes.forEach((byte) => {
binary += String.fromCharCode(byte);
});
return btoa(binary);
};
const assertSecureCryptoAvailable = () => {
if (!window.isSecureContext || !crypto?.subtle) {
throw new Error("当前浏览器环境不支持安全登录加密,请使用 HTTPS 或 localhost 访问系统");
}
};
export const encryptLoginPayload = async (
publicKeyPem: string,
payload: { email: string; password: string; challenge: string }
): Promise<string> => {
assertSecureCryptoAvailable();
const publicKey = await crypto.subtle.importKey(
"spki",
pemToArrayBuffer(publicKeyPem),
{
name: "RSA-OAEP",
hash: "SHA-256",
},
false,
["encrypt"]
);
const aesKey = await crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, true, ["encrypt"]);
const iv = crypto.getRandomValues(new Uint8Array(12));
const encoded = new TextEncoder().encode(JSON.stringify(payload));
const encryptedData = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, aesKey, encoded);
const rawAesKey = await crypto.subtle.exportKey("raw", aesKey);
const encryptedKey = await crypto.subtle.encrypt({ name: "RSA-OAEP" }, publicKey, rawAesKey);
const envelope = {
encrypted_key: arrayBufferToBase64(encryptedKey),
iv: arrayBufferToBase64(iv.buffer),
data: arrayBufferToBase64(encryptedData),
};
return btoa(JSON.stringify(envelope));
};