From f2f8fbbfb6e6ab4a47a9cfee4b1d9505c11eda58 Mon Sep 17 00:00:00 2001 From: Leendert de Borst Date: Thu, 23 Jan 2025 14:14:13 +0100 Subject: [PATCH] Add argon2id and SRP modules (#541) --- browser-extensions/chrome/package-lock.json | 135 +++++++++++++++- browser-extensions/chrome/package.json | 5 +- .../chrome/src/components/App.tsx | 23 ++- .../chrome/src/components/Login.tsx | 118 ++++++++++++++ .../chrome/src/services/SrpService.tsx | 146 ++++++++++++++++++ 5 files changed, 417 insertions(+), 10 deletions(-) create mode 100644 browser-extensions/chrome/src/components/Login.tsx create mode 100644 browser-extensions/chrome/src/services/SrpService.tsx diff --git a/browser-extensions/chrome/package-lock.json b/browser-extensions/chrome/package-lock.json index fd51acfae..b3409cf27 100644 --- a/browser-extensions/chrome/package-lock.json +++ b/browser-extensions/chrome/package-lock.json @@ -9,8 +9,11 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "argon2-browser": "^1.18.0", + "buffer": "^6.0.3", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "secure-remote-password": "^0.3.1" }, "devDependencies": { "@types/react": "^19.0.7", @@ -1280,6 +1283,18 @@ "dev": true, "license": "MIT" }, + "node_modules/argon2-browser": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/argon2-browser/-/argon2-browser-1.18.0.tgz", + "integrity": "sha512-ImVAGIItnFnvET1exhsQB7apRztcoC5TnlSqernMJDUjbc/DLq3UEYeXFrLPrlaIl8cVfwnXb6wX2KpFf2zxHw==", + "license": "MIT" + }, + "node_modules/array-buffer-to-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-to-hex/-/array-buffer-to-hex-1.0.0.tgz", + "integrity": "sha512-arycdkxgK1cj6s03GDb96tlCxOl1n3kg9M2OHseUc6Pqyqp+lgfceFPmG507eI5V+oxOSEnlOw/dFc7LXBXF4Q==", + "license": "MIT" + }, "node_modules/autoprefixer": { "version": "10.4.20", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", @@ -1325,6 +1340,26 @@ "dev": true, "license": "MIT" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1394,6 +1429,30 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -1515,6 +1574,21 @@ "node": ">= 8" } }, + "node_modules/crypto-digest-sync": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-digest-sync/-/crypto-digest-sync-1.0.0.tgz", + "integrity": "sha512-UQBOB5z+HF4iA8shKQ3PPwhCmdFAihwcytD1Qh4uiz78x04cZZmKtZ1F1VyAjkrA8uEZqXt2tMXfj3dJHtcbng==", + "license": "MIT" + }, + "node_modules/crypto-random-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-hex/-/crypto-random-hex-1.0.0.tgz", + "integrity": "sha512-1DuZQ03El13TRgfrqbbjW40Gvi4OKInny/Wxqj23/JMXe214C/3Tlz92bKXWDW3NZT5RjXUGdYW4qiIOUPf+cA==", + "license": "MIT", + "dependencies": { + "array-buffer-to-hex": "^1.0.0" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -1588,6 +1662,12 @@ "dev": true, "license": "MIT" }, + "node_modules/encode-utf8": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz", + "integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==", + "license": "MIT" + }, "node_modules/esbuild": { "version": "0.24.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", @@ -1837,6 +1917,32 @@ "node": ">= 0.4" } }, + "node_modules/hex-to-array-buffer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hex-to-array-buffer/-/hex-to-array-buffer-1.1.0.tgz", + "integrity": "sha512-vvl3IM8FfT1uOnHtEqyjkDK9Luqz6MQrH82qIvVnjyXxRhkeaEZyRRPiBgf2yym3nweRVEfayxt/1SoTXZYd4Q==", + "license": "MIT" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -1949,6 +2055,12 @@ "dev": true, "license": "MIT" }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "license": "MIT" + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -2160,6 +2272,12 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/pad-start": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pad-start/-/pad-start-1.0.2.tgz", + "integrity": "sha512-EBN8Ez1SVRcZT1XsIE4WkdnZ5coLoaChkIgAET6gIlaLhXqCz9upVk0DQWFtOYkrpTVvbEppRUnqhTiJrBdkfw==", + "license": "MIT" + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -2567,6 +2685,21 @@ "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", "license": "MIT" }, + "node_modules/secure-remote-password": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/secure-remote-password/-/secure-remote-password-0.3.1.tgz", + "integrity": "sha512-iEp/qLRfb9XYhfKFrPFfdeD7KVreCjhDKSTRP1G1nRIO0Sw1hjnVHD58ymOhiy9Zf5quHbDIbG9cTupji7qwnA==", + "license": "MIT", + "dependencies": { + "array-buffer-to-hex": "^1.0.0", + "crypto-digest-sync": "^1.0.0", + "crypto-random-hex": "^1.0.0", + "encode-utf8": "^1.0.1", + "hex-to-array-buffer": "^1.1.0", + "jsbn": "^1.1.0", + "pad-start": "^1.0.2" + } + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", diff --git a/browser-extensions/chrome/package.json b/browser-extensions/chrome/package.json index b10453118..9c04aff32 100644 --- a/browser-extensions/chrome/package.json +++ b/browser-extensions/chrome/package.json @@ -13,8 +13,11 @@ "license": "ISC", "description": "", "dependencies": { + "argon2-browser": "^1.18.0", + "buffer": "^6.0.3", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "secure-remote-password": "^0.3.1" }, "devDependencies": { "@types/react": "^19.0.7", diff --git a/browser-extensions/chrome/src/components/App.tsx b/browser-extensions/chrome/src/components/App.tsx index acc846b59..83d42b7fc 100644 --- a/browser-extensions/chrome/src/components/App.tsx +++ b/browser-extensions/chrome/src/components/App.tsx @@ -1,20 +1,27 @@ -import React from 'react'; +import React, { useState } from 'react'; import '../styles/tailwind.css'; import Button from './Button'; +import Login from './Login'; const App: React.FC = () => { + const [isLoggedIn, setIsLoggedIn] = useState(false); + const handleClick = () => { alert('Button clicked!'); }; return ( -
-

Hello, AliasVault Chrome Extension!

-
- -
+
+

AliasVault

+ {isLoggedIn ? ( +
+ +
+ ) : ( + + )}
); }; diff --git a/browser-extensions/chrome/src/components/Login.tsx b/browser-extensions/chrome/src/components/Login.tsx new file mode 100644 index 000000000..fa3293bfe --- /dev/null +++ b/browser-extensions/chrome/src/components/Login.tsx @@ -0,0 +1,118 @@ +import React, { useState } from 'react'; +import Button from './Button'; +import { srpService } from '../services/SrpService'; + +const Login: React.FC = () => { + const [credentials, setCredentials] = useState({ + username: '', + password: '', + }); + const [rememberMe, setRememberMe] = useState(false); + const [error, setError] = useState(null); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(null); + + try { + // 1. Initiate login to get salt and server ephemeral + const loginResponse = await srpService.initiateLogin(credentials.username); + + // 2. Validate login with SRP protocol + const validationResponse = await srpService.validateLogin( + credentials.username, + credentials.password, + rememberMe, + loginResponse + ); + + console.log(validationResponse); + + // 3. Handle 2FA if required + /*if (validationResponse.requiresTwoFactor) { + // TODO: Implement 2FA flow + console.log('2FA required'); + return; + } + + // 4. Store tokens + if (validationResponse.token) { + localStorage.setItem('accessToken', validationResponse.token.token); + localStorage.setItem('refreshToken', validationResponse.token.refreshToken); + } + + // 5. Redirect to home page + window.location.href = '/';*/ + + } catch (err) { + setError('Login failed. Please check your credentials and try again.'); + console.error('Login error:', err); + } + }; + + const handleChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setCredentials(prev => ({ + ...prev, + [name]: value + })); + }; + + return ( +
+
+ {error && ( +
+ {error} +
+ )} +
+ + +
+
+ + +
+
+ + +
+
+
+ ); +}; + +export default Login; \ No newline at end of file diff --git a/browser-extensions/chrome/src/services/SrpService.tsx b/browser-extensions/chrome/src/services/SrpService.tsx new file mode 100644 index 000000000..897042acd --- /dev/null +++ b/browser-extensions/chrome/src/services/SrpService.tsx @@ -0,0 +1,146 @@ +import srp from 'secure-remote-password/client' +import argon2 from 'argon2-browser/dist/argon2-bundled.min.js'; + +interface LoginInitiateResponse { + salt: string; + serverEphemeral: string; + encryptionType: string; + encryptionSettings: string; +} + +interface ValidateLoginResponse { + requiresTwoFactor: boolean; + token?: { + token: string; + refreshToken: string; + }; + serverSessionProof: string; +} + +class SrpService { + private static async deriveKeyFromPassword( + password: string, + salt: string, + encryptionType: string = 'Argon2id', + encryptionSettings: string = '{"iterations":2,"memory":67108864,"parallelism":4}' + ): Promise { + const settings = JSON.parse(encryptionSettings); + + try { + if (encryptionType !== 'Argon2Id') { + throw new Error('Unsupported encryption type'); + } + + const hash = await argon2.hash({ + pass: password, + salt: salt, + time: settings.iterations, + mem: settings.memory / 1024, // Convert bytes to KiB + parallelism: settings.parallelism, + hashLen: 32 // 32 bytes = 256 bits + }); + + console.log(hash); + return hash.hash; + } catch (error) { + console.error('Argon2 hashing failed:', error); + throw error; + } + } + + /** + * Generates a client ephemeral + * + * @returns + */ + private static generateEphemeral(): srp.Ephemeral { + return srp.generateEphemeral() + } + + /*private static derivePrivateKey(salt: string, username: string, passwordHash: string): string { + const hash = createHash('sha256') + .update(salt) + .update(username.toLowerCase()) + .update(passwordHash) + .digest('hex'); + return hash; + }*/ + + public async initiateLogin(username: string): Promise { + const response = await fetch('http://localhost:5092/v1/Auth/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ username: username.toLowerCase().trim() }) + }); + + if (!response.ok) { + throw new Error('Login initiation failed'); + } + + return await response.json(); + } + + public async validateLogin( + username: string, + password: string, + rememberMe: boolean, + loginResponse: LoginInitiateResponse + ): Promise { + // Promise { + // 1. Derive key from password + const passwordHash = await SrpService.deriveKeyFromPassword( + password, + loginResponse.salt, + loginResponse.encryptionType, + loginResponse.encryptionSettings + ); + + return true; + + /* + const passwordHashString = Buffer.from(passwordHash).toString('hex'); + + // 2. Generate client ephemeral + + + const clientEphemeral = SrpService.generateEphemeral(); + + // 3. Derive private key + const privateKey = SrpService.derivePrivateKey( + loginResponse.salt, + username, + passwordHashString + ); + + // 4. Derive session (simplified for example) + const sessionProof = createHash('sha256') + .update(privateKey) + .update(clientEphemeral.secret) + .update(loginResponse.serverEphemeral) + .digest('hex'); + + // 5. Send validation request + const response = await fetch('http://localhost:5092/v1/Auth/validate', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + username: username.toLowerCase().trim(), + rememberMe, + clientPublicEphemeral: clientEphemeral.public, + clientSessionProof: sessionProof + }) + }); + + if (!response.ok) { + throw new Error('Login validation failed'); + } + + return await response.json();*/ + } +} + +export const srpService = new SrpService(); \ No newline at end of file