mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-06-02 10:50:36 +09:00
Enhance encryption security
This commit is contained in:
parent
ad49cd0454
commit
5ac651ad8e
@ -103,10 +103,10 @@ async function requestBedrock(req: NextRequest) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Make request to AWS Bedrock
|
// Make request to AWS Bedrock
|
||||||
// console.log(
|
console.log(
|
||||||
// "[Bedrock Request] Final Body:",
|
"[Bedrock Request] Final Body:",
|
||||||
// JSON.stringify(requestBody, null, 2),
|
JSON.stringify(requestBody, null, 2),
|
||||||
// );
|
);
|
||||||
const res = await fetch(endpoint, {
|
const res = await fetch(endpoint, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers,
|
headers,
|
||||||
|
@ -137,7 +137,8 @@ export class BedrockApi implements LLMApi {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
// toolChoice: { auto: {} }
|
toolChoice: { auto: {} },
|
||||||
|
// toolChoice: { any: {} }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,16 +340,11 @@ export class BedrockApi implements LLMApi {
|
|||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
options.onController?.(controller);
|
options.onController?.(controller);
|
||||||
|
|
||||||
if (!accessStore.isValidBedrock()) {
|
|
||||||
throw new Error(
|
|
||||||
"Invalid AWS credentials. Please check your configuration and ensure ENCRYPTION_KEY is set.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let finalRequestBody = this.formatRequestBody(messages, modelConfig);
|
let finalRequestBody = this.formatRequestBody(messages, modelConfig);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const isApp = !!getClientConfig()?.isApp;
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
|
// const isApp = true;
|
||||||
const bedrockAPIPath = `${BEDROCK_BASE_URL}/model/${
|
const bedrockAPIPath = `${BEDROCK_BASE_URL}/model/${
|
||||||
modelConfig.model
|
modelConfig.model
|
||||||
}/invoke${shouldStream ? "-with-response-stream" : ""}`;
|
}/invoke${shouldStream ? "-with-response-stream" : ""}`;
|
||||||
@ -699,10 +695,10 @@ function bedrockStream(
|
|||||||
responseRes = res;
|
responseRes = res;
|
||||||
|
|
||||||
const contentType = res.headers.get("content-type");
|
const contentType = res.headers.get("content-type");
|
||||||
console.log(
|
// console.log(
|
||||||
"[Bedrock Stream Request] response content type: ",
|
// "[Bedrock Stream Request] response content type: ",
|
||||||
contentType,
|
// contentType,
|
||||||
);
|
// );
|
||||||
|
|
||||||
if (contentType?.startsWith("text/plain")) {
|
if (contentType?.startsWith("text/plain")) {
|
||||||
responseText = await res.text();
|
responseText = await res.text();
|
||||||
|
@ -2,7 +2,7 @@ import SHA256 from "crypto-js/sha256";
|
|||||||
import HmacSHA256 from "crypto-js/hmac-sha256";
|
import HmacSHA256 from "crypto-js/hmac-sha256";
|
||||||
import Hex from "crypto-js/enc-hex";
|
import Hex from "crypto-js/enc-hex";
|
||||||
import Utf8 from "crypto-js/enc-utf8";
|
import Utf8 from "crypto-js/enc-utf8";
|
||||||
import { AES, enc } from "crypto-js";
|
import { AES, enc, lib, PBKDF2, mode, pad, algo } from "crypto-js";
|
||||||
|
|
||||||
// Types and Interfaces
|
// Types and Interfaces
|
||||||
export interface BedrockCredentials {
|
export interface BedrockCredentials {
|
||||||
@ -29,13 +29,51 @@ type ParsedEvent = Record<string, any>;
|
|||||||
type EventResult = ParsedEvent[];
|
type EventResult = ParsedEvent[];
|
||||||
|
|
||||||
// Encryption utilities
|
// Encryption utilities
|
||||||
|
function generateSalt(): string {
|
||||||
|
const salt = lib.WordArray.random(128 / 8);
|
||||||
|
return salt.toString(enc.Base64);
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateIV(): string {
|
||||||
|
const iv = lib.WordArray.random(128 / 8);
|
||||||
|
return iv.toString(enc.Base64);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deriveKey(password: string, salt: string): lib.WordArray {
|
||||||
|
// Use PBKDF2 with SHA256 for key derivation
|
||||||
|
return PBKDF2(password, salt, {
|
||||||
|
keySize: 256 / 32,
|
||||||
|
iterations: 10000,
|
||||||
|
hasher: algo.SHA256,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using a dot as separator since it's not used in Base64
|
||||||
|
const SEPARATOR = ".";
|
||||||
|
|
||||||
export function encrypt(data: string, encryptionKey: string): string {
|
export function encrypt(data: string, encryptionKey: string): string {
|
||||||
if (!data) return "";
|
if (!data) return "";
|
||||||
if (!encryptionKey) {
|
if (!encryptionKey) {
|
||||||
throw new Error("Encryption key is required for AWS credential encryption");
|
throw new Error("Encryption key is required for AWS credential encryption");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return AES.encrypt(data, encryptionKey).toString();
|
// Generate salt and IV
|
||||||
|
const salt = generateSalt();
|
||||||
|
const iv = generateIV();
|
||||||
|
|
||||||
|
// Derive key using PBKDF2
|
||||||
|
const key = deriveKey(encryptionKey, salt);
|
||||||
|
|
||||||
|
// Encrypt the data
|
||||||
|
const encrypted = AES.encrypt(data, key, {
|
||||||
|
iv: enc.Base64.parse(iv),
|
||||||
|
mode: mode.CBC,
|
||||||
|
padding: pad.Pkcs7,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Combine salt, IV, and encrypted data
|
||||||
|
// Format: salt.iv.encryptedData
|
||||||
|
return [salt, iv, encrypted.toString()].join(SEPARATOR);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error("Failed to encrypt AWS credentials");
|
throw new Error("Failed to encrypt AWS credentials");
|
||||||
}
|
}
|
||||||
@ -47,12 +85,21 @@ export function decrypt(encryptedData: string, encryptionKey: string): string {
|
|||||||
throw new Error("Encryption key is required for AWS credential decryption");
|
throw new Error("Encryption key is required for AWS credential decryption");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const bytes = AES.decrypt(encryptedData, encryptionKey);
|
let components = encryptedData.split(SEPARATOR);
|
||||||
const decrypted = bytes.toString(enc.Utf8);
|
const [salt, iv, data] = components;
|
||||||
if (!decrypted && encryptedData) {
|
// For new format, use the provided salt and IV
|
||||||
|
const key = deriveKey(encryptionKey, salt);
|
||||||
|
const decrypted = AES.decrypt(data, key, {
|
||||||
|
iv: enc.Base64.parse(iv),
|
||||||
|
mode: mode.CBC,
|
||||||
|
padding: pad.Pkcs7,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = decrypted.toString(enc.Utf8);
|
||||||
|
if (!result) {
|
||||||
throw new Error("Failed to decrypt AWS credentials");
|
throw new Error("Failed to decrypt AWS credentials");
|
||||||
}
|
}
|
||||||
return decrypted;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error("Failed to decrypt AWS credentials");
|
throw new Error("Failed to decrypt AWS credentials");
|
||||||
}
|
}
|
||||||
@ -61,7 +108,9 @@ export function decrypt(encryptedData: string, encryptionKey: string): string {
|
|||||||
export function maskSensitiveValue(value: string): string {
|
export function maskSensitiveValue(value: string): string {
|
||||||
if (!value) return "";
|
if (!value) return "";
|
||||||
if (value.length <= 4) return value;
|
if (value.length <= 4) return value;
|
||||||
return "*".repeat(value.length - 4) + value.slice(-4);
|
// Use constant-time operations to prevent timing attacks
|
||||||
|
const masked = Buffer.alloc(value.length - 4, "*").toString();
|
||||||
|
return value.slice(0, 2) + masked + value.slice(-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// AWS Signing
|
// AWS Signing
|
||||||
|
Loading…
Reference in New Issue
Block a user