Skip to content

Commit

Permalink
Add user api encryption logic
Browse files Browse the repository at this point in the history
  • Loading branch information
klydra committed Jun 29, 2024
1 parent 6e708b9 commit ae6546d
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 21 deletions.
31 changes: 28 additions & 3 deletions api/elysia/routes/users.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { Elysia, t } from "elysia";
import { userCount, userCreate, userGet } from "api/drizzle/queries.ts";
import {
userCount,
userCreate,
userDelete,
userGet,
} from "api/drizzle/queries.ts";
import { UserUsername } from "api/drizzle/schema.ts";
import { SECRET_LOCK_DERIVED_LENGTH } from "encryption/src/secret-lock.ts";
import SecretKey, { SECRET_KEY_LENGTH } from "encryption/src/secret-key.ts";

export default new Elysia({ prefix: "/users" })
.get("/", async ({ error }) => {
Expand Down Expand Up @@ -52,22 +59,40 @@ export default new Elysia({ prefix: "/users" })
{
body: t.Object({
username: t.String({ format: "regex", regex: UserUsername }),
secretKey: t.String(),
secretKey: t.String({
minLength: SECRET_KEY_LENGTH,
maxLength: SECRET_KEY_LENGTH,
}),
}),
},
)
.post(
"/delete",
async ({ body: { username }, error }) => {
async ({ body: { username, derived }, error }) => {
const user = await userGet(username);

if (!user) return error(500, "Could not retrieve user");

if (user.length !== 1) return error(404, "User does not exist");

const secretKey = SecretKey.fromString(user[0].secretKey);

if (!secretKey.proof(derived)) return error(403, "Authentication failed");

const deleteUser = await userDelete(username);

if (!deleteUser) return error(500, "Failed to delete user");

if (deleteUser.rowsAffected !== 1)
return error(500, "Could not delete user");
},
{
body: t.Object({
username: t.String({ format: "regex", regex: UserUsername }),
derived: t.String({
minLength: SECRET_LOCK_DERIVED_LENGTH,
maxLength: SECRET_LOCK_DERIVED_LENGTH,
}),
}),
},
);
3 changes: 0 additions & 3 deletions encryption/index.ts

This file was deleted.

1 change: 0 additions & 1 deletion encryption/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"name": "encryption",
"module": "index.ts",
"type": "module",
"peerDependencies": {
"typescript": "^5.5.2"
Expand Down
10 changes: 6 additions & 4 deletions encryption/src/note.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import crypto from "crypto";
import type SecretKey from "./secret-key";
import type SecretLock from "./secret-lock";
import type SecretKey from "./secret-key.ts";
import type SecretLock from "./secret-lock.ts";

export const NOTE_IV_LENGTH = 32;

export default class Note {
iv: string;
Expand All @@ -12,7 +14,7 @@ export default class Note {
}

static fromString(note: string): Note {
return new Note(note.slice(0, 32), note.slice(32));
return new Note(note.slice(0, NOTE_IV_LENGTH), note.slice(NOTE_IV_LENGTH));
}

toString(): string {
Expand All @@ -24,7 +26,7 @@ export default class Note {
secretKey: SecretKey,
lock: SecretLock,
): Note {
const iv = crypto.randomBytes(32).toString("hex");
const iv = crypto.randomBytes(NOTE_IV_LENGTH).toString("hex");
const cipher = crypto.createCipheriv(
"aes-256-cbc",
secretKey.decrypt(lock),
Expand Down
56 changes: 49 additions & 7 deletions encryption/src/secret-key.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,61 @@
import crypto from "crypto";
import type SecretLock from "./secret-lock";
import type SecretLock from "./secret-lock.ts";

export const SECRET_KEY_IV_LENGTH = 32;
export const SECRET_KEY_PASSPORT_LENGTH = 32;
export const SECRET_KEY_PASSPORT_ITERATIONS = 100000;
export const SECRET_KEY_BLOB_LENGTH = 32;
export const SECRET_KEY_LENGTH =
SECRET_KEY_IV_LENGTH + SECRET_KEY_PASSPORT_LENGTH + SECRET_KEY_BLOB_LENGTH;

export default class SecretKey {
iv: string;
passport: string;
blob: string;

constructor(iv: string, blob: string) {
constructor(iv: string, passport: string, blob: string) {
this.iv = iv;
this.passport = passport;
this.blob = blob;
}

static fromString(note: string): SecretKey {
return new SecretKey(note.slice(0, 32), note.slice(32));
return new SecretKey(
note.slice(0, SECRET_KEY_IV_LENGTH),
note.slice(
SECRET_KEY_IV_LENGTH,
SECRET_KEY_IV_LENGTH + SECRET_KEY_PASSPORT_LENGTH,
),
note.slice(SECRET_KEY_IV_LENGTH + SECRET_KEY_PASSPORT_LENGTH),
);
}

toString(): string {
return this.iv + this.blob;
return this.iv + this.passport + this.blob;
}

static withLock(lock: SecretLock): SecretKey {
const iv = crypto.randomBytes(32).toString("hex");
const iv = crypto.randomBytes(SECRET_KEY_IV_LENGTH).toString("hex");
const cipher = crypto.createCipheriv("aes-256-cbc", lock.derive(), iv);

const secretKey = crypto.randomBytes(32).toString("hex");
const secretKey = crypto
.randomBytes(SECRET_KEY_BLOB_LENGTH)
.toString("hex");

let encrypted = cipher.update(secretKey, "utf8", "hex");
encrypted += cipher.final("hex");

return new SecretKey(iv, encrypted);
const passport = crypto
.pbkdf2Sync(
encrypted,
lock.derive(),
SECRET_KEY_PASSPORT_ITERATIONS,
SECRET_KEY_PASSPORT_LENGTH,
"sha512",
)
.toString("hex");

return new SecretKey(iv, passport, encrypted);
}

decrypt(lock: SecretLock): string {
Expand All @@ -42,4 +70,18 @@ export default class SecretKey {

return decrypted;
}

proof(derived: string): boolean {
const expected = crypto
.pbkdf2Sync(
this.blob,
derived,
SECRET_KEY_PASSPORT_ITERATIONS,
SECRET_KEY_PASSPORT_LENGTH,
"sha512",
)
.toString("hex");

return expected === this.passport;
}
}
14 changes: 12 additions & 2 deletions encryption/src/secret-lock.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import crypto from "crypto";

export const SECRET_LOCK_NC_LENGTH = 32;
export const SECRET_LOCK_DERIVED_LENGTH = 32;
export const SECRET_LOCK_NC_ITERATIONS = 100000;

export default class SecretLock {
nc: string;
password: string;
Expand All @@ -10,13 +14,19 @@ export default class SecretLock {
}

static withPassword(password: string): SecretLock {
const nc = crypto.randomBytes(32).toString("hex");
const nc = crypto.randomBytes(SECRET_LOCK_NC_LENGTH).toString("hex");
return new SecretLock(nc, password);
}

derive(): string {
return crypto
.pbkdf2Sync(this.password, this.nc, 1000, 32, "sha512")
.pbkdf2Sync(
this.password,
this.nc,
SECRET_LOCK_NC_ITERATIONS,
SECRET_LOCK_DERIVED_LENGTH,
"sha512",
)
.toString("hex");
}
}
2 changes: 1 addition & 1 deletion encryption/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": false,
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,

Expand Down

0 comments on commit ae6546d

Please sign in to comment.