74feca4467
- 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
57 lines
1.9 KiB
TypeScript
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));
|
|
};
|