@gjsify/tls 0.3.21 → 0.4.3

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.
@@ -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 unfqdn(e){return e.endsWith(`.`)?e.slice(0,-1):e}function splitHost(e){return unfqdn(e).toLowerCase().split(`.`)}function checkWildcard(e,t){let n=splitHost(t);return n.length===e.length?n[0]===`*`?n.slice(1).join(`.`)===e.slice(1).join(`.`):n.every((t,n)=>t===e[n]):!1}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=>checkWildcard(t,e)),o||(s=`Host: ${e}. is not in the cert's altnames: ${r}`);else{let r=n.CN;Array.isArray(r)?o=r.some(e=>checkWildcard(t,e)):r&&(o=checkWildcard(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}}var TLSSocket=class extends r{encrypted=!0;authorized=!1;authorizationError;alpnProtocol=!1;_tlsConnection=null;constructor(e,t){super()}_setupTlsStreams(e){this._tlsConnection=e,this._inputStream=e.get_input_stream(),this._outputStream=e.get_output_stream(),this._connection=e}getPeerCertificate(e){if(!this._tlsConnection)return{};try{return this._tlsConnection.get_peer_certificate()?{subject:{},issuer:{},valid_from:``,valid_to:``}:{}}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;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),l=e.TlsClientConnection.new(r,o);if(l.set_server_identity(o),n.ALPNProtocols&&n.ALPNProtocols.length>0)try{l.set_advertised_protocols(n.ALPNProtocols)}catch{}c||l.connect(`accept-certificate`,()=>!0);let u=new e.Cancellable;l.handshake_async(t.PRIORITY_DEFAULT,u,(e,t)=>{try{l.handshake_finish(t),i.authorized=!0,i._setupTlsStreams(l),i.alpnProtocol=i.getAlpnProtocol(),i._reading=!1,i._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(l),i.emit(`secureConnect`))}})}catch(e){i.destroy(e instanceof Error?e:Error(String(e)))}}),i.connect({port:a,host:o}),i}function createSecureContext(e){return{context:e||{}}}const l=[];function buildGioCertificate(t,n){let r=Array.isArray(t)?t.map(e=>typeof e==`string`?e:e.toString(`utf-8`)).join(`
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};
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
@@ -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
- CN?: string | string[];
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 fails, or undefined on success.
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): Error | undefined;
40
+ export declare function checkServerIdentity(hostname: string, cert: PeerCertificate): CertAltNameError | undefined;
23
41
  export interface SecureContextOptions {
24
- ca?: string | Buffer | Array<string | Buffer>;
25
- cert?: string | Buffer | Array<string | Buffer>;
26
- key?: string | Buffer | Array<string | Buffer>;
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
- constructor(socket?: Socket, options?: SecureContextOptions);
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
- /** Get the peer certificate info. */
53
- getPeerCertificate(_detailed?: boolean): any;
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 info. */
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 wraps a net.Server to accept TLS connections.
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 a context for SNI (Server Name Indication).
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
- * Upgrade a raw TCP socket to TLS using Gio.TlsServerConnection.
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 _default: {
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 _default;
176
+ export default tlsExports;
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
package/package.json CHANGED
@@ -1,44 +1,47 @@
1
1
  {
2
- "name": "@gjsify/tls",
3
- "version": "0.3.21",
4
- "description": "Node.js tls module for Gjs",
5
- "type": "module",
6
- "module": "lib/esm/index.js",
7
- "types": "lib/types/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./lib/types/index.d.ts",
11
- "default": "./lib/esm/index.js"
2
+ "name": "@gjsify/tls",
3
+ "version": "0.4.3",
4
+ "description": "Node.js tls module for Gjs",
5
+ "type": "module",
6
+ "module": "lib/esm/index.js",
7
+ "types": "lib/types/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/types/index.d.ts",
11
+ "default": "./lib/esm/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "lib"
16
+ ],
17
+ "scripts": {
18
+ "clear": "rm -rf lib tsconfig.tsbuildinfo tsconfig.types.tsbuildinfo test.gjs.mjs test.node.mjs || exit 0",
19
+ "check": "tsc --noEmit",
20
+ "build": "gjsify run build:gjsify && gjsify run build:types",
21
+ "build:gjsify": "gjsify build --library 'src/**/*.{ts,js}' --exclude 'src/**/*.spec.{mts,ts}' 'src/test.{mts,ts}'",
22
+ "build:types": "tsc",
23
+ "build:test": "gjsify run build:test:gjs && gjsify run build:test:node",
24
+ "build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs",
25
+ "build:test:node": "gjsify build src/test.mts --app node --outfile test.node.mjs",
26
+ "test": "gjsify run build:gjsify && gjsify run build:test && gjsify run test:node && gjsify run test:gjs",
27
+ "test:gjs": "gjsify run test.gjs.mjs",
28
+ "test:node": "node test.node.mjs"
29
+ },
30
+ "keywords": [
31
+ "gjs",
32
+ "node",
33
+ "tls"
34
+ ],
35
+ "devDependencies": {
36
+ "@gjsify/cli": "workspace:^",
37
+ "@gjsify/unit": "workspace:^",
38
+ "@types/node": "^25.6.2",
39
+ "typescript": "^6.0.3"
40
+ },
41
+ "dependencies": {
42
+ "@girs/gio-2.0": "2.88.0-4.0.0-rc.15",
43
+ "@girs/glib-2.0": "2.88.0-4.0.0-rc.15",
44
+ "@gjsify/net": "workspace:^",
45
+ "@gjsify/utils": "workspace:^"
12
46
  }
13
- },
14
- "scripts": {
15
- "clear": "rm -rf lib tsconfig.tsbuildinfo tsconfig.types.tsbuildinfo test.gjs.mjs test.node.mjs || exit 0",
16
- "check": "tsc --noEmit",
17
- "build": "yarn build:gjsify && yarn build:types",
18
- "build:gjsify": "gjsify build --library 'src/**/*.{ts,js}' --exclude 'src/**/*.spec.{mts,ts}' 'src/test.{mts,ts}'",
19
- "build:types": "tsc",
20
- "build:test": "yarn build:test:gjs && yarn build:test:node",
21
- "build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs",
22
- "build:test:node": "gjsify build src/test.mts --app node --outfile test.node.mjs",
23
- "test": "yarn build:gjsify && yarn build:test && yarn test:node && yarn test:gjs",
24
- "test:gjs": "gjsify run test.gjs.mjs",
25
- "test:node": "node test.node.mjs"
26
- },
27
- "keywords": [
28
- "gjs",
29
- "node",
30
- "tls"
31
- ],
32
- "devDependencies": {
33
- "@gjsify/cli": "^0.3.21",
34
- "@gjsify/unit": "^0.3.21",
35
- "@types/node": "^25.6.2",
36
- "typescript": "^6.0.3"
37
- },
38
- "dependencies": {
39
- "@girs/gio-2.0": "2.88.0-4.0.0-rc.14",
40
- "@girs/glib-2.0": "2.88.0-4.0.0-rc.14",
41
- "@gjsify/net": "^0.3.21",
42
- "@gjsify/utils": "^0.3.21"
43
- }
44
- }
47
+ }