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 { settings } from "../database/db/schema.js";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { databaseLogger } from "./logger.js";
|
import { databaseLogger } from "./logger.js";
|
||||||
|
import { SystemCrypto } from "./system-crypto.js";
|
||||||
|
|
||||||
interface KEKSalt {
|
interface KEKSalt {
|
||||||
salt: string;
|
salt: string;
|
||||||
@@ -76,12 +77,15 @@ class UserCrypto {
|
|||||||
let DEK: Buffer;
|
let DEK: Buffer;
|
||||||
|
|
||||||
if (existingEncryptedDEK) {
|
if (existingEncryptedDEK) {
|
||||||
const systemKey = this.deriveOIDCSystemKey(userId);
|
DEK = await this.decryptSystemWrappedDEK(
|
||||||
DEK = this.decryptDEK(existingEncryptedDEK, systemKey);
|
existingEncryptedDEK,
|
||||||
systemKey.fill(0);
|
userId,
|
||||||
|
"oidc",
|
||||||
|
`user_encrypted_dek_${userId}`,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
DEK = crypto.randomBytes(UserCrypto.DEK_LENGTH);
|
DEK = crypto.randomBytes(UserCrypto.DEK_LENGTH);
|
||||||
const systemKey = this.deriveOIDCSystemKey(userId);
|
const systemKey = await this.deriveOIDCSystemKey(userId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const encryptedDEK = this.encryptDEK(DEK, systemKey);
|
const encryptedDEK = this.encryptDEK(DEK, systemKey);
|
||||||
@@ -172,9 +176,12 @@ class UserCrypto {
|
|||||||
const oidcEncryptedDEK = await this.getOIDCEncryptedDEK(userId);
|
const oidcEncryptedDEK = await this.getOIDCEncryptedDEK(userId);
|
||||||
|
|
||||||
if (oidcEncryptedDEK) {
|
if (oidcEncryptedDEK) {
|
||||||
const systemKey = this.deriveOIDCSystemKey(userId);
|
const DEK = await this.decryptSystemWrappedDEK(
|
||||||
const DEK = this.decryptDEK(oidcEncryptedDEK, systemKey);
|
oidcEncryptedDEK,
|
||||||
systemKey.fill(0);
|
userId,
|
||||||
|
"oidc",
|
||||||
|
`user_encrypted_dek_oidc_${userId}`,
|
||||||
|
);
|
||||||
|
|
||||||
if (!DEK || DEK.length === 0) {
|
if (!DEK || DEK.length === 0) {
|
||||||
databaseLogger.error(
|
databaseLogger.error(
|
||||||
@@ -210,9 +217,12 @@ class UserCrypto {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const systemKey = this.deriveOIDCSystemKey(userId);
|
const DEK = await this.decryptSystemWrappedDEK(
|
||||||
const DEK = this.decryptDEK(encryptedDEK, systemKey);
|
encryptedDEK,
|
||||||
systemKey.fill(0);
|
userId,
|
||||||
|
"oidc",
|
||||||
|
`user_encrypted_dek_${userId}`,
|
||||||
|
);
|
||||||
|
|
||||||
if (!DEK || DEK.length === 0) {
|
if (!DEK || DEK.length === 0) {
|
||||||
await this.setupOIDCUserEncryption(userId, sessionDurationMs);
|
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);
|
const encryptedDEK = this.encryptDEK(existingDEK, systemKey);
|
||||||
systemKey.fill(0);
|
systemKey.fill(0);
|
||||||
|
|
||||||
@@ -271,9 +281,12 @@ class UserCrypto {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const systemKey = this.deriveWebAuthnSystemKey(userId);
|
const DEK = await this.decryptSystemWrappedDEK(
|
||||||
const DEK = this.decryptDEK(encryptedDEK, systemKey);
|
encryptedDEK,
|
||||||
systemKey.fill(0);
|
userId,
|
||||||
|
"webauthn",
|
||||||
|
`user_encrypted_dek_webauthn_${userId}`,
|
||||||
|
);
|
||||||
|
|
||||||
if (!DEK || DEK.length === 0) {
|
if (!DEK || DEK.length === 0) {
|
||||||
return false;
|
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);
|
const oidcEncryptedDEK = this.encryptDEK(existingDEK, systemKey);
|
||||||
systemKey.fill(0);
|
systemKey.fill(0);
|
||||||
|
|
||||||
@@ -556,12 +569,68 @@ class UserCrypto {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private deriveOIDCSystemKey(userId: string): Buffer {
|
private async deriveOIDCSystemKey(userId: string): Promise<Buffer> {
|
||||||
const systemSecret =
|
if (process.env.OIDC_SYSTEM_SECRET) {
|
||||||
process.env.OIDC_SYSTEM_SECRET || "termix-oidc-system-secret-default";
|
|
||||||
const salt = Buffer.from(userId, "utf8");
|
|
||||||
return crypto.pbkdf2Sync(
|
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,
|
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(
|
||||||
|
secret,
|
||||||
salt,
|
salt,
|
||||||
100000,
|
100000,
|
||||||
UserCrypto.KEK_LENGTH,
|
UserCrypto.KEK_LENGTH,
|
||||||
@@ -569,19 +638,39 @@ class UserCrypto {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private deriveWebAuthnSystemKey(userId: string): Buffer {
|
private async decryptSystemWrappedDEK(
|
||||||
const systemSecret =
|
encryptedDEK: EncryptedDEK,
|
||||||
process.env.WEBAUTHN_SYSTEM_SECRET ||
|
userId: string,
|
||||||
process.env.OIDC_SYSTEM_SECRET ||
|
type: "oidc" | "webauthn",
|
||||||
"termix-webauthn-system-secret-default";
|
settingKey: string,
|
||||||
const salt = Buffer.from(`webauthn:${userId}`, "utf8");
|
): Promise<Buffer> {
|
||||||
return crypto.pbkdf2Sync(
|
const systemKey =
|
||||||
systemSecret,
|
type === "oidc"
|
||||||
salt,
|
? await this.deriveOIDCSystemKey(userId)
|
||||||
100000,
|
: await this.deriveWebAuthnSystemKey(userId);
|
||||||
UserCrypto.KEK_LENGTH,
|
try {
|
||||||
"sha256",
|
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 {
|
private encryptDEK(dek: Buffer, kek: Buffer): EncryptedDEK {
|
||||||
|
|||||||
Reference in New Issue
Block a user