From: gaby Date: Sat, 25 Apr 2026 06:37:16 +0000 (+0200) Subject: test of htaccess CORS X-Git-Url: https://git.nothing2do.fr/?a=commitdiff_plain;p=diary-web.git test of htaccess CORS --- diff --git a/config/config.php b/config/config.php index e6e7be7..e1c61d6 100644 --- a/config/config.php +++ b/config/config.php @@ -2,15 +2,15 @@ // config/config.php // Configuration de la base de données PostgreSQL -define('DB_HOST', 'localhost'); -define('DB_NAME', 'diary'); -define('DB_USER', 'user'); -define('DB_PASS', 'password'); +define('DB_HOST', 'db_host'); +define('DB_NAME', 'db_name'); +define('DB_USER', 'db_user'); +define('DB_PASS', 'db_pass'); // Configuration WebAuthn define('WEBAUTHN_RP_NAME', 'Diary-web'); -$rpId = isset($_SERVER['HTTP_HOST']) ? parse_url((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'], PHP_URL_HOST) : 'nothing2do.fr'; -$origin = isset($_SERVER['HTTP_HOST']) ? (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] : 'https://www.nothing2do.fr'; +$rpId = 'dw.nothing2do.fr'; +$origin = 'https://dw.nothing2do.fr'; define('WEBAUTHN_RP_ID', $rpId); define('WEBAUTHN_ORIGIN', $origin); @@ -60,4 +60,4 @@ try { } catch (PDOException $e) { die("Erreur de connexion ou d'initialisation de la base de données : " . $e->getMessage()); } -?> \ No newline at end of file +?> diff --git a/include/WebAuthnManager.php b/include/WebAuthnManager.php index 128c1d1..fe447f5 100644 --- a/include/WebAuthnManager.php +++ b/include/WebAuthnManager.php @@ -41,16 +41,22 @@ class WebAuthnManager { public function generateRegistrationOptions($username) { $rpEntity = new PublicKeyCredentialRpEntity($this->rpName, $this->rpId); + + // Convert username to a valid user ID (must be ArrayBuffer or ArrayBufferView) + $userId = hash('sha256', $username, true); // Binary SHA-256 hash + $userEntity = new PublicKeyCredentialUserEntity( - $username, - $username, - $username + $username, // name + $userId, // id (must be binary) + $username // displayName ); $challenge = random_bytes(32); $_SESSION['challenge'] = base64_encode($challenge); $_SESSION['username'] = $username; + $_SESSION['user_id_hash'] = bin2hex($userId); // Store for later verification + // Note: We use standard base64 for JSON, not base64url $creationOptions = PublicKeyCredentialCreationOptions::create( $rpEntity, @@ -78,8 +84,10 @@ class WebAuthnManager { $challenge = base64_decode($_SESSION['challenge']); $username = $_SESSION['username']; + $userIdHash = isset($_SESSION['user_id_hash']) ? hex2bin($_SESSION['user_id_hash']) : hash('sha256', $username, true); unset($_SESSION['challenge']); unset($_SESSION['username']); + unset($_SESSION['user_id_hash']); try { error_log("WebAuthn register: decoding JSON response"); @@ -88,9 +96,28 @@ class WebAuthnManager { if ($attestationData === null) { error_log("WebAuthn register: JSON decode failed. Error: " . json_last_error_msg()); + error_log("WebAuthn register: Raw response: " . substr($attestationResponse, 0, 500)); return false; } + // Validate required fields + $requiredFields = ['id', 'rawId', 'response', 'type']; + foreach ($requiredFields as $field) { + if (!isset($attestationData[$field])) { + error_log("WebAuthn register: Missing required field: $field"); + return false; + } + } + + // Validate response fields + $responseFields = ['attestationObject', 'clientDataJSON']; + foreach ($responseFields as $field) { + if (!isset($attestationData['response'][$field])) { + error_log("WebAuthn register: Missing required response field: $field"); + return false; + } + } + error_log("WebAuthn register: JSON decoded successfully"); // Convert base64 back to binary @@ -126,14 +153,21 @@ class WebAuthnManager { } } - public function generateAuthenticationOptions() { + public function generateAuthenticationOptions($userId) { $challenge = random_bytes(32); $_SESSION['challenge'] = base64_encode($challenge); $rpEntity = new PublicKeyCredentialRpEntity($this->rpName, $this->rpId); - return PublicKeyCredentialRequestOptions::create($rpEntity, $challenge); + // Convert userId to binary format if it's not already + if (is_numeric($userId)) { + $userIdBinary = hash('sha256', strval($userId), true); + } else { + $userIdBinary = hash('sha256', $userId, true); + } + + return PublicKeyCredentialRequestOptions::create($rpEntity, $challenge, null, 'public-key', null, null, $userIdBinary); } public function authenticate($assertionResponse, $storedPublicKey) { diff --git a/public/index.php b/public/index.php index 75f41f8..eaebfff 100644 --- a/public/index.php +++ b/public/index.php @@ -1,5 +1,9 @@ generateAuthenticationOptions(); + $authenticationOptions = $webAuthnManager->generateAuthenticationOptions($user['id']); $_SESSION['authentication_username'] = $username; $_SESSION['authentication_user_id'] = $user['id']; $_SESSION['authentication_public_key'] = $yubikey['public_key']; diff --git a/public/register.php b/public/register.php index d5e2792..6a91ad3 100644 --- a/public/register.php +++ b/public/register.php @@ -1,9 +1,22 @@ connect(); $webAuthnManager = new WebAuthnManager(); @@ -182,21 +195,190 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { // Helper function to convert ArrayBuffer to Base64 function arrayBufferToBase64(buffer) { try { - const bytes = new Uint8Array(buffer); - let binary = ''; - for (let i = 0; i < bytes.byteLength; i++) { - binary += String.fromCharCode(bytes[i]); + // Handle ArrayBuffer directly + if (buffer instanceof ArrayBuffer) { + buffer = new Uint8Array(buffer); + } + + // Check if it's already a Uint8Array + if (!(buffer instanceof Uint8Array)) { + throw new Error('Input is not an ArrayBuffer or Uint8Array'); } - return btoa(binary); + + // Chunked conversion to avoid stack overflow + const chunkSize = 0x8000; // 32KB chunks + let result = ''; + + for (let i = 0; i < buffer.length; i += chunkSize) { + const chunk = buffer.subarray(i, i + chunkSize); + const binaryString = String.fromCharCode.apply(null, chunk); + result += btoa(binaryString); + } + + return result; } catch (error) { console.error('Error converting ArrayBuffer to Base64:', error); - console.error('Buffer:', buffer); - console.error('Buffer type:', typeof buffer); - console.error('Buffer length:', buffer ? buffer.byteLength : 'N/A'); - throw error; + console.error('Buffer type:', buffer ? buffer.constructor.name : 'null'); + console.error('Buffer length:', buffer ? buffer.length || buffer.byteLength : 'N/A'); + + // Try alternative method + try { + let binary = ''; + const bytes = new Uint8Array(buffer); + const len = bytes.byteLength; + + for (let i = 0; i < len; i++) { + binary += String.fromCharCode(bytes[i]); + } + + return btoa(binary); + } catch (fallbackError) { + console.error('Fallback method also failed:', fallbackError); + throw new Error('Failed to convert ArrayBuffer to Base64: ' + error.message); + } + } + } + + // Helper function to convert base64 to Uint8Array + function base64ToUint8Array(base64) { + try { + // Handle both standard base64 and base64url + let processedBase64 = base64.replace(/-/g, '+').replace(/_/g, '/'); + + // Add padding if needed + const padLength = processedBase64.length % 4; + const paddedBase64 = padLength ? processedBase64 + '==='.slice(padLength) : processedBase64; + + // Convert to binary string + const binaryString = atob(paddedBase64); + + // Convert to Uint8Array + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + + return bytes; + } catch (error) { + console.error('Error in base64ToUint8Array:', error); + console.error('Input:', base64); + console.error('Input length:', base64 ? base64.length : 'N/A'); + throw new Error('Failed to convert base64 to Uint8Array: ' + error.message); + } + } + + // Helper function to convert string to Uint8Array + function stringToUint8Array(str) { + try { + // If it's already in the right format, return as-is + if (str instanceof ArrayBuffer) { + return new Uint8Array(str); + } + + // If it's a hex string (from PHP hash) + if (/^[0-9a-f]+$/i.test(str)) { + const matches = str.match(/.{1,2}/g); + const bytes = new Uint8Array(matches.length); + for (let i = 0; i < matches.length; i++) { + bytes[i] = parseInt(matches[i], 16); + } + return bytes; + } + + // If it's base64 + try { + const binaryString = atob(str); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return bytes; + } catch (e) { + // Fallback: treat as regular string + const encoder = new TextEncoder(); + return encoder.encode(str); + } + } catch (error) { + console.error('Error converting string to Uint8Array:', error); + throw new Error('Failed to convert user ID: ' + error.message); } } + // Helper function to validate Base64 string + function isValidBase64(str) { + try { + // Check if string is valid Base64 + if (!/^[A-Za-z0-9+/]+={0,2}$/.test(str)) { + return false; + } + + // Try to decode it + const decoded = atob(str); + return decoded.length > 0; + } catch (e) { + return false; + } + } + + // Helper function to convert credential to a format suitable for server + function prepareCredentialForServer(credential) { + try { + console.log('Preparing credential for server...'); + + // Use the browser's native conversion where possible + const response = credential.response; + + // For rawId, use credential.id which is already base64url + const rawIdBase64 = credential.id; + + // Convert ArrayBuffer data using a more reliable method + const arrayBufferToBase64Safe = (buffer) => { + try { + // Method 1: Using FileReader (most reliable) + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + const result = reader.result; + // Remove data URL prefix if present + const base64 = result.split(',')[1] || result; + resolve(base64); + }; + reader.onerror = reject; + reader.readAsDataURL(new Blob([buffer])); + }); + } catch (e) { + // Fallback to manual conversion + const bytes = new Uint8Array(buffer); + let binary = ''; + for (let i = 0; i < bytes.byteLength; i++) { + binary += String.fromCharCode(bytes[i]); + } + return btoa(binary); + } + }; + + return Promise.all([ + arrayBufferToBase64Safe(response.attestationObject), + arrayBufferToBase64Safe(response.clientDataJSON) + ]) + .then(([attestationObjectBase64, clientDataJSONBase64]) => { + return { + id: credential.id, + rawId: rawIdBase64, + type: credential.type, + response: { + attestationObject: attestationObjectBase64, + clientDataJSON: clientDataJSONBase64, + transports: response.getTransports ? response.getTransports() : [] + } + }; + }) + .catch(error => { + console.error('Error in promise chain:', error); + throw new Error('Failed to prepare credential data: ' + error.message); + }); + } + document.addEventListener('DOMContentLoaded', function() { const form = document.getElementById('registrationForm'); const usernameInput = document.getElementById('username'); @@ -272,58 +454,94 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { webauthnMessage.textContent = 'Veuillez toucher votre YubiKey...'; // Convert the registration options to the format expected by the browser + console.log('Original registration options:', registrationOptions); + const publicKey = { challenge: Uint8Array.from(atob(registrationOptions.challenge), c => c.charCodeAt(0)), rp: registrationOptions.rp, - user: registrationOptions.user, + user: { + ...registrationOptions.user, + id: (() => { + const id = registrationOptions.user.id; + console.log('User ID type:', typeof id, 'value:', id); + + if (id instanceof ArrayBuffer) { + return new Uint8Array(id); + } + + // Try base64 first (this is what PHP sends) + try { + return base64ToUint8Array(id); + } catch (e) { + console.warn('base64 conversion failed, trying other methods:', e.message); + } + + // Try hex string + try { + if (/^[0-9a-f]+$/i.test(id)) { + const matches = id.match(/.{1,2}/g); + const bytes = new Uint8Array(matches.length); + for (let i = 0; i < matches.length; i++) { + bytes[i] = parseInt(matches[i], 16); + } + return bytes; + } + } catch (e) { + console.warn('Hex conversion failed:', e.message); + } + + // Try regular base64 + try { + const binaryString = atob(id); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return bytes; + } catch (e) { + console.warn('Base64 conversion failed:', e.message); + } + + // Last resort: treat as UTF-8 string + const encoder = new TextEncoder(); + return encoder.encode(id); + })() + }, pubKeyCredParams: registrationOptions.pubKeyCredParams, authenticatorSelection: registrationOptions.authenticatorSelection, timeout: registrationOptions.timeout, attestation: registrationOptions.attestation }; + + console.log('Converted publicKey options:', publicKey); // Call the WebAuthn API + console.log('Calling WebAuthn API with options:', registrationOptions); const credential = await navigator.credentials.create({ publicKey }); if (credential) { - // Send the attestation response to the server - // Convert credential to a format that can be sent to the server - const rawIdBase64 = arrayBufferToBase64(credential.rawId); - const attestationObjectBase64 = arrayBufferToBase64(credential.response.attestationObject); - const clientDataJSONBase64 = arrayBufferToBase64(credential.response.clientDataJSON); - - console.log('Raw ID (Base64):', rawIdBase64); - console.log('Attestation Object (Base64):', attestationObjectBase64); - console.log('Client Data JSON (Base64):', clientDataJSONBase64); - - // Vérifier que les données Base64 sont valides - if (!rawIdBase64 || !attestationObjectBase64 || !clientDataJSONBase64) { - throw new Error('Données Base64 invalides'); - } - - const attestationResponse = { - id: credential.id, - rawId: rawIdBase64, - response: { - attestationObject: attestationObjectBase64, - clientDataJSON: clientDataJSONBase64 - }, - type: credential.type - }; - - console.log('Attestation response:', attestationResponse); + console.log('WebAuthn credential received:', credential); + console.log('Credential ID:', credential.id); + console.log('Credential type:', credential.type); + console.log('Credential rawId:', credential.rawId); + console.log('Credential response:', credential.response); + // Send the attestation response to the server try { + console.log('Preparing credential for server (async)...'); + const attestationResponse = await prepareCredentialForServer(credential); + console.log('Prepared attestation response:', attestationResponse); + const attestationResponseJSON = JSON.stringify(attestationResponse); console.log('Attestation response JSON:', attestationResponseJSON); const response = await fetch('register.php', { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: `attestationResponse=${encodeURIComponent(attestationResponseJSON)}` - }); + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: `attestationResponse=${encodeURIComponent(attestationResponseJSON)}` + }); const responseText = await response.text(); console.log('Server response (step 2 - attestation):', responseText);