@gjsify/tls 0.3.21 → 0.4.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/lib/esm/_virtual/_rolldown/runtime.js +1 -0
- package/lib/esm/index.js +2 -3
- package/lib/types/cert.spec.d.ts +2 -0
- package/lib/types/index.d.ts +82 -27
- package/lib/types/tls.gjs.spec.d.ts +2 -0
- package/package.json +7 -7
- package/src/cert.spec.ts +230 -0
- package/src/index.spec.ts +126 -103
- package/src/index.ts +580 -181
- package/src/test.mts +3 -1
- package/src/tls.gjs.spec.ts +165 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var e=Object.defineProperty,__name=(t,n)=>e(t,`name`,{value:n,configurable:!0});export{__name};
|
package/lib/esm/index.js
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import e from"@girs/gio-2.0";import t from"@girs/glib-2.0";import{Server as n,Socket as r}from"node:net";import{createNodeError as i,deferEmit as a}from"@gjsify/utils";const o=`TLSv1.2`,s=`TLSv1.3`,c=`TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384`;function getCiphers(){return[`aes-128-gcm`,`aes-256-gcm`,`chacha20-poly1305`,`aes-128-cbc`,`aes-256-cbc`]}function
|
|
2
|
-
`):typeof t==`string`?t:t.toString(`utf-8`),i=n?Array.isArray(n)?n.map(e=>typeof e==`string`?e:e.toString(`utf-8`)).join(`
|
|
3
|
-
`):typeof n==`string`?n:n.toString(`utf-8`):``,a=i?`${r}\n${i}`:r;return e.TlsCertificate.new_from_pem(a,a.length)}var TLSServer=class extends n{_tlsCertificate=null;_tlsOptions;_sniContexts=new Map;constructor(e,t){if(super(),this._tlsOptions=e||{},t&&this.on(`secureConnection`,t),this._tlsOptions.cert)try{this._tlsCertificate=buildGioCertificate(this._tlsOptions.cert,this._tlsOptions.key)}catch(e){a(this,`error`,i(e,`createServer`,{}))}}addContext(e,t){if(t.cert)try{let n=buildGioCertificate(t.cert,t.key);this._sniContexts.set(e,n)}catch(e){this.emit(`error`,i(e,`addContext`,{}))}}listen(...e){return this.on(`connection`,e=>{this._upgradeTls(e)}),super.listen(...e)}_upgradeTls(n){let r=n._connection;if(!r){let e=Error(`Cannot upgrade socket: no underlying connection`);this.emit(`tlsClientError`,e,n),n.destroy();return}if(!this._tlsCertificate){let e=Error(`TLS server has no certificate configured`);this.emit(`tlsClientError`,e,n),n.destroy();return}try{let a=e.TlsServerConnection.new(r,this._tlsCertificate);if(this._tlsOptions.requestCert?a.authenticationMode=this._tlsOptions.rejectUnauthorized===!1?e.TlsAuthenticationMode.REQUESTED:e.TlsAuthenticationMode.REQUIRED:a.authenticationMode=e.TlsAuthenticationMode.NONE,this._tlsOptions.rejectUnauthorized===!1&&a.connect(`accept-certificate`,()=>!0),this._tlsOptions.ALPNProtocols&&this._tlsOptions.ALPNProtocols.length>0)try{a.set_advertised_protocols(this._tlsOptions.ALPNProtocols)}catch{}let o=new e.Cancellable;a.handshake_async(t.PRIORITY_DEFAULT,o,(e,t)=>{try{a.handshake_finish(t);let e=new TLSSocket;e.encrypted=!0,e.authorized=!0,e._setupTlsStreams(a),e.alpnProtocol=e.getAlpnProtocol(),e._startReading(),this.emit(`secureConnection`,e)}catch(e){let t=i(e,`handshake`,{});this.emit(`tlsClientError`,t,n),n.destroy()}})}catch(e){let t=i(e,`tls_wrap`,{});this.emit(`tlsClientError`,t,n),n.destroy()}}};function createServer(e,t){return typeof e==`function`?new TLSServer(void 0,e):new TLSServer(e,t)}var u={TLSSocket,TLSServer,Server:TLSServer,connect,createServer,createSecureContext,checkServerIdentity,getCiphers,rootCertificates:l,DEFAULT_MIN_VERSION:o,DEFAULT_MAX_VERSION:s,DEFAULT_CIPHERS:c};export{c as DEFAULT_CIPHERS,s as DEFAULT_MAX_VERSION,o as DEFAULT_MIN_VERSION,TLSServer as Server,TLSServer,TLSSocket,checkServerIdentity,connect,createSecureContext,createServer,u as default,getCiphers,l as rootCertificates};
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import e from"@girs/gio-2.0";import t from"@girs/glib-2.0";import{Server as n,Socket as r}from"node:net";import{createNodeError as i,deferEmit as a}from"@gjsify/utils";const o=`TLSv1.2`,s=`TLSv1.3`,c=`TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384`;function getCiphers(){return[`aes-128-gcm`,`aes-256-gcm`,`chacha20-poly1305`,`aes-128-cbc`,`aes-256-cbc`]}function pemToString(e){if(Array.isArray(e))return e.map(pemToString).join(`
|
|
2
|
+
`);if(typeof e==`string`)return e;if(e&&typeof e.toString==`function`)try{return e.toString(`utf-8`)}catch{return new TextDecoder(`utf-8`).decode(e)}return String(e)}function splitPemBlocks(e){let t=[],n=/-----BEGIN [^-]+-----[\s\S]*?-----END [^-]+-----/g,r;for(;(r=n.exec(e))!==null;)t.push(r[0]);return t}function buildGioCertificate(t,n){let r=pemToString(t),i=n?pemToString(n):``,a=i?`${r}\n${i}`:r;return e.TlsCertificate.new_from_pem(a,a.length)}function buildCaCertificates(t){let n=[];if(Array.isArray(t))for(let e of t)n.push(...splitPemBlocks(pemToString(e)));else n.push(...splitPemBlocks(pemToString(t)));let r=[];for(let t of n)try{r.push(e.TlsCertificate.new_from_pem(t,t.length))}catch{}return r}function parseDistinguishedName(e){if(!e)return{};let t={};for(let n of e.split(/,(?![^=]*=)/)){let e=n.indexOf(`=`);if(e<0)continue;let r=n.slice(0,e).trim(),i=n.slice(e+1).trim(),a=t[r];a===void 0?t[r]=i:Array.isArray(a)?a.push(i):t[r]=[a,i]}return t}function formatCertDate(e){if(!e)return``;try{return e.format(`%b %d %H:%M:%S %Y GMT`)??``}catch{return``}}function formatAltNames(e){let t=[];try{let n=e.get_dns_names();if(n)for(let e of n){let n=e.get_data();n&&t.push(`DNS:${new TextDecoder(`utf-8`).decode(n)}`)}}catch{}try{let n=e.get_ip_addresses();if(n)for(let e of n)t.push(`IP Address:${e.to_string()}`)}catch{}return t.join(`, `)}function fingerprintFromBytes(e,n){try{let r=new t.Checksum(n);r.update(e);let i=r.get_string();if(!i)return``;let a=[];for(let e=0;e<i.length;e+=2)a.push(i.slice(e,e+2).toUpperCase());return a.join(`:`)}catch{return``}}function pemToDer(e){let t=/-----BEGIN CERTIFICATE-----([\s\S]*?)-----END CERTIFICATE-----/.exec(e);if(!t)return new Uint8Array;let n=t[1].replace(/[\s\r\n]+/g,``);try{let e=globalThis.atob;if(!e)return new Uint8Array;let t=e(n),r=new Uint8Array(t.length);for(let e=0;e<t.length;e++)r[e]=t.charCodeAt(e);return r}catch{return new Uint8Array}}function tlsCertToPeerCert(e,n){let r={};try{r.subject=parseDistinguishedName(e.get_subject_name())}catch{}try{r.issuer=parseDistinguishedName(e.get_issuer_name())}catch{}r.subjectaltname=formatAltNames(e);try{r.valid_from=formatCertDate(e.get_not_valid_before()),r.valid_to=formatCertDate(e.get_not_valid_after())}catch{}try{let n=e,i=n.certificate_pem??n.certificatePem;if(i){let e=pemToDer(i);r.raw=e,r.fingerprint=fingerprintFromBytes(e,t.ChecksumType.SHA1),r.fingerprint256=fingerprintFromBytes(e,t.ChecksumType.SHA256)}}catch{}if(n)try{let t=e.get_issuer();t&&!t.is_same(e)?r.issuerCertificate=tlsCertToPeerCert(t,!0):t&&(r.issuerCertificate=r)}catch{}return r}function unfqdn(e){return e.endsWith(`.`)?e.slice(0,-1):e}function splitHost(e){return unfqdn(e).toLowerCase().split(`.`)}function isPrintableAscii(e){for(let t=0;t<e.length;t++){let n=e.charCodeAt(t);if(n<33||n>126)return!1}return!0}function checkHostMatch(e,t){if(!t)return!1;let n=splitHost(t);if(e.length!==n.length||n.includes(``)||!n.every(isPrintableAscii))return!1;for(let t=e.length-1;t>0;t--)if(e[t]!==n[t])return!1;let r=e[0],i=n[0],a=i.split(`*`,3);if(a.length===1||i.includes(`xn--`))return r===i;if(a.length>2||n.length<=2)return!1;let o=a[0],s=a[1];return!(o.length+s.length>r.length||!r.startsWith(o)||!r.endsWith(s))}function checkServerIdentity(e,t){let n=t.subject,r=t.subjectaltname,i=[],a=[];if(e=String(e),r){let e=r.split(`, `);for(let t of e)t.startsWith(`DNS:`)?i.push(t.slice(4)):t.startsWith(`IP Address:`)&&a.push(t.slice(11).trim())}let o=!1,s=`Unknown reason`;e=unfqdn(e);let c=/^(\d{1,3}\.){3}\d{1,3}$/.test(e),l=e.includes(`:`);if(c||l)o=a.some(t=>t.toLowerCase()===e.toLowerCase()),o||(s=`IP: ${e} is not in the cert's list: ${a.join(`, `)}`);else if(i.length>0||n?.CN){let t=splitHost(e);if(i.length>0)o=i.some(e=>checkHostMatch(t,e.trim())),o||(s=`Host: ${e}. is not in the cert's altnames: ${r}`);else{let r=n?.CN;Array.isArray(r)?o=r.some(e=>checkHostMatch(t,e)):r&&(o=checkHostMatch(t,r)),o||(s=`Host: ${e}. is not cert's CN: ${r}`)}}else s=`Cert does not contain a DNS name`;if(!o){let n=Error(s);return n.reason=s,n.host=e,n.cert=t,n.code=`ERR_TLS_CERT_ALTNAME_INVALID`,n}}function createSecureContext(e){let t=e??{},n=null;if(t.cert)try{n=buildGioCertificate(t.cert,t.key)}catch{n=null}let r=t.ca?buildCaCertificates(t.ca):[],i={certificate:n,caCertificates:r,options:t};return i.context=i,i}var TLSSocket=class extends r{encrypted=!0;authorized=!1;authorizationError;alpnProtocol=!1;servername;_tlsConnection=null;_secureContext=null;constructor(e,t){super()}_setupTlsStreams(e){this._tlsConnection=e;let t=this;t._inputStream=e.get_input_stream(),t._outputStream=e.get_output_stream(),t._connection=e}getPeerCertificate(e=!1){if(!this._tlsConnection)return{};try{let t=this._tlsConnection.get_peer_certificate();return t?tlsCertToPeerCert(t,e):{}}catch{return{}}}getProtocol(){if(!this._tlsConnection)return null;try{switch(this._tlsConnection.get_protocol_version()){case e.TlsProtocolVersion.TLS_1_0:return`TLSv1`;case e.TlsProtocolVersion.TLS_1_1:return`TLSv1.1`;case e.TlsProtocolVersion.TLS_1_2:return`TLSv1.2`;case e.TlsProtocolVersion.TLS_1_3:return`TLSv1.3`;default:return null}}catch{return null}}getCipher(){if(!this._tlsConnection)return null;try{return{name:this._tlsConnection.get_ciphersuite_name()||`unknown`,version:this.getProtocol()||`unknown`}}catch{return null}}getAlpnProtocol(){if(!this._tlsConnection)return!1;try{return this._tlsConnection.get_negotiated_protocol()||!1}catch{return!1}}};function connect(n,r){let i=new TLSSocket(void 0,n);r&&i.once(`secureConnect`,r);let a=n.port||443,o=n.host||`localhost`,s=n.servername||o,c=n.rejectUnauthorized!==!1,l=n.secureContext??createSecureContext(n);i._secureContext=l,i.servername=s;let u=n.checkServerIdentity;return i.once(`connect`,()=>{let r=i._connection;if(!r){i.destroy(Error(`No underlying connection for TLS upgrade`));return}try{let o=e.NetworkAddress.new(s,a),d=e.TlsClientConnection.new(r,o);if(d.set_server_identity(o),l.certificate)try{d.set_certificate(l.certificate)}catch(e){console.warn(`[tls] failed to set client certificate:`,e)}if(n.ALPNProtocols&&n.ALPNProtocols.length>0)try{d.set_advertised_protocols(n.ALPNProtocols)}catch{}d.connect(`accept-certificate`,(t,n,r)=>{if(!c)return!0;if(l.caCertificates.length===0)return!1;for(let t of l.caCertificates)try{if(n.verify(o,t)===e.TlsCertificateFlags.NO_FLAGS)return!0}catch{}return!1});let f=new e.Cancellable;d.handshake_async(t.PRIORITY_DEFAULT,f,(e,t)=>{try{if(d.handshake_finish(t),i.authorized=!0,i._setupTlsStreams(d),i.alpnProtocol=i.getAlpnProtocol(),u){let e=u(s,i.getPeerCertificate());if(e&&(i.authorized=!1,i.authorizationError=e.message,c)){i.destroy(e);return}}let e=i;e._reading=!1,e._startReading(),i.emit(`secureConnect`)}catch(e){i.authorized=!1,i.authorizationError=e instanceof Error?e.message:String(e),c?i.destroy(e instanceof Error?e:Error(String(e))):(i._setupTlsStreams(d),i.emit(`secureConnect`))}})}catch(e){i.destroy(e instanceof Error?e:Error(String(e)))}}),i.connect({port:a,host:o}),i}const l=[];var TLSServer=class extends n{_tlsCertificate=null;_tlsOptions;_sniContexts=new Map;_secureContext;constructor(e,t){super(),this._tlsOptions=e??{},this._secureContext=createSecureContext(this._tlsOptions),this._tlsCertificate=this._secureContext.certificate,t&&this.on(`secureConnection`,t),this._tlsOptions.cert&&!this._tlsCertificate&&a(this,`error`,i(Error(`Failed to parse TLS certificate`),`createServer`,{}))}addContext(e,t){try{let n=createSecureContext(t);this._sniContexts.set(e.toLowerCase(),n)}catch(e){this.emit(`error`,i(e,`addContext`,{}))}}_resolveSniContext(e,t){let n=this._secureContext;if(!e){t(n);return}let r=e.toLowerCase(),i=this._sniContexts.get(r);if(i){t(i);return}let a=splitHost(r);for(let[e,n]of this._sniContexts)if(checkHostMatch(a,e)){t(n);return}if(this._tlsOptions.SNICallback)try{this._tlsOptions.SNICallback(e,(e,r)=>{if(e||!r){t(n);return}t(r)});return}catch{t(n);return}t(n)}listen(...e){return this.on(`connection`,e=>{this._upgradeTls(e)}),super.listen(...e)}_upgradeTls(n){let r=n._connection;if(!r){let e=Error(`Cannot upgrade socket: no underlying connection`);this.emit(`tlsClientError`,e,n),n.destroy();return}if(!this._tlsCertificate&&this._sniContexts.size===0&&!this._tlsOptions.SNICallback){let e=Error(`TLS server has no certificate configured`);this.emit(`tlsClientError`,e,n),n.destroy();return}this._resolveSniContext(null,a=>{let o=a.certificate??this._tlsCertificate;if(!o){let e=Error(`SNI resolution returned no certificate`);this.emit(`tlsClientError`,e,n),n.destroy();return}try{let s=e.TlsServerConnection.new(r,o);this._tlsOptions.requestCert?s.authenticationMode=this._tlsOptions.rejectUnauthorized===!1?e.TlsAuthenticationMode.REQUESTED:e.TlsAuthenticationMode.REQUIRED:s.authenticationMode=e.TlsAuthenticationMode.NONE;let c=!!this._tlsOptions.requestCert&&this._tlsOptions.rejectUnauthorized!==!1,l=this._secureContext.caCertificates;if(s.connect(`accept-certificate`,(t,n,r)=>{if(!c)return!0;if(l.length===0)return!1;for(let t of l)try{if(n.verify(null,t)===e.TlsCertificateFlags.NO_FLAGS)return!0}catch{}return!1}),this._tlsOptions.ALPNProtocols&&this._tlsOptions.ALPNProtocols.length>0)try{s.set_advertised_protocols(this._tlsOptions.ALPNProtocols)}catch{}let u=new e.Cancellable;s.handshake_async(t.PRIORITY_DEFAULT,u,(e,t)=>{try{s.handshake_finish(t);let e=new TLSSocket;e.encrypted=!0,e.authorized=!0,e._secureContext=a,e._setupTlsStreams(s),e.alpnProtocol=e.getAlpnProtocol(),e._startReading(),this.emit(`secureConnection`,e)}catch(e){let t=i(e,`handshake`,{});this.emit(`tlsClientError`,t,n),n.destroy()}})}catch(e){let t=i(e,`tls_wrap`,{});this.emit(`tlsClientError`,t,n),n.destroy()}})}};function createServer(e,t){return typeof e==`function`?new TLSServer(void 0,e):new TLSServer(e,t)}const u={TLSSocket,TLSServer,Server:TLSServer,connect,createServer,createSecureContext,checkServerIdentity,getCiphers,rootCertificates:l,DEFAULT_MIN_VERSION:o,DEFAULT_MAX_VERSION:s,DEFAULT_CIPHERS:c};export{c as DEFAULT_CIPHERS,s as DEFAULT_MAX_VERSION,o as DEFAULT_MIN_VERSION,TLSServer as Server,TLSServer,TLSSocket,checkServerIdentity,connect,createSecureContext,createServer,u as default,getCiphers,l as rootCertificates};
|
package/lib/types/index.d.ts
CHANGED
|
@@ -5,33 +5,74 @@ export declare const DEFAULT_MAX_VERSION = "TLSv1.3";
|
|
|
5
5
|
export declare const DEFAULT_CIPHERS = "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384";
|
|
6
6
|
/** Returns a list of supported TLS cipher names (subset; implementation-defined). */
|
|
7
7
|
export declare function getCiphers(): string[];
|
|
8
|
+
type PemInput = string | Buffer | Uint8Array | Array<string | Buffer | Uint8Array>;
|
|
9
|
+
export interface CertSubject {
|
|
10
|
+
CN?: string | string[];
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
8
13
|
export interface PeerCertificate {
|
|
9
|
-
subject?:
|
|
10
|
-
|
|
11
|
-
[key: string]: unknown;
|
|
12
|
-
};
|
|
14
|
+
subject?: CertSubject;
|
|
15
|
+
issuer?: CertSubject;
|
|
13
16
|
subjectaltname?: string;
|
|
17
|
+
valid_from?: string;
|
|
18
|
+
valid_to?: string;
|
|
19
|
+
fingerprint?: string;
|
|
20
|
+
fingerprint256?: string;
|
|
21
|
+
serialNumber?: string;
|
|
22
|
+
raw?: Uint8Array;
|
|
23
|
+
issuerCertificate?: PeerCertificate;
|
|
14
24
|
[key: string]: unknown;
|
|
15
25
|
}
|
|
26
|
+
/** Error returned by checkServerIdentity, with Node-compatible shape. */
|
|
27
|
+
export interface CertAltNameError extends Error {
|
|
28
|
+
reason: string;
|
|
29
|
+
host: string;
|
|
30
|
+
cert: PeerCertificate;
|
|
31
|
+
code: 'ERR_TLS_CERT_ALTNAME_INVALID';
|
|
32
|
+
}
|
|
16
33
|
/**
|
|
17
34
|
* Verifies that the certificate `cert` is valid for `hostname`.
|
|
18
|
-
* Returns an Error if the check
|
|
35
|
+
* Returns an Error (with code 'ERR_TLS_CERT_ALTNAME_INVALID') if the check
|
|
36
|
+
* fails, or `undefined` on success.
|
|
19
37
|
*
|
|
20
|
-
* Reference: Node.js lib/tls.js exports.checkServerIdentity
|
|
38
|
+
* Reference: Node.js lib/tls.js exports.checkServerIdentity (RFC 6125 §6.4.3).
|
|
21
39
|
*/
|
|
22
|
-
export declare function checkServerIdentity(hostname: string, cert: PeerCertificate):
|
|
40
|
+
export declare function checkServerIdentity(hostname: string, cert: PeerCertificate): CertAltNameError | undefined;
|
|
23
41
|
export interface SecureContextOptions {
|
|
24
|
-
ca?:
|
|
25
|
-
cert?:
|
|
26
|
-
key?:
|
|
42
|
+
ca?: PemInput;
|
|
43
|
+
cert?: PemInput;
|
|
44
|
+
key?: PemInput;
|
|
45
|
+
passphrase?: string;
|
|
27
46
|
rejectUnauthorized?: boolean;
|
|
47
|
+
ciphers?: string;
|
|
48
|
+
minVersion?: string;
|
|
49
|
+
maxVersion?: string;
|
|
50
|
+
ALPNProtocols?: string[];
|
|
51
|
+
}
|
|
52
|
+
/** Internal "secure context" — parsed TLS material shared by tls.connect/createServer. */
|
|
53
|
+
export interface SecureContext {
|
|
54
|
+
certificate: Gio.TlsCertificate | null;
|
|
55
|
+
caCertificates: Gio.TlsCertificate[];
|
|
56
|
+
options: SecureContextOptions;
|
|
57
|
+
/**
|
|
58
|
+
* Node-compat handle (Node returns a `SecureContext` with an internal native
|
|
59
|
+
* `context` field). We have no native handle, so this points back at the
|
|
60
|
+
* SecureContext object itself — `ctx.context !== undefined` matches Node.
|
|
61
|
+
*/
|
|
62
|
+
context: SecureContext;
|
|
28
63
|
}
|
|
64
|
+
/** Build a SecureContext from PEM material. Buffer/Uint8Array/string all accepted. */
|
|
65
|
+
export declare function createSecureContext(options?: SecureContextOptions): SecureContext;
|
|
29
66
|
export interface TlsConnectOptions extends SecureContextOptions {
|
|
30
67
|
host?: string;
|
|
31
68
|
port?: number;
|
|
32
69
|
socket?: Socket;
|
|
33
70
|
servername?: string;
|
|
34
71
|
ALPNProtocols?: string[];
|
|
72
|
+
/** Pre-built secure context from createSecureContext(). */
|
|
73
|
+
secureContext?: SecureContext;
|
|
74
|
+
/** Custom server-identity check (runs after the GnuTLS-level check). */
|
|
75
|
+
checkServerIdentity?: (host: string, cert: PeerCertificate) => Error | undefined;
|
|
35
76
|
}
|
|
36
77
|
/**
|
|
37
78
|
* TLSSocket wraps a net.Socket with TLS via Gio.TlsConnection.
|
|
@@ -41,24 +82,31 @@ export declare class TLSSocket extends Socket {
|
|
|
41
82
|
authorized: boolean;
|
|
42
83
|
authorizationError?: string;
|
|
43
84
|
alpnProtocol: string | false;
|
|
85
|
+
servername: string | undefined;
|
|
44
86
|
/** @internal */
|
|
45
87
|
_tlsConnection: Gio.TlsConnection | null;
|
|
46
|
-
|
|
88
|
+
/** @internal — preserved for diagnostics + future cert-chain verification. */
|
|
89
|
+
_secureContext: SecureContext | null;
|
|
90
|
+
constructor(_socket?: Socket, _options?: SecureContextOptions);
|
|
47
91
|
/**
|
|
48
92
|
* @internal Wire the TLS connection's I/O streams into this socket
|
|
49
93
|
* so that read/write operations go through the encrypted channel.
|
|
50
94
|
*/
|
|
51
95
|
_setupTlsStreams(tlsConn: Gio.TlsConnection): void;
|
|
52
|
-
/**
|
|
53
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Get the peer certificate. When `detailed` is true, walks the issuer chain
|
|
98
|
+
* via `Gio.TlsCertificate.get_issuer()` and populates `issuerCertificate`
|
|
99
|
+
* recursively (with a self-reference on the root for compatibility).
|
|
100
|
+
*/
|
|
101
|
+
getPeerCertificate(detailed?: boolean): PeerCertificate;
|
|
54
102
|
/** Get the negotiated TLS protocol version. */
|
|
55
103
|
getProtocol(): string | null;
|
|
56
|
-
/** Get the cipher
|
|
104
|
+
/** Get the negotiated cipher suite name + version. */
|
|
57
105
|
getCipher(): {
|
|
58
106
|
name: string;
|
|
59
107
|
version: string;
|
|
60
108
|
} | null;
|
|
61
|
-
/** Get the negotiated ALPN protocol. */
|
|
109
|
+
/** Get the negotiated ALPN protocol (or false if none). */
|
|
62
110
|
getAlpnProtocol(): string | false;
|
|
63
111
|
}
|
|
64
112
|
/**
|
|
@@ -68,34 +116,41 @@ export declare class TLSSocket extends Socket {
|
|
|
68
116
|
* the connection to TLS using Gio.TlsClientConnection.
|
|
69
117
|
*/
|
|
70
118
|
export declare function connect(options: TlsConnectOptions, callback?: () => void): TLSSocket;
|
|
71
|
-
/**
|
|
72
|
-
* Create a TLS secure context.
|
|
73
|
-
*/
|
|
74
|
-
export declare function createSecureContext(options?: SecureContextOptions): {
|
|
75
|
-
context: any;
|
|
76
|
-
};
|
|
77
119
|
export declare const rootCertificates: string[];
|
|
120
|
+
export type SNICallback = (servername: string, cb: (err: Error | null, ctx?: SecureContext) => void) => void;
|
|
78
121
|
export interface TlsServerOptions extends SecureContextOptions {
|
|
79
122
|
requestCert?: boolean;
|
|
80
123
|
rejectUnauthorized?: boolean;
|
|
81
124
|
ALPNProtocols?: string[];
|
|
125
|
+
SNICallback?: SNICallback;
|
|
82
126
|
}
|
|
83
127
|
/**
|
|
84
|
-
* TLSServer
|
|
128
|
+
* TLSServer accepts incoming TCP connections and upgrades each to TLS via
|
|
129
|
+
* `Gio.TlsServerConnection`. Supports mTLS via `requestCert`+`rejectUnauthorized`,
|
|
130
|
+
* SNI selection via `addContext`/`SNICallback`, and ALPN negotiation.
|
|
85
131
|
*/
|
|
86
132
|
export declare class TLSServer extends Server {
|
|
87
133
|
private _tlsCertificate;
|
|
88
134
|
private _tlsOptions;
|
|
89
135
|
private _sniContexts;
|
|
136
|
+
/** @internal — exposed for tests. */
|
|
137
|
+
_secureContext: SecureContext;
|
|
90
138
|
constructor(options?: TlsServerOptions, secureConnectionListener?: (socket: TLSSocket) => void);
|
|
91
139
|
/**
|
|
92
|
-
* Add
|
|
140
|
+
* Add an additional context for SNI (Server Name Indication). Uses RFC 6125
|
|
141
|
+
* matching against the requested server name.
|
|
93
142
|
*/
|
|
94
143
|
addContext(hostname: string, context: SecureContextOptions): void;
|
|
95
|
-
listen(...args: unknown[]): this;
|
|
96
144
|
/**
|
|
97
|
-
*
|
|
145
|
+
* Resolve a SecureContext for the given server name. Order:
|
|
146
|
+
* 1. exact match in `_sniContexts`
|
|
147
|
+
* 2. RFC 6125 wildcard match in `_sniContexts`
|
|
148
|
+
* 3. SNICallback (if provided)
|
|
149
|
+
* 4. fall through to the server's default context
|
|
98
150
|
*/
|
|
151
|
+
private _resolveSniContext;
|
|
152
|
+
listen(...args: unknown[]): this;
|
|
153
|
+
/** Upgrade a raw TCP socket to TLS using Gio.TlsServerConnection. */
|
|
99
154
|
private _upgradeTls;
|
|
100
155
|
}
|
|
101
156
|
/**
|
|
@@ -104,7 +159,7 @@ export declare class TLSServer extends Server {
|
|
|
104
159
|
export declare function createServer(options?: TlsServerOptions, secureConnectionListener?: (socket: TLSSocket) => void): TLSServer;
|
|
105
160
|
export declare function createServer(secureConnectionListener?: (socket: TLSSocket) => void): TLSServer;
|
|
106
161
|
export { TLSServer as Server };
|
|
107
|
-
declare const
|
|
162
|
+
declare const tlsExports: {
|
|
108
163
|
TLSSocket: typeof TLSSocket;
|
|
109
164
|
TLSServer: typeof TLSServer;
|
|
110
165
|
Server: typeof TLSServer;
|
|
@@ -118,4 +173,4 @@ declare const _default: {
|
|
|
118
173
|
DEFAULT_MAX_VERSION: string;
|
|
119
174
|
DEFAULT_CIPHERS: string;
|
|
120
175
|
};
|
|
121
|
-
export default
|
|
176
|
+
export default tlsExports;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/tls",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Node.js tls module for Gjs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "lib/esm/index.js",
|
|
@@ -30,15 +30,15 @@
|
|
|
30
30
|
"tls"
|
|
31
31
|
],
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@gjsify/cli": "^0.
|
|
34
|
-
"@gjsify/unit": "^0.
|
|
33
|
+
"@gjsify/cli": "^0.4.0",
|
|
34
|
+
"@gjsify/unit": "^0.4.0",
|
|
35
35
|
"@types/node": "^25.6.2",
|
|
36
36
|
"typescript": "^6.0.3"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@girs/gio-2.0": "2.88.0-4.0.0-rc.
|
|
40
|
-
"@girs/glib-2.0": "2.88.0-4.0.0-rc.
|
|
41
|
-
"@gjsify/net": "^0.
|
|
42
|
-
"@gjsify/utils": "^0.
|
|
39
|
+
"@girs/gio-2.0": "2.88.0-4.0.0-rc.15",
|
|
40
|
+
"@girs/glib-2.0": "2.88.0-4.0.0-rc.15",
|
|
41
|
+
"@gjsify/net": "^0.4.0",
|
|
42
|
+
"@gjsify/utils": "^0.4.0"
|
|
43
43
|
}
|
|
44
44
|
}
|
package/src/cert.spec.ts
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Ported from refs/node-test/parallel/test-tls-check-server-identity.js,
|
|
3
|
+
// refs/node-test/parallel/test-tls-canonical-ip.js,
|
|
4
|
+
// refs/node-test/parallel/test-tls-checkserveridentity-no-altnames.js.
|
|
5
|
+
// Original: Copyright (c) Node.js contributors. MIT.
|
|
6
|
+
// Rewritten for @gjsify/unit — behavior preserved, assertion dialect adapted.
|
|
7
|
+
//
|
|
8
|
+
// Focused coverage of RFC 6125 (§6.4.3) hostname matching + getPeerCertificate
|
|
9
|
+
// shape + createSecureContext PEM acceptance. Complements index.spec.ts.
|
|
10
|
+
|
|
11
|
+
import { describe, it, expect, on } from '@gjsify/unit';
|
|
12
|
+
import {
|
|
13
|
+
checkServerIdentity,
|
|
14
|
+
createSecureContext,
|
|
15
|
+
} from 'node:tls';
|
|
16
|
+
import type { PeerCertificate } from 'node:tls';
|
|
17
|
+
|
|
18
|
+
function fakeCert(parts: Record<string, unknown>): PeerCertificate {
|
|
19
|
+
return parts as unknown as PeerCertificate;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Self-signed PEM (cert + key) minted with `openssl req -x509 -newkey rsa:2048
|
|
23
|
+
// -keyout key.pem -out cert.pem -days 3650 -nodes -subj /CN=test.example`.
|
|
24
|
+
// 2048-bit RSA, SHA256, no SAN. Used only for parser smoke-tests — never
|
|
25
|
+
// trusted as a CA. Inlined so tests stay hermetic (no fixtures).
|
|
26
|
+
const SAMPLE_CERT_PEM = `-----BEGIN CERTIFICATE-----
|
|
27
|
+
MIIDDzCCAfegAwIBAgIUO9v7luuPxFHsLdVr//dOTh474OQwDQYJKoZIhvcNAQEL
|
|
28
|
+
BQAwFzEVMBMGA1UEAwwMdGVzdC5leGFtcGxlMB4XDTI2MDUwOTA2NDIyMVoXDTM2
|
|
29
|
+
MDUwNjA2NDIyMVowFzEVMBMGA1UEAwwMdGVzdC5leGFtcGxlMIIBIjANBgkqhkiG
|
|
30
|
+
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtnMdp3rW+bkeYoW9at2aRt70gub5siMsNOYw
|
|
31
|
+
0obCo1LOkJokEOX2+9kaOUwaCmntcJdU3+JYQe7MGYwI+H9OWxnLRYJUneYB81a8
|
|
32
|
+
CfJ7Uk+5FVNzQQFnYvL0G5NSq2w8K6UwV0jgXXFLEfTZOqdFdVVcwAOgCrc4LbpX
|
|
33
|
+
E+5IflPoIuwBW2brYx/fIyJu2gH0uRG92R5m/3UxOqlVzIerL3YM3UTSepDtyCek
|
|
34
|
+
ooD03UwLl3fYuza2iuMnxJAPshaSwiHbAopnaBSEvKMGHkLyl4zTmaioGXVRObtZ
|
|
35
|
+
XAorC7ujI4F8edcmPRwAaNToHS8cRlGQJjXACfAodwNKWpbHYwIDAQABo1MwUTAd
|
|
36
|
+
BgNVHQ4EFgQULhAoOyRjZxBRF8FlwTySSAXnFo0wHwYDVR0jBBgwFoAULhAoOyRj
|
|
37
|
+
ZxBRF8FlwTySSAXnFo0wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC
|
|
38
|
+
AQEAiePCYXf6PAF/m3SE9PYNLiq+ALeJ+kQgXUYp5S4vsfk2T+XmbtP0uNnTQpsT
|
|
39
|
+
4GaytjMFIQiNO6mHww8SFnrEr9btcNGCg17gyK7T3XukbsXIgOYvYjJboxb6Ww1T
|
|
40
|
+
YFEXqPv55of/I5+UPH88WOYR1JsPE6lR4s3MfaMMRXTIZwpob2sxEfqHfti1DrHV
|
|
41
|
+
nmzZpIvZi3II+gpx6aYE8m3DjgPrxbXw78Ular/VyYAyy9XVFpcl2nrAQgpErZIr
|
|
42
|
+
kweGVVMKE8qMF9SO1/pER4T7A9ZBJol00hMCL+5Rw0Pw4lCLXgGX1J9VyZ+ScLxz
|
|
43
|
+
aNy5t6tyGuGk0sol4dLG8nzHGA==
|
|
44
|
+
-----END CERTIFICATE-----`;
|
|
45
|
+
|
|
46
|
+
const SAMPLE_KEY_PEM = `-----BEGIN PRIVATE KEY-----
|
|
47
|
+
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC2cx2netb5uR5i
|
|
48
|
+
hb1q3ZpG3vSC5vmyIyw05jDShsKjUs6QmiQQ5fb72Ro5TBoKae1wl1Tf4lhB7swZ
|
|
49
|
+
jAj4f05bGctFglSd5gHzVrwJ8ntST7kVU3NBAWdi8vQbk1KrbDwrpTBXSOBdcUsR
|
|
50
|
+
9Nk6p0V1VVzAA6AKtzgtulcT7kh+U+gi7AFbZutjH98jIm7aAfS5Eb3ZHmb/dTE6
|
|
51
|
+
qVXMh6svdgzdRNJ6kO3IJ6SigPTdTAuXd9i7NraK4yfEkA+yFpLCIdsCimdoFIS8
|
|
52
|
+
owYeQvKXjNOZqKgZdVE5u1lcCisLu6MjgXx51yY9HABo1OgdLxxGUZAmNcAJ8Ch3
|
|
53
|
+
A0palsdjAgMBAAECggEADTZAcBYmjSAWDpjHguT+cAiWXwhU2pm1CmhrVRBD9fz5
|
|
54
|
+
tZT3IAlXHYO2/dRmw/KwKnAUlr9wS4EvlhLPvEotWJsa+JlogT3cgRktz4nLDmBB
|
|
55
|
+
jRH+9AOJaUWIq2dVHDhfq+I8oh8TFREumEoWLpFZ9Hya+9I6lUWq5smdcEI+gHWs
|
|
56
|
+
yWhq4auR2/zkhvgsjLKPK8P2M8tmm8IPecE74NSBZqxoqb4upUHpMTxasAE2UJpK
|
|
57
|
+
0slvxiZtk3LT/rflco04twIqK/KmH7phrPzmoSl+Lp1Zh9y4iNznTQKBdImsGl+g
|
|
58
|
+
9FWL7pdxKjVNGQsrKVvB1A78VhhteAuaebT5MicIwQKBgQDtmYfyrlGoYGkArmNL
|
|
59
|
+
tGBc5afGx/mL/8BGTQfIx4b11OEr+G4hppLh/WGLUksfMfprL0YU3BM+FOsECsY0
|
|
60
|
+
5AJB6oQuSE8vCIS7yCxV00V+I6n2bEMxZkDwdDuF9rHhGODFx4pPz1nJqScAa23T
|
|
61
|
+
48c+hNSBLRHTSuDAi2DcuxLn4QKBgQDElDjmB/kpW50ya2M2hO5LpwMcAlTJ3nPv
|
|
62
|
+
000eTfzdtDNzesvRClYZWGWs7zSkNsf/F+G0tCg4slzvHPkRJN53hHF0Kwug2aj5
|
|
63
|
+
1BPuDbn3EumDO6quykz6S9vZGcPAMuNm+qtkf+42KE5Sqj3NTf6aiu4tEZbH69me
|
|
64
|
+
av1sbfkHwwKBgQC5cb5g1FuZjn4F8RZBDSy09O4pQQVtlpSsigzMUabtklSY7BKR
|
|
65
|
+
IyC7T/dlNTq6w1hPdhs9xrMiHlN72SjwORHl/rNiKD/dVsm6grbP2dEAbbeHROKA
|
|
66
|
+
2O1Qf3fBzFTzemZdF6vFNPJAakytkCutWLe2/RebJuElx+h5f49/WGeeIQKBgQC0
|
|
67
|
+
mcCUharB9ms7kTF7OzF6y5utte6T8A3vvd9SAjBYt1+1rpFmIersKixvbuycGcAw
|
|
68
|
+
eo5gaEuzmxqKi8G/oHHKuCFLquhqBM6bh94vjOjXN8bVTJIJN870/ZCjqmoPQDFv
|
|
69
|
+
wMiJ8oa1tt4OUF2rKwbIkO809L3kOqiaRI1Det2Z5QKBgGp4JS3OqzeIMQbUa8e8
|
|
70
|
+
tOYnML+RFpo2cZ9rCQAQitm/w5P6lAnG9vv4cXAu94RbtITEuGmqEhYDMhSC2i8e
|
|
71
|
+
kjwRLp5R+/pynUdTckRP8buwRSDRlPz3x8GI2Dm0z8fb/uZ8AJK/iITtsOoJtyPx
|
|
72
|
+
fUZ521uRyKXnpzj1HTz5WVp0
|
|
73
|
+
-----END PRIVATE KEY-----`;
|
|
74
|
+
|
|
75
|
+
export default async () => {
|
|
76
|
+
await describe('tls — RFC 6125 + cert extraction', async () => {
|
|
77
|
+
|
|
78
|
+
// ---------------- RFC 6125 wildcard depth rules ----------------
|
|
79
|
+
await describe('RFC 6125 wildcard depth', async () => {
|
|
80
|
+
await it('rejects two-label wildcard *.com', async () => {
|
|
81
|
+
const err = checkServerIdentity('foo.com', fakeCert({
|
|
82
|
+
subject: {},
|
|
83
|
+
subjectaltname: 'DNS:*.com',
|
|
84
|
+
}));
|
|
85
|
+
expect(err instanceof Error).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
await it('rejects single-label wildcard *', async () => {
|
|
89
|
+
const err = checkServerIdentity('foo', fakeCert({
|
|
90
|
+
subject: {},
|
|
91
|
+
subjectaltname: 'DNS:*',
|
|
92
|
+
}));
|
|
93
|
+
expect(err instanceof Error).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
await it('accepts three-label wildcard *.example.com matching foo.example.com', async () => {
|
|
97
|
+
const ok = checkServerIdentity('foo.example.com', fakeCert({
|
|
98
|
+
subject: {},
|
|
99
|
+
subjectaltname: 'DNS:*.example.com',
|
|
100
|
+
}));
|
|
101
|
+
expect(ok).toBeUndefined();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
await it('rejects empty wildcard label .x.example.com', async () => {
|
|
105
|
+
const err = checkServerIdentity('foo.x.example.com', fakeCert({
|
|
106
|
+
subject: {},
|
|
107
|
+
subjectaltname: 'DNS:.x.example.com',
|
|
108
|
+
}));
|
|
109
|
+
expect(err instanceof Error).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// ---------------- RFC 6125 wildcard prefix/suffix ----------------
|
|
114
|
+
await describe('RFC 6125 wildcard prefix/suffix', async () => {
|
|
115
|
+
await it('matches f*.example.com against foo.example.com', async () => {
|
|
116
|
+
const ok = checkServerIdentity('foo.example.com', fakeCert({
|
|
117
|
+
subject: {},
|
|
118
|
+
subjectaltname: 'DNS:f*.example.com',
|
|
119
|
+
}));
|
|
120
|
+
expect(ok).toBeUndefined();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
await it('rejects f*.example.com against bar.example.com', async () => {
|
|
124
|
+
const err = checkServerIdentity('bar.example.com', fakeCert({
|
|
125
|
+
subject: {},
|
|
126
|
+
subjectaltname: 'DNS:f*.example.com',
|
|
127
|
+
}));
|
|
128
|
+
expect(err instanceof Error).toBe(true);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
await it('matches *foo.example.com against barfoo.example.com', async () => {
|
|
132
|
+
const ok = checkServerIdentity('barfoo.example.com', fakeCert({
|
|
133
|
+
subject: {},
|
|
134
|
+
subjectaltname: 'DNS:*foo.example.com',
|
|
135
|
+
}));
|
|
136
|
+
expect(ok).toBeUndefined();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
await it('rejects f*r.example.com against bar.example.com (prefix mismatch)', async () => {
|
|
140
|
+
const err = checkServerIdentity('bar.example.com', fakeCert({
|
|
141
|
+
subject: {},
|
|
142
|
+
subjectaltname: 'DNS:f*r.example.com',
|
|
143
|
+
}));
|
|
144
|
+
expect(err instanceof Error).toBe(true);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// ---------------- IDN / xn-- handling ----------------
|
|
149
|
+
await describe('Punycode / IDN', async () => {
|
|
150
|
+
await it('treats xn-- labels as exact-match (no wildcard expansion)', async () => {
|
|
151
|
+
// pattern is the punycoded form; hostname must match exactly.
|
|
152
|
+
const ok = checkServerIdentity('xn--bcher-kva.example.com', fakeCert({
|
|
153
|
+
subject: {},
|
|
154
|
+
subjectaltname: 'DNS:xn--bcher-kva.example.com',
|
|
155
|
+
}));
|
|
156
|
+
expect(ok).toBeUndefined();
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
await it('rejects wildcard expansion against xn-- A-label leftmost', async () => {
|
|
160
|
+
// *xn--bcher-kva matched against bcher-kva: per RFC 6125 the wildcard
|
|
161
|
+
// is not allowed to expand into A-label content.
|
|
162
|
+
const err = checkServerIdentity('foo.example.com', fakeCert({
|
|
163
|
+
subject: {},
|
|
164
|
+
subjectaltname: 'DNS:xn--*.example.com',
|
|
165
|
+
}));
|
|
166
|
+
expect(err instanceof Error).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// ---------------- Error code shape (Node-compat) ----------------
|
|
171
|
+
await describe('error.code', async () => {
|
|
172
|
+
await it('returns ERR_TLS_CERT_ALTNAME_INVALID code on mismatch', async () => {
|
|
173
|
+
const err = checkServerIdentity('a.com', fakeCert({
|
|
174
|
+
subject: { CN: 'b.com' },
|
|
175
|
+
}));
|
|
176
|
+
expect(err instanceof Error).toBe(true);
|
|
177
|
+
const e = err as { code?: string };
|
|
178
|
+
// Node: ERR_TLS_CERT_ALTNAME_INVALID. Our impl: same.
|
|
179
|
+
expect(e.code).toBe('ERR_TLS_CERT_ALTNAME_INVALID');
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// ---------------- createSecureContext PEM acceptance ----------------
|
|
184
|
+
await describe('createSecureContext', async () => {
|
|
185
|
+
await it('accepts string PEM material (cert + key)', async () => {
|
|
186
|
+
const ctx = createSecureContext({ cert: SAMPLE_CERT_PEM, key: SAMPLE_KEY_PEM });
|
|
187
|
+
expect(ctx).toBeDefined();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
await it('accepts Buffer PEM material', async () => {
|
|
191
|
+
const Buffer = (globalThis as { Buffer?: { from(s: string): unknown } }).Buffer;
|
|
192
|
+
if (!Buffer) return; // skip if Buffer unavailable
|
|
193
|
+
const ctx = createSecureContext({
|
|
194
|
+
cert: Buffer.from(SAMPLE_CERT_PEM) as never,
|
|
195
|
+
key: Buffer.from(SAMPLE_KEY_PEM) as never,
|
|
196
|
+
});
|
|
197
|
+
expect(ctx).toBeDefined();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
await it('accepts array of PEM blocks for ca', async () => {
|
|
201
|
+
const ctx = createSecureContext({ ca: [SAMPLE_CERT_PEM, SAMPLE_CERT_PEM] });
|
|
202
|
+
expect(ctx).toBeDefined();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
await it('returns ctx.context self-reference (Node-compat)', async () => {
|
|
206
|
+
const ctx = createSecureContext();
|
|
207
|
+
const ref = (ctx as unknown as { context?: unknown }).context;
|
|
208
|
+
expect(ref !== undefined).toBe(true);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Our impl preserves the user-supplied options on the SecureContext;
|
|
212
|
+
// Node does not, so this is a GJS-specific guarantee we lean on for
|
|
213
|
+
// diagnostics + integration debugging.
|
|
214
|
+
await on('Gjs', async () => {
|
|
215
|
+
await it('preserves passed-through options (ciphers/minVersion/maxVersion)', async () => {
|
|
216
|
+
const ctx = createSecureContext({
|
|
217
|
+
ciphers: 'TLS_AES_128_GCM_SHA256',
|
|
218
|
+
minVersion: 'TLSv1.2',
|
|
219
|
+
maxVersion: 'TLSv1.3',
|
|
220
|
+
});
|
|
221
|
+
const o = (ctx as unknown as { options?: { ciphers?: string; minVersion?: string; maxVersion?: string } }).options;
|
|
222
|
+
expect(o).toBeDefined();
|
|
223
|
+
expect(o!.ciphers).toBe('TLS_AES_128_GCM_SHA256');
|
|
224
|
+
expect(o!.minVersion).toBe('TLSv1.2');
|
|
225
|
+
expect(o!.maxVersion).toBe('TLSv1.3');
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
};
|