@hg-ts/rsa 0.7.27 → 0.8.0

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 (177) hide show
  1. package/README.md +91 -0
  2. package/dist/X25519/key-pair.d.ts +4 -4
  3. package/dist/X25519/key-pair.d.ts.map +1 -1
  4. package/dist/X25519/key-pair.js +3 -5
  5. package/dist/X25519/key-pair.js.map +1 -1
  6. package/dist/X25519/private-key.d.ts +5 -4
  7. package/dist/X25519/private-key.d.ts.map +1 -1
  8. package/dist/X25519/private-key.js +8 -21
  9. package/dist/X25519/private-key.js.map +1 -1
  10. package/dist/X25519/public-key.d.ts +4 -6
  11. package/dist/X25519/public-key.d.ts.map +1 -1
  12. package/dist/X25519/public-key.js +8 -19
  13. package/dist/X25519/public-key.js.map +1 -1
  14. package/dist/X25519/x25519.test.d.ts +7 -6
  15. package/dist/X25519/x25519.test.d.ts.map +1 -1
  16. package/dist/X25519/x25519.test.js +70 -58
  17. package/dist/X25519/x25519.test.js.map +1 -1
  18. package/dist/aead/index.d.ts +2 -0
  19. package/dist/aead/index.d.ts.map +1 -0
  20. package/dist/aead/index.js +2 -0
  21. package/dist/aead/index.js.map +1 -0
  22. package/dist/aead/xchacha20-poly1305.d.ts +10 -0
  23. package/dist/aead/xchacha20-poly1305.d.ts.map +1 -0
  24. package/dist/aead/xchacha20-poly1305.js +48 -0
  25. package/dist/aead/xchacha20-poly1305.js.map +1 -0
  26. package/dist/aead/xchacha20-poly1305.test.d.ts +14 -0
  27. package/dist/aead/xchacha20-poly1305.test.d.ts.map +1 -0
  28. package/dist/aead/xchacha20-poly1305.test.js +140 -0
  29. package/dist/aead/xchacha20-poly1305.test.js.map +1 -0
  30. package/dist/base/index.d.ts +1 -0
  31. package/dist/base/index.d.ts.map +1 -1
  32. package/dist/base/index.js +1 -0
  33. package/dist/base/index.js.map +1 -1
  34. package/dist/base/key-capabilities.d.ts +36 -0
  35. package/dist/base/key-capabilities.d.ts.map +1 -0
  36. package/dist/base/key-capabilities.js +2 -0
  37. package/dist/base/key-capabilities.js.map +1 -0
  38. package/dist/base/key-pair.d.ts +3 -7
  39. package/dist/base/key-pair.d.ts.map +1 -1
  40. package/dist/base/key-pair.js.map +1 -1
  41. package/dist/base/key.d.ts +2 -1
  42. package/dist/base/key.d.ts.map +1 -1
  43. package/dist/base/key.js.map +1 -1
  44. package/dist/base/private-key.d.ts +2 -3
  45. package/dist/base/private-key.d.ts.map +1 -1
  46. package/dist/base/private-key.js.map +1 -1
  47. package/dist/base/public-key.d.ts +2 -3
  48. package/dist/base/public-key.d.ts.map +1 -1
  49. package/dist/base/public-key.js.map +1 -1
  50. package/dist/exceptions/hkdf-output-length.exception.d.ts +5 -0
  51. package/dist/exceptions/hkdf-output-length.exception.d.ts.map +1 -0
  52. package/dist/exceptions/hkdf-output-length.exception.js +7 -0
  53. package/dist/exceptions/hkdf-output-length.exception.js.map +1 -0
  54. package/dist/exceptions/index.d.ts +4 -0
  55. package/dist/exceptions/index.d.ts.map +1 -1
  56. package/dist/exceptions/index.js +4 -0
  57. package/dist/exceptions/index.js.map +1 -1
  58. package/dist/exceptions/invalid-encryption-key.exception.d.ts +5 -0
  59. package/dist/exceptions/invalid-encryption-key.exception.d.ts.map +1 -0
  60. package/dist/exceptions/invalid-encryption-key.exception.js +7 -0
  61. package/dist/exceptions/invalid-encryption-key.exception.js.map +1 -0
  62. package/dist/exceptions/invalid-pq-kem-key-length.exception.d.ts +5 -0
  63. package/dist/exceptions/invalid-pq-kem-key-length.exception.d.ts.map +1 -0
  64. package/dist/exceptions/invalid-pq-kem-key-length.exception.js +7 -0
  65. package/dist/exceptions/invalid-pq-kem-key-length.exception.js.map +1 -0
  66. package/dist/exceptions/invalid-pq-kem-message-length.exception.d.ts +5 -0
  67. package/dist/exceptions/invalid-pq-kem-message-length.exception.d.ts.map +1 -0
  68. package/dist/exceptions/invalid-pq-kem-message-length.exception.js +7 -0
  69. package/dist/exceptions/invalid-pq-kem-message-length.exception.js.map +1 -0
  70. package/dist/index.d.ts +3 -0
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +3 -0
  73. package/dist/index.js.map +1 -1
  74. package/dist/pq-kem/algorithm.d.ts +16 -0
  75. package/dist/pq-kem/algorithm.d.ts.map +1 -0
  76. package/dist/pq-kem/algorithm.js +25 -0
  77. package/dist/pq-kem/algorithm.js.map +1 -0
  78. package/dist/pq-kem/index.d.ts +5 -0
  79. package/dist/pq-kem/index.d.ts.map +1 -0
  80. package/dist/pq-kem/index.js +5 -0
  81. package/dist/pq-kem/index.js.map +1 -0
  82. package/dist/pq-kem/key-pair.d.ts +15 -0
  83. package/dist/pq-kem/key-pair.d.ts.map +1 -0
  84. package/dist/pq-kem/key-pair.js +44 -0
  85. package/dist/pq-kem/key-pair.js.map +1 -0
  86. package/dist/pq-kem/pq-kem.test.d.ts +18 -0
  87. package/dist/pq-kem/pq-kem.test.d.ts.map +1 -0
  88. package/dist/pq-kem/pq-kem.test.js +173 -0
  89. package/dist/pq-kem/pq-kem.test.js.map +1 -0
  90. package/dist/pq-kem/private-key.d.ts +16 -0
  91. package/dist/pq-kem/private-key.d.ts.map +1 -0
  92. package/dist/pq-kem/private-key.js +43 -0
  93. package/dist/pq-kem/private-key.js.map +1 -0
  94. package/dist/pq-kem/public-key.d.ts +18 -0
  95. package/dist/pq-kem/public-key.d.ts.map +1 -0
  96. package/dist/pq-kem/public-key.js +70 -0
  97. package/dist/pq-kem/public-key.js.map +1 -0
  98. package/dist/rsa/key-pair.d.ts +2 -2
  99. package/dist/rsa/key-pair.d.ts.map +1 -1
  100. package/dist/rsa/key-pair.js.map +1 -1
  101. package/dist/rsa/private-key.d.ts +3 -2
  102. package/dist/rsa/private-key.d.ts.map +1 -1
  103. package/dist/rsa/private-key.js +2 -1
  104. package/dist/rsa/private-key.js.map +1 -1
  105. package/dist/rsa/public-key.d.ts +3 -2
  106. package/dist/rsa/public-key.d.ts.map +1 -1
  107. package/dist/rsa/public-key.js +2 -1
  108. package/dist/rsa/public-key.js.map +1 -1
  109. package/dist/rsa/rsa.test.d.ts.map +1 -1
  110. package/dist/rsa/rsa.test.js +1 -0
  111. package/dist/rsa/rsa.test.js.map +1 -1
  112. package/dist/utils/hkdf.d.ts +26 -0
  113. package/dist/utils/hkdf.d.ts.map +1 -0
  114. package/dist/utils/hkdf.js +43 -0
  115. package/dist/utils/hkdf.js.map +1 -0
  116. package/dist/utils/hkdf.test.d.ts +7 -0
  117. package/dist/utils/hkdf.test.d.ts.map +1 -0
  118. package/dist/utils/hkdf.test.js +79 -0
  119. package/dist/utils/hkdf.test.js.map +1 -0
  120. package/dist/utils/hmac.d.ts +5 -0
  121. package/dist/utils/hmac.d.ts.map +1 -0
  122. package/dist/utils/hmac.js +16 -0
  123. package/dist/utils/hmac.js.map +1 -0
  124. package/dist/utils/hmac.test.d.ts +6 -0
  125. package/dist/utils/hmac.test.d.ts.map +1 -0
  126. package/dist/utils/hmac.test.js +33 -0
  127. package/dist/utils/hmac.test.js.map +1 -0
  128. package/dist/utils/index.d.ts +4 -0
  129. package/dist/utils/index.d.ts.map +1 -0
  130. package/dist/utils/index.js +4 -0
  131. package/dist/utils/index.js.map +1 -0
  132. package/dist/utils/kdf.d.ts +3 -0
  133. package/dist/utils/kdf.d.ts.map +1 -0
  134. package/dist/utils/kdf.js +10 -0
  135. package/dist/utils/kdf.js.map +1 -0
  136. package/package.json +16 -10
  137. package/src/X25519/key-pair.ts +9 -9
  138. package/src/X25519/private-key.ts +15 -30
  139. package/src/X25519/public-key.ts +15 -31
  140. package/src/X25519/x25519.test.ts +81 -68
  141. package/src/aead/index.ts +1 -0
  142. package/src/aead/xchacha20-poly1305.test.ts +147 -0
  143. package/src/aead/xchacha20-poly1305.ts +80 -0
  144. package/src/base/index.ts +1 -0
  145. package/src/base/key-capabilities.ts +54 -0
  146. package/src/base/key-pair.ts +6 -11
  147. package/src/base/key.ts +3 -1
  148. package/src/base/private-key.ts +2 -5
  149. package/src/base/public-key.ts +4 -5
  150. package/src/exceptions/hkdf-output-length.exception.ts +7 -0
  151. package/src/exceptions/index.ts +4 -0
  152. package/src/exceptions/invalid-encryption-key.exception.ts +7 -0
  153. package/src/exceptions/invalid-pq-kem-key-length.exception.ts +7 -0
  154. package/src/exceptions/invalid-pq-kem-message-length.exception.ts +7 -0
  155. package/src/index.ts +3 -0
  156. package/src/pq-kem/algorithm.ts +51 -0
  157. package/src/pq-kem/index.ts +4 -0
  158. package/src/pq-kem/key-pair.ts +76 -0
  159. package/src/pq-kem/pq-kem.test.ts +144 -0
  160. package/src/pq-kem/private-key.ts +67 -0
  161. package/src/pq-kem/public-key.ts +99 -0
  162. package/src/rsa/key-pair.ts +11 -5
  163. package/src/rsa/private-key.ts +9 -2
  164. package/src/rsa/public-key.ts +9 -2
  165. package/src/rsa/rsa.test.ts +1 -0
  166. package/src/utils/hkdf.test.ts +77 -0
  167. package/src/utils/hkdf.ts +89 -0
  168. package/src/utils/hmac.test.ts +43 -0
  169. package/src/utils/hmac.ts +21 -0
  170. package/src/utils/index.ts +3 -0
  171. package/src/utils/kdf.ts +17 -0
  172. package/tsconfig.json +3 -1
  173. package/dist/X25519/utils.d.ts +0 -2
  174. package/dist/X25519/utils.d.ts.map +0 -1
  175. package/dist/X25519/utils.js +0 -12
  176. package/dist/X25519/utils.js.map +0 -1
  177. package/src/X25519/utils.ts +0 -22
@@ -0,0 +1,76 @@
1
+ import Buffer from '@hg-ts/buffer';
2
+ import forge from 'node-forge';
3
+ import {
4
+ BaseKeyPair,
5
+ DecapsulatingPrivateKey,
6
+ EncapsulatingPublicKey,
7
+ KeyPairOptions,
8
+ KeyPairResult,
9
+ NewKeyPairOptions,
10
+ } from '../base/index.js';
11
+ import {
12
+ DEFAULT_PQ_KEM_ALGORITHM,
13
+ getPqKemImplementation,
14
+ type PqKemAlgorithm,
15
+ } from './algorithm.js';
16
+ import { PqKemPrivateKey } from './private-key.js';
17
+ import {
18
+ PqKemEncapsulation,
19
+ PqKemPublicKey,
20
+ } from './public-key.js';
21
+
22
+ export type PqKemKeyPairOptions = KeyPairOptions<PqKemPrivateKey, PqKemAlgorithm>;
23
+
24
+ export class PqKemKeyPair extends BaseKeyPair<
25
+ PqKemPrivateKey,
26
+ PqKemPublicKey,
27
+ PqKemKeyPairOptions
28
+ > implements EncapsulatingPublicKey, DecapsulatingPrivateKey {
29
+ public readonly bits: PqKemAlgorithm;
30
+
31
+ public constructor(options: PqKemKeyPairOptions = {}) {
32
+ super(options, PqKemPrivateKey);
33
+ this.bits = this.privateKeyInstance.bits;
34
+ }
35
+
36
+ public encapsulate(message?: string | Buffer): PqKemEncapsulation {
37
+ return this.publicKeyInstance.encapsulate(message);
38
+ }
39
+
40
+ public decapsulate(cipherText: string | Buffer): Buffer {
41
+ return this.privateKeyInstance.decapsulate(cipherText);
42
+ }
43
+
44
+ protected generateNewKeys(options: NewKeyPairOptions<PqKemAlgorithm>): KeyPairResult<
45
+ PqKemPrivateKey,
46
+ PqKemPublicKey
47
+ > {
48
+ const bits = options.bits ?? DEFAULT_PQ_KEM_ALGORITHM;
49
+ const implementation = getPqKemImplementation(bits);
50
+ const keyPair = options.seed
51
+ ? implementation.keygen(this.prepareSeed(options.seed, bits))
52
+ : implementation.keygen();
53
+ const privateKey = new PqKemPrivateKey(Buffer.from(keyPair.secretKey), bits);
54
+
55
+ return {
56
+ privateKey,
57
+ publicKey: new PqKemPublicKey(Buffer.from(keyPair.publicKey), bits),
58
+ };
59
+ }
60
+
61
+ private prepareSeed(seed: string, bits: PqKemAlgorithm): Buffer {
62
+ const seedLength = getPqKemImplementation(bits).lengths.seed;
63
+ const bytes = Buffer.from(seed, 'utf8');
64
+
65
+ if (bytes.length === seedLength) {
66
+ return bytes;
67
+ }
68
+
69
+ const hash = forge.md.sha512.create();
70
+ hash.update('PQKEM seed', 'utf8');
71
+ hash.update(bits.toString(), 'utf8');
72
+ hash.update(bytes.toString('binary'));
73
+
74
+ return Buffer.from(hash.digest().getBytes(), 'binary').subarray(0, seedLength);
75
+ }
76
+ }
@@ -0,0 +1,144 @@
1
+ import Buffer from '@hg-ts/buffer';
2
+ import {
3
+ Describe,
4
+ expect,
5
+ ExpectException,
6
+ Suite,
7
+ Test,
8
+ } from '@hg-ts/tests';
9
+ import { z } from '@hg-ts/validation';
10
+
11
+ import {
12
+ InvalidPqKemKeyLengthException,
13
+ InvalidPqKemMessageLengthException,
14
+ getPqKemImplementation,
15
+ PqKemKeyPair,
16
+ PqKemPrivateKey,
17
+ PqKemPublicKey,
18
+ } from '../index.js';
19
+
20
+ @Describe()
21
+ export class PqKemTest extends Suite {
22
+ @Test()
23
+ public async encapsulateAndDecapsulate(): Promise<void> {
24
+ const recipient = new PqKemKeyPair();
25
+
26
+ const encapsulation = recipient.encapsulate();
27
+ const sharedSecret = recipient.decapsulate(encapsulation.cipherText);
28
+
29
+ expect(sharedSecret).toMatchObject(encapsulation.sharedSecret);
30
+ }
31
+
32
+ @Test()
33
+ public async restoresKeys(): Promise<void> {
34
+ const keyPair = new PqKemKeyPair({ bits: 1024 });
35
+
36
+ const privateKey = PqKemPrivateKey.fromString(keyPair.privateKey);
37
+ const publicKey = PqKemPublicKey.fromString(keyPair.publicKey);
38
+
39
+ expect(privateKey.toString()).toBe(keyPair.privateKey);
40
+ expect(publicKey.toString()).toBe(keyPair.publicKey);
41
+ }
42
+
43
+ @Test()
44
+ public async restoresKeyPairFromPrivateKeyString(): Promise<void> {
45
+ const keyPair = new PqKemKeyPair({ bits: 512 });
46
+
47
+ const restored = new PqKemKeyPair({ privateKey: keyPair.privateKey });
48
+
49
+ expect(restored.bits).toBe(512);
50
+ expect(restored.privateKey).toBe(keyPair.privateKey);
51
+ expect(restored.publicKey).toBe(keyPair.publicKey);
52
+ }
53
+
54
+ @Test()
55
+ public async decapsulatesBase64CipherText(): Promise<void> {
56
+ const recipient = new PqKemKeyPair();
57
+ const encapsulation = recipient.encapsulate();
58
+
59
+ const sharedSecret = recipient.privateKeyInstance.decapsulate(
60
+ encapsulation.cipherText.toString('base64'),
61
+ );
62
+
63
+ expect(sharedSecret).toMatchObject(encapsulation.sharedSecret);
64
+ }
65
+
66
+ @Test()
67
+ public async encapsulatesWithMessage(): Promise<void> {
68
+ const recipient = new PqKemKeyPair();
69
+ const message = Buffer.alloc(getPqKemImplementation(recipient.bits).lengths.msg, 1);
70
+
71
+ const encapsulation = recipient.publicKeyInstance.encapsulate(message);
72
+
73
+ expect(recipient.decapsulate(encapsulation.cipherText)).toMatchObject(encapsulation.sharedSecret);
74
+ }
75
+
76
+ @Test()
77
+ public async encapsulatesWithStringMessage(): Promise<void> {
78
+ const recipient = new PqKemKeyPair();
79
+ const message = 'a'.repeat(getPqKemImplementation(recipient.bits).lengths.msg);
80
+
81
+ const encapsulation = recipient.publicKeyInstance.encapsulate(message);
82
+
83
+ expect(recipient.decapsulate(encapsulation.cipherText)).toMatchObject(encapsulation.sharedSecret);
84
+ }
85
+
86
+ @Test()
87
+ public async seeded(): Promise<void> {
88
+ const seed = Math.random().toString();
89
+ const first = new PqKemKeyPair({ seed });
90
+ const second = new PqKemKeyPair({ seed });
91
+
92
+ expect(first.privateKey).toBe(second.privateKey);
93
+ expect(first.publicKey).toBe(second.publicKey);
94
+ }
95
+
96
+ @Test()
97
+ public async seededWithExactSeedLength(): Promise<void> {
98
+ const seed = 'a'.repeat(getPqKemImplementation(768).lengths.seed);
99
+ const first = new PqKemKeyPair({ seed });
100
+ const second = new PqKemKeyPair({ seed });
101
+
102
+ expect(first.privateKey).toBe(second.privateKey);
103
+ expect(first.publicKey).toBe(second.publicKey);
104
+ }
105
+
106
+ @Test()
107
+ public async nativeKeysAreCopied(): Promise<void> {
108
+ const keyPair = new PqKemKeyPair();
109
+
110
+ expect(keyPair.privateKeyInstance.nativeKey).toMatchObject(keyPair.privateKeyInstance.nativeKey);
111
+ expect(keyPair.publicKeyInstance.nativeKey).toMatchObject(keyPair.publicKeyInstance.nativeKey);
112
+ }
113
+
114
+ @Test()
115
+ public async publicKeyValidationValid(): Promise<void> {
116
+ const keyPair = new PqKemKeyPair();
117
+
118
+ PqKemPublicKey.schema.parse(keyPair.publicKey);
119
+ }
120
+
121
+ @Test()
122
+ @ExpectException(z.ZodError)
123
+ public async publicKeyValidationFails(): Promise<void> {
124
+ PqKemPublicKey.schema.parse('{}');
125
+ }
126
+
127
+ @Test()
128
+ @ExpectException(InvalidPqKemMessageLengthException)
129
+ public async encapsulateFailsForInvalidMessageLength(): Promise<void> {
130
+ new PqKemKeyPair().encapsulate(Buffer.alloc(31));
131
+ }
132
+
133
+ @Test()
134
+ @ExpectException(InvalidPqKemKeyLengthException)
135
+ public async privateKeyFailsForInvalidKeyLength(): Promise<void> {
136
+ new PqKemPrivateKey(Buffer.alloc(1), 768).toString();
137
+ }
138
+
139
+ @Test()
140
+ @ExpectException(InvalidPqKemKeyLengthException)
141
+ public async publicKeyFailsForInvalidKeyLength(): Promise<void> {
142
+ new PqKemPublicKey(Buffer.alloc(1), 768).toString();
143
+ }
144
+ }
@@ -0,0 +1,67 @@
1
+ import Buffer from '@hg-ts/buffer';
2
+ import {
3
+ BasePrivateKey,
4
+ DecapsulatingPrivateKey,
5
+ } from '../base/index.js';
6
+ import { InvalidPqKemKeyLengthException } from '../exceptions/index.js';
7
+ import {
8
+ getPqKemImplementation,
9
+ inferPqKemBitsByKeyLength,
10
+ type PqKemAlgorithm,
11
+ type PqKemImplementation,
12
+ } from './algorithm.js';
13
+ import { PqKemPublicKey } from './public-key.js';
14
+
15
+ export class PqKemPrivateKey
16
+ extends BasePrivateKey<Buffer, PqKemPublicKey>
17
+ implements DecapsulatingPrivateKey {
18
+ public readonly bits: PqKemAlgorithm;
19
+
20
+ public constructor(key: Buffer, bits: PqKemAlgorithm) {
21
+ super(key);
22
+ this.bits = bits;
23
+ this.validateKey();
24
+ }
25
+
26
+ public decapsulate(cipherText: string | Buffer): Buffer {
27
+ const value = typeof cipherText === 'string'
28
+ ? Buffer.from(cipherText, 'base64')
29
+ : cipherText;
30
+
31
+ return Buffer.from(this.implementation.decapsulate(value, this.key));
32
+ }
33
+
34
+ public override toPublicKey(): PqKemPublicKey {
35
+ return new PqKemPublicKey(
36
+ Buffer.from(this.implementation.getPublicKey(this.key)),
37
+ this.bits,
38
+ );
39
+ }
40
+
41
+ public override toString(): string {
42
+ return this.key.toString('base64');
43
+ }
44
+
45
+ public override get nativeKey(): Buffer {
46
+ return Buffer.from(this.key);
47
+ }
48
+
49
+ public static fromString(value: string): PqKemPrivateKey {
50
+ const key = Buffer.from(value, 'base64');
51
+ const bits = inferPqKemBitsByKeyLength(key.length, 'secretKey');
52
+
53
+ return new PqKemPrivateKey(key, bits);
54
+ }
55
+
56
+ private get implementation(): PqKemImplementation {
57
+ return getPqKemImplementation(this.bits);
58
+ }
59
+
60
+ private validateKey(): void {
61
+ const { secretKey } = this.implementation.lengths;
62
+
63
+ if (this.key.length !== secretKey) {
64
+ throw new InvalidPqKemKeyLengthException('secretKey', this.key.length);
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,99 @@
1
+ import Buffer from '@hg-ts/buffer';
2
+ import { z } from '@hg-ts/validation';
3
+
4
+ import {
5
+ BasePublicKey,
6
+ EncapsulatingPublicKey,
7
+ type KemEncapsulation,
8
+ } from '../base/index.js';
9
+ import {
10
+ InvalidPqKemKeyLengthException,
11
+ InvalidPqKemMessageLengthException,
12
+ } from '../exceptions/index.js';
13
+ import {
14
+ getPqKemImplementation,
15
+ inferPqKemBitsByKeyLength,
16
+ type PqKemAlgorithm,
17
+ type PqKemImplementation,
18
+ } from './algorithm.js';
19
+
20
+ export type PqKemEncapsulation = KemEncapsulation;
21
+
22
+ const schema = z.string().transform((value, ctx) => {
23
+ try {
24
+ PqKemPublicKey.fromString(value);
25
+ return value;
26
+ } catch (error) {
27
+ ctx.issues.push({
28
+ message: error instanceof Error ? error.message : 'Invalid PQKEM public key',
29
+ fatal: true,
30
+ code: 'invalid_format',
31
+ input: value,
32
+ format: 'PQKEM Public Key',
33
+ });
34
+ return value;
35
+ }
36
+ }).pipe(z.string());
37
+
38
+ export class PqKemPublicKey extends BasePublicKey<Buffer> implements EncapsulatingPublicKey {
39
+ public readonly bits: PqKemAlgorithm;
40
+
41
+ public static schema = schema;
42
+
43
+ public constructor(key: Buffer, bits: PqKemAlgorithm) {
44
+ super(key);
45
+ this.bits = bits;
46
+ this.validateKey();
47
+ }
48
+
49
+ public encapsulate(message?: string | Buffer): PqKemEncapsulation {
50
+ const { cipherText, sharedSecret } = message
51
+ ? this.implementation.encapsulate(this.key, this.prepareMessage(message))
52
+ : this.implementation.encapsulate(this.key);
53
+
54
+ return {
55
+ cipherText: Buffer.from(cipherText),
56
+ sharedSecret: Buffer.from(sharedSecret),
57
+ };
58
+ }
59
+
60
+ public override toString(): string {
61
+ return this.key.toString('base64');
62
+ }
63
+
64
+ public override get nativeKey(): Buffer {
65
+ return Buffer.from(this.key);
66
+ }
67
+
68
+ public static fromString(value: string): PqKemPublicKey {
69
+ const key = Buffer.from(value, 'base64');
70
+ const bits = inferPqKemBitsByKeyLength(key.length, 'publicKey');
71
+
72
+ return new PqKemPublicKey(key, bits);
73
+ }
74
+
75
+ private prepareMessage(message: string | Buffer): Buffer {
76
+ const value = typeof message === 'string'
77
+ ? Buffer.from(message, 'utf8')
78
+ : message;
79
+ const { msg } = this.implementation.lengths;
80
+
81
+ if (value.length !== msg) {
82
+ throw new InvalidPqKemMessageLengthException(msg, value.length);
83
+ }
84
+
85
+ return value;
86
+ }
87
+
88
+ private get implementation(): PqKemImplementation {
89
+ return getPqKemImplementation(this.bits);
90
+ }
91
+
92
+ private validateKey(): void {
93
+ const { publicKey } = this.implementation.lengths;
94
+
95
+ if (this.key.length !== publicKey) {
96
+ throw new InvalidPqKemKeyLengthException('publicKey', this.key.length);
97
+ }
98
+ }
99
+ }
@@ -1,31 +1,37 @@
1
1
  import forge from 'node-forge';
2
2
  import {
3
3
  BaseKeyPair,
4
+ DecryptingPrivateKey,
5
+ EncryptingPublicKey,
4
6
  KeyPairOptions,
5
7
  KeyPairResult,
6
8
  NewKeyPairOptions,
9
+ SigningPrivateKey,
10
+ VerifyingPublicKey,
7
11
  } from '../base/index.js';
8
12
  import { RsaPrivateKey } from './private-key.js';
9
13
  import { RsaPublicKey } from './public-key.js';
10
14
 
11
- export class RsaKeyPair extends BaseKeyPair<RsaPrivateKey, RsaPublicKey> {
15
+ export class RsaKeyPair
16
+ extends BaseKeyPair<RsaPrivateKey, RsaPublicKey>
17
+ implements EncryptingPublicKey, DecryptingPrivateKey, SigningPrivateKey, VerifyingPublicKey {
12
18
  public constructor(options: KeyPairOptions<RsaPrivateKey> = {}) {
13
19
  super(options, RsaPrivateKey);
14
20
  }
15
21
 
16
- public override encrypt(value: string | Buffer): Buffer {
22
+ public encrypt(value: string | Buffer): Buffer {
17
23
  return this.publicKeyInstance.encrypt(value);
18
24
  }
19
25
 
20
- public override decrypt(value: string | Buffer): string {
26
+ public decrypt(value: string | Buffer): string {
21
27
  return this.privateKeyInstance.decrypt(value);
22
28
  }
23
29
 
24
- public override sign(value: string): Buffer {
30
+ public sign(value: string): Buffer {
25
31
  return this.privateKeyInstance.sign(value);
26
32
  }
27
33
 
28
- public override verify(signature: string | Buffer, value: string): boolean {
34
+ public verify(signature: string | Buffer, value: string): boolean {
29
35
  return this.publicKeyInstance.verify(signature, value);
30
36
  }
31
37
 
@@ -1,9 +1,16 @@
1
+ import Buffer from '@hg-ts/buffer';
1
2
  import forge from 'node-forge';
2
- import { BasePrivateKey } from '../base/index.js';
3
+ import {
4
+ BasePrivateKey,
5
+ DecryptingPrivateKey,
6
+ SigningPrivateKey,
7
+ } from '../base/index.js';
3
8
  import { InvalidDecryptionKeyExpection } from '../exceptions/index.js';
4
9
  import { RsaPublicKey } from './public-key.js';
5
10
 
6
- export class RsaPrivateKey extends BasePrivateKey<forge.pki.rsa.PrivateKey, RsaPublicKey> {
11
+ export class RsaPrivateKey
12
+ extends BasePrivateKey<forge.pki.rsa.PrivateKey, RsaPublicKey>
13
+ implements DecryptingPrivateKey, SigningPrivateKey {
7
14
  public decrypt(encrypted: string | Buffer): string {
8
15
  const md = this.getMd();
9
16
  const chunks = this.prepareToDecrypt(encrypted);
@@ -1,6 +1,11 @@
1
+ import Buffer from '@hg-ts/buffer';
1
2
  import { z } from '@hg-ts/validation';
2
3
  import forge from 'node-forge';
3
- import { BasePublicKey } from '../base/index.js';
4
+ import {
5
+ BasePublicKey,
6
+ EncryptingPublicKey,
7
+ VerifyingPublicKey,
8
+ } from '../base/index.js';
4
9
 
5
10
  const schema = z.string().transform((value, ctx) => {
6
11
  try {
@@ -19,7 +24,9 @@ const schema = z.string().transform((value, ctx) => {
19
24
  }
20
25
  }).pipe(z.string());
21
26
 
22
- export class RsaPublicKey extends BasePublicKey<forge.pki.rsa.PublicKey> {
27
+ export class RsaPublicKey
28
+ extends BasePublicKey<forge.pki.rsa.PublicKey>
29
+ implements EncryptingPublicKey, VerifyingPublicKey {
23
30
  public static schema = schema;
24
31
 
25
32
  public encrypt(value: string | Buffer): Buffer {
@@ -1,3 +1,4 @@
1
+ import Buffer from '@hg-ts/buffer';
1
2
  import {
2
3
  Describe,
3
4
  expect,
@@ -0,0 +1,77 @@
1
+ import Buffer from '@hg-ts/buffer';
2
+ import {
3
+ Describe,
4
+ expect,
5
+ ExpectException,
6
+ Suite,
7
+ Test,
8
+ } from '@hg-ts/tests';
9
+ import { HkdfOutputLengthException } from '../exceptions/index.js';
10
+
11
+ import { Hkdf } from './hkdf.js';
12
+
13
+ @Describe()
14
+ export class HkdfTest extends Suite {
15
+ @Test()
16
+ public async rfc5869Sha256CaseOne(): Promise<void> {
17
+ const inputKeyMaterial = Buffer.alloc(22, 0x0b);
18
+ const salt = Buffer.from('000102030405060708090a0b0c', 'hex');
19
+ const info = Buffer.from('f0f1f2f3f4f5f6f7f8f9', 'hex');
20
+
21
+ const hkdfInstance = new Hkdf({
22
+ info,
23
+ inputKeyMaterial,
24
+ length: 42,
25
+ salt,
26
+ });
27
+ const pseudorandomKey = hkdfInstance.extract();
28
+ const outputKeyMaterial = hkdfInstance.getBuffer();
29
+
30
+ expect(pseudorandomKey.toString('hex')).toBe(
31
+ '077709362c2e32df0ddc3f0dc47bba63'
32
+ + '90b6c73bb50f9c3122ec844ad7c2b3e5',
33
+ );
34
+ expect(outputKeyMaterial.toString('hex')).toBe(
35
+ '3cb25f25faacd57a90434f64d0362f2a'
36
+ + '2d2d0a90cf1a5a4c5db02d56ecc4c5bf'
37
+ + '34007208d5b887185865',
38
+ );
39
+ }
40
+
41
+ @Test()
42
+ public async expandMatchesFullHkdf(): Promise<void> {
43
+ const inputKeyMaterial = Buffer.from('shared-secret', 'utf8');
44
+ const salt = Buffer.from('root-key', 'utf8');
45
+ const info = Buffer.from('message-key', 'utf8');
46
+ const hkdfInstance = new Hkdf({
47
+ info,
48
+ inputKeyMaterial,
49
+ length: 64,
50
+ salt,
51
+ });
52
+ const pseudorandomKey = hkdfInstance.extract();
53
+
54
+ const expanded = hkdfInstance.expand({
55
+ info,
56
+ length: 64,
57
+ pseudorandomKey,
58
+ });
59
+ const derived = new Hkdf({
60
+ info,
61
+ inputKeyMaterial,
62
+ length: 64,
63
+ salt,
64
+ }).getBuffer();
65
+
66
+ expect(expanded).toMatchObject(derived);
67
+ }
68
+
69
+ @Test()
70
+ @ExpectException(HkdfOutputLengthException)
71
+ public async rejectsTooLargeOutputLength(): Promise<void> {
72
+ new Hkdf({
73
+ inputKeyMaterial: Buffer.from('shared-secret', 'utf8'),
74
+ length: (255 * 32) + 1,
75
+ }).getBuffer();
76
+ }
77
+ }
@@ -0,0 +1,89 @@
1
+ import Buffer from '@hg-ts/buffer';
2
+ import { HkdfOutputLengthException } from '../exceptions/index.js';
3
+ import {
4
+ hmac,
5
+ HMAC_DIGEST_LENGTH,
6
+ type HmacAlgorithm,
7
+ } from './hmac.js';
8
+
9
+ export type HkdfOptions = {
10
+ algorithm?: HmacAlgorithm;
11
+ info?: Buffer;
12
+ inputKeyMaterial: Buffer;
13
+ length: number;
14
+ salt?: Buffer;
15
+ };
16
+
17
+ export type HkdfExpandOptions = {
18
+ info?: Optional<Buffer>;
19
+ length: number;
20
+ pseudorandomKey: Buffer;
21
+ };
22
+
23
+ export class Hkdf {
24
+ private readonly algorithm: HmacAlgorithm;
25
+ private readonly info: Buffer;
26
+ private readonly inputKeyMaterial: Buffer;
27
+ private readonly length: number;
28
+ private readonly salt: Buffer;
29
+
30
+ public constructor({
31
+ algorithm = 'sha256',
32
+ info,
33
+ inputKeyMaterial,
34
+ length,
35
+ salt,
36
+ }: HkdfOptions) {
37
+ this.algorithm = algorithm;
38
+ this.info = info ?? Buffer.alloc(0);
39
+ this.inputKeyMaterial = inputKeyMaterial;
40
+ this.length = length;
41
+ this.salt = salt ?? Buffer.alloc(HMAC_DIGEST_LENGTH[this.algorithm]);
42
+ }
43
+
44
+ public getBuffer(): Buffer {
45
+ return this.expand({
46
+ length: this.length,
47
+ pseudorandomKey: this.extract(),
48
+ });
49
+ }
50
+
51
+ public extract(): Buffer {
52
+ return hmac(
53
+ this.algorithm,
54
+ this.salt,
55
+ this.inputKeyMaterial,
56
+ );
57
+ }
58
+
59
+ public expand({
60
+ length,
61
+ info,
62
+ pseudorandomKey,
63
+ }: HkdfExpandOptions): Buffer {
64
+ const digestLength = HMAC_DIGEST_LENGTH[this.algorithm];
65
+
66
+ if (length > 255 * digestLength) {
67
+ throw new HkdfOutputLengthException(255 * digestLength);
68
+ }
69
+
70
+ const expandInfo = info ?? this.info;
71
+ const blocks: Buffer[] = [];
72
+ let previous: Buffer = Buffer.alloc(0);
73
+ let generatedLength = 0;
74
+
75
+ for (let counter = 1; generatedLength < length; counter += 1) {
76
+ previous = hmac(
77
+ this.algorithm,
78
+ pseudorandomKey,
79
+ previous,
80
+ expandInfo,
81
+ Buffer.from([counter]),
82
+ );
83
+ blocks.push(previous);
84
+ generatedLength += previous.length;
85
+ }
86
+
87
+ return Buffer.concat(blocks, generatedLength).subarray(0, length);
88
+ }
89
+ }
@@ -0,0 +1,43 @@
1
+ import Buffer from '@hg-ts/buffer';
2
+ import {
3
+ Describe,
4
+ expect,
5
+ Suite,
6
+ Test,
7
+ } from '@hg-ts/tests';
8
+
9
+ import { hmac } from './hmac.js';
10
+
11
+ @Describe()
12
+ export class HmacTest extends Suite {
13
+ @Test()
14
+ public async sha256(): Promise<void> {
15
+ const actual = hmac(
16
+ 'sha256',
17
+ Buffer.alloc(20, 0x0b),
18
+ Buffer.from('Hi There', 'utf8'),
19
+ );
20
+
21
+ expect(actual.toString('hex')).toBe(
22
+ 'b0344c61d8db38535ca8afceaf0bf12b'
23
+ + '881dc200c9833da726e9376c2e32cff7',
24
+ );
25
+ }
26
+
27
+ @Test()
28
+ public async acceptsSeveralValues(): Promise<void> {
29
+ const complete = hmac(
30
+ 'sha256',
31
+ Buffer.from('Jefe', 'utf8'),
32
+ Buffer.from('what do ya want ', 'utf8'),
33
+ Buffer.from('for nothing?', 'utf8'),
34
+ );
35
+ const joined = hmac(
36
+ 'sha256',
37
+ Buffer.from('Jefe', 'utf8'),
38
+ Buffer.from('what do ya want for nothing?', 'utf8'),
39
+ );
40
+
41
+ expect(complete).toMatchObject(joined);
42
+ }
43
+ }