@hg-ts/rsa 0.7.25 → 0.7.26
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/README.md +331 -0
- package/dist/X25519/index.d.ts +4 -0
- package/dist/X25519/index.d.ts.map +1 -0
- package/dist/X25519/index.js +4 -0
- package/dist/X25519/index.js.map +1 -0
- package/dist/X25519/key-pair.d.ts +13 -0
- package/dist/X25519/key-pair.d.ts.map +1 -0
- package/dist/X25519/key-pair.js +39 -0
- package/dist/X25519/key-pair.js.map +1 -0
- package/dist/X25519/private-key.d.ts +11 -0
- package/dist/X25519/private-key.d.ts.map +1 -0
- package/dist/X25519/private-key.js +40 -0
- package/dist/X25519/private-key.js.map +1 -0
- package/dist/X25519/public-key.d.ts +13 -0
- package/dist/X25519/public-key.d.ts.map +1 -0
- package/dist/X25519/public-key.js +49 -0
- package/dist/X25519/public-key.js.map +1 -0
- package/dist/X25519/utils.d.ts +2 -0
- package/dist/X25519/utils.d.ts.map +1 -0
- package/dist/X25519/utils.js +12 -0
- package/dist/X25519/utils.js.map +1 -0
- package/dist/X25519/x25519.test.d.ts +18 -0
- package/dist/X25519/x25519.test.d.ts.map +1 -0
- package/dist/X25519/x25519.test.js +183 -0
- package/dist/X25519/x25519.test.js.map +1 -0
- package/dist/base/index.d.ts +5 -0
- package/dist/base/index.d.ts.map +1 -0
- package/dist/base/index.js +5 -0
- package/dist/base/index.js.map +1 -0
- package/dist/base/key-pair.d.ts +27 -0
- package/dist/base/key-pair.d.ts.map +1 -0
- package/dist/base/key-pair.js +29 -0
- package/dist/base/key-pair.js.map +1 -0
- package/dist/base/key.d.ts +7 -0
- package/dist/base/key.d.ts.map +1 -0
- package/dist/base/key.js +10 -0
- package/dist/base/key.js.map +1 -0
- package/dist/base/private-key.d.ts +11 -0
- package/dist/base/private-key.d.ts.map +1 -0
- package/dist/base/private-key.js +4 -0
- package/dist/base/private-key.js.map +1 -0
- package/dist/base/public-key.d.ts +6 -0
- package/dist/base/public-key.d.ts.map +1 -0
- package/dist/base/public-key.js +4 -0
- package/dist/base/public-key.js.map +1 -0
- package/dist/exceptions/index.d.ts +2 -0
- package/dist/exceptions/index.d.ts.map +1 -0
- package/dist/exceptions/index.js +2 -0
- package/dist/exceptions/index.js.map +1 -0
- package/dist/exceptions/invalid-decryption-key.expection.d.ts +5 -0
- package/dist/exceptions/invalid-decryption-key.expection.d.ts.map +1 -0
- package/dist/exceptions/invalid-decryption-key.expection.js +7 -0
- package/dist/exceptions/invalid-decryption-key.expection.js.map +1 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/rsa/index.d.ts +4 -0
- package/dist/rsa/index.d.ts.map +1 -0
- package/dist/rsa/index.js +4 -0
- package/dist/rsa/index.js.map +1 -0
- package/dist/rsa/key-pair.d.ts +12 -0
- package/dist/rsa/key-pair.d.ts.map +1 -0
- package/dist/rsa/key-pair.js +40 -0
- package/dist/rsa/key-pair.js.map +1 -0
- package/dist/rsa/private-key.d.ts +16 -0
- package/dist/rsa/private-key.d.ts.map +1 -0
- package/dist/rsa/private-key.js +65 -0
- package/dist/rsa/private-key.js.map +1 -0
- package/dist/rsa/public-key.d.ts +19 -0
- package/dist/rsa/public-key.d.ts.map +1 -0
- package/dist/{rsa.public-key.js → rsa/public-key.js} +42 -9
- package/dist/rsa/public-key.js.map +1 -0
- package/dist/{rsa.test.d.ts → rsa/rsa.test.d.ts} +2 -0
- package/dist/rsa/rsa.test.d.ts.map +1 -0
- package/dist/{rsa.test.js → rsa/rsa.test.js} +56 -29
- package/dist/rsa/rsa.test.js.map +1 -0
- package/package.json +11 -9
- package/src/X25519/index.ts +3 -0
- package/src/X25519/key-pair.ts +58 -0
- package/src/X25519/private-key.ts +54 -0
- package/src/X25519/public-key.ts +68 -0
- package/src/X25519/utils.ts +22 -0
- package/src/X25519/x25519.test.ts +150 -0
- package/src/base/index.ts +4 -0
- package/src/base/key-pair.ts +75 -0
- package/src/base/key.ts +13 -0
- package/src/base/private-key.ts +17 -0
- package/src/base/public-key.ts +7 -0
- package/src/exceptions/index.ts +1 -0
- package/src/exceptions/invalid-decryption-key.expection.ts +7 -0
- package/src/index.ts +4 -3
- package/src/rsa/index.ts +3 -0
- package/src/rsa/key-pair.ts +55 -0
- package/src/rsa/private-key.ts +82 -0
- package/src/rsa/public-key.ts +110 -0
- package/src/{rsa.test.ts → rsa/rsa.test.ts} +52 -31
- package/dist/rsa.base-key.d.ts +0 -17
- package/dist/rsa.base-key.d.ts.map +0 -1
- package/dist/rsa.base-key.js +0 -57
- package/dist/rsa.base-key.js.map +0 -1
- package/dist/rsa.key-pair.d.ts +0 -19
- package/dist/rsa.key-pair.d.ts.map +0 -1
- package/dist/rsa.key-pair.js +0 -46
- package/dist/rsa.key-pair.js.map +0 -1
- package/dist/rsa.private-key.d.ts +0 -13
- package/dist/rsa.private-key.d.ts.map +0 -1
- package/dist/rsa.private-key.js +0 -38
- package/dist/rsa.private-key.js.map +0 -1
- package/dist/rsa.public-key.d.ts +0 -13
- package/dist/rsa.public-key.d.ts.map +0 -1
- package/dist/rsa.public-key.js.map +0 -1
- package/dist/rsa.test.d.ts.map +0 -1
- package/dist/rsa.test.js.map +0 -1
- package/src/rsa.base-key.ts +0 -78
- package/src/rsa.key-pair.ts +0 -65
- package/src/rsa.private-key.ts +0 -50
- package/src/rsa.public-key.ts +0 -65
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { NotImplementedException } from '@hg-ts/exception';
|
|
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
|
+
import sodium from 'libsodium-wrappers';
|
|
11
|
+
|
|
12
|
+
import { InvalidDecryptionKeyExpection } from '../exceptions/index.js';
|
|
13
|
+
import { X25519KeyPair } from './key-pair.js';
|
|
14
|
+
import { X25519PrivateKey } from './private-key.js';
|
|
15
|
+
import { X25519PublicKey } from './public-key.js';
|
|
16
|
+
|
|
17
|
+
@Describe()
|
|
18
|
+
export class X25519Test extends Suite {
|
|
19
|
+
@Test()
|
|
20
|
+
public async encryption(): Promise<void> {
|
|
21
|
+
const bob = new X25519KeyPair();
|
|
22
|
+
const alice = new X25519KeyPair();
|
|
23
|
+
const value = Math.random().toString();
|
|
24
|
+
|
|
25
|
+
const encrypted = alice.encrypt(value, bob.publicKeyInstance);
|
|
26
|
+
|
|
27
|
+
expect(bob.decrypt(encrypted.toString('base64'), alice.publicKeyInstance)).toBe(value);
|
|
28
|
+
}
|
|
29
|
+
@Test()
|
|
30
|
+
public async duplicateEncryptionDifferences(): Promise<void> {
|
|
31
|
+
const alice = new X25519KeyPair();
|
|
32
|
+
const bob = new X25519KeyPair();
|
|
33
|
+
const value = Math.random().toString();
|
|
34
|
+
|
|
35
|
+
const encryptedA = alice.publicKeyInstance.encrypt(value, bob.privateKeyInstance);
|
|
36
|
+
const encryptedB = alice.publicKeyInstance.encrypt(value, bob.privateKeyInstance);
|
|
37
|
+
|
|
38
|
+
expect(encryptedA).not.toMatchObject(encryptedB);
|
|
39
|
+
expect(alice.decrypt(encryptedA, bob.publicKeyInstance)).toBe(value);
|
|
40
|
+
expect(alice.decrypt(encryptedB, bob.publicKeyInstance)).toBe(value);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@Test()
|
|
44
|
+
@ExpectException(InvalidDecryptionKeyExpection)
|
|
45
|
+
public async encryptionFailsForAnotherRecipient(): Promise<void> {
|
|
46
|
+
const bob = new X25519KeyPair();
|
|
47
|
+
const alice = new X25519KeyPair();
|
|
48
|
+
const anotherRecipient = new X25519KeyPair();
|
|
49
|
+
const value = Math.random().toString();
|
|
50
|
+
|
|
51
|
+
const encrypted = alice.encrypt(value, bob.publicKeyInstance);
|
|
52
|
+
|
|
53
|
+
anotherRecipient.decrypt(encrypted, alice.publicKeyInstance);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@Test()
|
|
57
|
+
public async encryptionBuffer(): Promise<void> {
|
|
58
|
+
const bob = new X25519KeyPair();
|
|
59
|
+
const alice = new X25519KeyPair();
|
|
60
|
+
const value = Buffer.from(Math.random().toString(), 'utf8');
|
|
61
|
+
|
|
62
|
+
const encrypted = alice.encrypt(value, bob.publicKeyInstance);
|
|
63
|
+
|
|
64
|
+
expect(Buffer.from(bob.decrypt(encrypted, alice.publicKeyInstance))).toMatchObject(value);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@Test()
|
|
68
|
+
@ExpectException(NotImplementedException)
|
|
69
|
+
public async signature(): Promise<void> {
|
|
70
|
+
const x25519 = new X25519KeyPair();
|
|
71
|
+
const value = Math.random().toString();
|
|
72
|
+
|
|
73
|
+
x25519.sign(value);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@Test()
|
|
77
|
+
@ExpectException(NotImplementedException)
|
|
78
|
+
public async verify(): Promise<void> {
|
|
79
|
+
const x25519 = new X25519KeyPair();
|
|
80
|
+
const value = Math.random().toString();
|
|
81
|
+
|
|
82
|
+
x25519.verify(Buffer.from(value), value);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@Test()
|
|
86
|
+
public async keyPairFromPrivateKeyString(): Promise<void> {
|
|
87
|
+
const x25519 = new X25519KeyPair();
|
|
88
|
+
|
|
89
|
+
const keyPairFromKey = new X25519KeyPair({ privateKey: x25519.privateKey });
|
|
90
|
+
|
|
91
|
+
expect(keyPairFromKey.privateKey).toBe(x25519.privateKey);
|
|
92
|
+
expect(keyPairFromKey.publicKey).toBe(x25519.publicKey);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@Test()
|
|
96
|
+
public async seededWithNonScalarBytesLength(): Promise<void> {
|
|
97
|
+
const seed = Math.random().toString();
|
|
98
|
+
const x25519A = new X25519KeyPair({ seed });
|
|
99
|
+
const x25519B = new X25519KeyPair({ seed });
|
|
100
|
+
|
|
101
|
+
expect(Buffer.from(seed, 'utf8').length !== sodium.crypto_scalarmult_SCALARBYTES).toBeTruthy();
|
|
102
|
+
expect(x25519A.privateKey).toBe(x25519B.privateKey);
|
|
103
|
+
expect(x25519A.publicKey).toBe(x25519B.publicKey);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@Test()
|
|
107
|
+
public async seededWithScalarBytesLength(): Promise<void> {
|
|
108
|
+
const seed = 'a'.repeat(sodium.crypto_scalarmult_SCALARBYTES);
|
|
109
|
+
const x25519A = new X25519KeyPair({ seed });
|
|
110
|
+
const x25519B = new X25519KeyPair({ seed });
|
|
111
|
+
|
|
112
|
+
expect(Buffer.from(seed, 'utf8').length).toBe(sodium.crypto_scalarmult_SCALARBYTES);
|
|
113
|
+
expect(x25519A.privateKey).toBe(Buffer.from(seed, 'utf8').toString('base64'));
|
|
114
|
+
expect(x25519A.privateKey).toBe(x25519B.privateKey);
|
|
115
|
+
expect(x25519A.publicKey).toBe(x25519B.publicKey);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@Test()
|
|
119
|
+
public async privateKeyFromString(): Promise<void> {
|
|
120
|
+
const x25519 = new X25519KeyPair();
|
|
121
|
+
const privateKey = X25519PrivateKey.fromString(x25519.privateKey);
|
|
122
|
+
|
|
123
|
+
expect(privateKey.toString()).toBe(x25519.privateKey);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@Test()
|
|
127
|
+
public async publicKeyFromString(): Promise<void> {
|
|
128
|
+
const x25519 = new X25519KeyPair();
|
|
129
|
+
const publicKey = X25519PublicKey.fromString(x25519.publicKey);
|
|
130
|
+
|
|
131
|
+
expect(publicKey.toString()).toBe(x25519.publicKey);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@Test()
|
|
135
|
+
public async publicKeyValidationValid(): Promise<void> {
|
|
136
|
+
const x25519 = new X25519KeyPair();
|
|
137
|
+
|
|
138
|
+
X25519PublicKey.schema.parse(x25519.publicKey);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@Test()
|
|
142
|
+
@ExpectException(z.ZodError)
|
|
143
|
+
public async publicKeyValidationFails(): Promise<void> {
|
|
144
|
+
X25519PublicKey.schema.parse('{}');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
public override async setUp(): Promise<void> {
|
|
148
|
+
await sodium.ready;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BasePrivateKey,
|
|
3
|
+
StaticPrivateKey,
|
|
4
|
+
} from './private-key.js';
|
|
5
|
+
import { BasePublicKey } from './public-key.js';
|
|
6
|
+
|
|
7
|
+
export type NewKeyPairOptions = {
|
|
8
|
+
seed?: string;
|
|
9
|
+
bits?: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type KeyPairOptions<TPrivateKey extends BasePrivateKey> = NewKeyPairOptions & {
|
|
13
|
+
privateKey?: TPrivateKey | string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type KeyPairResult<
|
|
17
|
+
TPrivateKey extends BasePrivateKey<unknown, TPublicKey>,
|
|
18
|
+
TPublicKey extends BasePublicKey,
|
|
19
|
+
> = {
|
|
20
|
+
privateKey: TPrivateKey;
|
|
21
|
+
publicKey: TPublicKey;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export abstract class BaseKeyPair<
|
|
25
|
+
TPrivateKey extends BasePrivateKey<unknown, TPublicKey>,
|
|
26
|
+
TPublicKey extends BasePublicKey,
|
|
27
|
+
TOptions extends KeyPairOptions<TPrivateKey> = KeyPairOptions<TPrivateKey>,
|
|
28
|
+
> {
|
|
29
|
+
public readonly privateKeyInstance: TPrivateKey;
|
|
30
|
+
public readonly publicKeyInstance: TPublicKey;
|
|
31
|
+
|
|
32
|
+
protected constructor(options: TOptions, privateKeyCtor: StaticPrivateKey<TPrivateKey>) {
|
|
33
|
+
const keyPair = this.generateKeys(options, privateKeyCtor);
|
|
34
|
+
|
|
35
|
+
this.privateKeyInstance = keyPair.privateKey;
|
|
36
|
+
this.publicKeyInstance = keyPair.publicKey;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public abstract encrypt(value: string | Buffer, ...additionalArguments: unknown[]): Buffer;
|
|
40
|
+
|
|
41
|
+
public abstract decrypt(value: string | Buffer, ...additionalArguments: unknown[]): string;
|
|
42
|
+
|
|
43
|
+
public abstract sign(value: string): Buffer;
|
|
44
|
+
|
|
45
|
+
public abstract verify(signature: string | Buffer, value: string): boolean;
|
|
46
|
+
|
|
47
|
+
public get publicKey(): string {
|
|
48
|
+
return this.publicKeyInstance.toString();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public get privateKey(): string {
|
|
52
|
+
return this.privateKeyInstance.toString();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
protected generateKeys(
|
|
56
|
+
options: TOptions,
|
|
57
|
+
privateKeyCtor: StaticPrivateKey<TPrivateKey>,
|
|
58
|
+
): KeyPairResult<TPrivateKey, TPublicKey> {
|
|
59
|
+
const { privateKey, ...newKeyOptions } = options;
|
|
60
|
+
|
|
61
|
+
if (privateKey) {
|
|
62
|
+
const copyPrivateKey = typeof privateKey === 'string'
|
|
63
|
+
? privateKeyCtor.fromString(privateKey)
|
|
64
|
+
: privateKey;
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
privateKey: copyPrivateKey,
|
|
68
|
+
publicKey: copyPrivateKey.toPublicKey(),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return this.generateNewKeys(newKeyOptions as NewKeyPairOptions);
|
|
73
|
+
}
|
|
74
|
+
protected abstract generateNewKeys(options: NewKeyPairOptions): KeyPairResult<TPrivateKey, TPublicKey>;
|
|
75
|
+
}
|
package/src/base/key.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { BaseKey } from './key.js';
|
|
2
|
+
import { BasePublicKey } from './public-key.js';
|
|
3
|
+
|
|
4
|
+
export type StaticPrivateKey<T extends BasePrivateKey> = Class<T, any[]> & {
|
|
5
|
+
fromString(privateKey: string): T;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export abstract class BasePrivateKey<
|
|
9
|
+
TNativeKey = unknown,
|
|
10
|
+
TPublicKey extends BasePublicKey = BasePublicKey,
|
|
11
|
+
> extends BaseKey<TNativeKey> {
|
|
12
|
+
public abstract decrypt(encrypted: string | Buffer, ...additionalArguments: unknown[]): string;
|
|
13
|
+
|
|
14
|
+
public abstract sign(value: string): Buffer;
|
|
15
|
+
|
|
16
|
+
public abstract toPublicKey(): TPublicKey;
|
|
17
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { BaseKey } from './key.js';
|
|
2
|
+
|
|
3
|
+
export abstract class BasePublicKey<TNativeKey = unknown> extends BaseKey<TNativeKey> {
|
|
4
|
+
public abstract encrypt(value: string | Buffer, ...additionalArguments: unknown[]): Buffer;
|
|
5
|
+
|
|
6
|
+
public abstract verify(signature: string | Buffer, value: string): boolean;
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './invalid-decryption-key.expection.js';
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export * from './
|
|
2
|
-
export * from './
|
|
3
|
-
export * from './rsa.
|
|
1
|
+
export * from './base/index.js';
|
|
2
|
+
export * from './exceptions/index.js';
|
|
3
|
+
export * from './rsa/index.js';
|
|
4
|
+
export * from './X25519/index.js';
|
package/src/rsa/index.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import forge from 'node-forge';
|
|
2
|
+
import {
|
|
3
|
+
BaseKeyPair,
|
|
4
|
+
KeyPairOptions,
|
|
5
|
+
KeyPairResult,
|
|
6
|
+
NewKeyPairOptions,
|
|
7
|
+
} from '../base/index.js';
|
|
8
|
+
import { RsaPrivateKey } from './private-key.js';
|
|
9
|
+
import { RsaPublicKey } from './public-key.js';
|
|
10
|
+
|
|
11
|
+
export class RsaKeyPair extends BaseKeyPair<RsaPrivateKey, RsaPublicKey> {
|
|
12
|
+
public constructor(options: KeyPairOptions<RsaPrivateKey> = {}) {
|
|
13
|
+
super(options, RsaPrivateKey);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public override encrypt(value: string | Buffer): Buffer {
|
|
17
|
+
return this.publicKeyInstance.encrypt(value);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public override decrypt(value: string | Buffer): string {
|
|
21
|
+
return this.privateKeyInstance.decrypt(value);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public override sign(value: string): Buffer {
|
|
25
|
+
return this.privateKeyInstance.sign(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public override verify(signature: string | Buffer, value: string): boolean {
|
|
29
|
+
return this.publicKeyInstance.verify(signature, value);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
protected generateNewKeys(options: NewKeyPairOptions): KeyPairResult<RsaPrivateKey, RsaPublicKey> {
|
|
33
|
+
const { seed, bits } = options;
|
|
34
|
+
const keyPairOptions: forge.pki.rsa.GenerateKeyPairOptions = {};
|
|
35
|
+
|
|
36
|
+
if (bits) {
|
|
37
|
+
keyPairOptions.bits = bits;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (seed) {
|
|
41
|
+
const prng = forge.random.createInstance();
|
|
42
|
+
prng.seedFileSync = (): string => seed;
|
|
43
|
+
prng.seedFile = (): string => seed;
|
|
44
|
+
|
|
45
|
+
keyPairOptions.prng = prng;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const keyPair = forge.pki.rsa.generateKeyPair(keyPairOptions);
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
privateKey: new RsaPrivateKey(keyPair.privateKey),
|
|
52
|
+
publicKey: new RsaPublicKey(keyPair.publicKey),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import forge from 'node-forge';
|
|
2
|
+
import { BasePrivateKey } from '../base/index.js';
|
|
3
|
+
import { InvalidDecryptionKeyExpection } from '../exceptions/index.js';
|
|
4
|
+
import { RsaPublicKey } from './public-key.js';
|
|
5
|
+
|
|
6
|
+
export class RsaPrivateKey extends BasePrivateKey<forge.pki.rsa.PrivateKey, RsaPublicKey> {
|
|
7
|
+
public decrypt(encrypted: string | Buffer): string {
|
|
8
|
+
const md = this.getMd();
|
|
9
|
+
const chunks = this.prepareToDecrypt(encrypted);
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const decryptedChunks = chunks.map(chunk => this.key.decrypt(chunk, 'RSAES-PKCS1-V1_5', { md }));
|
|
13
|
+
|
|
14
|
+
return this.formatDecryptedOutput(decryptedChunks.join(''));
|
|
15
|
+
} catch {
|
|
16
|
+
throw new InvalidDecryptionKeyExpection();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public sign(value: string): Buffer {
|
|
21
|
+
const hash = this.getMd();
|
|
22
|
+
hash.update(value, 'utf8');
|
|
23
|
+
|
|
24
|
+
const pss = forge.pss.create({
|
|
25
|
+
md: this.getMd(),
|
|
26
|
+
mgf: forge.mgf.mgf1.create(this.getMd()),
|
|
27
|
+
saltLength: 20,
|
|
28
|
+
});
|
|
29
|
+
const signedId = this.key.sign(hash, pss);
|
|
30
|
+
|
|
31
|
+
return Buffer.from(signedId, 'binary');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public toPublicKey(): RsaPublicKey {
|
|
35
|
+
return new RsaPublicKey(forge.pki.rsa.setPublicKey(this.key.n, this.key.e));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public override toString(): string {
|
|
39
|
+
return forge.pki.privateKeyToPem(this.key);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public static fromString(pemKey: string): RsaPrivateKey {
|
|
43
|
+
const key = forge.pki.privateKeyFromPem(pemKey);
|
|
44
|
+
|
|
45
|
+
return new RsaPrivateKey(key);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private prepareToDecrypt(data: string | Buffer): string[] {
|
|
49
|
+
const encryptedLength = this.keyLength * 2;
|
|
50
|
+
const encrypted = typeof data === 'string' ? data : data.toString('hex');
|
|
51
|
+
|
|
52
|
+
return this.splitBinaryInput(encrypted, encryptedLength)
|
|
53
|
+
.map(chunk => Buffer
|
|
54
|
+
.from(chunk, 'hex')
|
|
55
|
+
.toString('binary'),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private formatDecryptedOutput(data: string): string {
|
|
60
|
+
return forge.util.decodeUtf8(data);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private getMd(): forge.md.MessageDigest {
|
|
64
|
+
return forge.md.sha256.create();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private get keyLength(): number {
|
|
68
|
+
return Math.ceil(this.key.n.bitLength() / 8);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private splitBinaryInput(value: string, length: number): string[] {
|
|
72
|
+
const chunks: string[] = [];
|
|
73
|
+
let index = 0;
|
|
74
|
+
|
|
75
|
+
while (index < value.length) {
|
|
76
|
+
chunks.push(value.slice(index, index + length));
|
|
77
|
+
index += length;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return chunks;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { z } from '@hg-ts/validation';
|
|
2
|
+
import forge from 'node-forge';
|
|
3
|
+
import { BasePublicKey } from '../base/index.js';
|
|
4
|
+
|
|
5
|
+
const schema = z.string().transform((value, ctx) => {
|
|
6
|
+
try {
|
|
7
|
+
const publicKey = forge.pki.publicKeyFromPem(value);
|
|
8
|
+
|
|
9
|
+
return forge.pki.publicKeyToPem(publicKey);
|
|
10
|
+
} catch (error) {
|
|
11
|
+
ctx.issues.push({
|
|
12
|
+
message: 'Invalid public key',
|
|
13
|
+
fatal: true,
|
|
14
|
+
code: 'invalid_format',
|
|
15
|
+
input: value,
|
|
16
|
+
format: 'RSA Public Key',
|
|
17
|
+
});
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
}).pipe(z.string());
|
|
21
|
+
|
|
22
|
+
export class RsaPublicKey extends BasePublicKey<forge.pki.rsa.PublicKey> {
|
|
23
|
+
public static schema = schema;
|
|
24
|
+
|
|
25
|
+
public encrypt(value: string | Buffer): Buffer {
|
|
26
|
+
const md = this.getMd();
|
|
27
|
+
const chunks = this.prepareToEncrypt(value);
|
|
28
|
+
|
|
29
|
+
const encryptedChunks = chunks.map(chunk => this.key.encrypt(chunk, 'RSAES-PKCS1-V1_5', { md }));
|
|
30
|
+
|
|
31
|
+
return this.formatEncrypted(encryptedChunks);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public verify(signature: string | Buffer, value: string): boolean {
|
|
35
|
+
const hash = this.getMd();
|
|
36
|
+
hash.update(value, 'utf8');
|
|
37
|
+
|
|
38
|
+
const pss = forge.pss.create({
|
|
39
|
+
md: this.getMd(),
|
|
40
|
+
mgf: forge.mgf.mgf1.create(this.getMd()),
|
|
41
|
+
saltLength: 20,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const formattedSignature = this.formatSignature(signature);
|
|
45
|
+
|
|
46
|
+
return this.key.verify(hash.digest().bytes(), formattedSignature, pss);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public override toString(): string {
|
|
50
|
+
return forge.pki.publicKeyToPem(this.key);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public static fromString(pemKey: string): RsaPublicKey {
|
|
54
|
+
const key = forge.pki.publicKeyFromPem(pemKey);
|
|
55
|
+
|
|
56
|
+
return new RsaPublicKey(key);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private prepareToEncrypt(data: string | Buffer): string[] {
|
|
60
|
+
const input = this.formatDecryptedInputToBinary(data);
|
|
61
|
+
|
|
62
|
+
return this.splitBinaryInput(input, this.maxLength);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private formatDecryptedInputToBinary(data: string | Buffer): string {
|
|
66
|
+
if (typeof data === 'string') {
|
|
67
|
+
return forge.util.encodeUtf8(data);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return Buffer.from(data).toString('hex');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private formatSignature(data: string | Buffer): string {
|
|
74
|
+
if (typeof data === 'string') {
|
|
75
|
+
return data;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return Buffer.from(data).toString('binary');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private formatEncrypted(data: string[]): Buffer {
|
|
82
|
+
const chunks = data.map(chunk => Buffer.from(chunk, 'binary').toString('hex'));
|
|
83
|
+
|
|
84
|
+
return Buffer.from(chunks.join(''), 'hex');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private getMd(): forge.md.MessageDigest {
|
|
88
|
+
return forge.md.sha256.create();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private get maxLength(): number {
|
|
92
|
+
return this.keyLength - (this.getMd().digestLength * 2) - 2;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private get keyLength(): number {
|
|
96
|
+
return Math.ceil(this.key.n.bitLength() / 8);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private splitBinaryInput(value: string, length: number): string[] {
|
|
100
|
+
const chunks: string[] = [];
|
|
101
|
+
let index = 0;
|
|
102
|
+
|
|
103
|
+
while (index < value.length) {
|
|
104
|
+
chunks.push(value.slice(index, index + length));
|
|
105
|
+
index += length;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return chunks;
|
|
109
|
+
}
|
|
110
|
+
}
|