mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-07-16 05:13:36 +00:00
Merge commit from fork
This commit is contained in:
@@ -3,6 +3,7 @@ import { getDb } from "../database/db/index.js";
|
||||
import { settings } from "../database/db/schema.js";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { databaseLogger } from "./logger.js";
|
||||
import { SystemCrypto } from "./system-crypto.js";
|
||||
|
||||
interface KEKSalt {
|
||||
salt: string;
|
||||
@@ -76,12 +77,15 @@ class UserCrypto {
|
||||
let DEK: Buffer;
|
||||
|
||||
if (existingEncryptedDEK) {
|
||||
const systemKey = this.deriveOIDCSystemKey(userId);
|
||||
DEK = this.decryptDEK(existingEncryptedDEK, systemKey);
|
||||
systemKey.fill(0);
|
||||
DEK = await this.decryptSystemWrappedDEK(
|
||||
existingEncryptedDEK,
|
||||
userId,
|
||||
"oidc",
|
||||
`user_encrypted_dek_${userId}`,
|
||||
);
|
||||
} else {
|
||||
DEK = crypto.randomBytes(UserCrypto.DEK_LENGTH);
|
||||
const systemKey = this.deriveOIDCSystemKey(userId);
|
||||
const systemKey = await this.deriveOIDCSystemKey(userId);
|
||||
|
||||
try {
|
||||
const encryptedDEK = this.encryptDEK(DEK, systemKey);
|
||||
@@ -172,9 +176,12 @@ class UserCrypto {
|
||||
const oidcEncryptedDEK = await this.getOIDCEncryptedDEK(userId);
|
||||
|
||||
if (oidcEncryptedDEK) {
|
||||
const systemKey = this.deriveOIDCSystemKey(userId);
|
||||
const DEK = this.decryptDEK(oidcEncryptedDEK, systemKey);
|
||||
systemKey.fill(0);
|
||||
const DEK = await this.decryptSystemWrappedDEK(
|
||||
oidcEncryptedDEK,
|
||||
userId,
|
||||
"oidc",
|
||||
`user_encrypted_dek_oidc_${userId}`,
|
||||
);
|
||||
|
||||
if (!DEK || DEK.length === 0) {
|
||||
databaseLogger.error(
|
||||
@@ -210,9 +217,12 @@ class UserCrypto {
|
||||
return true;
|
||||
}
|
||||
|
||||
const systemKey = this.deriveOIDCSystemKey(userId);
|
||||
const DEK = this.decryptDEK(encryptedDEK, systemKey);
|
||||
systemKey.fill(0);
|
||||
const DEK = await this.decryptSystemWrappedDEK(
|
||||
encryptedDEK,
|
||||
userId,
|
||||
"oidc",
|
||||
`user_encrypted_dek_${userId}`,
|
||||
);
|
||||
|
||||
if (!DEK || DEK.length === 0) {
|
||||
await this.setupOIDCUserEncryption(userId, sessionDurationMs);
|
||||
@@ -254,7 +264,7 @@ class UserCrypto {
|
||||
);
|
||||
}
|
||||
|
||||
const systemKey = this.deriveWebAuthnSystemKey(userId);
|
||||
const systemKey = await this.deriveWebAuthnSystemKey(userId);
|
||||
const encryptedDEK = this.encryptDEK(existingDEK, systemKey);
|
||||
systemKey.fill(0);
|
||||
|
||||
@@ -271,9 +281,12 @@ class UserCrypto {
|
||||
return false;
|
||||
}
|
||||
|
||||
const systemKey = this.deriveWebAuthnSystemKey(userId);
|
||||
const DEK = this.decryptDEK(encryptedDEK, systemKey);
|
||||
systemKey.fill(0);
|
||||
const DEK = await this.decryptSystemWrappedDEK(
|
||||
encryptedDEK,
|
||||
userId,
|
||||
"webauthn",
|
||||
`user_encrypted_dek_webauthn_${userId}`,
|
||||
);
|
||||
|
||||
if (!DEK || DEK.length === 0) {
|
||||
return false;
|
||||
@@ -452,7 +465,7 @@ class UserCrypto {
|
||||
);
|
||||
}
|
||||
|
||||
const systemKey = this.deriveOIDCSystemKey(userId);
|
||||
const systemKey = await this.deriveOIDCSystemKey(userId);
|
||||
const oidcEncryptedDEK = this.encryptDEK(existingDEK, systemKey);
|
||||
systemKey.fill(0);
|
||||
|
||||
@@ -556,12 +569,68 @@ class UserCrypto {
|
||||
);
|
||||
}
|
||||
|
||||
private deriveOIDCSystemKey(userId: string): Buffer {
|
||||
const systemSecret =
|
||||
process.env.OIDC_SYSTEM_SECRET || "termix-oidc-system-secret-default";
|
||||
const salt = Buffer.from(userId, "utf8");
|
||||
private async deriveOIDCSystemKey(userId: string): Promise<Buffer> {
|
||||
if (process.env.OIDC_SYSTEM_SECRET) {
|
||||
return crypto.pbkdf2Sync(
|
||||
process.env.OIDC_SYSTEM_SECRET,
|
||||
Buffer.from(userId, "utf8"),
|
||||
100000,
|
||||
UserCrypto.KEK_LENGTH,
|
||||
"sha256",
|
||||
);
|
||||
}
|
||||
|
||||
const systemSecret = await SystemCrypto.getInstance().getEncryptionKey();
|
||||
return Buffer.from(
|
||||
crypto.hkdfSync(
|
||||
"sha256",
|
||||
systemSecret,
|
||||
Buffer.from(userId, "utf8"),
|
||||
Buffer.from("termix:oidc-user-kek", "utf8"),
|
||||
UserCrypto.KEK_LENGTH,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private async deriveWebAuthnSystemKey(userId: string): Promise<Buffer> {
|
||||
const configuredSecret =
|
||||
process.env.WEBAUTHN_SYSTEM_SECRET || process.env.OIDC_SYSTEM_SECRET;
|
||||
if (configuredSecret) {
|
||||
return crypto.pbkdf2Sync(
|
||||
configuredSecret,
|
||||
Buffer.from(`webauthn:${userId}`, "utf8"),
|
||||
100000,
|
||||
UserCrypto.KEK_LENGTH,
|
||||
"sha256",
|
||||
);
|
||||
}
|
||||
|
||||
const systemSecret = await SystemCrypto.getInstance().getEncryptionKey();
|
||||
return Buffer.from(
|
||||
crypto.hkdfSync(
|
||||
"sha256",
|
||||
systemSecret,
|
||||
Buffer.from(userId, "utf8"),
|
||||
Buffer.from("termix:webauthn-user-kek", "utf8"),
|
||||
UserCrypto.KEK_LENGTH,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
private deriveLegacySystemKey(
|
||||
userId: string,
|
||||
type: "oidc" | "webauthn",
|
||||
): Buffer {
|
||||
const secret =
|
||||
type === "oidc"
|
||||
? "termix-oidc-system-secret-default"
|
||||
: "termix-webauthn-system-secret-default";
|
||||
const salt = Buffer.from(
|
||||
type === "oidc" ? userId : `webauthn:${userId}`,
|
||||
"utf8",
|
||||
);
|
||||
return crypto.pbkdf2Sync(
|
||||
systemSecret,
|
||||
secret,
|
||||
salt,
|
||||
100000,
|
||||
UserCrypto.KEK_LENGTH,
|
||||
@@ -569,19 +638,39 @@ class UserCrypto {
|
||||
);
|
||||
}
|
||||
|
||||
private deriveWebAuthnSystemKey(userId: string): Buffer {
|
||||
const systemSecret =
|
||||
process.env.WEBAUTHN_SYSTEM_SECRET ||
|
||||
process.env.OIDC_SYSTEM_SECRET ||
|
||||
"termix-webauthn-system-secret-default";
|
||||
const salt = Buffer.from(`webauthn:${userId}`, "utf8");
|
||||
return crypto.pbkdf2Sync(
|
||||
systemSecret,
|
||||
salt,
|
||||
100000,
|
||||
UserCrypto.KEK_LENGTH,
|
||||
"sha256",
|
||||
);
|
||||
private async decryptSystemWrappedDEK(
|
||||
encryptedDEK: EncryptedDEK,
|
||||
userId: string,
|
||||
type: "oidc" | "webauthn",
|
||||
settingKey: string,
|
||||
): Promise<Buffer> {
|
||||
const systemKey =
|
||||
type === "oidc"
|
||||
? await this.deriveOIDCSystemKey(userId)
|
||||
: await this.deriveWebAuthnSystemKey(userId);
|
||||
try {
|
||||
return this.decryptDEK(encryptedDEK, systemKey);
|
||||
} catch {
|
||||
const legacyKey = this.deriveLegacySystemKey(userId, type);
|
||||
try {
|
||||
const dek = this.decryptDEK(encryptedDEK, legacyKey);
|
||||
const value = JSON.stringify(this.encryptDEK(dek, systemKey));
|
||||
await getDb()
|
||||
.update(settings)
|
||||
.set({ value })
|
||||
.where(eq(settings.key, settingKey));
|
||||
databaseLogger.info("Migrated legacy system-wrapped user key", {
|
||||
operation: "user_key_wrap_migrated",
|
||||
userId,
|
||||
type,
|
||||
});
|
||||
return dek;
|
||||
} finally {
|
||||
legacyKey.fill(0);
|
||||
}
|
||||
} finally {
|
||||
systemKey.fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
private encryptDEK(dek: Buffer, kek: Buffer): EncryptedDEK {
|
||||
|
||||
Reference in New Issue
Block a user