Skip to content

Commit 39e8812

Browse files
committed
refactor!: restructure multi-key support with Encryption wrapper class
1 parent f3fbdfc commit 39e8812

19 files changed

Lines changed: 462 additions & 271 deletions

factories/encryption.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* @boringnode/encryption
3+
*
4+
* @license MIT
5+
* @copyright Boring Node
6+
*/
7+
8+
import { Encryption, type EncryptionConfig } from '../src/encryption.ts'
9+
import { chacha20poly1305 } from '../src/drivers/chacha20_poly1305.ts'
10+
11+
export class EncryptionFactory {
12+
#config: EncryptionConfig
13+
14+
constructor(config?: EncryptionConfig) {
15+
this.#config =
16+
config ||
17+
chacha20poly1305({
18+
id: 'nova',
19+
keys: ['averylongradom32charactersstring'],
20+
})
21+
}
22+
23+
create() {
24+
return new Encryption(this.#config)
25+
}
26+
}

factories/encryption_manager.ts

Lines changed: 0 additions & 42 deletions
This file was deleted.

factories/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
* @copyright Boring Node
66
*/
77

8-
export { EncryptionManagerFactory } from './encryption_manager.ts'
8+
export { EncryptionFactory } from './encryption.ts'

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
".": "./build/index.js",
1515
"./drivers/*": "./build/src/drivers/*.js",
1616
"./factories": "./build/factories/main.js",
17+
"./message_verifier": "./build/src/message_verifier.js",
18+
"./base64": "./build/src/base64.js",
1719
"./types": "./build/src/types/main.js"
1820
},
1921
"scripts": {

src/drivers/aes_256_cbc.ts

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,18 @@ import * as errors from '../exceptions.ts'
1313
import type { AES256CBCConfig, CypherText, EncryptionDriverContract } from '../types/main.ts'
1414
import { base64UrlDecode, base64UrlEncode } from '../base64.ts'
1515

16+
export interface AES256CBCDriverConfig {
17+
id: string
18+
keys: string[]
19+
}
20+
21+
export function aes256cbc(config: AES256CBCDriverConfig) {
22+
return {
23+
driver: (key: string) => new AES256CBC({ id: config.id, key }),
24+
keys: config.keys,
25+
}
26+
}
27+
1628
export class AES256CBC extends BaseDriver implements EncryptionDriverContract {
1729
#config: AES256CBCConfig
1830

@@ -46,7 +58,7 @@ export class AES256CBC extends BaseDriver implements EncryptionDriverContract {
4658
*/
4759
const iv = randomBytes(16)
4860

49-
const { encryptionKey, authenticationKey } = this.#deriveKey(this.getFirstKey().key, iv)
61+
const { encryptionKey, authenticationKey } = this.#deriveKey(this.cryptoKey, iv)
5062

5163
/**
5264
* Creating chiper
@@ -121,30 +133,28 @@ export class AES256CBC extends BaseDriver implements EncryptionDriverContract {
121133
* Make sure the hash is correct, it means the first 2 parts of the
122134
* string are not tampered.
123135
*/
124-
for (const { key } of this.cryptoKeys) {
125-
const { encryptionKey, authenticationKey } = this.#deriveKey(key, iv)
126-
127-
const isValidHmac = new Hmac(authenticationKey).compare(
128-
`${cipherEncoded}${this.separator}${ivEncoded}`,
129-
macEncoded
130-
)
131-
132-
if (!isValidHmac) {
133-
continue
134-
}
135-
136-
/**
137-
* The Decipher can raise exceptions with malformed input, so we wrap it
138-
* to avoid leaking sensitive information
139-
*/
140-
try {
141-
const decipher = createDecipheriv('aes-256-cbc', encryptionKey, iv)
142-
const plainTextBuffer = Buffer.concat([decipher.update(cipherText), decipher.final()])
143-
return new MessageBuilder().verify(plainTextBuffer, purpose)
144-
} catch {}
136+
const { encryptionKey, authenticationKey } = this.#deriveKey(this.cryptoKey, iv)
137+
138+
const isValidHmac = new Hmac(authenticationKey).compare(
139+
`${cipherEncoded}${this.separator}${ivEncoded}`,
140+
macEncoded
141+
)
142+
143+
if (!isValidHmac) {
144+
return null
145145
}
146146

147-
return null
147+
/**
148+
* The Decipher can raise exceptions with malformed input, so we wrap it
149+
* to avoid leaking sensitive information
150+
*/
151+
try {
152+
const decipher = createDecipheriv('aes-256-cbc', encryptionKey, iv)
153+
const plainTextBuffer = Buffer.concat([decipher.update(cipherText), decipher.final()])
154+
return new MessageBuilder().verify(plainTextBuffer, purpose)
155+
} catch {
156+
return null
157+
}
148158
}
149159

150160
#deriveKey(masterKey: Buffer, iv: Buffer) {

src/drivers/aes_256_gcm.ts

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,18 @@ import * as errors from '../exceptions.ts'
1212
import { base64UrlDecode, base64UrlEncode } from '../base64.ts'
1313
import type { AES256GCMConfig, CypherText, EncryptionDriverContract } from '../types/main.ts'
1414

15+
export interface AES256GCMDriverConfig {
16+
id: string
17+
keys: string[]
18+
}
19+
20+
export function aes256gcm(config: AES256GCMDriverConfig) {
21+
return {
22+
driver: (key: string) => new AES256GCM({ id: config.id, key }),
23+
keys: config.keys,
24+
}
25+
}
26+
1527
export class AES256GCM extends BaseDriver implements EncryptionDriverContract {
1628
#config: AES256GCMConfig
1729

@@ -48,7 +60,7 @@ export class AES256GCM extends BaseDriver implements EncryptionDriverContract {
4860
/**
4961
* Creating chiper
5062
*/
51-
const cipher = createCipheriv('aes-256-gcm', this.getFirstKey().key, iv)
63+
const cipher = createCipheriv('aes-256-gcm', this.cryptoKey, iv)
5264

5365
if (purpose) {
5466
cipher.setAAD(Buffer.from(purpose), { plaintextLength: Buffer.byteLength(purpose) })
@@ -122,25 +134,23 @@ export class AES256GCM extends BaseDriver implements EncryptionDriverContract {
122134
return null
123135
}
124136

125-
for (const { key } of this.cryptoKeys) {
126-
/**
127-
* The Decipher can raise exceptions with malformed input, so we wrap it
128-
* to avoid leaking sensitive information
129-
*/
130-
try {
131-
const decipher = createDecipheriv('aes-256-gcm', key, iv)
137+
/**
138+
* The Decipher can raise exceptions with malformed input, so we wrap it
139+
* to avoid leaking sensitive information
140+
*/
141+
try {
142+
const decipher = createDecipheriv('aes-256-gcm', this.cryptoKey, iv)
132143

133-
if (purpose) {
134-
decipher.setAAD(Buffer.from(purpose), { plaintextLength: Buffer.byteLength(purpose) })
135-
}
144+
if (purpose) {
145+
decipher.setAAD(Buffer.from(purpose), { plaintextLength: Buffer.byteLength(purpose) })
146+
}
136147

137-
decipher.setAuthTag(tag)
148+
decipher.setAuthTag(tag)
138149

139-
const plain = Buffer.concat([decipher.update(cipherText), decipher.final()])
140-
return new MessageBuilder().verify(plain)
141-
} catch {}
150+
const plain = Buffer.concat([decipher.update(cipherText), decipher.final()])
151+
return new MessageBuilder().verify(plain)
152+
} catch {
153+
return null
142154
}
143-
144-
return null
145155
}
146156
}

src/drivers/base_driver.ts

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@
77

88
import { createHash } from 'node:crypto'
99
import * as errors from '../exceptions.ts'
10-
import { MessageVerifier } from '../message_verifier.ts'
1110
import type { BaseConfig, CypherText } from '../types/main.ts'
1211

1312
export abstract class BaseDriver {
1413
/**
15-
* The key for signing and encrypting values. It is derived
14+
* The key for encrypting values. It is derived
1615
* from the user provided secret.
1716
*/
18-
cryptoKeys = new Set<{ key: Buffer; verifier: MessageVerifier }>()
17+
cryptoKey: Buffer
1918

2019
/**
2120
* Use `dot` as a separator for joining encrypted value, iv and the
@@ -24,16 +23,8 @@ export abstract class BaseDriver {
2423
separator = '.'
2524

2625
protected constructor(config: BaseConfig) {
27-
if (!config.keys || !Array.isArray(config.keys)) {
28-
throw new errors.E_MISSING_ENCRYPTER_KEY()
29-
}
30-
31-
for (const key of config.keys) {
32-
this.#validateSecret(key)
33-
34-
const cryptoKey = createHash('sha256').update(key).digest()
35-
this.cryptoKeys.add({ key: cryptoKey, verifier: new MessageVerifier(key) })
36-
}
26+
this.#validateSecret(config.key)
27+
this.cryptoKey = createHash('sha256').update(config.key).digest()
3728
}
3829

3930
/**
@@ -53,15 +44,6 @@ export abstract class BaseDriver {
5344
return values.join(this.separator) as CypherText
5445
}
5546

56-
protected getFirstKey() {
57-
const [firstKey] = this.cryptoKeys
58-
return firstKey
59-
}
60-
61-
getMessageVerifier() {
62-
return this.getFirstKey().verifier
63-
}
64-
6547
/**
6648
* Encrypt a given piece of value using the app secret. A wide range of
6749
* data types are supported.

src/drivers/chacha20_poly1305.ts

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,18 @@ import * as errors from '../exceptions.ts'
1212
import type { ChaCha20Poly1305Config, CypherText, EncryptionDriverContract } from '../types/main.ts'
1313
import { base64UrlDecode, base64UrlEncode } from '../base64.ts'
1414

15+
export interface ChaCha20Poly1305DriverConfig {
16+
id: string
17+
keys: string[]
18+
}
19+
20+
export function chacha20poly1305(config: ChaCha20Poly1305DriverConfig) {
21+
return {
22+
driver: (key: string) => new ChaCha20Poly1305({ id: config.id, key }),
23+
keys: config.keys,
24+
}
25+
}
26+
1527
export class ChaCha20Poly1305 extends BaseDriver implements EncryptionDriverContract {
1628
#config: ChaCha20Poly1305Config
1729

@@ -48,7 +60,7 @@ export class ChaCha20Poly1305 extends BaseDriver implements EncryptionDriverCont
4860
/**
4961
* Creating cipher
5062
*/
51-
const cipher = createCipheriv('chacha20-poly1305', this.getFirstKey().key, iv, {
63+
const cipher = createCipheriv('chacha20-poly1305', this.cryptoKey, iv, {
5264
authTagLength: 16,
5365
})
5466

@@ -124,27 +136,25 @@ export class ChaCha20Poly1305 extends BaseDriver implements EncryptionDriverCont
124136
return null
125137
}
126138

127-
for (const { key } of this.cryptoKeys) {
128-
/**
129-
* The Decipher can raise exceptions with malformed input, so we wrap it
130-
* to avoid leaking sensitive information
131-
*/
132-
try {
133-
const decipher = createDecipheriv('chacha20-poly1305', key, iv, {
134-
authTagLength: 16,
135-
})
136-
137-
if (purpose) {
138-
decipher.setAAD(Buffer.from(purpose), { plaintextLength: Buffer.byteLength(purpose) })
139-
}
140-
141-
decipher.setAuthTag(tag)
142-
143-
const plain = Buffer.concat([decipher.update(cipherText), decipher.final()])
144-
return new MessageBuilder().verify(plain)
145-
} catch {}
146-
}
139+
/**
140+
* The Decipher can raise exceptions with malformed input, so we wrap it
141+
* to avoid leaking sensitive information
142+
*/
143+
try {
144+
const decipher = createDecipheriv('chacha20-poly1305', this.cryptoKey, iv, {
145+
authTagLength: 16,
146+
})
147+
148+
if (purpose) {
149+
decipher.setAAD(Buffer.from(purpose), { plaintextLength: Buffer.byteLength(purpose) })
150+
}
147151

148-
return null
152+
decipher.setAuthTag(tag)
153+
154+
const plain = Buffer.concat([decipher.update(cipherText), decipher.final()])
155+
return new MessageBuilder().verify(plain)
156+
} catch {
157+
return null
158+
}
149159
}
150160
}

0 commit comments

Comments
 (0)