fix: Fixed go vet issues. (#658)

* Fixed vet ./... errors.

* Fixed ESLint issues.
This commit is contained in:
SkywalkerJi
2025-11-07 03:28:01 +09:00
committed by GitHub
parent 40030fd1b6
commit bf0ebf8878
2 changed files with 105 additions and 95 deletions

View File

@@ -66,14 +66,12 @@ func TestDatabaseEncryption(t *testing.T) {
// TestHybridEncryption 測試混合加密(前端 → 後端場景) // TestHybridEncryption 測試混合加密(前端 → 後端場景)
func TestHybridEncryption(t *testing.T) { func TestHybridEncryption(t *testing.T) {
em, err := GetEncryptionManager() _, err := GetEncryptionManager()
if err != nil { if err != nil {
t.Fatalf("初始化加密管理器失敗: %v", err) t.Fatalf("初始化加密管理器失敗: %v", err)
} }
// 模擬前端加密私鑰 // 模擬前端加密私鑰
plaintext := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" // plaintext := "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
// 注意:這裡需要前端的 encryptWithServerPublicKey 實現 // 注意:這裡需要前端的 encryptWithServerPublicKey 實現
// 為了測試,我們直接使用後端的加密函數(實際前端使用 Web Crypto API // 為了測試,我們直接使用後端的加密函數(實際前端使用 Web Crypto API

View File

@@ -9,9 +9,11 @@
* 生成隨機混淆字串 (用於剪貼簿混淆) * 生成隨機混淆字串 (用於剪貼簿混淆)
*/ */
export function generateObfuscation(): string { export function generateObfuscation(): string {
const array = new Uint8Array(32); const array = new Uint8Array(32)
crypto.getRandomValues(array); crypto.getRandomValues(array)
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join(''); return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join(
''
)
} }
/** /**
@@ -26,47 +28,50 @@ export async function encryptWithServerPublicKey(
): Promise<string> { ): Promise<string> {
try { try {
// 1. 導入伺服器公鑰 // 1. 導入伺服器公鑰
const publicKey = await importRSAPublicKey(serverPublicKeyPEM); const publicKey = await importRSAPublicKey(serverPublicKeyPEM)
// 2. 生成隨機 AES 密鑰 (256-bit) // 2. 生成隨機 AES 密鑰 (256-bit)
const aesKey = await crypto.subtle.generateKey( const aesKey = await crypto.subtle.generateKey(
{ name: 'AES-GCM', length: 256 }, { name: 'AES-GCM', length: 256 },
true, true,
['encrypt'] ['encrypt']
); )
// 3. 使用 AES-GCM 加密數據 // 3. 使用 AES-GCM 加密數據
const iv = crypto.getRandomValues(new Uint8Array(12)); // 96-bit nonce const iv = crypto.getRandomValues(new Uint8Array(12)) // 96-bit nonce
const encodedText = new TextEncoder().encode(plaintext); const encodedText = new TextEncoder().encode(plaintext)
const encryptedData = await crypto.subtle.encrypt( const encryptedData = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv }, { name: 'AES-GCM', iv },
aesKey, aesKey,
encodedText encodedText
); )
// 4. 導出 AES 密鑰並用 RSA 加密 // 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( const encryptedAESKey = await crypto.subtle.encrypt(
{ name: 'RSA-OAEP' }, { name: 'RSA-OAEP' },
publicKey, publicKey,
exportedAESKey exportedAESKey
); )
// 5. 組合: [加密的 AES 密鑰長度(4字節)] + [加密的 AES 密鑰] + [IV] + [加密數據] // 5. 組合: [加密的 AES 密鑰長度(4字節)] + [加密的 AES 密鑰] + [IV] + [加密數據]
const result = new Uint8Array( const result = new Uint8Array(
4 + encryptedAESKey.byteLength + iv.length + encryptedData.byteLength 4 + encryptedAESKey.byteLength + iv.length + encryptedData.byteLength
); )
const view = new DataView(result.buffer); const view = new DataView(result.buffer)
view.setUint32(0, encryptedAESKey.byteLength, false); // 大端序 view.setUint32(0, encryptedAESKey.byteLength, false) // 大端序
result.set(new Uint8Array(encryptedAESKey), 4); result.set(new Uint8Array(encryptedAESKey), 4)
result.set(iv, 4 + encryptedAESKey.byteLength); result.set(iv, 4 + encryptedAESKey.byteLength)
result.set(new Uint8Array(encryptedData), 4 + encryptedAESKey.byteLength + iv.length); result.set(
new Uint8Array(encryptedData),
4 + encryptedAESKey.byteLength + iv.length
)
// 6. Base64 編碼 // 6. Base64 編碼
return arrayBufferToBase64(result); return arrayBufferToBase64(result)
} catch (error) { } catch (error) {
console.error('加密失敗:', error); console.error('加密失敗:', error)
throw new Error('加密過程中發生錯誤,請檢查伺服器公鑰是否有效'); throw new Error('加密過程中發生錯誤,請檢查伺服器公鑰是否有效')
} }
} }
@@ -78,10 +83,10 @@ async function importRSAPublicKey(pem: string): Promise<CryptoKey> {
const pemContents = pem const pemContents = pem
.replace(/-----BEGIN PUBLIC KEY-----/, '') .replace(/-----BEGIN PUBLIC KEY-----/, '')
.replace(/-----END PUBLIC KEY-----/, '') .replace(/-----END PUBLIC KEY-----/, '')
.replace(/\s/g, ''); .replace(/\s/g, '')
// Base64 解碼 // Base64 解碼
const binaryDer = base64ToArrayBuffer(pemContents); const binaryDer = base64ToArrayBuffer(pemContents)
// 導入為 CryptoKey // 導入為 CryptoKey
return crypto.subtle.importKey( return crypto.subtle.importKey(
@@ -93,14 +98,14 @@ async function importRSAPublicKey(pem: string): Promise<CryptoKey> {
}, },
true, true,
['encrypt'] ['encrypt']
); )
} }
// ==================== 二階段輸入 UI ==================== // ==================== 二階段輸入 UI ====================
export interface TwoStageInputResult { export interface TwoStageInputResult {
encryptedKey: string; encryptedKey: string
obfuscationLog: string[]; // 混淆記錄(用於審計) obfuscationLog: string[] // 混淆記錄(用於審計)
} }
/** /**
@@ -111,34 +116,37 @@ export interface TwoStageInputResult {
export async function twoStagePrivateKeyInput( export async function twoStagePrivateKeyInput(
serverPublicKey: string serverPublicKey: string
): Promise<TwoStageInputResult> { ): Promise<TwoStageInputResult> {
const obfuscationLog: string[] = []; const obfuscationLog: string[] = []
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// 創建自定義 Modal // 創建自定義 Modal
const modal = createTwoStageModal(async (part1: string, part2: string) => { const modal = createTwoStageModal(async (part1: string, part2: string) => {
try { try {
const fullKey = part1 + part2; const fullKey = part1 + part2
// 驗證私鑰格式 // 驗證私鑰格式
if (!validatePrivateKeyFormat(fullKey)) { 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 = ''; part1 = ''
part2 = ''; part2 = ''
resolve({ encryptedKey: encrypted, obfuscationLog }); resolve({ encryptedKey: encrypted, obfuscationLog })
} catch (error) { } 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, onSubmit: (part1: string, part2: string) => void,
obfuscationLog: string[] obfuscationLog: string[]
): HTMLElement { ): HTMLElement {
const modal = document.createElement('div'); const modal = document.createElement('div')
modal.style.cssText = ` modal.style.cssText = `
position: fixed; top: 0; left: 0; right: 0; bottom: 0; position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.8); z-index: 10000; background: rgba(0,0,0,0.8); z-index: 10000;
display: flex; align-items: center; justify-content: center; display: flex; align-items: center; justify-content: center;
`; `
const content = document.createElement('div'); const content = document.createElement('div')
content.style.cssText = ` content.style.cssText = `
background: #1a1a2e; padding: 2rem; border-radius: 8px; background: #1a1a2e; padding: 2rem; border-radius: 8px;
max-width: 500px; width: 90%; color: white; max-width: 500px; width: 90%; color: white;
`; `
let stage = 1; let stage = 1
let part1 = ''; let part1 = ''
const render = () => { const render = () => {
if (stage === 1) { if (stage === 1) {
@@ -190,34 +198,36 @@ function createTwoStageModal(
background: transparent; border: 1px solid #555; border-radius: 4px; background: transparent; border: 1px solid #555; border-radius: 4px;
color: #888; cursor: pointer;" color: #888; cursor: pointer;"
>取消</button> >取消</button>
`; `
const input = content.querySelector('#stage1-input') as HTMLInputElement; const input = content.querySelector('#stage1-input') as HTMLInputElement
const nextBtn = content.querySelector('#stage1-next') as HTMLButtonElement; const nextBtn = content.querySelector('#stage1-next') as HTMLButtonElement
const cancelBtn = content.querySelector('#cancel') as HTMLButtonElement; const cancelBtn = content.querySelector('#cancel') as HTMLButtonElement
input.focus(); input.focus()
input.addEventListener('input', () => { input.addEventListener('input', () => {
nextBtn.disabled = input.value.length < 10; nextBtn.disabled = input.value.length < 10
}); })
nextBtn.addEventListener('click', async () => { nextBtn.addEventListener('click', async () => {
part1 = input.value; part1 = input.value
input.value = ''; // 立即清除 input.value = '' // 立即清除
// 生成混淆字串並強制複製 // 生成混淆字串並強制複製
const obfuscation = generateObfuscation(); const obfuscation = generateObfuscation()
await navigator.clipboard.writeText(obfuscation); await navigator.clipboard.writeText(obfuscation)
obfuscationLog.push(`Stage1: ${new Date().toISOString()}`); obfuscationLog.push(`Stage1: ${new Date().toISOString()}`)
alert('⚠️ 已複製混淆字串到剪貼簿\n\n請在任意地方貼上一次避免監控然後點擊確定繼續'); alert(
stage = 2; '⚠️ 已複製混淆字串到剪貼簿\n\n請在任意地方貼上一次避免監控然後點擊確定繼續'
render(); )
}); stage = 2
render()
})
cancelBtn.addEventListener('click', () => { cancelBtn.addEventListener('click', () => {
modal.remove(); modal.remove()
}); })
} else if (stage === 2) { } else if (stage === 2) {
content.innerHTML = ` content.innerHTML = `
<h2 style="margin-bottom: 1rem;">🔐 安全輸入 - 第二階段</h2> <h2 style="margin-bottom: 1rem;">🔐 安全輸入 - 第二階段</h2>
@@ -243,33 +253,35 @@ function createTwoStageModal(
background: transparent; border: 1px solid #555; border-radius: 4px; background: transparent; border: 1px solid #555; border-radius: 4px;
color: #888; cursor: pointer;" color: #888; cursor: pointer;"
>← 返回上一步</button> >← 返回上一步</button>
`; `
const input = content.querySelector('#stage2-input') as HTMLInputElement; const input = content.querySelector('#stage2-input') as HTMLInputElement
const submitBtn = content.querySelector('#stage2-submit') as HTMLButtonElement; const submitBtn = content.querySelector(
const backBtn = content.querySelector('#back') as HTMLButtonElement; '#stage2-submit'
) as HTMLButtonElement
const backBtn = content.querySelector('#back') as HTMLButtonElement
input.focus(); input.focus()
submitBtn.addEventListener('click', async () => { submitBtn.addEventListener('click', async () => {
const part2 = input.value; const part2 = input.value
input.value = ''; // 立即清除 input.value = '' // 立即清除
obfuscationLog.push(`Stage2: ${new Date().toISOString()}`); obfuscationLog.push(`Stage2: ${new Date().toISOString()}`)
modal.remove(); modal.remove()
onSubmit(part1, part2); onSubmit(part1, part2)
}); })
backBtn.addEventListener('click', () => { backBtn.addEventListener('click', () => {
stage = 1; stage = 1
render(); render()
}); })
} }
}; }
render(); render()
modal.appendChild(content); modal.appendChild(content)
return modal; return modal
} }
/** /**
@@ -277,38 +289,38 @@ function createTwoStageModal(
*/ */
function validatePrivateKeyFormat(key: string): boolean { function validatePrivateKeyFormat(key: string): boolean {
// EVM 私鑰: 64 位十六進制 (可選 0x 前綴) // EVM 私鑰: 64 位十六進制 (可選 0x 前綴)
const evmPattern = /^(0x)?[0-9a-fA-F]{64}$/; const evmPattern = /^(0x)?[0-9a-fA-F]{64}$/
return evmPattern.test(key); return evmPattern.test(key)
} }
// ==================== 工具函數 ==================== // ==================== 工具函數 ====================
function arrayBufferToBase64(buffer: ArrayBuffer | Uint8Array): string { function arrayBufferToBase64(buffer: ArrayBuffer | Uint8Array): string {
const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer); const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer)
let binary = ''; let binary = ''
for (let i = 0; i < bytes.byteLength; i++) { 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 { function base64ToArrayBuffer(base64: string): ArrayBuffer {
const binary = atob(base64); const binary = atob(base64)
const bytes = new Uint8Array(binary.length); const bytes = new Uint8Array(binary.length)
for (let i = 0; i < binary.length; i++) { 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<string> { export async function fetchServerPublicKey(): Promise<string> {
const response = await fetch('/api/crypto/public-key'); const response = await fetch('/api/crypto/public-key')
if (!response.ok) { if (!response.ok) {
throw new Error('無法獲取伺服器公鑰'); throw new Error('無法獲取伺服器公鑰')
} }
const data = await response.json(); const data = await response.json()
return data.public_key; return data.public_key
} }