@nivinjoseph/n-sec 6.0.2 → 7.0.1

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.
Files changed (40) hide show
  1. package/.yarn/releases/yarn-4.14.1.cjs +940 -0
  2. package/.yarnrc.yml +6 -1
  3. package/README.md +114 -1
  4. package/dist/api-security/claim.js +2 -0
  5. package/dist/api-security/claim.js.map +1 -1
  6. package/dist/api-security/claims-identity.js +1 -0
  7. package/dist/api-security/claims-identity.js.map +1 -1
  8. package/dist/api-security/expired-token-exception.js +1 -0
  9. package/dist/api-security/expired-token-exception.js.map +1 -1
  10. package/dist/api-security/invalid-token-exception.js +2 -0
  11. package/dist/api-security/invalid-token-exception.js.map +1 -1
  12. package/dist/api-security/json-web-token.d.ts +1 -1
  13. package/dist/api-security/json-web-token.d.ts.map +1 -1
  14. package/dist/api-security/json-web-token.js +35 -10
  15. package/dist/api-security/json-web-token.js.map +1 -1
  16. package/dist/api-security/security-token.js +2 -0
  17. package/dist/api-security/security-token.js.map +1 -1
  18. package/dist/bin.js +1 -1
  19. package/dist/bin.js.map +1 -1
  20. package/dist/crypto/hash.d.ts +40 -0
  21. package/dist/crypto/hash.d.ts.map +1 -1
  22. package/dist/crypto/hash.js +60 -1
  23. package/dist/crypto/hash.js.map +1 -1
  24. package/dist/crypto/symmetric-encryption.d.ts +51 -3
  25. package/dist/crypto/symmetric-encryption.d.ts.map +1 -1
  26. package/dist/crypto/symmetric-encryption.js +90 -44
  27. package/dist/crypto/symmetric-encryption.js.map +1 -1
  28. package/dist/tsconfig.json +1 -0
  29. package/eslint.config.js +5 -5
  30. package/package.json +15 -15
  31. package/src/api-security/json-web-token.ts +37 -12
  32. package/src/bin.ts +1 -1
  33. package/src/crypto/hash.ts +66 -1
  34. package/src/crypto/symmetric-encryption.ts +98 -55
  35. package/test/hash.test.ts +89 -0
  36. package/test/hmac.test.ts +12 -12
  37. package/test/json-web-token.test.ts +76 -10
  38. package/test/symmetric-encryption.test.ts +51 -12
  39. package/tsconfig.json +5 -2
  40. package/.yarn/releases/yarn-4.0.2.cjs +0 -893
package/test/hash.test.ts CHANGED
@@ -164,6 +164,95 @@ await describe("Hash", async () =>
164
164
 
165
165
  Hash.createUsingSalt(password, salt);
166
166
  });
167
+ });
168
+
169
+ await describe("createForPassword", async () =>
170
+ {
171
+ await test("must return a 128-character uppercase hex string", () =>
172
+ {
173
+ const hash = Hash.createForPassword("password", "some-salt");
174
+ assert.match(hash, /^[0-9A-F]{128}$/);
175
+ });
176
+
177
+ await test("same password and salt must produce the same output", () =>
178
+ {
179
+ const hash1 = Hash.createForPassword("password", "some-salt");
180
+ const hash2 = Hash.createForPassword("password", "some-salt");
181
+ assert.strictEqual(hash1, hash2);
182
+ });
183
+
184
+ await test("different passwords with the same salt must produce different outputs", () =>
185
+ {
186
+ const hash1 = Hash.createForPassword("password1", "some-salt");
187
+ const hash2 = Hash.createForPassword("password2", "some-salt");
188
+ assert.notStrictEqual(hash1, hash2);
189
+ });
190
+
191
+ await test("same password with different salts must produce different outputs", () =>
192
+ {
193
+ const hash1 = Hash.createForPassword("password", "salt-1");
194
+ const hash2 = Hash.createForPassword("password", "salt-2");
195
+ assert.notStrictEqual(hash1, hash2);
196
+ });
197
+
198
+ await test("passwords differing only in trailing whitespace must produce different outputs", () =>
199
+ {
200
+ const hash1 = Hash.createForPassword("password", "some-salt");
201
+ const hash2 = Hash.createForPassword("password ", "some-salt");
202
+ assert.notStrictEqual(hash1, hash2);
203
+ });
204
+
205
+ await test("passwords differing only in leading whitespace must produce different outputs", () =>
206
+ {
207
+ const hash1 = Hash.createForPassword("password", "some-salt");
208
+ const hash2 = Hash.createForPassword(" password", "some-salt");
209
+ assert.notStrictEqual(hash1, hash2);
210
+ });
211
+ });
212
+
213
+ await describe("verifyPassword", async () =>
214
+ {
215
+ await test("must return true for the same password and salt that produced the hash", () =>
216
+ {
217
+ const hash = Hash.createForPassword("password", "some-salt");
218
+ assert.strictEqual(Hash.verifyPassword("password", "some-salt", hash), true);
219
+ });
220
+
221
+ await test("must accept the hash in lowercase as well as uppercase", () =>
222
+ {
223
+ const hash = Hash.createForPassword("password", "some-salt");
224
+ assert.strictEqual(Hash.verifyPassword("password", "some-salt", hash.toLowerCase()), true);
225
+ });
226
+
227
+ await test("must return false for a wrong password", () =>
228
+ {
229
+ const hash = Hash.createForPassword("password", "some-salt");
230
+ assert.strictEqual(Hash.verifyPassword("wrong-password", "some-salt", hash), false);
231
+ });
232
+
233
+ await test("must return false for a wrong salt", () =>
234
+ {
235
+ const hash = Hash.createForPassword("password", "some-salt");
236
+ assert.strictEqual(Hash.verifyPassword("password", "different-salt", hash), false);
237
+ });
238
+
239
+ await test("must return false for a tampered hash", () =>
240
+ {
241
+ const hash = Hash.createForPassword("password", "some-salt");
242
+ // eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
243
+ const tampered = (hash[0] === "A" ? "B" : "A") + hash.slice(1);
244
+ assert.strictEqual(Hash.verifyPassword("password", "some-salt", tampered), false);
245
+ });
246
+
247
+ await test("must return false for a hash of the wrong length", () =>
248
+ {
249
+ assert.strictEqual(Hash.verifyPassword("password", "some-salt", "ABCD"), false);
250
+ });
251
+
252
+ await test("must return false for a non-hex hash", () =>
253
+ {
254
+ assert.strictEqual(Hash.verifyPassword("password", "some-salt", "Z".repeat(128)), false);
255
+ });
167
256
 
168
257
 
169
258
 
package/test/hmac.test.ts CHANGED
@@ -8,9 +8,9 @@ await describe("Hmac", async () =>
8
8
  {
9
9
  await describe("create", async () =>
10
10
  {
11
- await test("should return string value that is not null, empty, whitespace or same as the key or input", async () =>
11
+ await test("should return string value that is not null, empty, whitespace or same as the key or input", () =>
12
12
  {
13
- const key = await SymmetricEncryption.generateKey();
13
+ const key = SymmetricEncryption.generateKey();
14
14
  const value = "hello world";
15
15
  const hmac = Hmac.create(key, value);
16
16
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
@@ -19,30 +19,30 @@ await describe("Hmac", async () =>
19
19
  assert.notStrictEqual(hmac, value);
20
20
  });
21
21
 
22
- await test("multiple invocations with the same key and value must return the same output", async () =>
22
+ await test("multiple invocations with the same key and value must return the same output", () =>
23
23
  {
24
- const key = await SymmetricEncryption.generateKey();
24
+ const key = SymmetricEncryption.generateKey();
25
25
  const value = "hello world";
26
26
  const hmac1 = Hmac.create(key, value);
27
27
  const hmac2 = Hmac.create(key, value);
28
28
  assert.strictEqual(hmac1, hmac2);
29
29
  });
30
30
 
31
- await test("multiple invocations with different keys and different values must return different outputs", async () =>
31
+ await test("multiple invocations with different keys and different values must return different outputs", () =>
32
32
  {
33
- const key1 = await SymmetricEncryption.generateKey();
33
+ const key1 = SymmetricEncryption.generateKey();
34
34
  const value1 = "hello world";
35
35
  const hmac1 = Hmac.create(key1, value1);
36
36
 
37
- const key2 = await SymmetricEncryption.generateKey();
37
+ const key2 = SymmetricEncryption.generateKey();
38
38
  const value2 = "goodbye world";
39
39
  const hmac2 = Hmac.create(key2, value2);
40
40
  assert.notStrictEqual(hmac1, hmac2);
41
41
  });
42
42
 
43
- await test("multiple invocations with the same key and different values must return different outputs", async () =>
43
+ await test("multiple invocations with the same key and different values must return different outputs", () =>
44
44
  {
45
- const key = await SymmetricEncryption.generateKey();
45
+ const key = SymmetricEncryption.generateKey();
46
46
  const value1 = "hello world";
47
47
  const value2 = "goodbye world";
48
48
  const hmac1 = Hmac.create(key, value1);
@@ -50,10 +50,10 @@ await describe("Hmac", async () =>
50
50
  assert.notStrictEqual(hmac1, hmac2);
51
51
  });
52
52
 
53
- await test("multiple invocations with different keys and the same value must return different outputs", async () =>
53
+ await test("multiple invocations with different keys and the same value must return different outputs", () =>
54
54
  {
55
- const key1 = await SymmetricEncryption.generateKey();
56
- const key2 = await SymmetricEncryption.generateKey();
55
+ const key1 = SymmetricEncryption.generateKey();
56
+ const key2 = SymmetricEncryption.generateKey();
57
57
  const value = "hello world";
58
58
  const hmac1 = Hmac.create(key1, value);
59
59
  const hmac2 = Hmac.create(key2, value);
@@ -2,20 +2,29 @@ import { describe, test } from "node:test";
2
2
  import assert from "node:assert";
3
3
  import { JsonWebToken } from "./../src/api-security/json-web-token.js";
4
4
  import { Claim } from "../src/api-security/claim.js";
5
- import { SymmetricEncryption } from "../src/index.js";
5
+ import { Hmac, SymmetricEncryption } from "../src/index.js";
6
6
  import { InvalidTokenException } from "../src/api-security/invalid-token-exception.js";
7
7
  import { ExpiredTokenException } from "../src/api-security/expired-token-exception.js";
8
8
 
9
9
 
10
+ function buildToken(key: string, headerJson: string, bodyJson: string): string
11
+ {
12
+ const headerHex = Buffer.from(headerJson, "utf8").toString("hex").toUpperCase();
13
+ const bodyHex = Buffer.from(bodyJson, "utf8").toString("hex").toUpperCase();
14
+ const signature = Hmac.create(key, `${headerHex}.${bodyHex}`);
15
+ return `${headerHex}.${bodyHex}.${signature}`;
16
+ }
17
+
18
+
10
19
  await describe("Json Web Token ", async () =>
11
20
  {
12
21
  await describe("Hmac", async () =>
13
22
  {
14
23
 
15
- await test("should successfully create a token using hmac with one claim", async () =>
24
+ await test("should successfully create a token using hmac with one claim", () =>
16
25
  {
17
26
  const claim = new Claim("this_claim", "ThisValue");
18
- const key = await SymmetricEncryption.generateKey();
27
+ const key = SymmetricEncryption.generateKey();
19
28
  const time = Date.now();
20
29
  const token = JsonWebToken.fromClaims("issuer1", 1, key, time + 10000000, [claim]).generateToken();
21
30
  const jwt = JsonWebToken.fromToken("issuer1", 1, key, token);
@@ -31,7 +40,7 @@ await describe("Json Web Token ", async () =>
31
40
  {
32
41
  const claim1 = new Claim("this_claim", "ThisValue");
33
42
  const claim2 = new Claim("that_claim", "ThatValue");
34
- const key = await SymmetricEncryption.generateKey();
43
+ const key = SymmetricEncryption.generateKey();
35
44
  const time = Date.now();
36
45
  const token = JsonWebToken.fromClaims("issuer1", 1, key, time + 10000000, [claim1, claim2]).generateToken();
37
46
  const jwt = JsonWebToken.fromToken("issuer1", 1, key, token);
@@ -47,7 +56,7 @@ await describe("Json Web Token ", async () =>
47
56
  {
48
57
  const claim1 = new Claim("this_claim", "ThisValue");
49
58
  const claim2 = new Claim("that_claim", "ThatValue");
50
- const key = await SymmetricEncryption.generateKey();
59
+ const key = SymmetricEncryption.generateKey();
51
60
  const time = Date.now();
52
61
  const token = JsonWebToken.fromClaims("issuer1", 1, key, time + 10000000, [claim1, claim2]).generateToken();
53
62
  const jwt = JsonWebToken.fromToken("issuer1", 1, key, token);
@@ -63,7 +72,7 @@ await describe("Json Web Token ", async () =>
63
72
  {
64
73
  const claim1 = new Claim("this_claim", "ThisValue");
65
74
  const claim2 = new Claim("that_claim", "ThatValue");
66
- const key = await SymmetricEncryption.generateKey();
75
+ const key = SymmetricEncryption.generateKey();
67
76
  const time = Date.now();
68
77
  const token = JsonWebToken.fromClaims("issuer1", 1, key, time + 10000000, [claim1, claim2]).generateToken();
69
78
  try
@@ -83,7 +92,7 @@ await describe("Json Web Token ", async () =>
83
92
  {
84
93
  const claim1 = new Claim("this_claim", "ThisValue");
85
94
  const claim2 = new Claim("that_claim", "ThatValue");
86
- const key = await SymmetricEncryption.generateKey();
95
+ const key = SymmetricEncryption.generateKey();
87
96
  const time = Date.now();
88
97
  const token = JsonWebToken.fromClaims("issuer1", 1, key, time, [claim1, claim2]).generateToken();
89
98
  try
@@ -125,8 +134,8 @@ await describe("Json Web Token ", async () =>
125
134
  {
126
135
  const claim1 = new Claim("this_claim", "ThisValue");
127
136
  const claim2 = new Claim("that_claim", "ThatValue");
128
- const key = await SymmetricEncryption.generateKey();
129
- const key2 = await SymmetricEncryption.generateKey();
137
+ const key = SymmetricEncryption.generateKey();
138
+ const key2 = SymmetricEncryption.generateKey();
130
139
  const time = Date.now();
131
140
  const token = JsonWebToken.fromClaims("issuer1", 1, key, time + 1000000, [claim1, claim2]).generateToken();
132
141
  try
@@ -146,7 +155,7 @@ await describe("Json Web Token ", async () =>
146
155
  {
147
156
  const claim1 = new Claim("this_claim", "ThisValue");
148
157
  const claim2 = new Claim("that_claim", "ThatValue");
149
- const key = await SymmetricEncryption.generateKey();
158
+ const key = SymmetricEncryption.generateKey();
150
159
  const time = Date.now();
151
160
  let token = JsonWebToken.fromClaims("issuer1", 1, key, time + 1000000, [claim1, claim2]).generateToken();
152
161
  token = token + "someStuff";
@@ -162,6 +171,63 @@ await describe("Json Web Token ", async () =>
162
171
  }
163
172
  assert.ok(false);
164
173
  });
174
+
175
+ await test("should throw an exception when body contains '__proto__' as a claim type", () =>
176
+ {
177
+ const key = SymmetricEncryption.generateKey();
178
+ const headerJson = JSON.stringify({ iss: "issuer1", alg: 1, exp: Date.now() + 10000000 });
179
+ const bodyJson = `{"__proto__":"evil"}`;
180
+ const token = buildToken(key, headerJson, bodyJson);
181
+ try
182
+ {
183
+ JsonWebToken.fromToken("issuer1", 1, key, token);
184
+ }
185
+ catch (exp)
186
+ {
187
+ assert.ok(exp instanceof InvalidTokenException);
188
+ assert.equal(exp.message, `Token '${token}' is invalid because body contains invalid key '__proto__'.`);
189
+ return;
190
+ }
191
+ assert.ok(false);
192
+ });
193
+
194
+ await test("should throw an exception when body contains 'constructor' as a claim type", () =>
195
+ {
196
+ const key = SymmetricEncryption.generateKey();
197
+ const headerJson = JSON.stringify({ iss: "issuer1", alg: 1, exp: Date.now() + 10000000 });
198
+ const bodyJson = `{"constructor":"evil"}`;
199
+ const token = buildToken(key, headerJson, bodyJson);
200
+ try
201
+ {
202
+ JsonWebToken.fromToken("issuer1", 1, key, token);
203
+ }
204
+ catch (exp)
205
+ {
206
+ assert.ok(exp instanceof InvalidTokenException);
207
+ assert.equal(exp.message, `Token '${token}' is invalid because body contains invalid key 'constructor'.`);
208
+ return;
209
+ }
210
+ assert.ok(false);
211
+ });
212
+
213
+ await test("should throw an exception when body contains 'prototype' as a claim type", () =>
214
+ {
215
+ const key = SymmetricEncryption.generateKey();
216
+ const headerJson = JSON.stringify({ iss: "issuer1", alg: 1, exp: Date.now() + 10000000 });
217
+ const bodyJson = `{"prototype":"evil"}`;
218
+ const token = buildToken(key, headerJson, bodyJson);
219
+ try
220
+ {
221
+ JsonWebToken.fromToken("issuer1", 1, key, token);
222
+ }
223
+ catch (exp)
224
+ {
225
+ assert.ok(exp instanceof InvalidTokenException);
226
+ assert.equal(exp.message, `Token '${token}' is invalid because body contains invalid key 'prototype'.`);
227
+ return;
228
+ }
229
+ assert.ok(false);
230
+ });
165
231
  });
166
232
 
167
233
  // await describe("digital Signature", () =>
@@ -1,7 +1,6 @@
1
1
  import assert from "node:assert";
2
2
  import { describe, test } from "node:test";
3
- import { SymmetricEncryption } from "./../src/index.js";
4
- // import { CryptoException } from "./../src/crypto-exception";
3
+ import { CryptoException, SymmetricEncryption } from "./../src/index.js";
5
4
  import "@nivinjoseph/n-ext";
6
5
 
7
6
 
@@ -9,30 +8,30 @@ await describe("SymmetricEncryption", async () =>
9
8
  {
10
9
  await describe("generateKey", async () =>
11
10
  {
12
- await test("must return string value that is not null, empty or whitespace", async () =>
11
+ await test("must return string value that is not null, empty or whitespace", () =>
13
12
  {
14
- const key = await SymmetricEncryption.generateKey();
13
+ const key = SymmetricEncryption.generateKey();
15
14
  console.log("key", key);
16
15
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
17
16
  assert.ok(key !== null && !key.isEmptyOrWhiteSpace());
18
17
  });
19
18
 
20
19
 
21
- await test("consecutive calls must yield unique values", async () =>
20
+ await test("consecutive calls must yield unique values", () =>
22
21
  {
23
- const key1 = await SymmetricEncryption.generateKey();
24
- const key2 = await SymmetricEncryption.generateKey();
22
+ const key1 = SymmetricEncryption.generateKey();
23
+ const key2 = SymmetricEncryption.generateKey();
25
24
  assert.notStrictEqual(key1, key2);
26
25
  });
27
26
  });
28
27
 
29
28
  await describe("encrypt", async () =>
30
29
  {
31
- await test("must return cipher text value when called with key and plain text value", async () =>
30
+ await test("must return cipher text value when called with key and plain text value", () =>
32
31
  {
33
32
  const key = "E25B269440F88601C453CD171D76EDDC11D8CF33230742DF8CAD5873D28F78B2";
34
33
  const value = "password";
35
- const encrypted = await SymmetricEncryption.encrypt(key, value);
34
+ const encrypted = SymmetricEncryption.encrypt(key, value);
36
35
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
37
36
  assert.ok(encrypted !== null);
38
37
  assert.notStrictEqual(encrypted, value);
@@ -172,15 +171,55 @@ await describe("SymmetricEncryption", async () =>
172
171
 
173
172
  await describe("decrypt", async () =>
174
173
  {
175
- await test("must return plain text value when called with key and cipher text value", async () =>
174
+ await test("must return plain text value when called with key and cipher text value", () =>
176
175
  {
177
- const key = await SymmetricEncryption.generateKey();
176
+ const key = SymmetricEncryption.generateKey();
178
177
  const value = "password";
179
- const encrypted = await SymmetricEncryption.encrypt(key, value);
178
+ const encrypted = SymmetricEncryption.encrypt(key, value);
180
179
  const decrypted = SymmetricEncryption.decrypt(key, encrypted);
181
180
  assert.strictEqual(decrypted, value);
182
181
  });
183
182
 
183
+ await test("round-trips successfully when the same aad is supplied to encrypt and decrypt", () =>
184
+ {
185
+ const key = SymmetricEncryption.generateKey();
186
+ const value = "password";
187
+ const aad = "user:42|purpose:session";
188
+ const encrypted = SymmetricEncryption.encrypt(key, value, aad);
189
+ const decrypted = SymmetricEncryption.decrypt(key, encrypted, aad);
190
+ assert.strictEqual(decrypted, value);
191
+ });
192
+
193
+ await test("throws CryptoException when decrypting with a different aad than was used to encrypt", () =>
194
+ {
195
+ const key = SymmetricEncryption.generateKey();
196
+ const encrypted = SymmetricEncryption.encrypt(key, "password", "user:42");
197
+ assert.throws(
198
+ () => SymmetricEncryption.decrypt(key, encrypted, "user:99"),
199
+ CryptoException
200
+ );
201
+ });
202
+
203
+ await test("throws CryptoException when decrypting without aad a ciphertext that was encrypted with aad", () =>
204
+ {
205
+ const key = SymmetricEncryption.generateKey();
206
+ const encrypted = SymmetricEncryption.encrypt(key, "password", "user:42");
207
+ assert.throws(
208
+ () => SymmetricEncryption.decrypt(key, encrypted),
209
+ CryptoException
210
+ );
211
+ });
212
+
213
+ await test("throws CryptoException when decrypting with aad a ciphertext that was encrypted without aad", () =>
214
+ {
215
+ const key = SymmetricEncryption.generateKey();
216
+ const encrypted = SymmetricEncryption.encrypt(key, "password");
217
+ assert.throws(
218
+ () => SymmetricEncryption.decrypt(key, encrypted, "user:42"),
219
+ CryptoException
220
+ );
221
+ });
222
+
184
223
  // await test("decrypt with a valid encryption key", async () =>
185
224
  // {
186
225
  // let key = await SymmetricEncryption.generateKey();
package/tsconfig.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "module": "NodeNext",
4
- "target": "ES2017",
4
+ "target": "ES2023",
5
5
  "lib": [
6
6
  "ES2023"
7
7
  ],
8
+ "types": [
9
+ "node"
10
+ ],
8
11
  "strict": true,
9
12
  "strictNullChecks": true,
10
13
  "strictFunctionTypes": true,
@@ -23,7 +26,7 @@
23
26
  "noEmitHelpers": true,
24
27
  "noImplicitOverride": true,
25
28
  "pretty": true,
26
- "esModuleInterop": false,
29
+ "esModuleInterop": true,
27
30
  "allowSyntheticDefaultImports": true
28
31
  }
29
32
  }