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
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
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));
|
||||
};
|
||||
Reference in New Issue
Block a user