visaljs 0.0.10 → 0.0.11
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 +12 -4
- package/THIRD_PARTY_LICENSES +31 -1
- package/cli.js +962 -68
- package/node/include/js_node.h +11 -1
- package/node/prelude/_http_common.js +29 -0
- package/node/prelude/async_hooks.js +50 -11
- package/node/prelude/child_process.js +31 -4
- package/node/prelude/console.js +311 -143
- package/node/prelude/crypto.js +6 -4
- package/node/prelude/dgram.js +1169 -0
- package/node/prelude/diagnostics_channel.js +3 -1
- package/node/prelude/dns.js +250 -45
- package/node/prelude/dns_promises.js +8 -2
- package/node/prelude/fs_promises.js +9 -1
- package/node/prelude/http.js +395 -86
- package/node/prelude/https.js +13 -4
- package/node/prelude/internal/console_class.js +7 -0
- package/node/prelude/internal/console_global.js +25 -0
- package/node/prelude/internal/crypto/key_encryption.js +217 -0
- package/node/prelude/internal/crypto/keys.js +161 -26
- package/node/prelude/internal/crypto/random.js +24 -0
- package/node/prelude/internal/crypto/util.js +2 -1
- package/node/prelude/internal/crypto_x509.js +161 -0
- package/node/prelude/internal/dns_classes.js +6 -0
- package/node/prelude/internal/dns_resolver.js +371 -0
- package/node/prelude/internal/errors.js +9 -0
- package/node/prelude/internal/events_on.js +82 -37
- package/node/prelude/internal/features.js +8 -4
- package/node/prelude/internal/fs_filehandle.js +10 -4
- package/node/prelude/internal/perf_histogram.js +1 -1
- package/node/prelude/internal/process_more.js +32 -17
- package/node/prelude/internal/process_signals.js +8 -2
- package/node/prelude/internal/punycode.ts +163 -0
- package/node/prelude/internal/readline.js +648 -274
- package/node/prelude/internal/readline_editor.js +1240 -0
- package/node/prelude/internal/string_width.js +43 -0
- package/node/prelude/internal/tls/common.js +145 -33
- package/node/prelude/internal/tls/wrap.js +182 -49
- package/node/prelude/internal/tls/x509.js +4 -1
- package/node/prelude/internal/udp.js +177 -0
- package/node/prelude/net.js +13 -1
- package/node/prelude/os.js +24 -6
- package/node/prelude/perf_hooks.js +37 -2
- package/node/prelude/punycode.js +2 -130
- package/node/prelude/readline.js +113 -4
- package/node/prelude/readline_promises.js +56 -32
- package/node/prelude/tls.js +21 -4
- package/node/prelude/util.js +3 -16
- package/node/src/host_dns.c +623 -0
- package/node/src/host_http.c +12 -0
- package/node/src/host_net.c +46 -0
- package/node/src/host_os.c +1 -6
- package/node/src/host_sys.c +70 -0
- package/node/src/host_udp.c +418 -0
- package/node/third_party/c-ares/LICENSE.md +24 -0
- package/node/third_party/c-ares/config/darwin/ares_config.h +573 -0
- package/node/third_party/c-ares/config/freebsd/ares_config.h +573 -0
- package/node/third_party/c-ares/config/linux/ares_config.h +573 -0
- package/node/third_party/c-ares/config/openbsd/ares_config.h +583 -0
- package/node/third_party/c-ares/include/ares.h +1237 -0
- package/node/third_party/c-ares/include/ares_build.h +202 -0
- package/node/third_party/c-ares/include/ares_dns.h +127 -0
- package/node/third_party/c-ares/include/ares_dns_record.h +1119 -0
- package/node/third_party/c-ares/include/ares_nameser.h +510 -0
- package/node/third_party/c-ares/include/ares_version.h +47 -0
- package/node/third_party/c-ares/src/lib/ares_addrinfo2hostent.c +305 -0
- package/node/third_party/c-ares/src/lib/ares_addrinfo_localhost.c +245 -0
- package/node/third_party/c-ares/src/lib/ares_android.c +496 -0
- package/node/third_party/c-ares/src/lib/ares_android.h +38 -0
- package/node/third_party/c-ares/src/lib/ares_cancel.c +87 -0
- package/node/third_party/c-ares/src/lib/ares_close_sockets.c +137 -0
- package/node/third_party/c-ares/src/lib/ares_conn.c +511 -0
- package/node/third_party/c-ares/src/lib/ares_conn.h +202 -0
- package/node/third_party/c-ares/src/lib/ares_cookie.c +461 -0
- package/node/third_party/c-ares/src/lib/ares_data.c +168 -0
- package/node/third_party/c-ares/src/lib/ares_data.h +92 -0
- package/node/third_party/c-ares/src/lib/ares_destroy.c +159 -0
- package/node/third_party/c-ares/src/lib/ares_free_hostent.c +54 -0
- package/node/third_party/c-ares/src/lib/ares_free_string.c +33 -0
- package/node/third_party/c-ares/src/lib/ares_freeaddrinfo.c +67 -0
- package/node/third_party/c-ares/src/lib/ares_getaddrinfo.c +806 -0
- package/node/third_party/c-ares/src/lib/ares_getenv.c +38 -0
- package/node/third_party/c-ares/src/lib/ares_getenv.h +34 -0
- package/node/third_party/c-ares/src/lib/ares_gethostbyaddr.c +229 -0
- package/node/third_party/c-ares/src/lib/ares_gethostbyname.c +341 -0
- package/node/third_party/c-ares/src/lib/ares_getnameinfo.c +437 -0
- package/node/third_party/c-ares/src/lib/ares_hosts_file.c +960 -0
- package/node/third_party/c-ares/src/lib/ares_inet_net_pton.h +31 -0
- package/node/third_party/c-ares/src/lib/ares_init.c +631 -0
- package/node/third_party/c-ares/src/lib/ares_ipv6.h +115 -0
- package/node/third_party/c-ares/src/lib/ares_library_init.c +190 -0
- package/node/third_party/c-ares/src/lib/ares_metrics.c +261 -0
- package/node/third_party/c-ares/src/lib/ares_options.c +492 -0
- package/node/third_party/c-ares/src/lib/ares_parse_into_addrinfo.c +179 -0
- package/node/third_party/c-ares/src/lib/ares_private.h +648 -0
- package/node/third_party/c-ares/src/lib/ares_process.c +1649 -0
- package/node/third_party/c-ares/src/lib/ares_qcache.c +442 -0
- package/node/third_party/c-ares/src/lib/ares_query.c +151 -0
- package/node/third_party/c-ares/src/lib/ares_search.c +623 -0
- package/node/third_party/c-ares/src/lib/ares_send.c +300 -0
- package/node/third_party/c-ares/src/lib/ares_set_socket_functions.c +590 -0
- package/node/third_party/c-ares/src/lib/ares_setup.h +224 -0
- package/node/third_party/c-ares/src/lib/ares_socket.c +427 -0
- package/node/third_party/c-ares/src/lib/ares_socket.h +163 -0
- package/node/third_party/c-ares/src/lib/ares_sortaddrinfo.c +447 -0
- package/node/third_party/c-ares/src/lib/ares_strerror.c +91 -0
- package/node/third_party/c-ares/src/lib/ares_sysconfig.c +642 -0
- package/node/third_party/c-ares/src/lib/ares_sysconfig_files.c +851 -0
- package/node/third_party/c-ares/src/lib/ares_sysconfig_mac.c +373 -0
- package/node/third_party/c-ares/src/lib/ares_sysconfig_win.c +673 -0
- package/node/third_party/c-ares/src/lib/ares_timeout.c +162 -0
- package/node/third_party/c-ares/src/lib/ares_update_servers.c +1376 -0
- package/node/third_party/c-ares/src/lib/ares_version.c +36 -0
- package/node/third_party/c-ares/src/lib/config-dos.h +114 -0
- package/node/third_party/c-ares/src/lib/config-win32.h +396 -0
- package/node/third_party/c-ares/src/lib/dsa/ares_array.c +409 -0
- package/node/third_party/c-ares/src/lib/dsa/ares_htable.c +451 -0
- package/node/third_party/c-ares/src/lib/dsa/ares_htable.h +174 -0
- package/node/third_party/c-ares/src/lib/dsa/ares_htable_asvp.c +224 -0
- package/node/third_party/c-ares/src/lib/dsa/ares_htable_dict.c +230 -0
- package/node/third_party/c-ares/src/lib/dsa/ares_htable_strvp.c +210 -0
- package/node/third_party/c-ares/src/lib/dsa/ares_htable_szvp.c +188 -0
- package/node/third_party/c-ares/src/lib/dsa/ares_htable_vpstr.c +186 -0
- package/node/third_party/c-ares/src/lib/dsa/ares_htable_vpvp.c +194 -0
- package/node/third_party/c-ares/src/lib/dsa/ares_llist.c +385 -0
- package/node/third_party/c-ares/src/lib/dsa/ares_slist.c +479 -0
- package/node/third_party/c-ares/src/lib/dsa/ares_slist.h +207 -0
- package/node/third_party/c-ares/src/lib/event/ares_event.h +191 -0
- package/node/third_party/c-ares/src/lib/event/ares_event_configchg.c +746 -0
- package/node/third_party/c-ares/src/lib/event/ares_event_epoll.c +192 -0
- package/node/third_party/c-ares/src/lib/event/ares_event_kqueue.c +248 -0
- package/node/third_party/c-ares/src/lib/event/ares_event_poll.c +140 -0
- package/node/third_party/c-ares/src/lib/event/ares_event_select.c +159 -0
- package/node/third_party/c-ares/src/lib/event/ares_event_thread.c +575 -0
- package/node/third_party/c-ares/src/lib/event/ares_event_wake_pipe.c +172 -0
- package/node/third_party/c-ares/src/lib/event/ares_event_win32.c +981 -0
- package/node/third_party/c-ares/src/lib/event/ares_event_win32.h +161 -0
- package/node/third_party/c-ares/src/lib/include/ares_array.h +276 -0
- package/node/third_party/c-ares/src/lib/include/ares_buf.h +732 -0
- package/node/third_party/c-ares/src/lib/include/ares_htable_asvp.h +130 -0
- package/node/third_party/c-ares/src/lib/include/ares_htable_dict.h +123 -0
- package/node/third_party/c-ares/src/lib/include/ares_htable_strvp.h +130 -0
- package/node/third_party/c-ares/src/lib/include/ares_htable_szvp.h +118 -0
- package/node/third_party/c-ares/src/lib/include/ares_htable_vpstr.h +111 -0
- package/node/third_party/c-ares/src/lib/include/ares_htable_vpvp.h +128 -0
- package/node/third_party/c-ares/src/lib/include/ares_llist.h +239 -0
- package/node/third_party/c-ares/src/lib/include/ares_mem.h +41 -0
- package/node/third_party/c-ares/src/lib/include/ares_str.h +261 -0
- package/node/third_party/c-ares/src/lib/inet_net_pton.c +451 -0
- package/node/third_party/c-ares/src/lib/inet_ntop.c +219 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_create_query.c +78 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_expand_name.c +99 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_expand_string.c +107 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_fds.c +80 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_getsock.c +85 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_parse_a_reply.c +107 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_parse_aaaa_reply.c +109 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_parse_caa_reply.c +137 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_parse_mx_reply.c +110 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_parse_naptr_reply.c +132 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_parse_ns_reply.c +154 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_parse_ptr_reply.c +213 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_parse_soa_reply.c +115 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_parse_srv_reply.c +114 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_parse_txt_reply.c +144 -0
- package/node/third_party/c-ares/src/lib/legacy/ares_parse_uri_reply.c +113 -0
- package/node/third_party/c-ares/src/lib/record/ares_dns_mapping.c +982 -0
- package/node/third_party/c-ares/src/lib/record/ares_dns_multistring.c +309 -0
- package/node/third_party/c-ares/src/lib/record/ares_dns_multistring.h +72 -0
- package/node/third_party/c-ares/src/lib/record/ares_dns_name.c +736 -0
- package/node/third_party/c-ares/src/lib/record/ares_dns_parse.c +1360 -0
- package/node/third_party/c-ares/src/lib/record/ares_dns_private.h +273 -0
- package/node/third_party/c-ares/src/lib/record/ares_dns_record.c +1701 -0
- package/node/third_party/c-ares/src/lib/record/ares_dns_write.c +1229 -0
- package/node/third_party/c-ares/src/lib/str/ares_buf.c +1510 -0
- package/node/third_party/c-ares/src/lib/str/ares_str.c +562 -0
- package/node/third_party/c-ares/src/lib/str/ares_strsplit.c +90 -0
- package/node/third_party/c-ares/src/lib/str/ares_strsplit.h +51 -0
- package/node/third_party/c-ares/src/lib/thirdparty/apple/dnsinfo.h +122 -0
- package/node/third_party/c-ares/src/lib/util/ares_iface_ips.c +640 -0
- package/node/third_party/c-ares/src/lib/util/ares_iface_ips.h +140 -0
- package/node/third_party/c-ares/src/lib/util/ares_math.c +167 -0
- package/node/third_party/c-ares/src/lib/util/ares_math.h +58 -0
- package/node/third_party/c-ares/src/lib/util/ares_rand.c +389 -0
- package/node/third_party/c-ares/src/lib/util/ares_rand.h +36 -0
- package/node/third_party/c-ares/src/lib/util/ares_threads.c +613 -0
- package/node/third_party/c-ares/src/lib/util/ares_threads.h +60 -0
- package/node/third_party/c-ares/src/lib/util/ares_time.h +49 -0
- package/node/third_party/c-ares/src/lib/util/ares_timeval.c +95 -0
- package/node/third_party/c-ares/src/lib/util/ares_uri.c +1627 -0
- package/node/third_party/c-ares/src/lib/util/ares_uri.h +252 -0
- package/node/third_party/c-ares/src/lib/windows_port.c +29 -0
- package/package.json +1 -1
- package/prebuilt/darwin-arm64/cares-a6286c65ca9374a3.a +0 -0
- package/prebuilt/darwin-x64/cares-a6286c65ca9374a3.a +0 -0
- package/prebuilt/linux-arm64/cares-4ba05ac8e948af8e.a +0 -0
- package/prebuilt/linux-arm64-musl/cares-4ba05ac8e948af8e.a +0 -0
- package/prebuilt/linux-x64/cares-4ba05ac8e948af8e.a +0 -0
- package/prebuilt/linux-x64-musl/cares-4ba05ac8e948af8e.a +0 -0
- package/prebuilt/win32-x64/boringssl-ce573858472b7fc9.a +0 -0
- package/prebuilt/win32-x64/brotli-20d86a61f30baeb3.a +0 -0
- package/prebuilt/win32-x64/cares-49c9ec95162b912d.a +0 -0
- package/prebuilt/win32-x64/uv-b11bae65d7fced82.a +0 -0
- package/prebuilt/win32-x64/zlib-4ce2317a4c3f5fb5.a +0 -0
- package/prebuilt/win32-x64/zstd-015315ac7c784c73.a +0 -0
- package/runtime/include/js_builtins.h +5 -0
- package/runtime/include/js_crypto.h +11 -0
- package/runtime/include/js_gc.h +13 -10
- package/runtime/include/js_jobs.h +2 -0
- package/runtime/include/js_object.h +58 -0
- package/runtime/include/jscontext.h +1 -0
- package/runtime/src/crypto_internal.h +1 -0
- package/runtime/src/crypto_subtle.c +66 -7
- package/runtime/src/crypto_tls.c +265 -19
- package/runtime/src/gc.c +73 -92
- package/runtime/src/inspect.c +18 -1
- package/runtime/src/jobs.c +37 -22
- package/runtime/src/object.c +49 -2
- package/runtime/src/os.c +16 -6
- package/runtime/src/os.h +2 -0
- package/runtime/src/process.c +97 -10
package/node/prelude/https.js
CHANGED
|
@@ -18,10 +18,11 @@ class ServerClass extends TlsServer {
|
|
|
18
18
|
options = {};
|
|
19
19
|
} else if (options === undefined || options === null) {
|
|
20
20
|
options = {};
|
|
21
|
-
} else if (typeof options !== "object") {
|
|
21
|
+
} else if (typeof options !== "object" || Array.isArray(options)) {
|
|
22
22
|
throw invalidArgType("options", "of type object", options);
|
|
23
23
|
}
|
|
24
|
-
|
|
24
|
+
/* only one of ALPNProtocols and ALPNCallback can be set: the default only when neither is */
|
|
25
|
+
super({ noDelay: true, ALPNProtocols: options.ALPNProtocols || options.ALPNCallback ? undefined : ["http/1.1"], ...options });
|
|
25
26
|
this._initHttp(options, requestListener, "secureConnection");
|
|
26
27
|
/* a handshake that fails is the client's error, as a request that does */
|
|
27
28
|
this.on("tlsClientError", (err, conn) => {
|
|
@@ -84,17 +85,25 @@ const nameOptions = ["ca", "cert", "clientCertEngine", "ciphers", "key", "pfx",
|
|
|
84
85
|
"maxVersion", "secureProtocol", "crl", "honorCipherOrder", "ecdhCurve", "dhparam", "secureOptions", "sessionIdContext", "sigalgs",
|
|
85
86
|
"privateKeyIdentifier", "privateKeyEngine"];
|
|
86
87
|
|
|
88
|
+
/* the options a value of which, false or 0 too, names a different connection */
|
|
89
|
+
const nameDefined = new Set(["rejectUnauthorized", "honorCipherOrder", "secureOptions"]);
|
|
90
|
+
|
|
91
|
+
function pfxAgentKey(pfx, passphrase) {
|
|
92
|
+
if (!Array.isArray(pfx)) return pfx;
|
|
93
|
+
return pfx.map((value) => `:${value?.buf || value}:${value?.passphrase || passphrase}`).join("");
|
|
94
|
+
}
|
|
95
|
+
|
|
87
96
|
Agent.prototype.getName = function getName(options = {}) {
|
|
88
97
|
let name = HttpAgent.prototype.getName.call(this, options);
|
|
89
98
|
for (const key of nameOptions) {
|
|
90
99
|
name += ":";
|
|
91
100
|
const value = options[key];
|
|
92
|
-
if (key
|
|
101
|
+
if (nameDefined.has(key)) {
|
|
93
102
|
if (value !== undefined) name += value;
|
|
94
103
|
} else if (key === "servername") {
|
|
95
104
|
if (value && value !== options.host) name += value;
|
|
96
105
|
} else if (value) {
|
|
97
|
-
name += value;
|
|
106
|
+
name += key === "sigalgs" ? JSON.stringify(value) : key === "pfx" ? pfxAgentKey(value, options.passphrase) : value;
|
|
98
107
|
}
|
|
99
108
|
}
|
|
100
109
|
return name;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// console.Console on the global console, as node:console puts it there: a feature module, linked
|
|
2
|
+
// into programs that name Console without importing node:console.
|
|
3
|
+
import { Console } from "../console.js";
|
|
4
|
+
|
|
5
|
+
if (globalThis.console.Console === undefined) {
|
|
6
|
+
Object.defineProperty(globalThis.console, "Console", { value: Console, writable: true, configurable: true });
|
|
7
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// The global console as Node's (lib/internal/console/global.js): Console's methods bound to an
|
|
2
|
+
// instance writing to process.stdout and process.stderr, looked up when first written to, and
|
|
3
|
+
// its _stdout and _stderr settable. A feature module (implicitModules in node/index.ts): linked
|
|
4
|
+
// into a program that replaces a stdio stream's write, which the runtime's console, writing to
|
|
5
|
+
// the fds, wouldn't go through. The runtime's console object stays the global (code may hold it).
|
|
6
|
+
// Ported from Node.js (Copyright Node.js contributors, MIT): node/third_party/nodejs/LICENSE.
|
|
7
|
+
import { Console, kBindProperties, kBindStreamsLazy } from "../console.js";
|
|
8
|
+
|
|
9
|
+
const global = globalThis.console;
|
|
10
|
+
const instance = Object.create(Console.prototype);
|
|
11
|
+
instance[kBindStreamsLazy](globalThis.process);
|
|
12
|
+
instance[kBindProperties](true, "auto");
|
|
13
|
+
|
|
14
|
+
for (const key of Object.keys(Console.prototype)) {
|
|
15
|
+
const method = Console.prototype[key].bind(instance);
|
|
16
|
+
Object.defineProperty(method, "name", { value: key });
|
|
17
|
+
Object.defineProperty(global, key, { value: method, writable: true, enumerable: true, configurable: true });
|
|
18
|
+
}
|
|
19
|
+
for (const name of ["_stdout", "_stderr"]) {
|
|
20
|
+
Object.defineProperty(global, name, {
|
|
21
|
+
get: () => instance[name],
|
|
22
|
+
set: (stream) => { instance[name] = stream; },
|
|
23
|
+
configurable: true,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// Encrypted private keys, as OpenSSL writes and reads them for Node: PKCS#8's
|
|
2
|
+
// EncryptedPrivateKeyInfo (PBES2, RFC 8018: PBKDF2 and a CBC cipher; "ENCRYPTED PRIVATE KEY" in
|
|
3
|
+
// PEM), and the PEM headers of a PKCS#1 or SEC1 key (Proc-Type, DEK-Info; the key from OpenSSL's
|
|
4
|
+
// EVP_BytesToKey with MD5). The ciphers and digests are the runtime's; the DER here is only these
|
|
5
|
+
// structures'.
|
|
6
|
+
import {
|
|
7
|
+
cryptoCipherFinal, cryptoCipherInfo, cryptoCipherNew, cryptoCipherUpdate, cryptoDigestFinal, cryptoDigestUpdate, cryptoHashNew,
|
|
8
|
+
cryptoPbkdf2, cryptoRandomFill,
|
|
9
|
+
} from "visaljs:host";
|
|
10
|
+
import { codeError, notSupported } from "../errors.js";
|
|
11
|
+
|
|
12
|
+
/* OpenSSL 3's choices for PEM_write_bio_PKCS8PrivateKey with a cipher */
|
|
13
|
+
const PBKDF2_ITERATIONS = 2048;
|
|
14
|
+
const SALT_LENGTH = 16;
|
|
15
|
+
/* the buffer OpenSSL's PEM password callback reads into */
|
|
16
|
+
const PEM_PASSPHRASE_MAX = 1024;
|
|
17
|
+
|
|
18
|
+
/* object identifiers' contents, in hex */
|
|
19
|
+
const OID_PBES2 = "2a864886f70d01050d";
|
|
20
|
+
const OID_PBKDF2 = "2a864886f70d01050c";
|
|
21
|
+
const PRFS = {
|
|
22
|
+
"2a864886f70d0207": "sha1", "2a864886f70d0208": "sha224", "2a864886f70d0209": "sha256", "2a864886f70d020a": "sha384",
|
|
23
|
+
"2a864886f70d020b": "sha512",
|
|
24
|
+
};
|
|
25
|
+
const OID_HMAC_SHA256 = "2a864886f70d0209";
|
|
26
|
+
const CIPHERS = {
|
|
27
|
+
"608648016503040102": "aes-128-cbc", "608648016503040116": "aes-192-cbc", "60864801650304012a": "aes-256-cbc",
|
|
28
|
+
"2a864886f70d0307": "des-ede3-cbc", "2b0e030207": "des-cbc",
|
|
29
|
+
};
|
|
30
|
+
const CIPHER_OIDS = Object.fromEntries(Object.entries(CIPHERS).map(([oid, name]) => [name, oid]));
|
|
31
|
+
|
|
32
|
+
export const missingPassphrase = () => codeError(TypeError, "ERR_MISSING_PASSPHRASE", "Passphrase required for encrypted key");
|
|
33
|
+
/* OpenSSL 3's PEM reader without a passphrase: its password callback gives up */
|
|
34
|
+
const pemCancelled = () => codeError(Error, "ERR_OSSL_CRYPTO_INTERRUPTED_OR_CANCELLED", "error:07880109:common libcrypto routines::interrupted or cancelled");
|
|
35
|
+
const badDecrypt = () => codeError(Error, "ERR_OSSL_BAD_DECRYPT", "error:1C800064:Provider routines::bad decrypt");
|
|
36
|
+
const decodeError = () => codeError(Error, "ERR_OSSL_ASN1_WRONG_TAG", "error:0680007B:asn1 encoding routines::wrong tag");
|
|
37
|
+
|
|
38
|
+
/* ---- DER: just the shapes above ---------------------------------------------------------------- */
|
|
39
|
+
|
|
40
|
+
function tlv(tag, body) {
|
|
41
|
+
const n = body.length;
|
|
42
|
+
const len = n < 0x80 ? [n] : n < 0x100 ? [0x81, n] : n < 0x10000 ? [0x82, n >> 8, n & 0xff] : [0x83, n >> 16, (n >> 8) & 0xff, n & 0xff];
|
|
43
|
+
return Buffer.concat([Buffer.from([tag, ...len]), body]);
|
|
44
|
+
}
|
|
45
|
+
const seq = (...items) => tlv(0x30, Buffer.concat(items));
|
|
46
|
+
const oid = (hex) => tlv(0x06, Buffer.from(hex, "hex"));
|
|
47
|
+
const octets = (bytes) => tlv(0x04, bytes);
|
|
48
|
+
function integer(n) {
|
|
49
|
+
const bytes = [];
|
|
50
|
+
do {
|
|
51
|
+
bytes.unshift(n & 0xff);
|
|
52
|
+
n = Math.floor(n / 256);
|
|
53
|
+
} while (n > 0);
|
|
54
|
+
if (bytes[0] & 0x80) bytes.unshift(0);
|
|
55
|
+
return tlv(0x02, Buffer.from(bytes));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The elements of the TLV at `pos`: { tag, body, end }; a decode error past the input. */
|
|
59
|
+
function read(der, pos) {
|
|
60
|
+
if (pos + 2 > der.length) throw decodeError();
|
|
61
|
+
const tag = der[pos];
|
|
62
|
+
let len = der[pos + 1];
|
|
63
|
+
let start = pos + 2;
|
|
64
|
+
if (len & 0x80) {
|
|
65
|
+
const n = len & 0x7f;
|
|
66
|
+
if (n === 0 || n > 3 || start + n > der.length) throw decodeError();
|
|
67
|
+
len = 0;
|
|
68
|
+
for (let i = 0; i < n; i++) len = len * 256 + der[start + i];
|
|
69
|
+
start += n;
|
|
70
|
+
}
|
|
71
|
+
if (start + len > der.length) throw decodeError();
|
|
72
|
+
return { tag, body: der.subarray(start, start + len), end: start + len };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function children(der) {
|
|
76
|
+
const out = [];
|
|
77
|
+
for (let pos = 0; pos < der.length;) {
|
|
78
|
+
const el = read(der, pos);
|
|
79
|
+
out.push(el);
|
|
80
|
+
pos = el.end;
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function expect(el, tag) {
|
|
86
|
+
if (el === undefined || el.tag !== tag) throw decodeError();
|
|
87
|
+
return el.body;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Whether DER PKCS#8 is an EncryptedPrivateKeyInfo: its first element is an AlgorithmIdentifier
|
|
91
|
+
* (a SEQUENCE), where a PrivateKeyInfo's is its version (an INTEGER). */
|
|
92
|
+
export function isEncryptedPkcs8(der) {
|
|
93
|
+
try {
|
|
94
|
+
const [first] = children(expect(read(der, 0), 0x30));
|
|
95
|
+
return first !== undefined && first.tag === 0x30;
|
|
96
|
+
} catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* ---- the primitives ---------------------------------------------------------------------------- */
|
|
102
|
+
|
|
103
|
+
function cipherInfo(name) {
|
|
104
|
+
const info = cryptoCipherInfo(name);
|
|
105
|
+
if (info === null) throw codeError(Error, "ERR_CRYPTO_UNKNOWN_CIPHER", "Unknown cipher");
|
|
106
|
+
const [canonical, , , , ivLength, keyLength] = info;
|
|
107
|
+
return { name: canonical, ivLength, keyLength };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function runCipher(name, encrypt, key, iv, data) {
|
|
111
|
+
const handle = cryptoCipherNew(name, encrypt, key, iv, -1);
|
|
112
|
+
if (typeof handle === "number") throw notSupported(`The cipher ${name} for a private key`);
|
|
113
|
+
const head = cryptoCipherUpdate(handle, data);
|
|
114
|
+
const tail = cryptoCipherFinal(handle);
|
|
115
|
+
if (typeof head === "number" || typeof tail === "number") throw badDecrypt();
|
|
116
|
+
return Buffer.concat([head, tail]);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function pbkdf2(digest, passphrase, salt, iterations, keyLength) {
|
|
120
|
+
const key = cryptoPbkdf2(digest, passphrase, salt, iterations, keyLength);
|
|
121
|
+
if (!key) throw decodeError();
|
|
122
|
+
return key;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* OpenSSL's EVP_BytesToKey with MD5 and one iteration: D(i) = MD5(D(i-1) ‖ passphrase ‖ salt) */
|
|
126
|
+
function bytesToKey(passphrase, salt, keyLength) {
|
|
127
|
+
const parts = [];
|
|
128
|
+
let total = 0;
|
|
129
|
+
let prev = Buffer.alloc(0);
|
|
130
|
+
while (total < keyLength) {
|
|
131
|
+
const md5 = cryptoHashNew("md5", undefined);
|
|
132
|
+
cryptoDigestUpdate(md5, prev);
|
|
133
|
+
cryptoDigestUpdate(md5, passphrase);
|
|
134
|
+
cryptoDigestUpdate(md5, salt);
|
|
135
|
+
prev = cryptoDigestFinal(md5);
|
|
136
|
+
parts.push(prev);
|
|
137
|
+
total += prev.length;
|
|
138
|
+
}
|
|
139
|
+
return Buffer.concat(parts).subarray(0, keyLength);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function random(n) {
|
|
143
|
+
const b = Buffer.alloc(n);
|
|
144
|
+
cryptoRandomFill(b, 0, n);
|
|
145
|
+
return b;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* ---- PKCS#8 ------------------------------------------------------------------------------------ */
|
|
149
|
+
|
|
150
|
+
/** A PrivateKeyInfo in an EncryptedPrivateKeyInfo, as OpenSSL 3 writes one. */
|
|
151
|
+
export function encryptPkcs8(der, cipherName, passphrase) {
|
|
152
|
+
const { name, ivLength, keyLength } = cipherInfo(cipherName);
|
|
153
|
+
const cipherOid = CIPHER_OIDS[name];
|
|
154
|
+
if (cipherOid === undefined) throw notSupported(`Encrypting a private key with ${cipherName}`);
|
|
155
|
+
const salt = random(SALT_LENGTH);
|
|
156
|
+
const iv = random(ivLength);
|
|
157
|
+
const key = pbkdf2("sha256", passphrase, salt, PBKDF2_ITERATIONS, keyLength);
|
|
158
|
+
const encrypted = runCipher(name, true, key, iv, der);
|
|
159
|
+
const kdf = seq(oid(OID_PBKDF2), seq(octets(salt), integer(PBKDF2_ITERATIONS), seq(oid(OID_HMAC_SHA256), Buffer.from([5, 0]))));
|
|
160
|
+
return seq(seq(oid(OID_PBES2), seq(kdf, seq(oid(cipherOid), octets(iv)))), octets(encrypted));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** The PrivateKeyInfo an EncryptedPrivateKeyInfo holds (PBES2 with PBKDF2). */
|
|
164
|
+
export function decryptPkcs8(der, passphrase) {
|
|
165
|
+
if (passphrase === undefined) throw missingPassphrase();
|
|
166
|
+
const [algorithm, data] = children(expect(read(der, 0), 0x30));
|
|
167
|
+
const [scheme, params] = children(expect(algorithm, 0x30));
|
|
168
|
+
if (expect(scheme, 0x06).toString("hex") !== OID_PBES2) throw notSupported("A private key encrypted without PBES2");
|
|
169
|
+
const [kdf, enc] = children(expect(params, 0x30));
|
|
170
|
+
const [kdfOid, kdfParams] = children(expect(kdf, 0x30));
|
|
171
|
+
if (expect(kdfOid, 0x06).toString("hex") !== OID_PBKDF2) throw notSupported("A private key encrypted without PBKDF2");
|
|
172
|
+
const [salt, iterations, ...rest] = children(expect(kdfParams, 0x30));
|
|
173
|
+
let digest = "sha1"; /* RFC 8018's default PRF */
|
|
174
|
+
for (const el of rest) {
|
|
175
|
+
if (el.tag !== 0x30) continue; /* keyLength: the cipher's anyway */
|
|
176
|
+
const prf = PRFS[expect(children(el.body)[0], 0x06).toString("hex")];
|
|
177
|
+
if (prf === undefined) throw notSupported("This private key's PBKDF2 digest");
|
|
178
|
+
digest = prf;
|
|
179
|
+
}
|
|
180
|
+
const [cipherOid, iv] = children(expect(enc, 0x30));
|
|
181
|
+
const name = CIPHERS[expect(cipherOid, 0x06).toString("hex")];
|
|
182
|
+
if (name === undefined) throw notSupported("This private key's cipher");
|
|
183
|
+
const count = expect(iterations, 0x02).reduce((n, b) => n * 256 + b, 0);
|
|
184
|
+
const { keyLength } = cipherInfo(name);
|
|
185
|
+
const key = pbkdf2(digest, passphrase, expect(salt, 0x04), count, keyLength);
|
|
186
|
+
return runCipher(name, false, key, expect(iv, 0x04), expect(data, 0x04));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* ---- PEM's own encryption (PKCS#1, SEC1) ------------------------------------------------------- */
|
|
190
|
+
|
|
191
|
+
/** The headers and body of an encrypted PEM key, as OpenSSL's PEM_write_bio_PrivateKey writes them. */
|
|
192
|
+
export function encryptPemBody(der, cipherName, passphrase) {
|
|
193
|
+
const { name, ivLength, keyLength } = cipherInfo(cipherName);
|
|
194
|
+
const iv = random(ivLength);
|
|
195
|
+
const key = bytesToKey(passphrase, iv.subarray(0, 8), keyLength);
|
|
196
|
+
const body = runCipher(name, true, key, iv, der);
|
|
197
|
+
return { headers: `Proc-Type: 4,ENCRYPTED\nDEK-Info: ${name.toUpperCase()},${iv.toString("hex").toUpperCase()}\n\n`, body };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** The DER of an encrypted PEM key: its DEK-Info header names the cipher and the IV. */
|
|
201
|
+
export function decryptPemBody(headers, body, passphrase) {
|
|
202
|
+
if (passphrase === undefined) throw pemCancelled();
|
|
203
|
+
if (passphrase.length > PEM_PASSPHRASE_MAX) throw codeError(Error, "ERR_OSSL_PEM_BAD_PASSWORD_READ", "error:0480006D:PEM routines::bad password read");
|
|
204
|
+
const dek = /^DEK-Info:\s*([A-Za-z0-9-]+),([0-9A-Fa-f]+)\s*$/m.exec(headers);
|
|
205
|
+
if (dek === null) throw codeError(Error, "ERR_OSSL_PEM_BAD_DECRYPT", "error:04800065:PEM routines::bad decrypt");
|
|
206
|
+
const { name, keyLength } = cipherInfo(dek[1].toLowerCase());
|
|
207
|
+
const iv = Buffer.from(dek[2], "hex");
|
|
208
|
+
const key = bytesToKey(passphrase, iv.subarray(0, 8), keyLength);
|
|
209
|
+
return runCipher(name, false, key, iv, body);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function checkPemPassphrase(passphrase) {
|
|
213
|
+
if (passphrase === undefined) throw pemCancelled();
|
|
214
|
+
if (passphrase !== undefined && passphrase.length > PEM_PASSPHRASE_MAX) {
|
|
215
|
+
throw codeError(Error, "ERR_OSSL_PEM_BAD_PASSWORD_READ", "error:0480006D:PEM routines::bad password read");
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
// here; DER is the runtime's.
|
|
5
5
|
// Ported from Node.js (Copyright Node.js contributors, MIT): node/third_party/nodejs/LICENSE.
|
|
6
6
|
import {
|
|
7
|
-
cryptoHashInfo, cryptoKeyEquals, cryptoRandomFill, cryptoKeyExport, cryptoKeyFromParts, cryptoKeyGenerate, cryptoKeyHandleOf, cryptoKeyInfo,
|
|
7
|
+
cryptoCipherInfo, cryptoEcPoint, cryptoHashInfo, cryptoKeyEquals, cryptoRandomFill, cryptoKeyExport, cryptoKeyFromParts, cryptoKeyGenerate, cryptoKeyHandleOf, cryptoKeyInfo,
|
|
8
8
|
cryptoKeyParse, cryptoKeyParts, cryptoKeyPublic, cryptoKeySecret, cryptoKeySecretBytes, cryptoToCryptoKey,
|
|
9
9
|
} from "visaljs:host";
|
|
10
10
|
import { codeError, invalidArgType, invalidArgValue, notSupported, outOfRange, validateFunction, validateString } from "../errors.js";
|
|
11
11
|
import { dhKeyParts, dhParameters } from "./diffiehellman.js";
|
|
12
12
|
import { kKeyType } from "./key_type.js";
|
|
13
|
+
import { checkPemPassphrase, decryptPemBody, decryptPkcs8, encryptPemBody, encryptPkcs8, isEncryptedPkcs8 } from "./key_encryption.js";
|
|
13
14
|
import { isAnyArrayBuffer, isArrayBufferView, kHandle, validateNumber, validateObject } from "./util.js";
|
|
14
15
|
|
|
15
16
|
const kAsymmetricKeyType = Symbol("kAsymmetricKeyType");
|
|
@@ -130,6 +131,8 @@ export class KeyObject {
|
|
|
130
131
|
}
|
|
131
132
|
}
|
|
132
133
|
|
|
134
|
+
Object.defineProperty(KeyObject.prototype, Symbol.toStringTag, { configurable: true, value: "KeyObject" });
|
|
135
|
+
|
|
133
136
|
class SecretKeyObject extends KeyObject {
|
|
134
137
|
constructor(handle) {
|
|
135
138
|
super("secret", handle);
|
|
@@ -201,6 +204,7 @@ class PublicKeyObject extends AsymmetricKeyObject {
|
|
|
201
204
|
|
|
202
205
|
export(options) {
|
|
203
206
|
if (options && options.format === "jwk") return exportJwk(this[kHandle], false);
|
|
207
|
+
if (options && options.format === "raw-public") return exportRawPublic(this[kHandle], options.type);
|
|
204
208
|
const { format, type } = parseKeyEncoding(options, this.asymmetricKeyType, true);
|
|
205
209
|
return exportKey(this[kHandle], format, type, false);
|
|
206
210
|
}
|
|
@@ -212,13 +216,13 @@ class PrivateKeyObject extends AsymmetricKeyObject {
|
|
|
212
216
|
}
|
|
213
217
|
|
|
214
218
|
export(options) {
|
|
215
|
-
if (options && options.format
|
|
216
|
-
|
|
217
|
-
return exportJwk(this[kHandle], true);
|
|
219
|
+
if (options?.passphrase !== undefined && options.format !== "pem" && options.format !== "der") {
|
|
220
|
+
throw incompatible(options.format, "does not support encryption");
|
|
218
221
|
}
|
|
219
|
-
|
|
220
|
-
if (
|
|
221
|
-
|
|
222
|
+
if (options && options.format === "jwk") return exportJwk(this[kHandle], true);
|
|
223
|
+
if (options && (options.format === "raw-private" || options.format === "raw-seed")) return exportRawPrivate(this[kHandle], options.format);
|
|
224
|
+
const { format, type, cipher, passphrase } = parseKeyEncoding(options, this.asymmetricKeyType, false);
|
|
225
|
+
return exportKey(this[kHandle], format, type, true, cipher, passphrase);
|
|
222
226
|
}
|
|
223
227
|
}
|
|
224
228
|
|
|
@@ -268,31 +272,66 @@ function parseKeyEncoding(enc, keyType, isPublic, objName) {
|
|
|
268
272
|
const format = parseKeyFormat(enc.format, isInput ? "pem" : undefined, option("format", objName));
|
|
269
273
|
const isRequired = (!isInput || format === "der") && format !== "jwk";
|
|
270
274
|
const type = parseKeyType(enc.type, isRequired, keyType, isPublic, option("type", objName));
|
|
271
|
-
let cipher, passphrase;
|
|
275
|
+
let cipher, passphrase, encoding;
|
|
272
276
|
if (isPublic !== true) {
|
|
273
|
-
({ cipher, passphrase } = enc);
|
|
277
|
+
({ cipher, passphrase, encoding } = enc);
|
|
274
278
|
if (!isInput) {
|
|
275
279
|
if (cipher != null) {
|
|
276
280
|
if (typeof cipher !== "string") throw invalidArgValue(option("cipher", objName), "is invalid", cipher);
|
|
277
281
|
if (format === "der" && (type === "pkcs1" || type === "sec1")) throw incompatible(type, "does not support encryption");
|
|
278
282
|
} else if (passphrase !== undefined) {
|
|
279
|
-
throw invalidArgValue(option("cipher", objName), "is
|
|
283
|
+
throw invalidArgValue(option("cipher", objName), "is required when a passphrase is specified", cipher);
|
|
280
284
|
}
|
|
281
285
|
}
|
|
286
|
+
if ((isInput && passphrase !== undefined && !isStringOrBuffer(passphrase)) || (!isInput && cipher != null && !isStringOrBuffer(passphrase))) {
|
|
287
|
+
throw invalidArgValue(option("passphrase", objName), "is invalid", passphrase);
|
|
288
|
+
}
|
|
289
|
+
/* as Node's: an unknown cipher before any work (a key pair isn't generated for nothing) */
|
|
290
|
+
if (!isInput && cipher != null && cryptoCipherInfo(cipher) === null) throw codeError(Error, "ERR_CRYPTO_UNKNOWN_CIPHER", "Unknown cipher");
|
|
282
291
|
}
|
|
292
|
+
if (passphrase !== undefined) passphrase = passphraseBytes(passphrase, encoding);
|
|
283
293
|
return { format, type, cipher, passphrase };
|
|
284
294
|
}
|
|
285
295
|
|
|
286
296
|
const PEM_LABELS = { [SPKI]: "PUBLIC KEY", [PKCS8]: "PRIVATE KEY", [SEC1]: "EC PRIVATE KEY" };
|
|
287
297
|
|
|
288
|
-
|
|
298
|
+
/* Node's IsRSAPrivateKey: an RSAPrivateKey begins with its version, a one-byte INTEGER 0 or 1, where
|
|
299
|
+
* an RSAPublicKey begins with its modulus */
|
|
300
|
+
function isRsaPrivateDer(der) {
|
|
301
|
+
if (der.length < 2 || der[0] !== 0x30) return false;
|
|
302
|
+
const lengthBytes = der[1] & 0x80 ? der[1] & 0x7f : 0;
|
|
303
|
+
const i = 2 + lengthBytes;
|
|
304
|
+
return der[i] === 0x02 && der[i + 1] === 1 && (der[i + 2] === 0 || der[i + 2] === 1);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/* an ArrayBuffer's or a view's (a DataView too) bytes, not copied */
|
|
308
|
+
const viewBytes = (v) => (isAnyArrayBuffer(v) ? Buffer.from(v) : Buffer.from(v.buffer, v.byteOffset, v.byteLength));
|
|
309
|
+
|
|
310
|
+
/* Node's getArrayBufferOrView: a passphrase's bytes (a string in `encoding`, else UTF-8) */
|
|
311
|
+
function passphraseBytes(passphrase, encoding) {
|
|
312
|
+
if (typeof passphrase === "string") return Buffer.from(passphrase, Buffer.isEncoding(encoding) ? encoding : "utf8");
|
|
313
|
+
return viewBytes(passphrase);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const pem = (label, headers, der) => {
|
|
317
|
+
const body = der.toString("base64").replace(/(.{64})/g, "$1\n").replace(/\n$/, "");
|
|
318
|
+
return `-----BEGIN ${label}-----\n${headers}${body}\n-----END ${label}-----\n`;
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
function exportKey(handle, format, type, isPrivate, cipher, passphrase) {
|
|
289
322
|
const encoding = ENCODING_NAMES[type];
|
|
290
|
-
|
|
323
|
+
let der = cryptoKeyExport(handle, encoding);
|
|
291
324
|
if (typeof der === "number") throw keyError(der);
|
|
325
|
+
const encrypt = cipher != null;
|
|
326
|
+
if (encrypt && encoding === PKCS8) {
|
|
327
|
+
der = encryptPkcs8(der, cipher, passphrase);
|
|
328
|
+
return format === "der" ? der : pem("ENCRYPTED PRIVATE KEY", "", der);
|
|
329
|
+
}
|
|
292
330
|
if (format === "der") return der;
|
|
293
331
|
const label = encoding === PKCS1 ? (isPrivate ? "RSA PRIVATE KEY" : "RSA PUBLIC KEY") : PEM_LABELS[encoding];
|
|
294
|
-
|
|
295
|
-
|
|
332
|
+
if (!encrypt) return pem(label, "", der);
|
|
333
|
+
const { headers, body } = encryptPemBody(der, cipher, passphrase);
|
|
334
|
+
return pem(label, headers, body);
|
|
296
335
|
}
|
|
297
336
|
|
|
298
337
|
/* A PEM's label → the encoding it holds, and whether it is private */
|
|
@@ -307,25 +346,101 @@ const PEM_TYPES = {
|
|
|
307
346
|
|
|
308
347
|
/** A key handle from PEM or DER: the private key when `wantPrivate`, else a public one (a
|
|
309
348
|
* private key's public part, as createPublicKey takes it). */
|
|
310
|
-
function parseKey(data, format, type, wantPrivate) {
|
|
349
|
+
function parseKey(data, format, type, wantPrivate, passphrase) {
|
|
311
350
|
if (format === "der") {
|
|
312
351
|
const encoding = ENCODING_NAMES[type];
|
|
313
|
-
const isPrivate = encoding === PKCS8 || encoding === SEC1 || (encoding === PKCS1 && wantPrivate);
|
|
352
|
+
const isPrivate = encoding === PKCS8 || encoding === SEC1 || (encoding === PKCS1 && (wantPrivate || isRsaPrivateDer(viewBytes(data))));
|
|
353
|
+
if (encoding === PKCS8) {
|
|
354
|
+
const bytes = viewBytes(data);
|
|
355
|
+
if (isEncryptedPkcs8(bytes)) data = decryptPkcs8(bytes, passphrase);
|
|
356
|
+
}
|
|
314
357
|
const handle = cryptoKeyParse(data, encoding, isPrivate);
|
|
315
358
|
if (typeof handle === "number") throw keyError(handle);
|
|
316
359
|
return wantPrivate || !isPrivate ? handle : cryptoKeyPublic(handle);
|
|
317
360
|
}
|
|
318
|
-
const text = typeof data === "string" ? data :
|
|
319
|
-
|
|
361
|
+
const text = typeof data === "string" ? data : viewBytes(data).toString("latin1");
|
|
362
|
+
/* the first block that holds a key, as OpenSSL's PEM reader: an EC PARAMETERS block may come before it */
|
|
363
|
+
const m = [...text.matchAll(/-----BEGIN ([A-Z0-9 ]+)-----\r?\n([\s\S]*?)-----END \1-----/g)]
|
|
364
|
+
.find((b) => b[1] === "ENCRYPTED PRIVATE KEY" || Object.hasOwn(PEM_TYPES, b[1])) ?? null;
|
|
320
365
|
if (m === null) throw keyError(1);
|
|
321
|
-
|
|
322
|
-
|
|
366
|
+
let label = m[1], body = m[2], der;
|
|
367
|
+
if (label === "ENCRYPTED PRIVATE KEY") {
|
|
368
|
+
checkPemPassphrase(passphrase);
|
|
369
|
+
der = decryptPkcs8(Buffer.from(body, "base64"), passphrase);
|
|
370
|
+
label = "PRIVATE KEY";
|
|
371
|
+
} else if (/^Proc-Type:\s*4,ENCRYPTED/m.test(body)) {
|
|
372
|
+
const split = body.search(/\r?\n\r?\n/);
|
|
373
|
+
der = decryptPemBody(body.slice(0, split), Buffer.from(body.slice(split), "base64"), passphrase);
|
|
374
|
+
} else {
|
|
375
|
+
der = Buffer.from(body, "base64");
|
|
376
|
+
}
|
|
377
|
+
const known = PEM_TYPES[label];
|
|
323
378
|
if (known === undefined || (wantPrivate && !known[1])) throw keyError(1);
|
|
324
|
-
const handle = cryptoKeyParse(
|
|
379
|
+
const handle = cryptoKeyParse(der, known[0], known[1]);
|
|
325
380
|
if (typeof handle === "number") throw keyError(handle);
|
|
326
381
|
return known[1] && !wantPrivate ? cryptoKeyPublic(handle) : handle;
|
|
327
382
|
}
|
|
328
383
|
|
|
384
|
+
/* ---- raw keys (Node 24): the public point or key, the private scalar or key -------------------- */
|
|
385
|
+
|
|
386
|
+
/* the curves raw keys name (Node takes the JWK names too): [OpenSSL's name, JWK's, scalar bytes] */
|
|
387
|
+
const P256 = ["prime256v1", "P-256", 32], P384 = ["secp384r1", "P-384", 48], P521 = ["secp521r1", "P-521", 66];
|
|
388
|
+
const RAW_CURVES = {
|
|
389
|
+
"P-256": P256, prime256v1: P256, "P-384": P384, secp384r1: P384, "P-521": P521, secp521r1: P521, secp256k1: ["secp256k1", "secp256k1", 32],
|
|
390
|
+
};
|
|
391
|
+
const RAW_KINDS = new Set(["ec", "ed25519", "x25519"]);
|
|
392
|
+
const rawUnsupported = (format) => incompatible(format, "is not supported for this key type");
|
|
393
|
+
|
|
394
|
+
function exportRawPublic(handle, type = "uncompressed") {
|
|
395
|
+
const [, kind, namedCurve] = cryptoKeyInfo(handle);
|
|
396
|
+
if (!RAW_KINDS.has(kind)) throw rawUnsupported("raw-public");
|
|
397
|
+
const point = cryptoKeyParts(handle)[0];
|
|
398
|
+
if (kind !== "ec") return point;
|
|
399
|
+
if (type !== "compressed" && type !== "uncompressed") throw invalidArgValue("options.type", "must be one of: 'compressed', 'uncompressed'", type);
|
|
400
|
+
return type === "uncompressed" ? point : cryptoEcPoint(namedCurve, point, 2);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function exportRawPrivate(handle, format) {
|
|
404
|
+
const [, kind] = cryptoKeyInfo(handle);
|
|
405
|
+
if (format === "raw-seed" || !RAW_KINDS.has(kind)) throw rawUnsupported(format);
|
|
406
|
+
return cryptoKeyParts(handle)[1];
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/** A key handle from raw bytes: { key, format, asymmetricKeyType, namedCurve }. */
|
|
410
|
+
function rawKeyHandle(key, format, wantPrivate) {
|
|
411
|
+
const { key: data, asymmetricKeyType: kind } = key;
|
|
412
|
+
if (wantPrivate && format === "raw-public") throw invalidArgValue("key.format", "is invalid", format);
|
|
413
|
+
if (!isArrayBufferView(data) && !isAnyArrayBuffer(data)) {
|
|
414
|
+
throw invalidArgType("key.key", "an instance of ArrayBuffer, Buffer, TypedArray, or DataView", data);
|
|
415
|
+
}
|
|
416
|
+
validateString(kind, "key.asymmetricKeyType");
|
|
417
|
+
if (kind === "ec") validateString(key.namedCurve, "key.namedCurve");
|
|
418
|
+
if (kind === "rsa" || kind === "rsa-pss" || kind === "dsa" || kind === "dh") throw rawUnsupported(format);
|
|
419
|
+
if (!RAW_KINDS.has(kind)) throw invalidArgValue("key.asymmetricKeyType", "is invalid: Invalid asymmetricKeyType", kind);
|
|
420
|
+
if (format === "raw-seed") throw rawUnsupported(format);
|
|
421
|
+
const bytes = viewBytes(data);
|
|
422
|
+
const isPrivate = format === "raw-private";
|
|
423
|
+
let handle;
|
|
424
|
+
if (kind === "ec") {
|
|
425
|
+
const curve = RAW_CURVES[key.namedCurve];
|
|
426
|
+
if (curve === undefined) throw codeError(TypeError, "ERR_CRYPTO_INVALID_CURVE", "Invalid EC curve name");
|
|
427
|
+
const [name, jwk, size] = curve;
|
|
428
|
+
let point;
|
|
429
|
+
if (isPrivate) {
|
|
430
|
+
if (bytes.length !== size) throw invalidArgValue("key.key", "is invalid", data);
|
|
431
|
+
} else {
|
|
432
|
+
point = cryptoEcPoint(name, bytes, 4);
|
|
433
|
+
if (typeof point === "number") throw invalidArgValue("key.key", "is invalid", data);
|
|
434
|
+
}
|
|
435
|
+
handle = cryptoKeyFromParts("ec", isPrivate, jwk, isPrivate ? [undefined, bytes] : [point]);
|
|
436
|
+
} else {
|
|
437
|
+
if (bytes.length !== 32) throw invalidArgValue("key.key", "is invalid", data);
|
|
438
|
+
handle = cryptoKeyFromParts(kind, isPrivate, undefined, isPrivate ? [undefined, bytes] : [bytes]);
|
|
439
|
+
}
|
|
440
|
+
if (typeof handle === "number") throw invalidArgValue("key.key", "is invalid", data);
|
|
441
|
+
return isPrivate && !wantPrivate ? cryptoKeyPublic(handle) : handle;
|
|
442
|
+
}
|
|
443
|
+
|
|
329
444
|
/* ---- JWK --------------------------------------------------------------------------------------- */
|
|
330
445
|
|
|
331
446
|
const b64u = (buf) => buf.toString("base64url");
|
|
@@ -445,12 +560,13 @@ function prepareAsymmetricKey(key, ctx) {
|
|
|
445
560
|
validateObject(data, "key.key");
|
|
446
561
|
return { key: keyFromJwk(data, !wantPrivate) };
|
|
447
562
|
}
|
|
563
|
+
if (format === "raw-public" || format === "raw-private" || format === "raw-seed") return { key: rawKeyHandle(key, format, wantPrivate) };
|
|
448
564
|
if (!isStringOrBuffer(data)) {
|
|
449
565
|
throw invalidArgType("key.key", "of type string or an instance of ArrayBuffer, Buffer, TypedArray, or DataView", data);
|
|
450
566
|
}
|
|
451
|
-
const { format: fmt, type } = parseKeyEncoding(key, undefined, wantPrivate ? false : undefined, "key");
|
|
567
|
+
const { format: fmt, type, passphrase } = parseKeyEncoding(key, undefined, wantPrivate ? false : undefined, "key");
|
|
452
568
|
const bytes = typeof data === "string" && fmt === "der" ? Buffer.from(data, encoding ?? "utf8") : data;
|
|
453
|
-
return { key: parseKey(bytes, fmt, type, wantPrivate) };
|
|
569
|
+
return { key: parseKey(bytes, fmt, type, wantPrivate, passphrase) };
|
|
454
570
|
}
|
|
455
571
|
/* Node 24 lists URL where a private key is wanted (an OpenSSL store's; not visaljs's yet) */
|
|
456
572
|
const expected = ctx === CREATE_PRIVATE
|
|
@@ -526,17 +642,34 @@ export function generateKey(type, options, callback) {
|
|
|
526
642
|
later(() => callback(null, key));
|
|
527
643
|
}
|
|
528
644
|
|
|
645
|
+
/* Node's parseKeyFormatAndType for the raw formats of a key pair's encodings */
|
|
646
|
+
function rawEncoding(enc, isPublic) {
|
|
647
|
+
const { format, type } = enc;
|
|
648
|
+
const name = isPublic ? "options.publicKeyEncoding" : "options.privateKeyEncoding";
|
|
649
|
+
if (format === "raw-public") {
|
|
650
|
+
if (!isPublic) throw invalidArgValue(`${name}.format`, "is invalid", format);
|
|
651
|
+
if (type !== undefined && type !== "uncompressed" && type !== "compressed") throw invalidArgValue(`${name}.type`, "is invalid", type);
|
|
652
|
+
return { format, type };
|
|
653
|
+
}
|
|
654
|
+
if (format === "raw-private" || format === "raw-seed") {
|
|
655
|
+
if (isPublic) throw invalidArgValue(`${name}.format`, "is invalid", format);
|
|
656
|
+
if (type !== undefined) throw invalidArgValue(`${name}.type`, "is invalid", type);
|
|
657
|
+
if (enc.cipher != null || enc.passphrase !== undefined) throw incompatible("raw format", "does not support encryption");
|
|
658
|
+
return { format };
|
|
659
|
+
}
|
|
660
|
+
return undefined;
|
|
661
|
+
}
|
|
662
|
+
|
|
529
663
|
function pairEncodings(keyType, options) {
|
|
530
664
|
const { publicKeyEncoding, privateKeyEncoding } = options ?? {};
|
|
531
665
|
let pub, priv;
|
|
532
666
|
if (publicKeyEncoding != null) {
|
|
533
667
|
if (typeof publicKeyEncoding !== "object") throw invalidArgValue("options.publicKeyEncoding", "is invalid", publicKeyEncoding);
|
|
534
|
-
pub = publicKeyEncoding.format === "jwk" ? { format: "jwk" } : parseKeyEncoding(publicKeyEncoding, keyType, true, "options.publicKeyEncoding");
|
|
668
|
+
pub = publicKeyEncoding.format === "jwk" ? { format: "jwk" } : rawEncoding(publicKeyEncoding, true) ?? parseKeyEncoding(publicKeyEncoding, keyType, true, "options.publicKeyEncoding");
|
|
535
669
|
}
|
|
536
670
|
if (privateKeyEncoding != null) {
|
|
537
671
|
if (typeof privateKeyEncoding !== "object") throw invalidArgValue("options.privateKeyEncoding", "is invalid", privateKeyEncoding);
|
|
538
|
-
priv = privateKeyEncoding.format === "jwk" ? { format: "jwk" } : parseKeyEncoding(privateKeyEncoding, keyType, false, "options.privateKeyEncoding");
|
|
539
|
-
if (priv.cipher != null) throw notSupported("Encrypting a private key");
|
|
672
|
+
priv = privateKeyEncoding.format === "jwk" ? { format: "jwk" } : rawEncoding(privateKeyEncoding, false) ?? parseKeyEncoding(privateKeyEncoding, keyType, false, "options.privateKeyEncoding");
|
|
540
673
|
}
|
|
541
674
|
return { pub, priv };
|
|
542
675
|
}
|
|
@@ -644,3 +777,5 @@ export function generateKeyPair(type, options, callback) {
|
|
|
644
777
|
}
|
|
645
778
|
later(() => callback(null, result.publicKey, result.privateKey));
|
|
646
779
|
}
|
|
780
|
+
/* as Node's: util.promisify(generateKeyPair) resolves to { publicKey, privateKey } */
|
|
781
|
+
Object.defineProperty(generateKeyPair, Symbol.for("visaljs.util.customPromisifyArgs"), { value: ["publicKey", "privateKey"] });
|
|
@@ -111,6 +111,30 @@ export function randomInt(min, max, callback) {
|
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
let hexBytes = null;
|
|
115
|
+
|
|
116
|
+
/* Node's getUnbufferedUUIDv7: 48 bits of Date.now(), then version 7, the variant and random bits */
|
|
117
|
+
export function randomUUIDv7(options) {
|
|
118
|
+
if (options !== undefined) validateObject(options, "options");
|
|
119
|
+
const { disableEntropyCache = false } = options || {};
|
|
120
|
+
validateBoolean(disableEntropyCache, "options.disableEntropyCache");
|
|
121
|
+
const b = randomFillSync(new Uint8Array(16), 6);
|
|
122
|
+
const now = Date.now();
|
|
123
|
+
const msb = now / 2 ** 32;
|
|
124
|
+
b[0] = msb >>> 8;
|
|
125
|
+
b[1] = msb;
|
|
126
|
+
b[2] = now >>> 24;
|
|
127
|
+
b[3] = now >>> 16;
|
|
128
|
+
b[4] = now >>> 8;
|
|
129
|
+
b[5] = now;
|
|
130
|
+
b[6] = (b[6] & 0x0f) | 0x70;
|
|
131
|
+
b[8] = (b[8] & 0x3f) | 0x80;
|
|
132
|
+
hexBytes ??= Array.from({ length: 256 }, (_, i) => (i < 16 ? "0" : "") + i.toString(16));
|
|
133
|
+
let out = "";
|
|
134
|
+
for (let i = 0; i < 16; i++) out += (i === 4 || i === 6 || i === 8 || i === 10 ? "-" : "") + hexBytes[b[i]];
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
|
|
114
138
|
export function randomUUID(options) {
|
|
115
139
|
if (options !== undefined) validateObject(options, "options");
|
|
116
140
|
const { disableEntropyCache = false } = options || {};
|
|
@@ -36,8 +36,9 @@ export function validateEncoding(data, enc) {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/** Node's validateObject: an object, not null nor an array. */
|
|
39
40
|
export function validateObject(value, name) {
|
|
40
|
-
if (value === null || typeof value !== "object") throw invalidArgType(name, "of type object", value);
|
|
41
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw invalidArgType(name, "of type object", value);
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
export function validateBoolean(value, name) {
|