mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-18 21:40:41 -04:00
Add argon2id and SRP modules (#541)
This commit is contained in:
135
browser-extensions/chrome/package-lock.json
generated
135
browser-extensions/chrome/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 (
|
||||
<div className="bg-blue-500 text-white p-4">
|
||||
<h1>Hello, AliasVault Chrome Extension!</h1>
|
||||
<div className="mt-4">
|
||||
<Button onClick={handleClick}>
|
||||
Click me!
|
||||
</Button>
|
||||
</div>
|
||||
<div className="min-h-screen bg-blue-500 items-center justify-center p-4">
|
||||
<h1 className="text-white text-2xl mb-8">AliasVault</h1>
|
||||
{isLoggedIn ? (
|
||||
<div className="mt-4">
|
||||
<Button onClick={handleClick}>
|
||||
Click me!
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<Login />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
118
browser-extensions/chrome/src/components/Login.tsx
Normal file
118
browser-extensions/chrome/src/components/Login.tsx
Normal file
@@ -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<string | null>(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<HTMLInputElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setCredentials(prev => ({
|
||||
...prev,
|
||||
[name]: value
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-md">
|
||||
<form onSubmit={handleSubmit} className="bg-white w-full shadow-md rounded px-8 pt-6 pb-8 mb-4">
|
||||
{error && (
|
||||
<div className="mb-4 text-red-500 text-sm">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-4">
|
||||
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="username">
|
||||
Username
|
||||
</label>
|
||||
<input
|
||||
className="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
||||
id="username"
|
||||
type="text"
|
||||
name="username"
|
||||
placeholder="Enter your username"
|
||||
value={credentials.username}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-6">
|
||||
<label className="block text-gray-700 text-sm font-bold mb-2" htmlFor="password">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
className="shadow appearance-none border rounded py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline"
|
||||
id="password"
|
||||
type="password"
|
||||
name="password"
|
||||
placeholder="Enter your password"
|
||||
value={credentials.password}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={rememberMe}
|
||||
onChange={(e) => setRememberMe(e.target.checked)}
|
||||
className="mr-2"
|
||||
/>
|
||||
<span className="text-sm">Remember me</span>
|
||||
</label>
|
||||
<Button type="submit">
|
||||
Login
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
146
browser-extensions/chrome/src/services/SrpService.tsx
Normal file
146
browser-extensions/chrome/src/services/SrpService.tsx
Normal file
@@ -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<Uint8Array> {
|
||||
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<LoginInitiateResponse> {
|
||||
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<boolean> {
|
||||
// Promise<ValidateLoginResponse> {
|
||||
// 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();
|
||||
Reference in New Issue
Block a user