From b6b667d5b3d92799c0e27bf45386bf6e7569c04b Mon Sep 17 00:00:00 2001 From: ajvpot <553597+ajvpot@users.noreply.github.com> Date: Sun, 6 Jul 2025 00:00:00 +0000 Subject: [PATCH] hex keys --- src/components/ConfigContext.tsx | 58 ++++++++++++++++++++++++++------ src/lib/meshcore.ts | 27 ++++++++++++--- src/lib/meshcore_decrypt.ts | 24 ++++++++++++- 3 files changed, 93 insertions(+), 16 deletions(-) diff --git a/src/components/ConfigContext.tsx b/src/components/ConfigContext.tsx index 6b0aca9..50cdfdd 100644 --- a/src/components/ConfigContext.tsx +++ b/src/components/ConfigContext.tsx @@ -226,15 +226,31 @@ function ConfigPopover({ config, setConfig, onClose, anchorRef, onOpenKeyModal } ); } -function validateMeshcoreKey(base64Key: string): string | null { - if (!base64Key) return null; +// Add a helper to decode base64 or hex +function decodeKeyString(key: string): Buffer | null { + if (!key) return null; + // Try base64 first try { - const bytes = Buffer.from(base64Key, 'base64'); - if (bytes.length !== 16) { - return 'Key must decode to exactly 16 bytes.'; - } - } catch { - return 'Invalid base64 encoding.'; + const b = Buffer.from(key, 'base64'); + if (b.length === 16) return b; + } catch {} + // Try hex (with or without 0x) + let hex = key.trim(); + if (hex.startsWith('0x')) hex = hex.slice(2); + if (/^[0-9a-fA-F]{32}$/.test(hex)) { + try { + const b = Buffer.from(hex, 'hex'); + if (b.length === 16) return b; + } catch {} + } + return null; +} + +function validateMeshcoreKey(key: string): string | null { + if (!key) return null; + const decoded = decodeKeyString(key); + if (!decoded) { + return 'Key must be 16 bytes, in base64 or hex (32 hex digits)'; } return null; } @@ -263,7 +279,17 @@ function MeshcoreKeyModal({ config, setConfig, onClose }: { config: Config, setC disabled readOnly /> - ID: {getChannelIdFromKey(PUBLIC_MESHCORE_KEY.privateKey)} + {/* Only show ID if valid */} + {(() => { + try { + const id = getChannelIdFromKey(PUBLIC_MESHCORE_KEY.privateKey); + return ( + ID: {id} + ); + } catch { + return null; + } + })()}
- ID: {getChannelIdFromKey(key.privateKey)} + {/* Only show ID if valid */} + {(() => { + try { + const id = getChannelIdFromKey(key.privateKey); + return ( + ID: {id} + ); + } catch { + return null; + } + })()}
{ const updated = [...(config.meshcoreKeys || [])]; diff --git a/src/lib/meshcore.ts b/src/lib/meshcore.ts index 899667d..e6a91cb 100644 --- a/src/lib/meshcore.ts +++ b/src/lib/meshcore.ts @@ -3,16 +3,35 @@ import { createHash } from "crypto"; // Module-level cache for channel IDs const channelIdCache: Record = {}; +// Add a helper to decode base64 or hex +function decodeKeyString(key: string): Buffer { + // Try base64 first + try { + const b = Buffer.from(key, 'base64'); + if (b.length === 16) return b; + } catch {} + // Try hex (with or without 0x) + let hex = key.trim(); + if (hex.startsWith('0x')) hex = hex.slice(2); + if (/^[0-9a-fA-F]{32}$/.test(hex)) { + try { + const b = Buffer.from(hex, 'hex'); + if (b.length === 16) return b; + } catch {} + } + throw new Error('Invalid key format: must be 16 bytes, base64 or hex'); +} + /** * Returns the channel id for a given base64-encoded key. * Decodes the key, hashes it with SHA-256, and returns the first byte as hex. * Results are cached for performance. */ -export function getChannelIdFromKey(base64Key: string): string { - if (channelIdCache[base64Key]) return channelIdCache[base64Key]; - const keyBytes = Buffer.from(base64Key, 'base64'); +export function getChannelIdFromKey(key: string): string { + if (channelIdCache[key]) return channelIdCache[key]; + const keyBytes = decodeKeyString(key); const hash = createHash('sha256').update(keyBytes).digest(); const id = hash[0].toString(16).padStart(2, '0'); - channelIdCache[base64Key] = id; + channelIdCache[key] = id; return id; } \ No newline at end of file diff --git a/src/lib/meshcore_decrypt.ts b/src/lib/meshcore_decrypt.ts index 7ac839c..88b08ad 100644 --- a/src/lib/meshcore_decrypt.ts +++ b/src/lib/meshcore_decrypt.ts @@ -17,6 +17,28 @@ function base64ToBytes(b64: string): Uint8Array { return Uint8Array.from(atob(b64), c => c.charCodeAt(0)); } +// Add a helper to decode base64 or hex +function decodeKeyString(key: string): Uint8Array { + // Try base64 first + try { + const b = Uint8Array.from(atob(key), c => c.charCodeAt(0)); + if (b.length === 16) return b; + } catch {} + // Try hex (with or without 0x) + let hex = key.trim(); + if (hex.startsWith('0x')) hex = hex.slice(2); + if (/^[0-9a-fA-F]{32}$/.test(hex)) { + try { + const b = new Uint8Array(hex.length / 2); + for (let i = 0; i < hex.length; i += 2) { + b[i / 2] = parseInt(hex.slice(i, i + 2), 16); + } + if (b.length === 16) return b; + } catch {} + } + throw new Error('Invalid key format: must be 16 bytes, base64 or hex'); +} + // Helper: HMAC-SHA256, returns Uint8Array async function hmacSha256(key: Uint8Array, data: Uint8Array): Promise { if (window.crypto?.subtle) { @@ -89,7 +111,7 @@ export async function decryptMeshcoreGroupMessage({ for (const base64Key of knownKeys) { let keyBytes: Uint8Array; try { - keyBytes = base64ToBytes(base64Key); + keyBytes = decodeKeyString(base64Key); } catch (e) { console.warn("Skipping invalid base64 meshcore key:", base64Key, e); failures.push({ key: base64Key, reason: `base64 decode error: ${e}` });