fix android shit + fix clients not showing if 2 clients have the same username and pc name as another client
This commit is contained in:
@@ -2,6 +2,37 @@
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const androidMachineIDFile = "config/android_machine_id"
|
||||
|
||||
func platformMachineID() string {
|
||||
return ""
|
||||
data, err := os.ReadFile(androidMachineIDFile)
|
||||
if err == nil {
|
||||
if id := strings.TrimSpace(string(data)); id != "" {
|
||||
return id
|
||||
}
|
||||
}
|
||||
|
||||
b := make([]byte, 16)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
log.Printf("[identity] WARNING: failed to generate android machine ID: %v", err)
|
||||
return ""
|
||||
}
|
||||
id := hex.EncodeToString(b)
|
||||
|
||||
if err := os.MkdirAll("config", 0700); err != nil {
|
||||
log.Printf("[identity] WARNING: failed to create config dir for android machine ID: %v", err)
|
||||
return id
|
||||
}
|
||||
if err := os.WriteFile(androidMachineIDFile, []byte(id), 0600); err != nil {
|
||||
log.Printf("[identity] WARNING: failed to persist android machine ID: %v", err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
@@ -662,6 +662,11 @@ export function clientExists(id: string): boolean {
|
||||
return !!row?.id;
|
||||
}
|
||||
|
||||
export function getClientPublicKeyById(id: string): string | null {
|
||||
const row = db.query<{ public_key: string | null }>(`SELECT public_key FROM clients WHERE id=? LIMIT 1`).get(id);
|
||||
return row?.public_key ?? null;
|
||||
}
|
||||
|
||||
export function listDistinctCountries(): { code: string; count: number }[] {
|
||||
const rows = db
|
||||
.query<{ code: string; count: number }>(
|
||||
|
||||
@@ -549,3 +549,67 @@ describe("purgatory — IP banning", () => {
|
||||
expect(truncated).toBe("A".repeat(200));
|
||||
});
|
||||
});
|
||||
|
||||
describe("purgatory — HWID collision prevention", () => {
|
||||
test("two different public keys produce different key fingerprints", async () => {
|
||||
const kpA = await generateKeypair();
|
||||
const kpB = await generateKeypair();
|
||||
const fpA = computeKeyFingerprint(kpA.publicKeyBase64);
|
||||
const fpB = computeKeyFingerprint(kpB.publicKeyBase64);
|
||||
expect(fpA).not.toBe(fpB);
|
||||
// fingerprints are 64-char hex
|
||||
expect(fpA).toMatch(/^[a-f0-9]{64}$/);
|
||||
expect(fpB).toMatch(/^[a-f0-9]{64}$/);
|
||||
});
|
||||
|
||||
test("key fingerprint is deterministic for same public key", async () => {
|
||||
const kp = await generateKeypair();
|
||||
const fp1 = computeKeyFingerprint(kp.publicKeyBase64);
|
||||
const fp2 = computeKeyFingerprint(kp.publicKeyBase64);
|
||||
expect(fp1).toBe(fp2);
|
||||
});
|
||||
|
||||
test("collision detection assigns different IDs to same-HWID machines", async () => {
|
||||
// Simulate: Machine A and Machine B share HWID "colliding-hwid"
|
||||
// but have different key pairs. After collision detection,
|
||||
// Machine B should get the key fingerprint as its ID.
|
||||
const kpA = await generateKeypair();
|
||||
const kpB = await generateKeypair();
|
||||
const sharedHwid = "colliding-hwid";
|
||||
|
||||
const fpA = computeKeyFingerprint(kpA.publicKeyBase64);
|
||||
const fpB = computeKeyFingerprint(kpB.publicKeyBase64);
|
||||
|
||||
// Machine A: lookupClientByPublicKey → null, no existing client at ID → keeps HWID
|
||||
const wsA = createMockWs({ clientId: sharedHwid });
|
||||
// Simulate server logic: no existing key at this ID → keep URL ID
|
||||
const existingPkAtId: string | null = null; // DB empty
|
||||
if (existingPkAtId && existingPkAtId !== kpA.publicKeyBase64) {
|
||||
wsA.data.clientId = fpA;
|
||||
}
|
||||
expect(wsA.data.clientId).toBe(sharedHwid); // Machine A keeps HWID
|
||||
|
||||
// Machine B: lookupClientByPublicKey → null, but ID already taken by PK_A
|
||||
const wsB = createMockWs({ clientId: sharedHwid });
|
||||
const existingPkAtIdForB: string | null = kpA.publicKeyBase64; // Machine A's key is stored at this ID
|
||||
if (existingPkAtIdForB && existingPkAtIdForB !== kpB.publicKeyBase64) {
|
||||
wsB.data.clientId = fpB;
|
||||
}
|
||||
expect(wsB.data.clientId).toBe(fpB); // Machine B gets reassigned
|
||||
expect(wsB.data.clientId).not.toBe(sharedHwid);
|
||||
});
|
||||
|
||||
test("no collision when same machine reconnects with same public key", async () => {
|
||||
const kp = await generateKeypair();
|
||||
const hwid = "same-machine-hwid";
|
||||
const fp = computeKeyFingerprint(kp.publicKeyBase64);
|
||||
|
||||
const ws = createMockWs({ clientId: hwid });
|
||||
// Same key already at this ID → no collision
|
||||
const existingPkAtId: string | null = kp.publicKeyBase64;
|
||||
if (existingPkAtId && existingPkAtId !== kp.publicKeyBase64) {
|
||||
ws.data.clientId = fp;
|
||||
}
|
||||
expect(ws.data.clientId).toBe(hwid); // keeps original ID
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import { v4 as uuidv4 } from "uuid";
|
||||
import geoip from "geoip-lite";
|
||||
import { logAudit, AuditAction } from "../../auditLog";
|
||||
import * as clientManager from "../../clientManager";
|
||||
import { clientExists, setOnlineState, upsertClientRow, getClientEnrollmentStatus, setClientEnrollmentStatus, lookupClientByPublicKey } from "../../db";
|
||||
import { clientExists, setOnlineState, upsertClientRow, getClientEnrollmentStatus, setClientEnrollmentStatus, lookupClientByPublicKey, getClientPublicKeyById } from "../../db";
|
||||
import { getConfig } from "../../config";
|
||||
import { logger } from "../../logger";
|
||||
import { metrics } from "../../metrics";
|
||||
@@ -251,6 +251,12 @@ export async function handleWebSocketMessage(
|
||||
ws.data.clientId = existing.id;
|
||||
} else {
|
||||
enrollmentStatus = requireApproval ? "pending" : "approved";
|
||||
|
||||
const existingPk = getClientPublicKeyById(ws.data.clientId);
|
||||
if (existingPk && existingPk !== publicKey) {
|
||||
ws.data.clientId = keyFingerprint;
|
||||
logger.info(`[purgatory] ID collision detected — reassigned to ${keyFingerprint}`);
|
||||
}
|
||||
}
|
||||
|
||||
const resolvedId = ws.data.clientId;
|
||||
|
||||
Reference in New Issue
Block a user