Skip to content

Commit ccb37cb

Browse files
author
poulphunter
committed
encryption if key passed
1 parent 0fd305d commit ccb37cb

File tree

6 files changed

+233
-14
lines changed

6 files changed

+233
-14
lines changed

package-lock.json

Lines changed: 113 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"@types/hast": "^3.0.4",
1919
"@vscode/markdown-it-katex": "^1.1.1",
2020
"autoprefixer": "^10.4.20",
21+
"buffer": "^6.0.3",
2122
"daisyui": "^4.12.14",
2223
"dexie": "^4.0.11",
2324
"dexie-export-import": "^4.1.4",
@@ -26,6 +27,7 @@
2627
"i18next-browser-languagedetector": "^8.0.4",
2728
"i18next-http-backend": "^3.0.2",
2829
"katex": "^0.16.15",
30+
"node-forge": "^1.3.1",
2931
"postcss": "^8.4.49",
3032
"react": "^18.3.1",
3133
"react-dom": "^18.3.1",
@@ -46,9 +48,11 @@
4648
"devDependencies": {
4749
"@eslint/js": "^9.17.0",
4850
"@types/markdown-it": "^14.1.2",
49-
"@types/node": "^22.13.1",
51+
"@types/node": "^22.17.0",
52+
"@types/node-forge": "^1.3.13",
5053
"@types/react": "^18.3.18",
5154
"@types/react-dom": "^18.3.5",
55+
"@types/react-router": "^5.1.20",
5256
"@vitejs/plugin-react": "^4.3.4",
5357
"eslint": "^9.17.0",
5458
"eslint-plugin-react-hooks": "^5.0.0",

src/Config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ export const BASE_URL:string =
2121
parseHashParams(window.location.hash)['h'] ??
2222
parseHashParams(window.location.hash)['host'] ??
2323
new URL('.', document.baseURI).href.toString().replace(/\/$/, '');
24+
25+
export const ENCRYPT_KEY:string =
26+
parseHashParams(window.location.hash)['k'] ??
27+
parseHashParams(window.location.hash)['key'] ??
28+
'';
2429
export const INIT_MESSAGE:string =
2530
parseHashParams(window.location.hash)['m'] ??
2631
parseHashParams(window.location.hash)['message'] ??

src/crypt.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
'use strict';
2+
'use strict';
3+
4+
import forge from 'node-forge';
5+
import { Buffer } from 'buffer';
6+
import { ENCRYPT_KEY } from './Config.ts';
7+
8+
export class Crypt {
9+
key = ENCRYPT_KEY;
10+
11+
stringToHex(str: string): string {
12+
let hex = '';
13+
for (let i = 0; i < str.length; i++) {
14+
// Get the ASCII value of the character
15+
const asciiValue = str.charCodeAt(i);
16+
17+
// Convert the ASCII value to a hexadecimal string and pad with leading zeros if necessary
18+
const hexValue = asciiValue.toString(16).padStart(2, '0');
19+
20+
// Append the hexadecimal value to the result string
21+
hex += hexValue;
22+
}
23+
return hex.toLowerCase(); // Convert to uppercase for consistency
24+
}
25+
26+
hexToString(hexString: string): string {
27+
let str = '';
28+
for (let i = 0; i < hexString.length; i += 2) {
29+
// Get the hexadecimal pair
30+
const hexPair = hexString.substring(i, i + 2);
31+
32+
// Convert the hexadecimal pair to a decimal number
33+
const decimalValue = parseInt(hexPair, 16);
34+
35+
// Convert the decimal number to a character
36+
const char = String.fromCharCode(decimalValue);
37+
38+
// Append the character to the result string
39+
str += char;
40+
}
41+
return str;
42+
}
43+
44+
encrypt(data: string): string {
45+
try {
46+
const key = forge.util.createBuffer(Buffer.from(this.key, 'hex'));
47+
const iv = forge.random.getBytesSync(16);
48+
const cipher = forge.cipher.createCipher('AES-GCM', key);
49+
cipher.start({
50+
iv: iv, // should be a 12-byte binary-encoded string or byte buffer
51+
// additionalData: "binary-encoded string", // optional
52+
tagLength: 128, // optional, defaults to 128 bits
53+
});
54+
cipher.update(forge.util.createBuffer(forge.util.encodeUtf8(data)));
55+
cipher.finish();
56+
const encrypted = cipher.output;
57+
const tag = cipher.mode.tag;
58+
const ivh = this.stringToHex(iv);
59+
return ivh + tag.toHex() + encrypted.toHex();
60+
} catch (err) {
61+
console.log(err);
62+
}
63+
return '';
64+
}
65+
66+
decrypt(data: string): string {
67+
try {
68+
const key = forge.util.createBuffer(Buffer.from(this.key, 'hex'));
69+
const ivh = data.substring(0, 32);
70+
const iv = this.hexToString(ivh);
71+
data = data.substring(32);
72+
const tagh = data.substring(0, 32);
73+
const tag = forge.util.createBuffer(Buffer.from(tagh, 'hex'));
74+
data = data.substring(32);
75+
const data2 = forge.util.createBuffer(Buffer.from(data, 'hex'));
76+
77+
const decipher = forge.cipher.createDecipher('AES-GCM', key);
78+
decipher.start({
79+
iv: iv,
80+
// additionalData: "binary-encoded string", // optional
81+
tagLength: 128, // optional, defaults to 128 bits
82+
tag: tag, // authentication tag from encryption
83+
});
84+
decipher.update(data2);
85+
const pass = decipher.finish();
86+
// pass is false if there was a failure (eg: authentication tag didn't match)
87+
if (pass) {
88+
return forge.util.decodeUtf8(decipher.output.data);
89+
}
90+
} catch (err) {
91+
console.log(err);
92+
}
93+
return '';
94+
}
95+
}

src/utils/app.context.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@ import {
2626
BASE_URL,
2727
CONFIG_DEFAULT,
2828
CONFIG_DEFAULT_KEY,
29+
ENCRYPT_KEY,
2930
isDev,
3031
PROMPT_JSON,
3132
} from '../Config';
3233
import { matchPath, useLocation, useNavigate } from 'react-router';
3334
import useStateCallback from './UseStateCallback.tsx';
3435
import { useTranslation } from 'react-i18next';
3536
import i18next from 'i18next';
37+
import { Crypt } from '../crypt';
3638

3739
type languageOption = { language: string; code: string };
3840

@@ -405,7 +407,7 @@ export const AppContextProvider = ({
405407
'Content-Type': 'application/json',
406408
...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),
407409
},
408-
body: JSON.stringify(params),
410+
body: ENCRYPT_KEY===''?JSON.stringify(params):JSON.stringify({message:new Crypt().encrypt(JSON.stringify(params))}),
409411
signal: abortController.signal,
410412
});
411413
if (fetchResponse.status !== 200) {

src/utils/misc.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { APIMessage, Message } from './types';
44

55
// ponyfill for missing ReadableStream asyncIterator on Safari
66
import { asyncIterator } from '@sec-ant/readable-stream/ponyfill/asyncIterator';
7+
import { ENCRYPT_KEY } from '../Config.ts';
8+
import { Crypt } from '../crypt.ts';
79

810
// eslint-disable-next-line @typescript-eslint/no-explicit-any
911
export const isString = (x: any) => !!x.toLowerCase;
@@ -21,11 +23,17 @@ export async function* getSSEStreamAsync(fetchResponse: Response) {
2123
.pipeThrough(new TextLineStream());
2224
// @ts-expect-error asyncIterator complains about type, but it should work
2325
for await (const line of asyncIterator(lines)) {
24-
if (line.startsWith('data:') && !line.endsWith('[DONE]')) {
25-
const data = JSON.parse(line.slice(5));
26+
let line2=line;
27+
if (ENCRYPT_KEY!=='')
28+
{
29+
line2=new Crypt().decrypt(line2.replaceAll('"',''));
30+
line2=line2.replaceAll("\n",'');
31+
}
32+
if (line2.startsWith('data:') && !line2.endsWith('[DONE]')) {
33+
const data = JSON.parse(line2.slice(5));
2634
yield data;
27-
} else if (line.startsWith('error:')) {
28-
const data = JSON.parse(line.slice(6));
35+
} else if (line2.startsWith('error:')) {
36+
const data = JSON.parse(line2.slice(6));
2937
throw new Error(data.message || 'Unknown error');
3038
}
3139
}

0 commit comments

Comments
 (0)