MEK 관련 DB 스키마 생성

This commit is contained in:
static
2024-12-29 18:01:02 +09:00
parent 46938ef700
commit bbba449819
5 changed files with 76 additions and 11 deletions

View File

@@ -47,7 +47,7 @@ export const setUserClientStateToPending = async (userId: number, clientId: numb
export const createUserClientChallenge = async ( export const createUserClientChallenge = async (
userId: number, userId: number,
clientId: number, clientId: number,
challenge: string, answer: string,
allowedIp: string, allowedIp: string,
expiresAt: Date, expiresAt: Date,
) => { ) => {
@@ -56,20 +56,20 @@ export const createUserClientChallenge = async (
.values({ .values({
userId, userId,
clientId, clientId,
challenge, answer,
allowedIp, allowedIp,
expiresAt, expiresAt,
}) })
.execute(); .execute();
}; };
export const getUserClientChallenge = async (challenge: string, ip: string) => { export const getUserClientChallenge = async (answer: string, ip: string) => {
const challenges = await db const challenges = await db
.select() .select()
.from(userClientChallenge) .from(userClientChallenge)
.where( .where(
and( and(
eq(userClientChallenge.challenge, challenge), eq(userClientChallenge.answer, answer),
eq(userClientChallenge.allowedIp, ip), eq(userClientChallenge.allowedIp, ip),
gt(userClientChallenge.expiresAt, new Date()), gt(userClientChallenge.expiresAt, new Date()),
), ),

View File

@@ -18,7 +18,6 @@ export const userClient = sqliteTable(
state: text("state", { enum: ["challenging", "pending", "active"] }) state: text("state", { enum: ["challenging", "pending", "active"] })
.notNull() .notNull()
.default("challenging"), .default("challenging"),
encKey: text("encrypted_key"),
}, },
(t) => ({ (t) => ({
pk: primaryKey({ columns: [t.userId, t.clientId] }), pk: primaryKey({ columns: [t.userId, t.clientId] }),
@@ -33,7 +32,7 @@ export const userClientChallenge = sqliteTable("user_client_challenge", {
clientId: integer("client_id") clientId: integer("client_id")
.notNull() .notNull()
.references(() => client.id), .references(() => client.id),
challenge: text("challenge").notNull().unique(), // Base64 answer: text("challenge").notNull().unique(), // Base64
allowedIp: text("allowed_ip").notNull(), allowedIp: text("allowed_ip").notNull(),
expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(), expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
}); });

View File

@@ -1,3 +1,4 @@
export * from "./client"; export * from "./client";
export * from "./mek";
export * from "./token"; export * from "./token";
export * from "./user"; export * from "./user";

View File

@@ -0,0 +1,65 @@
import { sqliteTable, text, integer, primaryKey, foreignKey } from "drizzle-orm/sqlite-core";
import { client } from "./client";
import { user } from "./user";
export const mek = sqliteTable(
"master_encryption_key",
{
userId: integer("user_id")
.notNull()
.references(() => user.id),
version: integer("version").notNull(),
createdBy: integer("created_by")
.notNull()
.references(() => client.id),
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
state: text("state", { enum: ["pending", "active", "retired", "dead"] })
.notNull()
.default("pending"),
},
(t) => ({
pk: primaryKey({ columns: [t.userId, t.version] }),
}),
);
export const clientMek = sqliteTable(
"client_master_encryption_key",
{
userId: integer("user_id")
.notNull()
.references(() => user.id),
clientId: integer("client_id")
.notNull()
.references(() => client.id),
mekVersion: integer("master_encryption_key_version").notNull(),
encMek: text("encrypted_master_encryption_key").notNull(),
},
(t) => ({
pk: primaryKey({ columns: [t.userId, t.clientId, t.mekVersion] }),
ref: foreignKey({
columns: [t.userId, t.mekVersion],
foreignColumns: [mek.userId, mek.version],
}),
}),
);
export const mekChallenge = sqliteTable(
"master_encryption_key_challenge",
{
userId: integer("user_id")
.notNull()
.references(() => user.id),
mekVersion: integer("master_encryption_key_version").notNull(),
answer: text("answer").notNull().unique(), // Base64
challenge: text("challenge").unique(), // Base64
allowedIp: text("allowed_ip").notNull(),
expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
},
(t) => ({
pk: primaryKey({ columns: [t.userId, t.mekVersion] }),
ref: foreignKey({
columns: [t.userId, t.mekVersion],
foreignColumns: [mek.userId, mek.version],
}),
}),
);

View File

@@ -17,13 +17,13 @@ const expiresIn = ms(env.challenge.pubKeyExp);
const expiresAt = () => new Date(Date.now() + expiresIn); const expiresAt = () => new Date(Date.now() + expiresIn);
const generateChallenge = async (userId: number, ip: string, clientId: number, pubKey: string) => { const generateChallenge = async (userId: number, ip: string, clientId: number, pubKey: string) => {
const challenge = await promisify(randomBytes)(32); const answer = await promisify(randomBytes)(32);
const challengeBase64 = challenge.toString("base64"); const answerBase64 = answer.toString("base64");
await createUserClientChallenge(userId, clientId, challengeBase64, ip, expiresAt()); await createUserClientChallenge(userId, clientId, answerBase64, ip, expiresAt());
const pubKeyPem = `-----BEGIN PUBLIC KEY-----\n${pubKey}\n-----END PUBLIC KEY-----`; const pubKeyPem = `-----BEGIN PUBLIC KEY-----\n${pubKey}\n-----END PUBLIC KEY-----`;
const challengeEncrypted = publicEncrypt({ key: pubKeyPem, oaepHash: "sha256" }, challenge); const challenge = publicEncrypt({ key: pubKeyPem, oaepHash: "sha256" }, answer);
return challengeEncrypted.toString("base64"); return challenge.toString("base64");
}; };
export const registerPubKey = async (userId: number, ip: string, pubKey: string) => { export const registerPubKey = async (userId: number, ip: string, pubKey: string) => {