**Motivations:** - Initialisation du versionning git pour le projet **Root causes:** - N/A (Nouveau projet) **Correctifs:** - N/A **Evolutions:** - Structure initiale du projet - Ajout du .gitignore **Pages affectées:** - Tous les fichiers
101 lines
3.0 KiB
JavaScript
101 lines
3.0 KiB
JavaScript
/**
|
|
* Ed25519 keypair: generate, store in localStorage, sign requests.
|
|
* Public key exported as SPKI base64url for server verification.
|
|
*/
|
|
|
|
const STORAGE_KEY_PUBLIC = "builazoo_public_key";
|
|
const STORAGE_KEY_PRIVATE = "builazoo_private_key";
|
|
|
|
/**
|
|
* @returns {Promise<CryptoKeyPair | null>}
|
|
*/
|
|
function generateKeyPair() {
|
|
return crypto.subtle.generateKey(
|
|
{ name: "Ed25519" },
|
|
true,
|
|
["sign", "verify"]
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param {ArrayBuffer} buf
|
|
* @returns {string}
|
|
*/
|
|
function base64url(buf) {
|
|
const b64 = btoa(String.fromCharCode(...new Uint8Array(buf)));
|
|
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
}
|
|
|
|
/**
|
|
* @returns {Promise<{ publicKeyBase64: string, privateKey: CryptoKey } | null>}
|
|
*/
|
|
export async function getOrCreateKeyPair() {
|
|
if (typeof crypto === "undefined" || !crypto.subtle) {
|
|
throw new Error("Connexion sécurisée requise (HTTPS ou localhost) pour créer un compte.");
|
|
}
|
|
try {
|
|
const pubRaw = localStorage.getItem(STORAGE_KEY_PUBLIC);
|
|
const privRaw = localStorage.getItem(STORAGE_KEY_PRIVATE);
|
|
if (pubRaw && privRaw) {
|
|
const privateKey = await crypto.subtle.importKey(
|
|
"pkcs8",
|
|
base64urlDecodeToBuf(privRaw),
|
|
{ name: "Ed25519" },
|
|
true,
|
|
["sign"]
|
|
);
|
|
await crypto.subtle.importKey(
|
|
"spki",
|
|
base64urlDecodeToBuf(pubRaw),
|
|
{ name: "Ed25519" },
|
|
true,
|
|
["verify"]
|
|
);
|
|
return { publicKeyBase64: pubRaw, privateKey };
|
|
}
|
|
const pair = await generateKeyPair();
|
|
const [pubExported, privExported] = await Promise.all([
|
|
crypto.subtle.exportKey("spki", pair.publicKey),
|
|
crypto.subtle.exportKey("pkcs8", pair.privateKey),
|
|
]);
|
|
localStorage.setItem(STORAGE_KEY_PUBLIC, base64url(pubExported));
|
|
localStorage.setItem(STORAGE_KEY_PRIVATE, base64url(privExported));
|
|
return { publicKeyBase64: base64url(pubExported), privateKey: pair.privateKey };
|
|
} catch (e) {
|
|
console.warn("getOrCreateKeyPair failed", e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {string} str base64url
|
|
* @returns {ArrayBuffer}
|
|
*/
|
|
function base64urlDecodeToBuf(str) {
|
|
const pad = (4 - (str.length % 4)) % 4;
|
|
const b64 = (str + "==".slice(0, pad)).replace(/-/g, "+").replace(/_/g, "/");
|
|
const binary = atob(b64);
|
|
const buf = new Uint8Array(binary.length);
|
|
for (let i = 0; i < binary.length; i++) buf[i] = binary.charCodeAt(i);
|
|
return buf.buffer;
|
|
}
|
|
|
|
/**
|
|
* @param {CryptoKey} privateKey
|
|
* @param {string} message
|
|
* @returns {Promise<string>} base64url signature
|
|
*/
|
|
export async function signMessage(privateKey, message) {
|
|
const enc = new TextEncoder().encode(message);
|
|
const sig = await crypto.subtle.sign("Ed25519", privateKey, enc);
|
|
return base64url(sig);
|
|
}
|
|
|
|
/**
|
|
* Call after register to ensure we have keys stored (already done by getOrCreateKeyPair).
|
|
* @returns {boolean} true if keys exist
|
|
*/
|
|
export function hasStoredKeys() {
|
|
return Boolean(localStorage.getItem(STORAGE_KEY_PUBLIC) && localStorage.getItem(STORAGE_KEY_PRIVATE));
|
|
}
|