Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7351f9048a | ||
|
|
84ff218a30 | ||
|
|
3e89775e60 |
No files matched your search
@@ -103,7 +103,7 @@ jobs:
|
|||||||
working-directory: desktop
|
working-directory: desktop
|
||||||
run: npx electron-builder --win nsis --x64 --publish never
|
run: npx electron-builder --win nsis --x64 --publish never
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: desktop-linux-windows-${{ github.sha }}
|
name: desktop-linux-windows-${{ github.sha }}
|
||||||
path: |
|
path: |
|
||||||
@@ -128,8 +128,13 @@ jobs:
|
|||||||
script: |
|
script: |
|
||||||
cd ${{ secrets.DEPLOY_PATH }}
|
cd ${{ secrets.DEPLOY_PATH }}
|
||||||
echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.necloud.us -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
echo "${{ secrets.REGISTRY_TOKEN }}" | docker login git.necloud.us -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
||||||
docker compose pull backend frontend
|
if docker compose version > /dev/null 2>&1; then
|
||||||
docker compose up -d --no-build backend frontend
|
docker compose pull backend frontend
|
||||||
|
docker compose up -d --no-build backend frontend
|
||||||
|
else
|
||||||
|
docker-compose pull backend frontend
|
||||||
|
docker-compose up -d --no-build backend frontend
|
||||||
|
fi
|
||||||
docker logout git.necloud.us
|
docker logout git.necloud.us
|
||||||
docker image prune -f
|
docker image prune -f
|
||||||
|
|
||||||
@@ -141,7 +146,7 @@ jobs:
|
|||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
steps:
|
steps:
|
||||||
- name: Download desktop artifacts
|
- name: Download desktop artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: desktop-linux-windows-${{ github.sha }}
|
name: desktop-linux-windows-${{ github.sha }}
|
||||||
path: ./release-assets
|
path: ./release-assets
|
||||||
|
|||||||
@@ -50,24 +50,23 @@ export async function decryptMasterKey(encB64: string, derived: CryptoKey): Prom
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── X25519 ECDH keypair ──────────────────────────────────────────────────────
|
// ── ECDH P-256 keypair ───────────────────────────────────────────────────────
|
||||||
|
// P-256 is universally supported across all browsers and Electron versions.
|
||||||
|
// Raw public key is 65 bytes (uncompressed point: 0x04 || x || y).
|
||||||
|
|
||||||
|
const ECDH_ALGO = { name: 'ECDH', namedCurve: 'P-256' } as const;
|
||||||
|
const PUB_KEY_BYTES = 65; // uncompressed P-256 point
|
||||||
|
|
||||||
export async function generateX25519Keypair(): Promise<{
|
export async function generateX25519Keypair(): Promise<{
|
||||||
publicKeyB64: string;
|
publicKeyB64: string;
|
||||||
privateKey: CryptoKey;
|
privateKey: CryptoKey;
|
||||||
pkcs8Bytes: Uint8Array<ArrayBuffer>;
|
pkcs8Bytes: Uint8Array<ArrayBuffer>;
|
||||||
}> {
|
}> {
|
||||||
const kp = await crypto.subtle.generateKey({ name: 'X25519' } as any, true, ['deriveBits']);
|
const kp = await crypto.subtle.generateKey(ECDH_ALGO, true, ['deriveBits']);
|
||||||
const publicRaw = await crypto.subtle.exportKey('raw', (kp as CryptoKeyPair).publicKey);
|
const publicRaw = await crypto.subtle.exportKey('raw', (kp as CryptoKeyPair).publicKey);
|
||||||
const pkcs8 = await crypto.subtle.exportKey('pkcs8', (kp as CryptoKeyPair).privateKey);
|
const pkcs8 = await crypto.subtle.exportKey('pkcs8', (kp as CryptoKeyPair).privateKey);
|
||||||
|
|
||||||
const privateKey = await crypto.subtle.importKey(
|
const privateKey = await crypto.subtle.importKey('pkcs8', pkcs8, ECDH_ALGO, false, ['deriveBits']);
|
||||||
'pkcs8',
|
|
||||||
pkcs8,
|
|
||||||
{ name: 'X25519' } as any,
|
|
||||||
false,
|
|
||||||
['deriveBits']
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
publicKeyB64: bufToB64(publicRaw),
|
publicKeyB64: bufToB64(publicRaw),
|
||||||
@@ -85,12 +84,12 @@ export async function decryptPrivateKeyToCryptoKey(
|
|||||||
master: CryptoKey
|
master: CryptoKey
|
||||||
): Promise<CryptoKey> {
|
): Promise<CryptoKey> {
|
||||||
const pkcs8 = await aesDecryptRaw(encB64, master);
|
const pkcs8 = await aesDecryptRaw(encB64, master);
|
||||||
return crypto.subtle.importKey('pkcs8', pkcs8, { name: 'X25519' } as any, false, ['deriveBits']);
|
return crypto.subtle.importKey('pkcs8', pkcs8, ECDH_ALGO, false, ['deriveBits']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Conversation key exchange (ephemeral X25519 ECDH sealed box) ─────────────
|
// ── Conversation key exchange (ephemeral ECDH P-256 sealed box) ───────────────
|
||||||
//
|
//
|
||||||
// Wire format: ephemeral_pub_key(32) | nonce(12) | ciphertext
|
// Wire format: ephemeral_pub_key(65) | nonce(12) | ciphertext
|
||||||
|
|
||||||
export function generateConvKeyRaw(): Uint8Array<ArrayBuffer> {
|
export function generateConvKeyRaw(): Uint8Array<ArrayBuffer> {
|
||||||
return crypto.getRandomValues(new Uint8Array(32));
|
return crypto.getRandomValues(new Uint8Array(32));
|
||||||
@@ -100,23 +99,15 @@ export async function sealConvKey(
|
|||||||
keyRaw: Uint8Array<ArrayBuffer>,
|
keyRaw: Uint8Array<ArrayBuffer>,
|
||||||
recipientPublicKeyB64: string
|
recipientPublicKeyB64: string
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const recipientPubKey = await crypto.subtle.importKey(
|
const recipientPubKey = await crypto.subtle.importKey('raw', b64ToBuf(recipientPublicKeyB64), ECDH_ALGO, false, []);
|
||||||
'raw',
|
|
||||||
b64ToBuf(recipientPublicKeyB64),
|
|
||||||
{ name: 'X25519' } as any,
|
|
||||||
false,
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const ephemeral = await crypto.subtle.generateKey({ name: 'X25519' } as any, true, [
|
const ephemeral = await crypto.subtle.generateKey(ECDH_ALGO, true, ['deriveBits']);
|
||||||
'deriveBits',
|
|
||||||
]);
|
|
||||||
const ephemeralPubRaw = new Uint8Array(
|
const ephemeralPubRaw = new Uint8Array(
|
||||||
await crypto.subtle.exportKey('raw', (ephemeral as CryptoKeyPair).publicKey)
|
await crypto.subtle.exportKey('raw', (ephemeral as CryptoKeyPair).publicKey)
|
||||||
);
|
);
|
||||||
|
|
||||||
const sharedBits = await crypto.subtle.deriveBits(
|
const sharedBits = await crypto.subtle.deriveBits(
|
||||||
{ name: 'X25519', public: recipientPubKey } as any,
|
{ name: 'ECDH', public: recipientPubKey },
|
||||||
(ephemeral as CryptoKeyPair).privateKey,
|
(ephemeral as CryptoKeyPair).privateKey,
|
||||||
256
|
256
|
||||||
);
|
);
|
||||||
@@ -126,10 +117,10 @@ export async function sealConvKey(
|
|||||||
const nonce = crypto.getRandomValues(new Uint8Array(12));
|
const nonce = crypto.getRandomValues(new Uint8Array(12));
|
||||||
const ct = await crypto.subtle.encrypt({ name: 'AES-GCM', iv: nonce }, aesKey, keyRaw);
|
const ct = await crypto.subtle.encrypt({ name: 'AES-GCM', iv: nonce }, aesKey, keyRaw);
|
||||||
|
|
||||||
const out = new Uint8Array(32 + 12 + ct.byteLength);
|
const out = new Uint8Array(PUB_KEY_BYTES + 12 + ct.byteLength);
|
||||||
out.set(ephemeralPubRaw, 0);
|
out.set(ephemeralPubRaw, 0);
|
||||||
out.set(nonce, 32);
|
out.set(nonce, PUB_KEY_BYTES);
|
||||||
out.set(new Uint8Array(ct), 44);
|
out.set(new Uint8Array(ct), PUB_KEY_BYTES + 12);
|
||||||
return bufToB64(out);
|
return bufToB64(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,20 +129,14 @@ export async function openSealedConvKey(
|
|||||||
myPrivateKey: CryptoKey
|
myPrivateKey: CryptoKey
|
||||||
): Promise<Uint8Array<ArrayBuffer>> {
|
): Promise<Uint8Array<ArrayBuffer>> {
|
||||||
const data = b64ToBuf(sealedB64);
|
const data = b64ToBuf(sealedB64);
|
||||||
const ephemeralPubRaw = u8(data, 0, 32);
|
const ephemeralPubRaw = u8(data, 0, PUB_KEY_BYTES);
|
||||||
const nonce = u8(data, 32, 44);
|
const nonce = u8(data, PUB_KEY_BYTES, PUB_KEY_BYTES + 12);
|
||||||
const ct = u8(data, 44);
|
const ct = u8(data, PUB_KEY_BYTES + 12);
|
||||||
|
|
||||||
const ephemeralPubKey = await crypto.subtle.importKey(
|
const ephemeralPubKey = await crypto.subtle.importKey('raw', ephemeralPubRaw, ECDH_ALGO, false, []);
|
||||||
'raw',
|
|
||||||
ephemeralPubRaw,
|
|
||||||
{ name: 'X25519' } as any,
|
|
||||||
false,
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const sharedBits = await crypto.subtle.deriveBits(
|
const sharedBits = await crypto.subtle.deriveBits(
|
||||||
{ name: 'X25519', public: ephemeralPubKey } as any,
|
{ name: 'ECDH', public: ephemeralPubKey },
|
||||||
myPrivateKey,
|
myPrivateKey,
|
||||||
256
|
256
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export default function RegisterPage() {
|
|||||||
socket.connect(accessToken);
|
socket.connect(accessToken);
|
||||||
navigate('/messages');
|
navigate('/messages');
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(err.response?.data?.error || 'Registration failed');
|
setError(err.response?.data?.error || err.message || 'Registration failed');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user