@gjsify/crypto 0.3.13 → 0.3.14
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/lib/esm/asn1.js +576 -450
- package/lib/esm/bigint-math.js +37 -28
- package/lib/esm/cipher.js +1252 -1229
- package/lib/esm/constants.js +12 -13
- package/lib/esm/crypto-utils.js +54 -36
- package/lib/esm/dh.js +408 -368
- package/lib/esm/ecdh.js +403 -321
- package/lib/esm/ecdsa.js +138 -111
- package/lib/esm/hash.js +100 -89
- package/lib/esm/hkdf.js +65 -47
- package/lib/esm/hmac.js +95 -90
- package/lib/esm/index.js +75 -148
- package/lib/esm/key-object.js +348 -307
- package/lib/esm/mgf1.js +30 -24
- package/lib/esm/pbkdf2.js +66 -59
- package/lib/esm/public-encrypt.js +203 -156
- package/lib/esm/random.js +137 -124
- package/lib/esm/rsa-oaep.js +94 -87
- package/lib/esm/rsa-pss.js +95 -88
- package/lib/esm/scrypt.js +116 -115
- package/lib/esm/sign.js +267 -237
- package/lib/esm/timing-safe-equal.js +16 -11
- package/lib/esm/x509.js +215 -206
- package/package.json +7 -7
package/lib/esm/random.js
CHANGED
|
@@ -1,138 +1,151 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
|
+
|
|
3
|
+
//#region src/random.ts
|
|
2
4
|
const hasWebCrypto = typeof globalThis.crypto !== "undefined" && typeof globalThis.crypto.getRandomValues === "function";
|
|
5
|
+
/**
|
|
6
|
+
* Fill a Uint8Array with random bytes, using WebCrypto or GLib.Random fallback.
|
|
7
|
+
*/
|
|
3
8
|
function fillRandom(view) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
9
|
+
if (hasWebCrypto) {
|
|
10
|
+
for (let offset = 0; offset < view.length; offset += 65536) {
|
|
11
|
+
const length = Math.min(view.length - offset, 65536);
|
|
12
|
+
const slice = new Uint8Array(view.buffer, view.byteOffset + offset, length);
|
|
13
|
+
globalThis.crypto.getRandomValues(slice);
|
|
14
|
+
}
|
|
15
|
+
} else {
|
|
16
|
+
try {
|
|
17
|
+
const GLib = imports.gi.GLib;
|
|
18
|
+
for (let i = 0; i < view.length; i++) {
|
|
19
|
+
view[i] = GLib.random_int_range(0, 256);
|
|
20
|
+
}
|
|
21
|
+
} catch {
|
|
22
|
+
for (let i = 0; i < view.length; i++) {
|
|
23
|
+
view[i] = Math.floor(Math.random() * 256);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
22
27
|
}
|
|
23
28
|
function randomBytes(size, callback) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
29
|
+
if (typeof size !== "number" || size < 0 || !Number.isInteger(size)) {
|
|
30
|
+
throw new TypeError(`The "size" argument must be a non-negative integer. Received ${size}`);
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const buf = Buffer.alloc(size);
|
|
34
|
+
if (size > 0) {
|
|
35
|
+
fillRandom(new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
|
|
36
|
+
}
|
|
37
|
+
if (callback) {
|
|
38
|
+
callback(null, buf);
|
|
39
|
+
} else {
|
|
40
|
+
return buf;
|
|
41
|
+
}
|
|
42
|
+
} catch (err) {
|
|
43
|
+
if (callback) {
|
|
44
|
+
callback(err, Buffer.alloc(0));
|
|
45
|
+
} else {
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
44
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Fill a buffer with cryptographically strong pseudo-random data (synchronous).
|
|
52
|
+
*/
|
|
45
53
|
function randomFillSync(buffer, offset = 0, size) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
const length = size ?? buffer.length - offset;
|
|
55
|
+
if (offset < 0 || offset > buffer.length) {
|
|
56
|
+
throw new RangeError(`The value of "offset" is out of range. Received ${offset}`);
|
|
57
|
+
}
|
|
58
|
+
if (length < 0 || offset + length > buffer.length) {
|
|
59
|
+
throw new RangeError(`The value of "size" is out of range. Received ${length}`);
|
|
60
|
+
}
|
|
61
|
+
if (length > 0) {
|
|
62
|
+
const byteOffset = buffer instanceof Buffer ? buffer.byteOffset : buffer.byteOffset;
|
|
63
|
+
const view = new Uint8Array(buffer.buffer, byteOffset + offset, length);
|
|
64
|
+
fillRandom(view);
|
|
65
|
+
}
|
|
66
|
+
return buffer;
|
|
59
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Fill a buffer with cryptographically strong pseudo-random data (async).
|
|
70
|
+
*/
|
|
60
71
|
function randomFill(buffer, offset, size, callback) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
72
|
+
let _offset;
|
|
73
|
+
let _size;
|
|
74
|
+
let _callback;
|
|
75
|
+
if (typeof offset === "function") {
|
|
76
|
+
_callback = offset;
|
|
77
|
+
_offset = 0;
|
|
78
|
+
_size = buffer.length;
|
|
79
|
+
} else if (typeof size === "function") {
|
|
80
|
+
_callback = size;
|
|
81
|
+
_offset = offset;
|
|
82
|
+
_size = buffer.length - offset;
|
|
83
|
+
} else {
|
|
84
|
+
_callback = callback;
|
|
85
|
+
_offset = offset;
|
|
86
|
+
_size = size;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
randomFillSync(buffer, _offset, _size);
|
|
90
|
+
_callback(null, buffer);
|
|
91
|
+
} catch (err) {
|
|
92
|
+
_callback(err, buffer);
|
|
93
|
+
}
|
|
83
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Generate a random UUID v4 string.
|
|
97
|
+
*/
|
|
84
98
|
function randomUUID() {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
99
|
+
if (hasWebCrypto && typeof globalThis.crypto.randomUUID === "function") {
|
|
100
|
+
return globalThis.crypto.randomUUID();
|
|
101
|
+
}
|
|
102
|
+
const bytes = new Uint8Array(16);
|
|
103
|
+
fillRandom(bytes);
|
|
104
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
105
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
106
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
107
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
94
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Generate a random integer between min (inclusive) and max (exclusive).
|
|
111
|
+
*/
|
|
95
112
|
function randomInt(min, max, callback) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
113
|
+
let _min;
|
|
114
|
+
let _max;
|
|
115
|
+
let _callback;
|
|
116
|
+
if (typeof max === "function") {
|
|
117
|
+
_callback = max;
|
|
118
|
+
_max = min;
|
|
119
|
+
_min = 0;
|
|
120
|
+
} else if (typeof max === "number") {
|
|
121
|
+
_min = min;
|
|
122
|
+
_max = max;
|
|
123
|
+
_callback = callback;
|
|
124
|
+
} else {
|
|
125
|
+
_max = min;
|
|
126
|
+
_min = 0;
|
|
127
|
+
_callback = callback;
|
|
128
|
+
}
|
|
129
|
+
if (!Number.isInteger(_min)) {
|
|
130
|
+
throw new TypeError(`The "min" argument must be a safe integer. Received ${_min}`);
|
|
131
|
+
}
|
|
132
|
+
if (!Number.isInteger(_max)) {
|
|
133
|
+
throw new TypeError(`The "max" argument must be a safe integer. Received ${_max}`);
|
|
134
|
+
}
|
|
135
|
+
if (_min >= _max) {
|
|
136
|
+
throw new RangeError(`The value of "min" must be less than "max". Received min: ${_min}, max: ${_max}`);
|
|
137
|
+
}
|
|
138
|
+
const range = _max - _min;
|
|
139
|
+
const bytes = new Uint32Array(1);
|
|
140
|
+
const view = new Uint8Array(bytes.buffer);
|
|
141
|
+
fillRandom(view);
|
|
142
|
+
const value = _min + bytes[0] % range;
|
|
143
|
+
if (_callback) {
|
|
144
|
+
_callback(null, value);
|
|
145
|
+
} else {
|
|
146
|
+
return value;
|
|
147
|
+
}
|
|
131
148
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
randomFillSync,
|
|
136
|
-
randomInt,
|
|
137
|
-
randomUUID
|
|
138
|
-
};
|
|
149
|
+
|
|
150
|
+
//#endregion
|
|
151
|
+
export { randomBytes, randomFill, randomFillSync, randomInt, randomUUID };
|
package/lib/esm/rsa-oaep.js
CHANGED
|
@@ -1,95 +1,102 @@
|
|
|
1
|
-
import { Hash } from "./hash.js";
|
|
2
|
-
import { randomBytes } from "./random.js";
|
|
3
|
-
import { mgf1 } from "./mgf1.js";
|
|
4
1
|
import { parsePemKey, rsaKeySize } from "./asn1.js";
|
|
2
|
+
import { bigIntToBytes, bytesToBigInt, modPow } from "./bigint-math.js";
|
|
5
3
|
import { hashSize } from "./crypto-utils.js";
|
|
6
|
-
import {
|
|
4
|
+
import { randomBytes } from "./random.js";
|
|
5
|
+
import { Hash } from "./hash.js";
|
|
6
|
+
import { mgf1 } from "./mgf1.js";
|
|
7
|
+
|
|
8
|
+
//#region src/rsa-oaep.ts
|
|
7
9
|
function hashDigest(algo, data) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
const h = new Hash(algo);
|
|
11
|
+
h.update(data);
|
|
12
|
+
return new Uint8Array(h.digest());
|
|
11
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* RSA-OAEP encrypt using PEM public key.
|
|
16
|
+
*/
|
|
12
17
|
function rsaOaepEncrypt(hashAlgo, pubKeyPem, plaintext, label) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
18
|
+
const parsed = parsePemKey(pubKeyPem);
|
|
19
|
+
let n, e;
|
|
20
|
+
if (parsed.type === "rsa-public") {
|
|
21
|
+
({n, e} = parsed.components);
|
|
22
|
+
} else if (parsed.type === "rsa-private") {
|
|
23
|
+
({n, e} = parsed.components);
|
|
24
|
+
} else {
|
|
25
|
+
throw new Error("RSA-OAEP: expected RSA key");
|
|
26
|
+
}
|
|
27
|
+
const keyBytes = Math.ceil(rsaKeySize(n) / 8);
|
|
28
|
+
const hLen = hashSize(hashAlgo);
|
|
29
|
+
if (plaintext.length > keyBytes - 2 * hLen - 2) {
|
|
30
|
+
throw new Error("RSA-OAEP: message too long");
|
|
31
|
+
}
|
|
32
|
+
const lHash = hashDigest(hashAlgo, label || new Uint8Array(0));
|
|
33
|
+
const dbLen = keyBytes - hLen - 1;
|
|
34
|
+
const DB = new Uint8Array(dbLen);
|
|
35
|
+
DB.set(lHash, 0);
|
|
36
|
+
DB[dbLen - plaintext.length - 1] = 1;
|
|
37
|
+
DB.set(plaintext, dbLen - plaintext.length);
|
|
38
|
+
const seed = new Uint8Array(randomBytes(hLen));
|
|
39
|
+
const dbMask = mgf1(hashAlgo, seed, dbLen);
|
|
40
|
+
const maskedDB = new Uint8Array(dbLen);
|
|
41
|
+
for (let i = 0; i < dbLen; i++) maskedDB[i] = DB[i] ^ dbMask[i];
|
|
42
|
+
const seedMask = mgf1(hashAlgo, maskedDB, hLen);
|
|
43
|
+
const maskedSeed = new Uint8Array(hLen);
|
|
44
|
+
for (let i = 0; i < hLen; i++) maskedSeed[i] = seed[i] ^ seedMask[i];
|
|
45
|
+
const EM = new Uint8Array(keyBytes);
|
|
46
|
+
EM[0] = 0;
|
|
47
|
+
EM.set(maskedSeed, 1);
|
|
48
|
+
EM.set(maskedDB, 1 + hLen);
|
|
49
|
+
const m = bytesToBigInt(EM);
|
|
50
|
+
const c = modPow(m, e, n);
|
|
51
|
+
return bigIntToBytes(c, keyBytes);
|
|
47
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* RSA-OAEP decrypt using PEM private key.
|
|
55
|
+
*/
|
|
48
56
|
function rsaOaepDecrypt(hashAlgo, privKeyPem, ciphertext, label) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
57
|
+
const parsed = parsePemKey(privKeyPem);
|
|
58
|
+
if (parsed.type !== "rsa-private") throw new Error("RSA-OAEP: expected RSA private key");
|
|
59
|
+
const { n, d } = parsed.components;
|
|
60
|
+
const keyBytes = Math.ceil(rsaKeySize(n) / 8);
|
|
61
|
+
const hLen = hashSize(hashAlgo);
|
|
62
|
+
if (ciphertext.length !== keyBytes || keyBytes < 2 * hLen + 2) {
|
|
63
|
+
throw new Error("RSA-OAEP: decryption error");
|
|
64
|
+
}
|
|
65
|
+
const c = bytesToBigInt(ciphertext);
|
|
66
|
+
const m = modPow(c, d, n);
|
|
67
|
+
const EM = bigIntToBytes(m, keyBytes);
|
|
68
|
+
const Y = EM[0];
|
|
69
|
+
const maskedSeed = EM.slice(1, 1 + hLen);
|
|
70
|
+
const maskedDB = EM.slice(1 + hLen);
|
|
71
|
+
const dbLen = keyBytes - hLen - 1;
|
|
72
|
+
const seedMask = mgf1(hashAlgo, maskedDB, hLen);
|
|
73
|
+
const seed = new Uint8Array(hLen);
|
|
74
|
+
for (let i = 0; i < hLen; i++) seed[i] = maskedSeed[i] ^ seedMask[i];
|
|
75
|
+
const dbMask = mgf1(hashAlgo, seed, dbLen);
|
|
76
|
+
const DB = new Uint8Array(dbLen);
|
|
77
|
+
for (let i = 0; i < dbLen; i++) DB[i] = maskedDB[i] ^ dbMask[i];
|
|
78
|
+
const lHash = hashDigest(hashAlgo, label || new Uint8Array(0));
|
|
79
|
+
const lHashPrime = DB.slice(0, hLen);
|
|
80
|
+
let valid = Y === 0 ? 1 : 0;
|
|
81
|
+
for (let i = 0; i < hLen; i++) {
|
|
82
|
+
valid &= lHash[i] === lHashPrime[i] ? 1 : 0;
|
|
83
|
+
}
|
|
84
|
+
let sepIdx = -1;
|
|
85
|
+
for (let i = hLen; i < dbLen; i++) {
|
|
86
|
+
if (DB[i] === 1) {
|
|
87
|
+
sepIdx = i;
|
|
88
|
+
break;
|
|
89
|
+
} else if (DB[i] !== 0) {
|
|
90
|
+
valid = 0;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (sepIdx === -1) valid = 0;
|
|
95
|
+
if (!valid) {
|
|
96
|
+
throw new Error("RSA-OAEP: decryption error");
|
|
97
|
+
}
|
|
98
|
+
return DB.slice(sepIdx + 1);
|
|
91
99
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
};
|
|
100
|
+
|
|
101
|
+
//#endregion
|
|
102
|
+
export { rsaOaepDecrypt, rsaOaepEncrypt };
|
package/lib/esm/rsa-pss.js
CHANGED
|
@@ -1,100 +1,107 @@
|
|
|
1
|
-
import { Hash } from "./hash.js";
|
|
2
|
-
import { randomBytes } from "./random.js";
|
|
3
|
-
import { mgf1 } from "./mgf1.js";
|
|
4
1
|
import { parsePemKey, rsaKeySize } from "./asn1.js";
|
|
2
|
+
import { bigIntToBytes, bytesToBigInt, modPow } from "./bigint-math.js";
|
|
5
3
|
import { hashSize } from "./crypto-utils.js";
|
|
6
|
-
import {
|
|
4
|
+
import { randomBytes } from "./random.js";
|
|
5
|
+
import { Hash } from "./hash.js";
|
|
6
|
+
import { mgf1 } from "./mgf1.js";
|
|
7
|
+
|
|
8
|
+
//#region src/rsa-pss.ts
|
|
7
9
|
function hashDigest(algo, data) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
const h = new Hash(algo);
|
|
11
|
+
h.update(data);
|
|
12
|
+
return new Uint8Array(h.digest());
|
|
11
13
|
}
|
|
12
14
|
function emsaPssEncode(mHash, emBits, hashAlgo, saltLength) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
15
|
+
const hLen = hashSize(hashAlgo);
|
|
16
|
+
const emLen = Math.ceil(emBits / 8);
|
|
17
|
+
if (emLen < hLen + saltLength + 2) {
|
|
18
|
+
throw new Error("RSA-PSS: encoding error — key too short");
|
|
19
|
+
}
|
|
20
|
+
const salt = saltLength > 0 ? new Uint8Array(randomBytes(saltLength)) : new Uint8Array(0);
|
|
21
|
+
const mPrime = new Uint8Array(8 + hLen + saltLength);
|
|
22
|
+
mPrime.set(mHash, 8);
|
|
23
|
+
mPrime.set(salt, 8 + hLen);
|
|
24
|
+
const H = hashDigest(hashAlgo, mPrime);
|
|
25
|
+
const dbLen = emLen - hLen - 1;
|
|
26
|
+
const DB = new Uint8Array(dbLen);
|
|
27
|
+
DB[dbLen - saltLength - 1] = 1;
|
|
28
|
+
DB.set(salt, dbLen - saltLength);
|
|
29
|
+
const dbMask = mgf1(hashAlgo, H, dbLen);
|
|
30
|
+
const maskedDB = new Uint8Array(dbLen);
|
|
31
|
+
for (let i = 0; i < dbLen; i++) maskedDB[i] = DB[i] ^ dbMask[i];
|
|
32
|
+
const zeroBits = 8 * emLen - emBits;
|
|
33
|
+
if (zeroBits > 0) maskedDB[0] &= 255 >>> zeroBits;
|
|
34
|
+
const EM = new Uint8Array(emLen);
|
|
35
|
+
EM.set(maskedDB, 0);
|
|
36
|
+
EM.set(H, dbLen);
|
|
37
|
+
EM[emLen - 1] = 188;
|
|
38
|
+
return EM;
|
|
37
39
|
}
|
|
38
40
|
function emsaPssVerify(mHash, EM, emBits, hashAlgo, saltLength) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
41
|
+
const hLen = hashSize(hashAlgo);
|
|
42
|
+
const emLen = Math.ceil(emBits / 8);
|
|
43
|
+
if (emLen < hLen + saltLength + 2) return false;
|
|
44
|
+
if (EM[emLen - 1] !== 188) return false;
|
|
45
|
+
const dbLen = emLen - hLen - 1;
|
|
46
|
+
const maskedDB = EM.slice(0, dbLen);
|
|
47
|
+
const H = EM.slice(dbLen, dbLen + hLen);
|
|
48
|
+
const zeroBits = 8 * emLen - emBits;
|
|
49
|
+
if (zeroBits > 0 && (maskedDB[0] & 255 << 8 - zeroBits) !== 0) return false;
|
|
50
|
+
const dbMask = mgf1(hashAlgo, H, dbLen);
|
|
51
|
+
const DB = new Uint8Array(dbLen);
|
|
52
|
+
for (let i = 0; i < dbLen; i++) DB[i] = maskedDB[i] ^ dbMask[i];
|
|
53
|
+
if (zeroBits > 0) DB[0] &= 255 >>> zeroBits;
|
|
54
|
+
for (let i = 0; i < dbLen - saltLength - 1; i++) {
|
|
55
|
+
if (DB[i] !== 0) return false;
|
|
56
|
+
}
|
|
57
|
+
if (DB[dbLen - saltLength - 1] !== 1) return false;
|
|
58
|
+
const salt = DB.slice(dbLen - saltLength);
|
|
59
|
+
const mPrime = new Uint8Array(8 + hLen + saltLength);
|
|
60
|
+
mPrime.set(mHash, 8);
|
|
61
|
+
mPrime.set(salt, 8 + hLen);
|
|
62
|
+
const HPrime = hashDigest(hashAlgo, mPrime);
|
|
63
|
+
if (H.length !== HPrime.length) return false;
|
|
64
|
+
let diff = 0;
|
|
65
|
+
for (let i = 0; i < H.length; i++) diff |= H[i] ^ HPrime[i];
|
|
66
|
+
return diff === 0;
|
|
65
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* RSA-PSS sign using PEM private key.
|
|
70
|
+
*/
|
|
66
71
|
function rsaPssSign(hashAlgo, privKeyPem, data, saltLength) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
const parsed = parsePemKey(privKeyPem);
|
|
73
|
+
if (parsed.type !== "rsa-private") throw new Error("RSA-PSS: expected RSA private key");
|
|
74
|
+
const { n, d } = parsed.components;
|
|
75
|
+
const keyBits = rsaKeySize(n);
|
|
76
|
+
const keyBytes = Math.ceil(keyBits / 8);
|
|
77
|
+
const mHash = hashDigest(hashAlgo, data);
|
|
78
|
+
const EM = emsaPssEncode(mHash, keyBits - 1, hashAlgo, saltLength);
|
|
79
|
+
const m = bytesToBigInt(EM);
|
|
80
|
+
const s = modPow(m, d, n);
|
|
81
|
+
return bigIntToBytes(s, keyBytes);
|
|
77
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* RSA-PSS verify using PEM public key.
|
|
85
|
+
*/
|
|
78
86
|
function rsaPssVerify(hashAlgo, pubKeyPem, signature, data, saltLength) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
87
|
+
const parsed = parsePemKey(pubKeyPem);
|
|
88
|
+
let n, e;
|
|
89
|
+
if (parsed.type === "rsa-public") {
|
|
90
|
+
({n, e} = parsed.components);
|
|
91
|
+
} else if (parsed.type === "rsa-private") {
|
|
92
|
+
({n, e} = parsed.components);
|
|
93
|
+
} else {
|
|
94
|
+
throw new Error("RSA-PSS: expected RSA key");
|
|
95
|
+
}
|
|
96
|
+
const keyBits = rsaKeySize(n);
|
|
97
|
+
const keyBytes = Math.ceil(keyBits / 8);
|
|
98
|
+
if (signature.length !== keyBytes) return false;
|
|
99
|
+
const s = bytesToBigInt(signature);
|
|
100
|
+
const m = modPow(s, e, n);
|
|
101
|
+
const EM = bigIntToBytes(m, Math.ceil((keyBits - 1) / 8));
|
|
102
|
+
const mHash = hashDigest(hashAlgo, data);
|
|
103
|
+
return emsaPssVerify(mHash, EM, keyBits - 1, hashAlgo, saltLength);
|
|
96
104
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
};
|
|
105
|
+
|
|
106
|
+
//#endregion
|
|
107
|
+
export { rsaPssSign, rsaPssVerify };
|