feat: X25519 ECDH sealed-box E2E encryption, zero-knowledge key exchange
- Replace libsodium with pure Web Crypto X25519 ECDH for key exchange - Sealed-box pattern: ephemeral keypair + HKDF + AES-256-GCM for each recipient - Store encrypted conversation key per-user so each party decrypts with their own private key - PBKDF2-derived master key wraps X25519 private key (pkcs8) server-side for cross-device login - Fix health endpoint ordering so it never hits auth middleware (401 bug) - Fix postgres port exposure in docker-compose for backend connectivity - Fix DrivePage react-query v5 onSuccess → useEffect migration - Fix MessagesPage: use <Link> instead of <a href> to avoid page reload losing in-memory keys - Fix formatDistanceToNow crash on WebSocket messages lacking sent_at (add fallback + null guard) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
937bf8ad2d
commit
46f0a4b71e
10 files changed
+308
-185
No files matched your search
@@ -8,12 +8,14 @@ import {
|
||||
deriveKeyFromPassword,
|
||||
generateMasterKey,
|
||||
encryptMasterKey,
|
||||
generateX25519Keypair,
|
||||
encryptPrivateKey,
|
||||
} from '../crypto';
|
||||
import { socket } from '../lib/ws';
|
||||
|
||||
export default function RegisterPage() {
|
||||
const navigate = useNavigate();
|
||||
const { setAuth, setMasterKey, setUsername } = useAuthStore();
|
||||
const { setAuth, setMasterKey, setKeyPair, setUsername } = useAuthStore();
|
||||
const [username, setUsernameField] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
@@ -29,15 +31,15 @@ export default function RegisterPage() {
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
// Generate all crypto material client-side
|
||||
// All crypto material generated client-side — nothing sensitive leaves the browser in plaintext
|
||||
const kdfSalt = await generateKdfSalt();
|
||||
const derivedKey = await deriveKeyFromPassword(password, kdfSalt);
|
||||
const masterKey = await generateMasterKey();
|
||||
const encryptedMasterKey = await encryptMasterKey(masterKey, derivedKey);
|
||||
|
||||
// Minimal public key placeholder (full ECDH exchange would use X25519 via libsodium)
|
||||
const publicKey = 'placeholder-pubkey';
|
||||
const keySignature = 'placeholder-sig';
|
||||
// X25519 keypair for ECDH conversation key exchange
|
||||
const { publicKeyB64, privateKey, pkcs8Bytes } = await generateX25519Keypair();
|
||||
const encryptedPrivKey = await encryptPrivateKey(pkcs8Bytes, masterKey);
|
||||
|
||||
const res = await api.post('/auth/register', {
|
||||
username,
|
||||
@@ -45,13 +47,14 @@ export default function RegisterPage() {
|
||||
password,
|
||||
kdfSalt,
|
||||
encryptedMasterKey,
|
||||
publicKey,
|
||||
keySignature,
|
||||
publicKey: publicKeyB64,
|
||||
encryptedPrivateKey: encryptedPrivKey,
|
||||
});
|
||||
|
||||
const { accessToken, refreshToken, userId } = res.data;
|
||||
setAuth({ accessToken, refreshToken, userId });
|
||||
setMasterKey(masterKey);
|
||||
setKeyPair(publicKeyB64, privateKey);
|
||||
setUsername(username);
|
||||
|
||||
socket.connect(accessToken);
|
||||
@@ -86,7 +89,7 @@ export default function RegisterPage() {
|
||||
</p>
|
||||
|
||||
<button type="submit" disabled={loading} className="btn-primary w-full">
|
||||
{loading ? 'Creating account…' : 'Create account'}
|
||||
{loading ? 'Generating keys…' : 'Create account'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
|
||||
Reference in new issue
Block a user