@joclaim/tls 0.1.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.
- package/README.md +221 -0
- package/lib/crypto/common.d.ts +3 -0
- package/lib/crypto/common.js +26 -0
- package/lib/crypto/index.d.ts +3 -0
- package/lib/crypto/index.js +4 -0
- package/lib/crypto/insecure-rand.d.ts +1 -0
- package/lib/crypto/insecure-rand.js +9 -0
- package/lib/crypto/pure-js.d.ts +2 -0
- package/lib/crypto/pure-js.js +144 -0
- package/lib/crypto/webcrypto.d.ts +3 -0
- package/lib/crypto/webcrypto.js +310 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +4 -0
- package/lib/make-tls-client.d.ts +74 -0
- package/lib/make-tls-client.js +657 -0
- package/lib/scripts/build-jsc.d.ts +1 -0
- package/lib/scripts/build-jsc.js +20 -0
- package/lib/scripts/ca-template.d.ts +5 -0
- package/lib/scripts/ca-template.js +6 -0
- package/lib/scripts/fallbacks/crypto.d.ts +4 -0
- package/lib/scripts/fallbacks/crypto.js +2 -0
- package/lib/scripts/handshake.d.ts +1 -0
- package/lib/scripts/handshake.js +61 -0
- package/lib/scripts/jsc.d.ts +28 -0
- package/lib/scripts/jsc.js +92 -0
- package/lib/scripts/update-ca-certs.d.ts +1 -0
- package/lib/scripts/update-ca-certs.js +29 -0
- package/lib/types/crypto.d.ts +62 -0
- package/lib/types/crypto.js +1 -0
- package/lib/types/index.d.ts +15 -0
- package/lib/types/index.js +4 -0
- package/lib/types/logger.d.ts +6 -0
- package/lib/types/logger.js +1 -0
- package/lib/types/tls.d.ts +141 -0
- package/lib/types/tls.js +1 -0
- package/lib/types/x509.d.ts +32 -0
- package/lib/types/x509.js +1 -0
- package/lib/utils/additional-root-cas.d.ts +1 -0
- package/lib/utils/additional-root-cas.js +197 -0
- package/lib/utils/client-hello.d.ts +23 -0
- package/lib/utils/client-hello.js +167 -0
- package/lib/utils/constants.d.ts +239 -0
- package/lib/utils/constants.js +244 -0
- package/lib/utils/decryption-utils.d.ts +64 -0
- package/lib/utils/decryption-utils.js +166 -0
- package/lib/utils/finish-messages.d.ts +11 -0
- package/lib/utils/finish-messages.js +49 -0
- package/lib/utils/generics.d.ts +35 -0
- package/lib/utils/generics.js +146 -0
- package/lib/utils/index.d.ts +18 -0
- package/lib/utils/index.js +18 -0
- package/lib/utils/key-share.d.ts +13 -0
- package/lib/utils/key-share.js +72 -0
- package/lib/utils/key-update.d.ts +2 -0
- package/lib/utils/key-update.js +14 -0
- package/lib/utils/logger.d.ts +2 -0
- package/lib/utils/logger.js +15 -0
- package/lib/utils/make-queue.d.ts +3 -0
- package/lib/utils/make-queue.js +22 -0
- package/lib/utils/mozilla-root-cas.d.ts +5 -0
- package/lib/utils/mozilla-root-cas.js +4459 -0
- package/lib/utils/packets.d.ts +51 -0
- package/lib/utils/packets.js +148 -0
- package/lib/utils/parse-alert.d.ts +7 -0
- package/lib/utils/parse-alert.js +28 -0
- package/lib/utils/parse-certificate.d.ts +29 -0
- package/lib/utils/parse-certificate.js +188 -0
- package/lib/utils/parse-client-hello.d.ts +11 -0
- package/lib/utils/parse-client-hello.js +39 -0
- package/lib/utils/parse-extensions.d.ts +11 -0
- package/lib/utils/parse-extensions.js +74 -0
- package/lib/utils/parse-server-hello.d.ts +10 -0
- package/lib/utils/parse-server-hello.js +52 -0
- package/lib/utils/session-ticket.d.ts +17 -0
- package/lib/utils/session-ticket.js +51 -0
- package/lib/utils/wrapped-record.d.ts +25 -0
- package/lib/utils/wrapped-record.js +191 -0
- package/lib/utils/x509.d.ts +5 -0
- package/lib/utils/x509.js +124 -0
- package/package.json +82 -0
package/README.md
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
<div>
|
|
2
|
+
<div>
|
|
3
|
+
<img src="https://raw.githubusercontent.com/reclaimprotocol/.github/main/assets/banners/TLS.png" />
|
|
4
|
+
</div>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
A TLS client implementation in typescript. This library is fully compatible with the browser (without any polyfills), and on any other JavaScript environment.
|
|
8
|
+
|
|
9
|
+
As all the cryptography is handled by either "webcrypto" or a "pure-js" implementation if webcrypto is not available.
|
|
10
|
+
|
|
11
|
+
## Dependencies
|
|
12
|
+
|
|
13
|
+
1. The ChaCha20-Poly1305 cipher is not supported via WebCrypto -- so we utilise `@stablelib/chacha20-poly1305` to provide this functionality.
|
|
14
|
+
2. To handle X509 certificate validation we utilise `@peculiar/x509`
|
|
15
|
+
3. Optionally a few other crypto dependencies are used when using the `pure-js` implementation. These can be excluded when using WebCrypto.
|
|
16
|
+
|
|
17
|
+
## Supported Crypto Suites & Versions
|
|
18
|
+
|
|
19
|
+
### TLS
|
|
20
|
+
- TLS 1.2
|
|
21
|
+
- TLS 1.3
|
|
22
|
+
|
|
23
|
+
### Curves
|
|
24
|
+
- X25519 (only on NodeJs -- not supported in the browser)
|
|
25
|
+
- P-256 (SECP256R1)
|
|
26
|
+
- P-384 (SECP384R1)
|
|
27
|
+
|
|
28
|
+
### Signature Algorithms
|
|
29
|
+
- RSA-PSS-RSAE-SHA256
|
|
30
|
+
- ECDSA-SECP256R1-SHA256
|
|
31
|
+
- ECDSA-SECP256R1-SHA384
|
|
32
|
+
- ECDSA-SECP384R1-SHA256
|
|
33
|
+
- ECDSA-SECP384R1-SHA384
|
|
34
|
+
- RSA-PKCS1-SHA256
|
|
35
|
+
- RSA-PKCS1-SHA384
|
|
36
|
+
- RSA-PKCS1-SHA512
|
|
37
|
+
- RSA-PKCS1-SHA1
|
|
38
|
+
|
|
39
|
+
### Cipher Suites (TLS 1.3)
|
|
40
|
+
- AES-128-GCM-SHA256
|
|
41
|
+
- AES-256-GCM-SHA384
|
|
42
|
+
- CHACHA20-POLY1305-SHA256
|
|
43
|
+
|
|
44
|
+
### Cipher Suites (TLS 1.2)
|
|
45
|
+
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
|
|
46
|
+
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
|
|
47
|
+
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
|
48
|
+
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
|
|
49
|
+
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
|
|
50
|
+
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
|
|
51
|
+
- TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
|
|
52
|
+
- TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
|
|
53
|
+
|
|
54
|
+
### Certificates
|
|
55
|
+
- The entire Mozilla CA store is supported
|
|
56
|
+
- A few additional certificates have also been added. See `src/utils/root-ca.ts`
|
|
57
|
+
- We also implement fetching intermediate certificates via AIA fetching. Any fetched certificates are also then verified against the root CA store.
|
|
58
|
+
|
|
59
|
+
## Install
|
|
60
|
+
|
|
61
|
+
Edge version:
|
|
62
|
+
``` sh
|
|
63
|
+
npm i git+https://github.com/reclaimprotocol/tls
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Set Crypto Implementation
|
|
67
|
+
|
|
68
|
+
When on the browser, NodeJS or another NodeJS like runtime (such as Bun), you can set the crypto implementation to use the native `webcrypto` API. This is the most performant way to use this library.
|
|
69
|
+
``` ts
|
|
70
|
+
import { setCryptoImplementation } from '@reclaimprotocol/tls'
|
|
71
|
+
import { webcryptoCrypto } from '@reclaimprotocol/tls/webcrypto'
|
|
72
|
+
|
|
73
|
+
setCryptoImplementation(webcryptoCrypto)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
If webcrypto is not available, you can use the `pure-js` implementation. This is slower, but works in all JavaScript environments -- even JavascriptCore.
|
|
77
|
+
``` ts
|
|
78
|
+
import { setCryptoImplementation } from '@reclaimprotocol/tls'
|
|
79
|
+
import { pureJsCrypto } from '@reclaimprotocol/tls/pure-js'
|
|
80
|
+
|
|
81
|
+
setCryptoImplementation(pureJsCrypto)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Example Usage
|
|
85
|
+
|
|
86
|
+
After you've set the crypto implementation, you can use the TLS client like this:
|
|
87
|
+
|
|
88
|
+
``` ts
|
|
89
|
+
import { Socket } from 'net'
|
|
90
|
+
import { makeTLSClient, uint8ArrayToBinaryStr } from '@reclaimprotocol/tls'
|
|
91
|
+
|
|
92
|
+
const socket = new Socket()
|
|
93
|
+
const host = 'www.google.com'
|
|
94
|
+
const port = 443
|
|
95
|
+
|
|
96
|
+
const tls = makeTLSClient({
|
|
97
|
+
host,
|
|
98
|
+
// verify the server's certificate
|
|
99
|
+
// disable when using self-signed certificates
|
|
100
|
+
// or if you don't care about the authenticity
|
|
101
|
+
// of the server
|
|
102
|
+
verifyServerCertificate: true,
|
|
103
|
+
// only use the following cipher suites
|
|
104
|
+
// leave undefined to use all supported cipher suites
|
|
105
|
+
cipherSuites: [
|
|
106
|
+
'TLS_CHACHA20_POLY1305_SHA256'
|
|
107
|
+
// ... other suites
|
|
108
|
+
],
|
|
109
|
+
// write raw bytes to the socket
|
|
110
|
+
async write({ header, content }) {
|
|
111
|
+
socket.write(header)
|
|
112
|
+
socket.write(content)
|
|
113
|
+
},
|
|
114
|
+
onHandshake() {
|
|
115
|
+
console.log('handshake completed successfully')
|
|
116
|
+
// write encrypted data to the socket
|
|
117
|
+
const getReq = `GET / HTTP/1.1\r\nHost: ${host}\r\n\r\n`
|
|
118
|
+
tls.write(Buffer.from(getReq))
|
|
119
|
+
},
|
|
120
|
+
onApplicationData(plaintext) {
|
|
121
|
+
const str = uint8ArrayToBinaryStr(plaintext)
|
|
122
|
+
console.log('received application data: ', str)
|
|
123
|
+
},
|
|
124
|
+
onTlsEnd(error) {
|
|
125
|
+
console.error('TLS connect ended: ', error)
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
socket.on('data', tls.handleReceivedBytes)
|
|
130
|
+
|
|
131
|
+
// start handshake as soon as the socket connects
|
|
132
|
+
socket.on('connect', () => tls.startHandshake())
|
|
133
|
+
|
|
134
|
+
// use the TCP socket to connect to the server
|
|
135
|
+
socket.connect({ host, port })
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Misc API Usage
|
|
139
|
+
|
|
140
|
+
Handle a Session Ticket & Resume Session with a PSK
|
|
141
|
+
``` ts
|
|
142
|
+
const tlsClient = makeTlsClient({
|
|
143
|
+
// ... other options
|
|
144
|
+
onSessionTicket(ticket) {
|
|
145
|
+
// get a PSK (pre-shared key) from the session ticket
|
|
146
|
+
const psk = tls.getPskFromTicket(ticket)
|
|
147
|
+
// this can be used to resume a session
|
|
148
|
+
// if disconnected, using
|
|
149
|
+
// tls.startHandshake({ psk })
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Handle received certificates
|
|
155
|
+
``` ts
|
|
156
|
+
const tlsClient = makeTlsClient({
|
|
157
|
+
// ... other options
|
|
158
|
+
|
|
159
|
+
// handle received certificates
|
|
160
|
+
// (if you want to for some reason)
|
|
161
|
+
onRecvCertificates({ certificates }) {
|
|
162
|
+
// do something I guess?
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Use the TLS KeyUpdate method to update the traffic keys. This sends a KeyUpdate message to the server & generates a fresh set of keys to encrypt/decrypt data. The server will then respond with a KeyUpdate message of its own. This is useful for forward secrecy.
|
|
168
|
+
```ts
|
|
169
|
+
await tls.updateTrafficKeys()
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Testing
|
|
173
|
+
|
|
174
|
+
Once you clone the repository, install dependencies via `npm i`, you can run the tests using:
|
|
175
|
+
```bash
|
|
176
|
+
npm run test:webcrypto
|
|
177
|
+
```
|
|
178
|
+
to test the WebCrypto implementation, or
|
|
179
|
+
```bash
|
|
180
|
+
npm run test:pure-js
|
|
181
|
+
```
|
|
182
|
+
to test the PureJS implementation.
|
|
183
|
+
|
|
184
|
+
If you want to test a connection to a host, you can use the `handshake.ts` script. This script will connect to the specified host and port, perform a TLS handshake, and log the result.
|
|
185
|
+
```bash
|
|
186
|
+
npm run handshake -- --host www.google.com
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
if you want to test `javascriptcore` compatibility, you can run the [jsc](/src/tests/jsc.test_mac.ts) test. This will run the tests in a JavaScriptCore environment, which is useful for testing compatibility with an ECMAScript environment that does not support WebCrypto.
|
|
190
|
+
Before you run the `jsc` test, make sure you have the `jsc` binary installed on your system & have built the `jsc` file using the `npm run build:jsc` command.
|
|
191
|
+
|
|
192
|
+
## Updating CA certificates
|
|
193
|
+
```bash
|
|
194
|
+
npm run update:root-ca
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Contributing to Our Project
|
|
198
|
+
|
|
199
|
+
We're excited that you're interested in contributing to our project! Before you get started, please take a moment to review the following guidelines.
|
|
200
|
+
|
|
201
|
+
## Code of Conduct
|
|
202
|
+
|
|
203
|
+
Please read and follow our [Code of Conduct](https://github.com/reclaimprotocol/.github/blob/main/Code-of-Conduct.md) to ensure a positive and inclusive environment for all contributors.
|
|
204
|
+
|
|
205
|
+
## Security
|
|
206
|
+
|
|
207
|
+
If you discover any security-related issues, please refer to our [Security Policy](https://github.com/reclaimprotocol/.github/blob/main/SECURITY.md) for information on how to responsibly disclose vulnerabilities.
|
|
208
|
+
|
|
209
|
+
## Contributor License Agreement
|
|
210
|
+
|
|
211
|
+
Before contributing to this project, please read and sign our [Contributor License Agreement (CLA)](https://github.com/reclaimprotocol/.github/blob/main/CLA.md).
|
|
212
|
+
|
|
213
|
+
## Indie Hackers
|
|
214
|
+
|
|
215
|
+
For Indie Hackers: [Check out our guidelines and potential grant opportunities](https://github.com/reclaimprotocol/.github/blob/main/Indie-Hackers.md)
|
|
216
|
+
|
|
217
|
+
## License
|
|
218
|
+
|
|
219
|
+
This project is licensed under a [custom license](https://github.com/reclaimprotocol/.github/blob/main/LICENSE). By contributing to this project, you agree that your contributions will be licensed under its terms.
|
|
220
|
+
|
|
221
|
+
Thank you for your contributions!
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { OriginatorPublicKey } from '@peculiar/asn1-cms';
|
|
2
|
+
import { RSAPublicKey } from '@peculiar/asn1-rsa';
|
|
3
|
+
import { AsnParser } from '@peculiar/asn1-schema';
|
|
4
|
+
export function parseRsaPublicKeyFromAsn1(asn1) {
|
|
5
|
+
const parsed = AsnParser.parse(asn1, OriginatorPublicKey);
|
|
6
|
+
const rsaPubKey = AsnParser.parse(parsed.publicKey, RSAPublicKey);
|
|
7
|
+
return {
|
|
8
|
+
e: bufToBigint(bufToUint8Array(rsaPubKey.publicExponent)),
|
|
9
|
+
n: bufToBigint(bufToUint8Array(rsaPubKey.modulus)),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function bufToUint8Array(buf) {
|
|
13
|
+
if (buf instanceof Uint8Array) {
|
|
14
|
+
return buf;
|
|
15
|
+
}
|
|
16
|
+
return new Uint8Array(buf);
|
|
17
|
+
}
|
|
18
|
+
const BITS = 8n;
|
|
19
|
+
function bufToBigint(buf) {
|
|
20
|
+
let ret = 0n;
|
|
21
|
+
for (const i of buf.values()) {
|
|
22
|
+
const bi = BigInt(i);
|
|
23
|
+
ret = (ret << BITS) + bi;
|
|
24
|
+
}
|
|
25
|
+
return ret;
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function randomBytes(length: number): Uint8Array<ArrayBuffer>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function randomBytes(length) {
|
|
2
|
+
// not the most secure but has to do for an env
|
|
3
|
+
// without crypto.getRandomValues
|
|
4
|
+
const bytes = new Uint8Array(length);
|
|
5
|
+
for (let i = 0; i < length; i++) {
|
|
6
|
+
bytes[i] = Math.floor(Math.random() * 256) % 256;
|
|
7
|
+
}
|
|
8
|
+
return bytes;
|
|
9
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { cbc as aesCbc, gcm as aesGcm } from '@noble/ciphers/aes';
|
|
2
|
+
import { chacha20poly1305 } from '@noble/ciphers/chacha';
|
|
3
|
+
import { x25519 } from '@noble/curves/ed25519';
|
|
4
|
+
import { p256, p384 } from '@noble/curves/nist';
|
|
5
|
+
import { extract } from '@noble/hashes/hkdf';
|
|
6
|
+
import { hmac } from '@noble/hashes/hmac';
|
|
7
|
+
import { sha1 } from '@noble/hashes/legacy';
|
|
8
|
+
import { sha256, sha384 } from '@noble/hashes/sha2';
|
|
9
|
+
import { OriginatorPublicKey } from '@peculiar/asn1-cms';
|
|
10
|
+
import { AsnParser } from '@peculiar/asn1-schema';
|
|
11
|
+
import { mgf1, PKCS1_KEM, PKCS1_SHA256, PKCS1_SHA384, PKCS1_SHA512, PSS } from 'micro-rsa-dsa-dh/rsa.js';
|
|
12
|
+
import { asciiToUint8Array, concatenateUint8Arrays } from "../utils/generics.js";
|
|
13
|
+
import { bufToUint8Array, parseRsaPublicKeyFromAsn1 } from "./common.js";
|
|
14
|
+
import { randomBytes } from "./insecure-rand.js";
|
|
15
|
+
const CURVE_MAP = {
|
|
16
|
+
'P-256': p256,
|
|
17
|
+
'P-384': p384,
|
|
18
|
+
'X25519': x25519
|
|
19
|
+
};
|
|
20
|
+
const AUTH_CIPHER_MAP = {
|
|
21
|
+
'AES-128-GCM': aesGcm,
|
|
22
|
+
'AES-256-GCM': aesGcm,
|
|
23
|
+
'CHACHA20-POLY1305': chacha20poly1305,
|
|
24
|
+
};
|
|
25
|
+
const HASH_MAP = {
|
|
26
|
+
'SHA-1': sha1,
|
|
27
|
+
'SHA-256': sha256,
|
|
28
|
+
'SHA-384': sha384
|
|
29
|
+
};
|
|
30
|
+
const AUTH_TAG_BYTE_LENGTH = 16;
|
|
31
|
+
export const pureJsCrypto = {
|
|
32
|
+
importKey(_, raw) {
|
|
33
|
+
return raw;
|
|
34
|
+
},
|
|
35
|
+
exportKey(key) {
|
|
36
|
+
return key;
|
|
37
|
+
},
|
|
38
|
+
async generateKeyPair(alg) {
|
|
39
|
+
const curve = CURVE_MAP[alg];
|
|
40
|
+
const secretKey = curve.utils.randomSecretKey();
|
|
41
|
+
if (alg === 'P-256' || alg === 'P-384') {
|
|
42
|
+
// @ts-expect-error: need uncompressed public key for TLS
|
|
43
|
+
const pubKey = curve.getPublicKey(secretKey, false);
|
|
44
|
+
return { privKey: secretKey, pubKey };
|
|
45
|
+
}
|
|
46
|
+
return { privKey: secretKey, pubKey: curve.getPublicKey(secretKey) };
|
|
47
|
+
},
|
|
48
|
+
calculateSharedSecret(alg, privateKey, publicKey) {
|
|
49
|
+
const curve = CURVE_MAP[alg];
|
|
50
|
+
if (!curve) {
|
|
51
|
+
throw new Error(`Unsupported algorithm: ${alg}`);
|
|
52
|
+
}
|
|
53
|
+
const secret = curve.getSharedSecret(privateKey, publicKey);
|
|
54
|
+
if (alg === 'P-256' || alg === 'P-384') {
|
|
55
|
+
// from noble curves, the secret is packed with 1 y-coordinate byte
|
|
56
|
+
// so we need to remove the first byte
|
|
57
|
+
return secret.slice(1); // remove the first byte
|
|
58
|
+
}
|
|
59
|
+
return secret;
|
|
60
|
+
},
|
|
61
|
+
randomBytes: randomBytes,
|
|
62
|
+
asymmetricEncrypt(cipherSuite, { publicKey, data }) {
|
|
63
|
+
if (cipherSuite !== 'RSA-PCKS1_5') {
|
|
64
|
+
throw new Error(`Unsupported cipher suite ${cipherSuite}`);
|
|
65
|
+
}
|
|
66
|
+
return PKCS1_KEM.encrypt(parseRsaPublicKeyFromAsn1(publicKey), data);
|
|
67
|
+
},
|
|
68
|
+
encrypt(cipherSuite, { key, iv, data }) {
|
|
69
|
+
if (cipherSuite !== 'AES-128-CBC') {
|
|
70
|
+
throw new Error(`Unsupported cipher suite: ${cipherSuite}`);
|
|
71
|
+
}
|
|
72
|
+
const cipher = aesCbc(key, iv, { disablePadding: true });
|
|
73
|
+
return cipher.encrypt(data);
|
|
74
|
+
},
|
|
75
|
+
decrypt(cipherSuite, { key, iv, data }) {
|
|
76
|
+
if (cipherSuite !== 'AES-128-CBC') {
|
|
77
|
+
throw new Error(`Unsupported cipher suite: ${cipherSuite}`);
|
|
78
|
+
}
|
|
79
|
+
const cipher = aesCbc(key, iv, { disablePadding: true });
|
|
80
|
+
const decrypted = cipher.decrypt(data);
|
|
81
|
+
return decrypted;
|
|
82
|
+
},
|
|
83
|
+
authenticatedEncrypt(cipherSuite, { key, iv, data, aead }) {
|
|
84
|
+
const cipher = AUTH_CIPHER_MAP[cipherSuite](key, iv, aead);
|
|
85
|
+
const ciphertext = cipher.encrypt(data);
|
|
86
|
+
return {
|
|
87
|
+
ciphertext: ciphertext.slice(0, -AUTH_TAG_BYTE_LENGTH),
|
|
88
|
+
authTag: ciphertext.slice(-AUTH_TAG_BYTE_LENGTH),
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
authenticatedDecrypt(cipherSuite, { key, iv, data, aead, authTag }) {
|
|
92
|
+
const cipher = AUTH_CIPHER_MAP[cipherSuite](key, iv, aead);
|
|
93
|
+
const decrypted = cipher.decrypt(concatenateUint8Arrays([data, authTag]));
|
|
94
|
+
return { plaintext: decrypted };
|
|
95
|
+
},
|
|
96
|
+
verify(alg, { data, signature, publicKey }) {
|
|
97
|
+
if (alg === 'ECDSA-SECP384R1-SHA384'
|
|
98
|
+
|| alg === 'ECDSA-SECP384R1-SHA256'
|
|
99
|
+
|| alg === 'ECDSA-SECP256R1-SHA384'
|
|
100
|
+
|| alg === 'ECDSA-SECP256R1-SHA256') {
|
|
101
|
+
const parsedPubKey = parseAsn1PublicKey(publicKey);
|
|
102
|
+
const curv = alg.includes('P-384') ? p384 : p256;
|
|
103
|
+
return curv
|
|
104
|
+
.verify(signature, data, parsedPubKey, { prehash: true, format: 'der' });
|
|
105
|
+
}
|
|
106
|
+
if (alg === 'RSA-PSS-SHA256') {
|
|
107
|
+
const rsaPubKey = parseRsaPublicKeyFromAsn1(publicKey);
|
|
108
|
+
const pss = PSS(sha256, mgf1(sha256), 32);
|
|
109
|
+
return pss.verify(rsaPubKey, data, signature);
|
|
110
|
+
}
|
|
111
|
+
if (alg === 'RSA-PKCS1-SHA256') {
|
|
112
|
+
const rsaPubKey = parseRsaPublicKeyFromAsn1(publicKey);
|
|
113
|
+
return PKCS1_SHA256.verify(rsaPubKey, data, signature);
|
|
114
|
+
}
|
|
115
|
+
if (alg === 'RSA-PKCS1-SHA384') {
|
|
116
|
+
const rsaPubKey = parseRsaPublicKeyFromAsn1(publicKey);
|
|
117
|
+
return PKCS1_SHA384.verify(rsaPubKey, data, signature);
|
|
118
|
+
}
|
|
119
|
+
if (alg === 'RSA-PKCS1-SHA512') {
|
|
120
|
+
const rsaPubKey = parseRsaPublicKeyFromAsn1(publicKey);
|
|
121
|
+
return PKCS1_SHA512.verify(rsaPubKey, data, signature);
|
|
122
|
+
}
|
|
123
|
+
throw new Error(`Unsupported signature algorithm: ${alg}`);
|
|
124
|
+
},
|
|
125
|
+
hash(alg, data) {
|
|
126
|
+
const hasher = HASH_MAP[alg].create();
|
|
127
|
+
hasher.update(data);
|
|
128
|
+
return hasher.digest();
|
|
129
|
+
},
|
|
130
|
+
hmac(alg, key, data) {
|
|
131
|
+
return hmac(HASH_MAP[alg], key, data);
|
|
132
|
+
},
|
|
133
|
+
extract(alg, hashLength, ikm, salt) {
|
|
134
|
+
salt = typeof salt === 'string' ? asciiToUint8Array(salt) : salt;
|
|
135
|
+
if (!salt.length) {
|
|
136
|
+
salt = new Uint8Array(hashLength).fill(0);
|
|
137
|
+
}
|
|
138
|
+
return extract(HASH_MAP[alg], ikm, salt);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
function parseAsn1PublicKey(pubKey) {
|
|
142
|
+
const parsed = AsnParser.parse(pubKey, OriginatorPublicKey);
|
|
143
|
+
return bufToUint8Array(parsed.publicKey);
|
|
144
|
+
}
|