|
| 1 | +// Generate random AES-256 key and GCM nonce/IV |
| 2 | +function generateRandomKeyAndIV() { |
| 3 | + // Generate 32 random bytes for AES-256 key |
| 4 | + const keyBytes = new Uint8Array(32); |
| 5 | + crypto.getRandomValues(keyBytes); |
| 6 | + |
| 7 | + // Generate 12 random bytes for AES-GCM nonce/IV |
| 8 | + const ivBytes = new Uint8Array(12); |
| 9 | + crypto.getRandomValues(ivBytes); |
| 10 | + |
| 11 | + // Convert to hex strings |
| 12 | + const key = Array.from(keyBytes) |
| 13 | + .map(byte => byte.toString(16).padStart(2, '0')) |
| 14 | + .join(''); |
| 15 | + |
| 16 | + const iv = Array.from(ivBytes) |
| 17 | + .map(byte => byte.toString(16).padStart(2, '0')) |
| 18 | + .join(''); |
| 19 | + |
| 20 | + return { key, iv }; |
| 21 | +} |
| 22 | + |
| 23 | +async function AESDecryptString(val, key, iv) { |
| 24 | + // Convert hex strings to ArrayBuffers |
| 25 | + const encryptedData = new Uint8Array(val.length / 2); |
| 26 | + for (let i = 0; i < val.length; i += 2) { |
| 27 | + encryptedData[i / 2] = parseInt(val.substr(i, 2), 16); |
| 28 | + } |
| 29 | + |
| 30 | + const keyBytes = new Uint8Array(key.length / 2); |
| 31 | + for (let i = 0; i < key.length; i += 2) { |
| 32 | + keyBytes[i / 2] = parseInt(key.substr(i, 2), 16); |
| 33 | + } |
| 34 | + |
| 35 | + const ivBytes = new Uint8Array(iv.length / 2); |
| 36 | + for (let i = 0; i < iv.length; i += 2) { |
| 37 | + ivBytes[i / 2] = parseInt(iv.substr(i, 2), 16); |
| 38 | + } |
| 39 | + |
| 40 | + // Import the AES key |
| 41 | + const cryptoKey = await crypto.subtle.importKey( |
| 42 | + 'raw', |
| 43 | + keyBytes, |
| 44 | + { name: 'AES-GCM' }, |
| 45 | + false, |
| 46 | + ['decrypt'] |
| 47 | + ); |
| 48 | + |
| 49 | + // Decrypt the data |
| 50 | + const decryptedBuffer = await crypto.subtle.decrypt( |
| 51 | + { |
| 52 | + name: 'AES-GCM', |
| 53 | + iv: ivBytes |
| 54 | + }, |
| 55 | + cryptoKey, |
| 56 | + encryptedData |
| 57 | + ); |
| 58 | + |
| 59 | + // Convert back to string |
| 60 | + return new TextDecoder('utf-8').decode(decryptedBuffer); |
| 61 | +} |
| 62 | + |
| 63 | +const TEMP_KV_TRUST_FOR_TESTSUITE = "TEMP_KV_TRUST_FOR_TESTSUITE"; |
| 64 | +function _selectKeys() { |
| 65 | + if (Phoenix.isTestWindow) { |
| 66 | + // this could be an iframe in a spec runner window or the spec runner window itself. |
| 67 | + const kvj = window.top.sessionStorage.getItem(TEMP_KV_TRUST_FOR_TESTSUITE); |
| 68 | + if(!kvj) { |
| 69 | + const kv = generateRandomKeyAndIV(); |
| 70 | + window.top.sessionStorage.setItem(TEMP_KV_TRUST_FOR_TESTSUITE, JSON.stringify(kv)); |
| 71 | + return kv; |
| 72 | + } |
| 73 | + try{ |
| 74 | + return JSON.parse(kvj); |
| 75 | + } catch (e) { |
| 76 | + console.error("Error parsing test suite trust keyring, defaulting to random which may not work!", e); |
| 77 | + } |
| 78 | + } |
| 79 | + return generateRandomKeyAndIV(); |
| 80 | +} |
| 81 | + |
| 82 | +const PHCODE_API_KEY = "PHCODE_API_KEY"; |
| 83 | +const { key, iv } = _selectKeys(); |
| 84 | +// this key is set at boot time as a truct base for all the core components before any extensions are loaded. |
| 85 | +// just before extensions are loaded, this key is blanked. This can be used by core modules to talk with other |
| 86 | +// core modules securely without worrying about interception by extensions. |
| 87 | +// KernalModeTrust should only be available within all code that loads before the first default/any extension. |
| 88 | +window.KernalModeTrust = { |
| 89 | + aesKeys: { key, iv }, |
| 90 | + setPhoenixAPIKey, |
| 91 | + getPhoenixAPIKey, |
| 92 | + removePhoenixAPIKey, |
| 93 | + AESDecryptString, |
| 94 | + generateRandomKeyAndIV, |
| 95 | + dismantleKeyring |
| 96 | +}; |
| 97 | +if(Phoenix.isSpecRunnerWindow){ |
| 98 | + window.specRunnerTestKernalModeTrust = window.KernalModeTrust; |
| 99 | +} |
| 100 | +// key is 64 hex characters, iv is 24 hex characters |
| 101 | + |
| 102 | +async function setPhoenixAPIKey(apiKey) { |
| 103 | + if(!window.__TAURI__){ |
| 104 | + throw new Error("Phoenix API key can only be set in tauri shell!"); |
| 105 | + } |
| 106 | + return window.__TAURI__.tauri.invoke("store_credential", {scopeName: PHCODE_API_KEY, secretVal: apiKey}); |
| 107 | +} |
| 108 | + |
| 109 | +async function getPhoenixAPIKey() { |
| 110 | + if(!window.__TAURI__){ |
| 111 | + throw new Error("Phoenix API key can only be get in tauri shell!"); |
| 112 | + } |
| 113 | + const encryptedKey = await window.__TAURI__.tauri.invoke("get_credential", {scopeName: PHCODE_API_KEY}); |
| 114 | + if(!encryptedKey){ |
| 115 | + return null; |
| 116 | + } |
| 117 | + return AESDecryptString(encryptedKey, key, iv); |
| 118 | +} |
| 119 | + |
| 120 | +async function removePhoenixAPIKey() { |
| 121 | + if(!window.__TAURI__){ |
| 122 | + throw new Error("Phoenix API key can only be set in tauri shell!"); |
| 123 | + } |
| 124 | + return window.__TAURI__.tauri.invoke("delete_credential", {scopeName: PHCODE_API_KEY}); |
| 125 | +} |
| 126 | + |
| 127 | +let _dismatled = false; |
| 128 | +async function dismantleKeyring() { |
| 129 | + if(!_dismatled){ |
| 130 | + throw new Error("Keyring can only be dismantled once!"); |
| 131 | + // and once dismantled, the next line should be reload page. this is a strict security posture requirement to |
| 132 | + // prevent extensions from stealing sensitive info from system key ring as once the trust in invalidated, |
| 133 | + // the tauri get_system key ring cred apis will work for anyone who does the first call. |
| 134 | + } |
| 135 | + _dismatled = true; |
| 136 | + if(!key || !iv){ |
| 137 | + console.error("Invalid kernal keys supplied to shutdown. Ignoring kernal trust reset at shutdown."); |
| 138 | + return; |
| 139 | + } |
| 140 | + if(!window.__TAURI__){ |
| 141 | + return; |
| 142 | + } |
| 143 | + return window.__TAURI__.tauri.invoke("remove_trust_window_aes_key", {key, iv}); |
| 144 | +} |
| 145 | + |
| 146 | +export async function initTrustRing() { |
| 147 | + if(!window.__TAURI__){ |
| 148 | + return; |
| 149 | + } |
| 150 | + await window.__TAURI__.tauri.invoke("trust_window_aes_key", {key, iv}); |
| 151 | +} |
0 commit comments