From deac456703c50791f007362fcd376ed2ce9612dc Mon Sep 17 00:00:00 2001 From: SkywalkerJi Date: Fri, 7 Nov 2025 03:28:01 +0900 Subject: [PATCH] fix: Fixed go vet issues. (#658) * Fixed vet ./... errors. * Fixed ESLint issues. --- crypto/encryption_test.go | 6 +- web/src/lib/crypto.ts | 194 ++++++++++++++++++++------------------ 2 files changed, 105 insertions(+), 95 deletions(-) diff --git a/crypto/encryption_test.go b/crypto/encryption_test.go index bf2e3aa1..1e65a962 100644 --- a/crypto/encryption_test.go +++ b/crypto/encryption_test.go @@ -66,14 +66,12 @@ func TestDatabaseEncryption(t *testing.T) { // TestHybridEncryption 測試混合加密(前端 → 後端場景) func TestHybridEncryption(t *testing.T) { - em, err := GetEncryptionManager() + _, err := GetEncryptionManager() if err != nil { t.Fatalf("初始化加密管理器失敗: %v", err) } - // 模擬前端加密私鑰 - plaintext := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" - + // plaintext := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" // 注意:這裡需要前端的 encryptWithServerPublicKey 實現 // 為了測試,我們直接使用後端的加密函數(實際前端使用 Web Crypto API) diff --git a/web/src/lib/crypto.ts b/web/src/lib/crypto.ts index 0ab69869..913a290f 100644 --- a/web/src/lib/crypto.ts +++ b/web/src/lib/crypto.ts @@ -9,9 +9,11 @@ * 生成隨機混淆字串 (用於剪貼簿混淆) */ export function generateObfuscation(): string { - const array = new Uint8Array(32); - crypto.getRandomValues(array); - return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join(''); + const array = new Uint8Array(32) + crypto.getRandomValues(array) + return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join( + '' + ) } /** @@ -26,47 +28,50 @@ export async function encryptWithServerPublicKey( ): Promise { try { // 1. 導入伺服器公鑰 - const publicKey = await importRSAPublicKey(serverPublicKeyPEM); + const publicKey = await importRSAPublicKey(serverPublicKeyPEM) // 2. 生成隨機 AES 密鑰 (256-bit) const aesKey = await crypto.subtle.generateKey( { name: 'AES-GCM', length: 256 }, true, ['encrypt'] - ); + ) // 3. 使用 AES-GCM 加密數據 - const iv = crypto.getRandomValues(new Uint8Array(12)); // 96-bit nonce - const encodedText = new TextEncoder().encode(plaintext); + const iv = crypto.getRandomValues(new Uint8Array(12)) // 96-bit nonce + const encodedText = new TextEncoder().encode(plaintext) const encryptedData = await crypto.subtle.encrypt( { name: 'AES-GCM', iv }, aesKey, encodedText - ); + ) // 4. 導出 AES 密鑰並用 RSA 加密 - const exportedAESKey = await crypto.subtle.exportKey('raw', aesKey); + const exportedAESKey = await crypto.subtle.exportKey('raw', aesKey) const encryptedAESKey = await crypto.subtle.encrypt( { name: 'RSA-OAEP' }, publicKey, exportedAESKey - ); + ) // 5. 組合: [加密的 AES 密鑰長度(4字節)] + [加密的 AES 密鑰] + [IV] + [加密數據] const result = new Uint8Array( 4 + encryptedAESKey.byteLength + iv.length + encryptedData.byteLength - ); - const view = new DataView(result.buffer); - view.setUint32(0, encryptedAESKey.byteLength, false); // 大端序 - result.set(new Uint8Array(encryptedAESKey), 4); - result.set(iv, 4 + encryptedAESKey.byteLength); - result.set(new Uint8Array(encryptedData), 4 + encryptedAESKey.byteLength + iv.length); + ) + const view = new DataView(result.buffer) + view.setUint32(0, encryptedAESKey.byteLength, false) // 大端序 + result.set(new Uint8Array(encryptedAESKey), 4) + result.set(iv, 4 + encryptedAESKey.byteLength) + result.set( + new Uint8Array(encryptedData), + 4 + encryptedAESKey.byteLength + iv.length + ) // 6. Base64 編碼 - return arrayBufferToBase64(result); + return arrayBufferToBase64(result) } catch (error) { - console.error('加密失敗:', error); - throw new Error('加密過程中發生錯誤,請檢查伺服器公鑰是否有效'); + console.error('加密失敗:', error) + throw new Error('加密過程中發生錯誤,請檢查伺服器公鑰是否有效') } } @@ -78,10 +83,10 @@ async function importRSAPublicKey(pem: string): Promise { const pemContents = pem .replace(/-----BEGIN PUBLIC KEY-----/, '') .replace(/-----END PUBLIC KEY-----/, '') - .replace(/\s/g, ''); + .replace(/\s/g, '') // Base64 解碼 - const binaryDer = base64ToArrayBuffer(pemContents); + const binaryDer = base64ToArrayBuffer(pemContents) // 導入為 CryptoKey return crypto.subtle.importKey( @@ -93,14 +98,14 @@ async function importRSAPublicKey(pem: string): Promise { }, true, ['encrypt'] - ); + ) } // ==================== 二階段輸入 UI ==================== export interface TwoStageInputResult { - encryptedKey: string; - obfuscationLog: string[]; // 混淆記錄(用於審計) + encryptedKey: string + obfuscationLog: string[] // 混淆記錄(用於審計) } /** @@ -111,34 +116,37 @@ export interface TwoStageInputResult { export async function twoStagePrivateKeyInput( serverPublicKey: string ): Promise { - const obfuscationLog: string[] = []; + const obfuscationLog: string[] = [] return new Promise((resolve, reject) => { // 創建自定義 Modal const modal = createTwoStageModal(async (part1: string, part2: string) => { try { - const fullKey = part1 + part2; + const fullKey = part1 + part2 // 驗證私鑰格式 if (!validatePrivateKeyFormat(fullKey)) { - throw new Error('私鑰格式不正確(應為 64 位十六進制或 0x 開頭)'); + throw new Error('私鑰格式不正確(應為 64 位十六進制或 0x 開頭)') } // 加密 - const encrypted = await encryptWithServerPublicKey(fullKey, serverPublicKey); + const encrypted = await encryptWithServerPublicKey( + fullKey, + serverPublicKey + ) // 清除敏感數據 - part1 = ''; - part2 = ''; + part1 = '' + part2 = '' - resolve({ encryptedKey: encrypted, obfuscationLog }); + resolve({ encryptedKey: encrypted, obfuscationLog }) } catch (error) { - reject(error); + reject(error) } - }, obfuscationLog); + }, obfuscationLog) - document.body.appendChild(modal); - }); + document.body.appendChild(modal) + }) } /** @@ -148,21 +156,21 @@ function createTwoStageModal( onSubmit: (part1: string, part2: string) => void, obfuscationLog: string[] ): HTMLElement { - const modal = document.createElement('div'); + const modal = document.createElement('div') modal.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.8); z-index: 10000; display: flex; align-items: center; justify-content: center; - `; + ` - const content = document.createElement('div'); + const content = document.createElement('div') content.style.cssText = ` background: #1a1a2e; padding: 2rem; border-radius: 8px; max-width: 500px; width: 90%; color: white; - `; + ` - let stage = 1; - let part1 = ''; + let stage = 1 + let part1 = '' const render = () => { if (stage === 1) { @@ -190,34 +198,36 @@ function createTwoStageModal( background: transparent; border: 1px solid #555; border-radius: 4px; color: #888; cursor: pointer;" >取消 - `; + ` - const input = content.querySelector('#stage1-input') as HTMLInputElement; - const nextBtn = content.querySelector('#stage1-next') as HTMLButtonElement; - const cancelBtn = content.querySelector('#cancel') as HTMLButtonElement; + const input = content.querySelector('#stage1-input') as HTMLInputElement + const nextBtn = content.querySelector('#stage1-next') as HTMLButtonElement + const cancelBtn = content.querySelector('#cancel') as HTMLButtonElement - input.focus(); + input.focus() input.addEventListener('input', () => { - nextBtn.disabled = input.value.length < 10; - }); + nextBtn.disabled = input.value.length < 10 + }) nextBtn.addEventListener('click', async () => { - part1 = input.value; - input.value = ''; // 立即清除 + part1 = input.value + input.value = '' // 立即清除 // 生成混淆字串並強制複製 - const obfuscation = generateObfuscation(); - await navigator.clipboard.writeText(obfuscation); - obfuscationLog.push(`Stage1: ${new Date().toISOString()}`); + const obfuscation = generateObfuscation() + await navigator.clipboard.writeText(obfuscation) + obfuscationLog.push(`Stage1: ${new Date().toISOString()}`) - alert('⚠️ 已複製混淆字串到剪貼簿\n\n請在任意地方貼上一次(避免監控),然後點擊確定繼續'); - stage = 2; - render(); - }); + alert( + '⚠️ 已複製混淆字串到剪貼簿\n\n請在任意地方貼上一次(避免監控),然後點擊確定繼續' + ) + stage = 2 + render() + }) cancelBtn.addEventListener('click', () => { - modal.remove(); - }); + modal.remove() + }) } else if (stage === 2) { content.innerHTML = `

🔐 安全輸入 - 第二階段

@@ -243,33 +253,35 @@ function createTwoStageModal( background: transparent; border: 1px solid #555; border-radius: 4px; color: #888; cursor: pointer;" >← 返回上一步 - `; + ` - const input = content.querySelector('#stage2-input') as HTMLInputElement; - const submitBtn = content.querySelector('#stage2-submit') as HTMLButtonElement; - const backBtn = content.querySelector('#back') as HTMLButtonElement; + const input = content.querySelector('#stage2-input') as HTMLInputElement + const submitBtn = content.querySelector( + '#stage2-submit' + ) as HTMLButtonElement + const backBtn = content.querySelector('#back') as HTMLButtonElement - input.focus(); + input.focus() submitBtn.addEventListener('click', async () => { - const part2 = input.value; - input.value = ''; // 立即清除 + const part2 = input.value + input.value = '' // 立即清除 - obfuscationLog.push(`Stage2: ${new Date().toISOString()}`); + obfuscationLog.push(`Stage2: ${new Date().toISOString()}`) - modal.remove(); - onSubmit(part1, part2); - }); + modal.remove() + onSubmit(part1, part2) + }) backBtn.addEventListener('click', () => { - stage = 1; - render(); - }); + stage = 1 + render() + }) } - }; + } - render(); - modal.appendChild(content); - return modal; + render() + modal.appendChild(content) + return modal } /** @@ -277,38 +289,38 @@ function createTwoStageModal( */ function validatePrivateKeyFormat(key: string): boolean { // EVM 私鑰: 64 位十六進制 (可選 0x 前綴) - const evmPattern = /^(0x)?[0-9a-fA-F]{64}$/; - return evmPattern.test(key); + const evmPattern = /^(0x)?[0-9a-fA-F]{64}$/ + return evmPattern.test(key) } // ==================== 工具函數 ==================== function arrayBufferToBase64(buffer: ArrayBuffer | Uint8Array): string { - const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer); - let binary = ''; + const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer) + let binary = '' for (let i = 0; i < bytes.byteLength; i++) { - binary += String.fromCharCode(bytes[i]); + binary += String.fromCharCode(bytes[i]) } - return btoa(binary); + return btoa(binary) } function base64ToArrayBuffer(base64: string): ArrayBuffer { - const binary = atob(base64); - const bytes = new Uint8Array(binary.length); + const binary = atob(base64) + const bytes = new Uint8Array(binary.length) for (let i = 0; i < binary.length; i++) { - bytes[i] = binary.charCodeAt(i); + bytes[i] = binary.charCodeAt(i) } - return bytes.buffer; + return bytes.buffer } /** * 從伺服器獲取公鑰 */ export async function fetchServerPublicKey(): Promise { - const response = await fetch('/api/crypto/public-key'); + const response = await fetch('/api/crypto/public-key') if (!response.ok) { - throw new Error('無法獲取伺服器公鑰'); + throw new Error('無法獲取伺服器公鑰') } - const data = await response.json(); - return data.public_key; + const data = await response.json() + return data.public_key }