Skip to content

Commit

Permalink
Refactor to allow encrypting with metadata (such as salt) in the future
Browse files Browse the repository at this point in the history
  • Loading branch information
jpwilliams committed Jul 15, 2024
1 parent d9436d1 commit 34ec059
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 36 deletions.
40 changes: 37 additions & 3 deletions packages/middleware-encryption/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,48 @@ export abstract class EncryptionService {
public abstract identifier: string;

/**
* Given an `unknown` value, encrypts it and returns the encrypted value as a
* `string`.
* Given an `unknown` value, encrypts it and returns the the encrypted value.
*/
public abstract encrypt(value: unknown): MaybePromise<string>;
public abstract encrypt(
value: unknown
): MaybePromise<EncryptionService.PartialEncryptedValue>;

/**
* Given an encrypted `string`, decrypts it and returns the decrypted value as
* any value.
*/
public abstract decrypt(value: string): MaybePromise<unknown>;
}

export namespace EncryptionService {
/**
* A marker used to identify encrypted values without having to guess.
*/
export const ENCRYPTION_MARKER = "__ENCRYPTED__";

/**
* A marker used to identify the strategy used for encryption.
*/
export const STRATEGY_MARKER = "__STRATEGY__";

/**
* The encrypted value as it will be sent to Inngest.
*/
export interface EncryptedValue {
[ENCRYPTION_MARKER]: true;
[STRATEGY_MARKER]: string | undefined;
data: string;
}

/**
* A partial encrypted value, allowing an encryption service to specify the
* data and any other metadata needed to decrypt the value.
*/
export interface PartialEncryptedValue
extends Omit<
EncryptedValue,
typeof ENCRYPTION_MARKER | typeof STRATEGY_MARKER
> {
[key: string]: unknown;
}
}
47 changes: 18 additions & 29 deletions packages/middleware-encryption/src/stages.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type MiddlewareRegisterReturn } from "inngest";
import { type EncryptionService } from "./middleware";
import { EncryptionService } from "./middleware";
import { LEGACY_V0Service } from "./strategies/legacy";
import { LibSodiumEncryptionService } from "./strategies/libSodium";

Expand Down Expand Up @@ -64,11 +64,13 @@ export const getEncryptionStages = (
...opts.legacyV0Service,
});

const encryptValue = async (value: unknown): Promise<EncryptedValue> => {
const encryptValue = async (
value: unknown
): Promise<EncryptionService.EncryptedValue> => {
return {
[ENCRYPTION_MARKER]: true,
[STRATEGY_MARKER]: service.identifier,
data: await service.encrypt(value),
[EncryptionService.ENCRYPTION_MARKER]: true,
[EncryptionService.STRATEGY_MARKER]: service.identifier,
...(await service.encrypt(value)),
};
};

Expand Down Expand Up @@ -103,9 +105,11 @@ export const getEncryptionStages = (
): Promise<unknown> => {
// if the entire value is encrypted, match it and decrypt
if (isEncryptedValue(data)) {
if (service.identifier !== data[STRATEGY_MARKER]) {
if (service.identifier !== data[EncryptionService.STRATEGY_MARKER]) {
throw new Error(
`Mismatched encryption service; received an event payload using "${data[STRATEGY_MARKER]}", but the configured encryption service is "${service.identifier}"`
`Mismatched encryption service; received an event payload using "${
data[EncryptionService.STRATEGY_MARKER]
}", but the configured encryption service is "${service.identifier}"`
);
}

Expand Down Expand Up @@ -198,14 +202,6 @@ export const getEncryptionStages = (
},
};
};
/**
* The encrypted value as it will be sent to Inngest.
*/
export interface EncryptedValue {
[ENCRYPTION_MARKER]: true;
[STRATEGY_MARKER]: string | undefined;
data: string;
}

type InputTransformer = NonNullable<
Awaited<
Expand All @@ -223,24 +219,17 @@ type FunctionRunHook = NonNullable<MiddlewareRegisterReturn["onFunctionRun"]>;

type SendEventHook = NonNullable<MiddlewareRegisterReturn["onSendEvent"]>;

export const isEncryptedValue = (value: unknown): value is EncryptedValue => {
export const isEncryptedValue = (
value: unknown
): value is EncryptionService.EncryptedValue => {
return (
typeof value === "object" &&
value !== null &&
ENCRYPTION_MARKER in value &&
value[ENCRYPTION_MARKER] === true &&
EncryptionService.ENCRYPTION_MARKER in value &&
value[EncryptionService.ENCRYPTION_MARKER] === true &&
"data" in value &&
typeof value["data"] === "string" &&
(!(STRATEGY_MARKER in value) || typeof value[STRATEGY_MARKER] === "string")
(!(EncryptionService.STRATEGY_MARKER in value) ||
typeof value[EncryptionService.STRATEGY_MARKER] === "string")
);
};

/**
* A marker used to identify encrypted values without having to guess.
*/
const ENCRYPTION_MARKER = "__ENCRYPTED__";

/**
* A marker used to identify the strategy used for encryption.
*/
const STRATEGY_MARKER = "__STRATEGY__";
6 changes: 4 additions & 2 deletions packages/middleware-encryption/src/strategies/aes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ export class AESEncryptionService implements EncryptionService {
this.keys = keys as [string, ...string[]];
}

encrypt(value: unknown): string {
return AES.encrypt(JSON.stringify(value), this.keys[0]).toString();
encrypt(value: unknown): EncryptionService.PartialEncryptedValue {
return {
data: AES.encrypt(JSON.stringify(value), this.keys[0]).toString(),
};
}

decrypt(value: string): unknown {
Expand Down
8 changes: 6 additions & 2 deletions packages/middleware-encryption/src/strategies/libSodium.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ export class LibSodiumEncryptionService implements EncryptionService {
});
}

async encrypt(value: unknown): Promise<string> {
async encrypt(
value: unknown
): Promise<EncryptionService.PartialEncryptedValue> {
const keys = await this.keys;

const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
Expand All @@ -53,7 +55,9 @@ export class LibSodiumEncryptionService implements EncryptionService {
combined.set(nonce);
combined.set(ciphertext, nonce.length);

return sodium.to_base64(combined, sodium.base64_variants.ORIGINAL);
return {
data: sodium.to_base64(combined, sodium.base64_variants.ORIGINAL),
};
}

async decrypt(value: string): Promise<unknown> {
Expand Down

0 comments on commit 34ec059

Please sign in to comment.