@vaharoni/devops 1.2.8 → 1.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app-support/crypto/aes.d.ts +15 -0
- package/dist/app-support/crypto/aes.d.ts.map +1 -0
- package/dist/app-support/crypto/aes.js +56 -0
- package/dist/app-support/crypto/aes.spec.d.ts +2 -0
- package/dist/app-support/crypto/aes.spec.d.ts.map +1 -0
- package/dist/app-support/crypto/aes.spec.js +58 -0
- package/dist/app-support/crypto/index.d.ts +1 -0
- package/dist/app-support/crypto/index.d.ts.map +1 -1
- package/dist/app-support/crypto/index.js +1 -0
- package/package.json +1 -1
- package/src/app-support/crypto/aes.spec.ts +66 -0
- package/src/app-support/crypto/aes.ts +66 -0
- package/src/app-support/crypto/index.ts +1 -0
- package/dist/test.d.ts +0 -2
- package/dist/test.d.ts.map +0 -1
- package/dist/test.js +0 -1
- package/src/test.ts +0 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encrypts a plaintext string using AES-256-GCM with the given key.
|
|
3
|
+
* @param plaintext
|
|
4
|
+
* @param key - The key to use for encryption. If not provided, the key will be derived from the MONOREPO_BASE_SECRET environment variable.
|
|
5
|
+
* @returns The encrypted ciphertext.
|
|
6
|
+
*/
|
|
7
|
+
export declare function encryptAes256Gcm(plaintext: string, key?: Buffer): string;
|
|
8
|
+
/**
|
|
9
|
+
* Decrypts a ciphertext string using AES-256-GCM with the given key.
|
|
10
|
+
* @param ciphertext - The ciphertext to decrypt.
|
|
11
|
+
* @param key - The key to use for decryption. If not provided, the key will be derived from the MONOREPO_BASE_SECRET environment variable.
|
|
12
|
+
* @returns The decrypted plaintext.
|
|
13
|
+
*/
|
|
14
|
+
export declare function decryptAes256Gcm(ciphertext: string, key?: Buffer): string;
|
|
15
|
+
//# sourceMappingURL=aes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aes.d.ts","sourceRoot":"","sources":["../../../src/app-support/crypto/aes.ts"],"names":[],"mappings":"AAaA;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAqBxE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAiBzE"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
const ALGORITHM = "aes-256-gcm";
|
|
3
|
+
const IV_LENGTH = 12; // GCM recommended IV length
|
|
4
|
+
const AUTH_TAG_LENGTH = 16;
|
|
5
|
+
function getKey() {
|
|
6
|
+
const keyStr = process.env.MONOREPO_BASE_SECRET;
|
|
7
|
+
if (!keyStr)
|
|
8
|
+
throw new Error("MONOREPO_BASE_SECRET not set");
|
|
9
|
+
// The secret is 32 random bytes stored as hex (64 chars) - decode it directly
|
|
10
|
+
return Buffer.from(keyStr, "hex");
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Encrypts a plaintext string using AES-256-GCM with the given key.
|
|
14
|
+
* @param plaintext
|
|
15
|
+
* @param key - The key to use for encryption. If not provided, the key will be derived from the MONOREPO_BASE_SECRET environment variable.
|
|
16
|
+
* @returns The encrypted ciphertext.
|
|
17
|
+
*/
|
|
18
|
+
export function encryptAes256Gcm(plaintext, key) {
|
|
19
|
+
key ??= getKey();
|
|
20
|
+
const iv = crypto.randomBytes(IV_LENGTH);
|
|
21
|
+
const cipher = crypto.createCipheriv(ALGORITHM, key, iv, {
|
|
22
|
+
authTagLength: AUTH_TAG_LENGTH,
|
|
23
|
+
});
|
|
24
|
+
const encrypted = Buffer.concat([
|
|
25
|
+
cipher.update(plaintext, "utf8"),
|
|
26
|
+
cipher.final(),
|
|
27
|
+
]);
|
|
28
|
+
const authTag = cipher.getAuthTag();
|
|
29
|
+
// Format: iv:authTag:ciphertext (all base64)
|
|
30
|
+
return [
|
|
31
|
+
iv.toString("base64"),
|
|
32
|
+
authTag.toString("base64"),
|
|
33
|
+
encrypted.toString("base64"),
|
|
34
|
+
].join(":");
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Decrypts a ciphertext string using AES-256-GCM with the given key.
|
|
38
|
+
* @param ciphertext - The ciphertext to decrypt.
|
|
39
|
+
* @param key - The key to use for decryption. If not provided, the key will be derived from the MONOREPO_BASE_SECRET environment variable.
|
|
40
|
+
* @returns The decrypted plaintext.
|
|
41
|
+
*/
|
|
42
|
+
export function decryptAes256Gcm(ciphertext, key) {
|
|
43
|
+
key ??= getKey();
|
|
44
|
+
const [ivB64, authTagB64, encryptedB64] = ciphertext.split(":");
|
|
45
|
+
const iv = Buffer.from(ivB64, "base64");
|
|
46
|
+
const authTag = Buffer.from(authTagB64, "base64");
|
|
47
|
+
const encrypted = Buffer.from(encryptedB64, "base64");
|
|
48
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv, {
|
|
49
|
+
authTagLength: AUTH_TAG_LENGTH,
|
|
50
|
+
});
|
|
51
|
+
decipher.setAuthTag(authTag);
|
|
52
|
+
return Buffer.concat([
|
|
53
|
+
decipher.update(encrypted),
|
|
54
|
+
decipher.final(),
|
|
55
|
+
]).toString("utf8");
|
|
56
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aes.spec.d.ts","sourceRoot":"","sources":["../../../src/app-support/crypto/aes.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { encryptAes256Gcm, decryptAes256Gcm } from './aes';
|
|
3
|
+
describe('AES-256-GCM encrypt and decrypt', () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
// AES-256 requires a 32-byte key (64 hex characters)
|
|
6
|
+
process.env.MONOREPO_BASE_SECRET = '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef';
|
|
7
|
+
});
|
|
8
|
+
it('should encrypt and decrypt a simple string', () => {
|
|
9
|
+
const plaintext = 'hello world';
|
|
10
|
+
const encrypted = encryptAes256Gcm(plaintext);
|
|
11
|
+
const decrypted = decryptAes256Gcm(encrypted);
|
|
12
|
+
expect(decrypted).toEqual(plaintext);
|
|
13
|
+
});
|
|
14
|
+
it('should encrypt and decrypt an empty string', () => {
|
|
15
|
+
const plaintext = '';
|
|
16
|
+
const encrypted = encryptAes256Gcm(plaintext);
|
|
17
|
+
const decrypted = decryptAes256Gcm(encrypted);
|
|
18
|
+
expect(decrypted).toEqual(plaintext);
|
|
19
|
+
});
|
|
20
|
+
it('should encrypt and decrypt unicode text', () => {
|
|
21
|
+
const plaintext = '你好世界 🌍 héllo';
|
|
22
|
+
const encrypted = encryptAes256Gcm(plaintext);
|
|
23
|
+
const decrypted = decryptAes256Gcm(encrypted);
|
|
24
|
+
expect(decrypted).toEqual(plaintext);
|
|
25
|
+
});
|
|
26
|
+
it('should produce different ciphertexts for the same plaintext (random IV)', () => {
|
|
27
|
+
const plaintext = 'same input';
|
|
28
|
+
const encrypted1 = encryptAes256Gcm(plaintext);
|
|
29
|
+
const encrypted2 = encryptAes256Gcm(plaintext);
|
|
30
|
+
expect(encrypted1).not.toEqual(encrypted2);
|
|
31
|
+
// But both should decrypt to the same value
|
|
32
|
+
expect(decryptAes256Gcm(encrypted1)).toEqual(plaintext);
|
|
33
|
+
expect(decryptAes256Gcm(encrypted2)).toEqual(plaintext);
|
|
34
|
+
});
|
|
35
|
+
it('should produce ciphertext in expected format (iv:authTag:ciphertext)', () => {
|
|
36
|
+
const encrypted = encryptAes256Gcm('test');
|
|
37
|
+
const parts = encrypted.split(':');
|
|
38
|
+
expect(parts).toHaveLength(3);
|
|
39
|
+
// All parts should be valid base64
|
|
40
|
+
parts.forEach((part) => {
|
|
41
|
+
expect(() => Buffer.from(part, 'base64')).not.toThrow();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
it('should throw on tampered ciphertext', () => {
|
|
45
|
+
const encrypted = encryptAes256Gcm('sensitive data');
|
|
46
|
+
const [iv, authTag, ciphertext] = encrypted.split(':');
|
|
47
|
+
const tamperedCiphertext = `${iv}:${authTag}:${ciphertext.slice(0, -1)}x`;
|
|
48
|
+
expect(() => decryptAes256Gcm(tamperedCiphertext)).toThrow();
|
|
49
|
+
});
|
|
50
|
+
it('should throw on tampered auth tag', () => {
|
|
51
|
+
const encrypted = encryptAes256Gcm('sensitive data');
|
|
52
|
+
const [iv, _authTag, ciphertext] = encrypted.split(':');
|
|
53
|
+
// Use a completely different auth tag (all zeros)
|
|
54
|
+
const fakeAuthTag = Buffer.alloc(16, 0).toString('base64');
|
|
55
|
+
const tamperedTag = `${iv}:${fakeAuthTag}:${ciphertext}`;
|
|
56
|
+
expect(() => decryptAes256Gcm(tamperedTag)).toThrow();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/app-support/crypto/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/app-support/crypto/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAE3D;;;;;;GAMG;AACH,qBAAa,aAAa;IACL,OAAO,EAAE,MAAM;gBAAf,OAAO,EAAE,MAAM;IAElC,QAAQ;IAIR,aAAa,CAAC,KAAK,EAAE,MAAM;IAO3B,uBAAuB,CAAC,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI;CAO5D"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { generateInternalAuthToken, parseInternalAuthTokenOrThrow } from "./internal-token";
|
|
2
|
+
export { encryptAes256Gcm, decryptAes256Gcm } from "./aes";
|
|
2
3
|
/**
|
|
3
4
|
* A simple token generation/verification class that relies on the subject field of an internal JWT-like token. It can:
|
|
4
5
|
* - generate a short-lived (60s) token with the given subject
|
package/package.json
CHANGED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { encryptAes256Gcm, decryptAes256Gcm } from './aes';
|
|
3
|
+
|
|
4
|
+
describe('AES-256-GCM encrypt and decrypt', () => {
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
// AES-256 requires a 32-byte key (64 hex characters)
|
|
7
|
+
process.env.MONOREPO_BASE_SECRET = '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef';
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should encrypt and decrypt a simple string', () => {
|
|
11
|
+
const plaintext = 'hello world';
|
|
12
|
+
const encrypted = encryptAes256Gcm(plaintext);
|
|
13
|
+
const decrypted = decryptAes256Gcm(encrypted);
|
|
14
|
+
expect(decrypted).toEqual(plaintext);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should encrypt and decrypt an empty string', () => {
|
|
18
|
+
const plaintext = '';
|
|
19
|
+
const encrypted = encryptAes256Gcm(plaintext);
|
|
20
|
+
const decrypted = decryptAes256Gcm(encrypted);
|
|
21
|
+
expect(decrypted).toEqual(plaintext);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should encrypt and decrypt unicode text', () => {
|
|
25
|
+
const plaintext = '你好世界 🌍 héllo';
|
|
26
|
+
const encrypted = encryptAes256Gcm(plaintext);
|
|
27
|
+
const decrypted = decryptAes256Gcm(encrypted);
|
|
28
|
+
expect(decrypted).toEqual(plaintext);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should produce different ciphertexts for the same plaintext (random IV)', () => {
|
|
32
|
+
const plaintext = 'same input';
|
|
33
|
+
const encrypted1 = encryptAes256Gcm(plaintext);
|
|
34
|
+
const encrypted2 = encryptAes256Gcm(plaintext);
|
|
35
|
+
expect(encrypted1).not.toEqual(encrypted2);
|
|
36
|
+
// But both should decrypt to the same value
|
|
37
|
+
expect(decryptAes256Gcm(encrypted1)).toEqual(plaintext);
|
|
38
|
+
expect(decryptAes256Gcm(encrypted2)).toEqual(plaintext);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should produce ciphertext in expected format (iv:authTag:ciphertext)', () => {
|
|
42
|
+
const encrypted = encryptAes256Gcm('test');
|
|
43
|
+
const parts = encrypted.split(':');
|
|
44
|
+
expect(parts).toHaveLength(3);
|
|
45
|
+
// All parts should be valid base64
|
|
46
|
+
parts.forEach((part) => {
|
|
47
|
+
expect(() => Buffer.from(part, 'base64')).not.toThrow();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should throw on tampered ciphertext', () => {
|
|
52
|
+
const encrypted = encryptAes256Gcm('sensitive data');
|
|
53
|
+
const [iv, authTag, ciphertext] = encrypted.split(':');
|
|
54
|
+
const tamperedCiphertext = `${iv}:${authTag}:${ciphertext.slice(0, -1)}x`;
|
|
55
|
+
expect(() => decryptAes256Gcm(tamperedCiphertext)).toThrow();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should throw on tampered auth tag', () => {
|
|
59
|
+
const encrypted = encryptAes256Gcm('sensitive data');
|
|
60
|
+
const [iv, _authTag, ciphertext] = encrypted.split(':');
|
|
61
|
+
// Use a completely different auth tag (all zeros)
|
|
62
|
+
const fakeAuthTag = Buffer.alloc(16, 0).toString('base64');
|
|
63
|
+
const tamperedTag = `${iv}:${fakeAuthTag}:${ciphertext}`;
|
|
64
|
+
expect(() => decryptAes256Gcm(tamperedTag)).toThrow();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
|
|
3
|
+
const ALGORITHM = "aes-256-gcm";
|
|
4
|
+
const IV_LENGTH = 12; // GCM recommended IV length
|
|
5
|
+
const AUTH_TAG_LENGTH = 16;
|
|
6
|
+
|
|
7
|
+
function getKey(): Buffer {
|
|
8
|
+
const keyStr = process.env.MONOREPO_BASE_SECRET;
|
|
9
|
+
if (!keyStr) throw new Error("MONOREPO_BASE_SECRET not set");
|
|
10
|
+
// The secret is 32 random bytes stored as hex (64 chars) - decode it directly
|
|
11
|
+
return Buffer.from(keyStr, "hex");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Encrypts a plaintext string using AES-256-GCM with the given key.
|
|
16
|
+
* @param plaintext
|
|
17
|
+
* @param key - The key to use for encryption. If not provided, the key will be derived from the MONOREPO_BASE_SECRET environment variable.
|
|
18
|
+
* @returns The encrypted ciphertext.
|
|
19
|
+
*/
|
|
20
|
+
export function encryptAes256Gcm(plaintext: string, key?: Buffer): string {
|
|
21
|
+
key ??= getKey();
|
|
22
|
+
const iv = crypto.randomBytes(IV_LENGTH);
|
|
23
|
+
|
|
24
|
+
const cipher = crypto.createCipheriv(ALGORITHM, key, iv, {
|
|
25
|
+
authTagLength: AUTH_TAG_LENGTH,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const encrypted = Buffer.concat([
|
|
29
|
+
cipher.update(plaintext, "utf8"),
|
|
30
|
+
cipher.final(),
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
const authTag = cipher.getAuthTag();
|
|
34
|
+
|
|
35
|
+
// Format: iv:authTag:ciphertext (all base64)
|
|
36
|
+
return [
|
|
37
|
+
iv.toString("base64"),
|
|
38
|
+
authTag.toString("base64"),
|
|
39
|
+
encrypted.toString("base64"),
|
|
40
|
+
].join(":");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Decrypts a ciphertext string using AES-256-GCM with the given key.
|
|
45
|
+
* @param ciphertext - The ciphertext to decrypt.
|
|
46
|
+
* @param key - The key to use for decryption. If not provided, the key will be derived from the MONOREPO_BASE_SECRET environment variable.
|
|
47
|
+
* @returns The decrypted plaintext.
|
|
48
|
+
*/
|
|
49
|
+
export function decryptAes256Gcm(ciphertext: string, key?: Buffer): string {
|
|
50
|
+
key ??= getKey();
|
|
51
|
+
const [ivB64, authTagB64, encryptedB64] = ciphertext.split(":");
|
|
52
|
+
|
|
53
|
+
const iv = Buffer.from(ivB64, "base64");
|
|
54
|
+
const authTag = Buffer.from(authTagB64, "base64");
|
|
55
|
+
const encrypted = Buffer.from(encryptedB64, "base64");
|
|
56
|
+
|
|
57
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv, {
|
|
58
|
+
authTagLength: AUTH_TAG_LENGTH,
|
|
59
|
+
});
|
|
60
|
+
decipher.setAuthTag(authTag);
|
|
61
|
+
|
|
62
|
+
return Buffer.concat([
|
|
63
|
+
decipher.update(encrypted),
|
|
64
|
+
decipher.final(),
|
|
65
|
+
]).toString("utf8");
|
|
66
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { generateInternalAuthToken, parseInternalAuthTokenOrThrow } from "./internal-token";
|
|
2
|
+
export { encryptAes256Gcm, decryptAes256Gcm } from "./aes";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* A simple token generation/verification class that relies on the subject field of an internal JWT-like token. It can:
|
package/dist/test.d.ts
DELETED
package/dist/test.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../src/test.ts"],"names":[],"mappings":""}
|
package/dist/test.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/src/test.ts
DELETED
|
File without changes
|