@gjsify/tls 0.4.21 → 0.4.22

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
+ import"./_virtual/_rolldown/runtime.js";import{TLSSocket as e}from"./tls-socket.js";import{createSecureContext as t}from"./secure-context.js";import n from"@girs/gio-2.0";import r from"@girs/glib-2.0";function connect(i,a){let o=new e(void 0,i);a&&o.once(`secureConnect`,a);let s=i.port||443,c=i.host||`localhost`,l=i.servername||c,u=i.rejectUnauthorized!==!1,d=i.secureContext??t(i);o._secureContext=d,o.servername=l;let f=i.checkServerIdentity;return o.once(`connect`,()=>{let e=o._connection;if(!e){o.destroy(Error(`No underlying connection for TLS upgrade`));return}try{let t=n.NetworkAddress.new(l,s),a=n.TlsClientConnection.new(e,t);if(a.set_server_identity(t),d.certificate)try{a.set_certificate(d.certificate)}catch(e){console.warn(`[tls] failed to set client certificate:`,e)}if(i.ALPNProtocols&&i.ALPNProtocols.length>0)try{a.set_advertised_protocols(i.ALPNProtocols)}catch{}a.connect(`accept-certificate`,(e,r,i)=>{if(!u)return!0;if(d.caCertificates.length===0)return!1;for(let e of d.caCertificates)try{if(r.verify(t,e)===n.TlsCertificateFlags.NO_FLAGS)return!0}catch{}return!1});let c=new n.Cancellable;a.handshake_async(r.PRIORITY_DEFAULT,c,(e,t)=>{try{if(a.handshake_finish(t),o.authorized=!0,o._setupTlsStreams(a),o.alpnProtocol=o.getAlpnProtocol(),f){let e=f(l,o.getPeerCertificate());if(e&&(o.authorized=!1,o.authorizationError=e.message,u)){o.destroy(e);return}}let e=o;e._reading=!1,e._startReading(),o.emit(`secureConnect`)}catch(e){o.authorized=!1,o.authorizationError=e instanceof Error?e.message:String(e),u?o.destroy(e instanceof Error?e:Error(String(e))):(o._setupTlsStreams(a),o.emit(`secureConnect`))}})}catch(e){o.destroy(e instanceof Error?e:Error(String(e)))}}),o.connect({port:s,host:c}),o}export{connect};
package/lib/esm/index.js CHANGED
@@ -1,2 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";import{parseClientHelloSni as e}from"./internal/sni-parser.js";import t from"@girs/gio-2.0";import n from"@girs/glib-2.0";import{Server as r,Socket as i}from"node:net";import{createNodeError as a,deferEmit as o}from"@gjsify/utils";const s=`TLSv1.2`,c=`TLSv1.3`,l=`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(e,n){let r=pemToString(e),i=n?pemToString(n):``,a=i?`${r}\n${i}`:r;return t.TlsCertificate.new_from_pem(a,a.length)}function buildCaCertificates(e){let n=[];if(Array.isArray(e))for(let t of e)n.push(...splitPemBlocks(pemToString(t)));else n.push(...splitPemBlocks(pemToString(e)));let r=[];for(let e of n)try{r.push(t.TlsCertificate.new_from_pem(e,e.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,t){try{let r=new n.Checksum(t);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,t){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 t=e,i=t.certificate_pem??t.certificatePem;if(i){let e=pemToDer(i);r.raw=e,r.fingerprint=fingerprintFromBytes(e,n.ChecksumType.SHA1),r.fingerprint256=fingerprintFromBytes(e,n.ChecksumType.SHA256)}}catch{}if(t)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 i{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 t.TlsProtocolVersion.TLS_1_0:return`TLSv1`;case t.TlsProtocolVersion.TLS_1_1:return`TLSv1.1`;case t.TlsProtocolVersion.TLS_1_2:return`TLSv1.2`;case t.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(e,r){let i=new TLSSocket(void 0,e);r&&i.once(`secureConnect`,r);let a=e.port||443,o=e.host||`localhost`,s=e.servername||o,c=e.rejectUnauthorized!==!1,l=e.secureContext??createSecureContext(e);i._secureContext=l,i.servername=s;let u=e.checkServerIdentity;return i.once(`connect`,()=>{let r=i._connection;if(!r){i.destroy(Error(`No underlying connection for TLS upgrade`));return}try{let o=t.NetworkAddress.new(s,a),d=t.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(e.ALPNProtocols&&e.ALPNProtocols.length>0)try{d.set_advertised_protocols(e.ALPNProtocols)}catch{}d.connect(`accept-certificate`,(e,n,r)=>{if(!c)return!0;if(l.caCertificates.length===0)return!1;for(let e of l.caCertificates)try{if(n.verify(o,e)===t.TlsCertificateFlags.NO_FLAGS)return!0}catch{}return!1});let f=new t.Cancellable;d.handshake_async(n.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 u=[];var TLSServer=class extends r{_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&&o(this,`error`,a(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`,a(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(r){let i=r._connection;if(!i){let e=Error(`Cannot upgrade socket: no underlying connection`);this.emit(`tlsClientError`,e,r),r.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,r),r.destroy();return}let o=i,s=o.get_input_stream(),c=o.get_output_stream(),l=t.BufferedInputStream.new_sized(s,4096);l.fill_async(4096,n.PRIORITY_DEFAULT,null,(i,o)=>{let s=null;try{l.fill_finish(o),s=e(l.peek_buffer())}catch{}this._resolveSniContext(s,e=>{let i=e.certificate??this._tlsCertificate;if(!i){let e=Error(`SNI resolution returned no certificate`);this.emit(`tlsClientError`,e,r),r.destroy();return}try{let o=new t.SimpleIOStream({inputStream:l,outputStream:c}),s=t.TlsServerConnection.new(o,i);this._tlsOptions.requestCert?s.authenticationMode=this._tlsOptions.rejectUnauthorized===!1?t.TlsAuthenticationMode.REQUESTED:t.TlsAuthenticationMode.REQUIRED:s.authenticationMode=t.TlsAuthenticationMode.NONE;let u=!!this._tlsOptions.requestCert&&this._tlsOptions.rejectUnauthorized!==!1,d=this._secureContext.caCertificates;if(s.connect(`accept-certificate`,(e,n,r)=>{if(!u)return!0;if(d.length===0)return!1;for(let e of d)try{if(n.verify(null,e)===t.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 f=new t.Cancellable;s.handshake_async(n.PRIORITY_DEFAULT,f,(t,n)=>{try{s.handshake_finish(n);let t=new TLSSocket;t.encrypted=!0,t.authorized=!0,t._secureContext=e,t._setupTlsStreams(s),t.alpnProtocol=t.getAlpnProtocol(),t._startReading(),this.emit(`secureConnection`,t)}catch(e){let t=a(e,`handshake`,{});this.emit(`tlsClientError`,t,r),r.destroy()}})}catch(e){let t=a(e,`tls_wrap`,{});this.emit(`tlsClientError`,t,r),r.destroy()}})})}};function createServer(e,t){return typeof e==`function`?new TLSServer(void 0,e):new TLSServer(e,t)}const d={TLSSocket,TLSServer,Server:TLSServer,connect,createServer,createSecureContext,checkServerIdentity,getCiphers,rootCertificates:u,DEFAULT_MIN_VERSION:s,DEFAULT_MAX_VERSION:c,DEFAULT_CIPHERS:l};export{l as DEFAULT_CIPHERS,c as DEFAULT_MAX_VERSION,s as DEFAULT_MIN_VERSION,TLSServer as Server,TLSServer,TLSSocket,checkServerIdentity,connect,createSecureContext,createServer,d as default,getCiphers,u as rootCertificates};
1
+ import"./_virtual/_rolldown/runtime.js";import{TLSSocket as e}from"./tls-socket.js";import{createSecureContext as t}from"./secure-context.js";import{connect as n}from"./connect.js";import{checkServerIdentity as r}from"./internal/hostname.js";import{TLSServer as i,createServer as a}from"./tls-server.js";import{OcspCertStatus as o,OcspResponseStatus as s,hasOcspSupport as c,parseOcspResponse as l}from"./ocsp.js";const u=`TLSv1.2`,d=`TLSv1.3`,f=`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`]}const p=[],m={TLSSocket:e,TLSServer:i,Server:i,connect:n,createServer:a,createSecureContext:t,checkServerIdentity:r,getCiphers,rootCertificates:p,DEFAULT_MIN_VERSION:u,DEFAULT_MAX_VERSION:d,DEFAULT_CIPHERS:f,parseOcspResponse:l,hasOcspSupport:c,OcspCertStatus:o,OcspResponseStatus:s};export{f as DEFAULT_CIPHERS,d as DEFAULT_MAX_VERSION,u as DEFAULT_MIN_VERSION,o as OcspCertStatus,s as OcspResponseStatus,i as Server,i as TLSServer,e as TLSSocket,r as checkServerIdentity,n as connect,t as createSecureContext,a as createServer,m as default,getCiphers,c as hasOcspSupport,l as parseOcspResponse,p as rootCertificates};
@@ -0,0 +1 @@
1
+ import"../_virtual/_rolldown/runtime.js";import{pemToDer as e}from"./pem.js";import t from"@girs/glib-2.0";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 tlsCertToPeerCert(n,r){let i={};try{i.subject=parseDistinguishedName(n.get_subject_name())}catch{}try{i.issuer=parseDistinguishedName(n.get_issuer_name())}catch{}i.subjectaltname=formatAltNames(n);try{i.valid_from=formatCertDate(n.get_not_valid_before()),i.valid_to=formatCertDate(n.get_not_valid_after())}catch{}try{let r=n,a=r.certificate_pem??r.certificatePem;if(a){let n=e(a);i.raw=n,i.fingerprint=fingerprintFromBytes(n,t.ChecksumType.SHA1),i.fingerprint256=fingerprintFromBytes(n,t.ChecksumType.SHA256)}}catch{}if(r)try{let e=n.get_issuer();e&&!e.is_same(n)?i.issuerCertificate=tlsCertToPeerCert(e,!0):e&&(i.issuerCertificate=i)}catch{}return i}export{fingerprintFromBytes,formatAltNames,formatCertDate,parseDistinguishedName,tlsCertToPeerCert};
@@ -0,0 +1 @@
1
+ import"../_virtual/_rolldown/runtime.js";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}}export{checkHostMatch,checkServerIdentity,isPrintableAscii,splitHost,unfqdn};
@@ -0,0 +1,2 @@
1
+ import"../_virtual/_rolldown/runtime.js";import e from"@girs/gio-2.0";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 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}}export{buildCaCertificates,buildGioCertificate,pemToDer,pemToString,splitPemBlocks};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import{OcspCertStatus as e,OcspResponseStatus as t,hasNativeTls as n,parseOcspResponse as r}from"@gjsify/tls-native";function hasOcspSupport(){return n()}function parseOcspResponse(e){return r(e)}export{e as OcspCertStatus,t as OcspResponseStatus,hasOcspSupport,parseOcspResponse};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import{buildCaCertificates as e,buildGioCertificate as t}from"./internal/pem.js";function createSecureContext(n){let r=n??{},i=null;if(r.cert)try{i=t(r.cert,r.key)}catch{i=null}let a=r.ca?e(r.ca):[],o={certificate:i,caCertificates:a,options:r};return o.context=o,o}export{createSecureContext};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import{TLSSocket as e}from"./tls-socket.js";import{createSecureContext as t}from"./secure-context.js";import{checkHostMatch as n,splitHost as r}from"./internal/hostname.js";import{parseClientHelloSni as i}from"./internal/sni-parser.js";import a from"@girs/gio-2.0";import o from"@girs/glib-2.0";import{Server as s}from"node:net";import{createNodeError as c,deferEmit as l}from"@gjsify/utils";var TLSServer=class extends s{_tlsCertificate=null;_tlsOptions;_sniContexts=new Map;_secureContext;constructor(e,n){super(),this._tlsOptions=e??{},this._secureContext=t(this._tlsOptions),this._tlsCertificate=this._secureContext.certificate,n&&this.on(`secureConnection`,n),this._tlsOptions.cert&&!this._tlsCertificate&&l(this,`error`,c(Error(`Failed to parse TLS certificate`),`createServer`,{}))}addContext(e,n){try{let r=t(n);this._sniContexts.set(e.toLowerCase(),r)}catch(e){this.emit(`error`,c(e,`addContext`,{}))}}_resolveSniContext(e,t){let i=this._secureContext;if(!e){t(i);return}let a=e.toLowerCase(),o=this._sniContexts.get(a);if(o){t(o);return}let s=r(a);for(let[e,r]of this._sniContexts)if(n(s,e)){t(r);return}if(this._tlsOptions.SNICallback)try{this._tlsOptions.SNICallback(e,(e,n)=>{if(e||!n){t(i);return}t(n)});return}catch{t(i);return}t(i)}listen(...e){return this.on(`connection`,e=>{this._upgradeTls(e)}),super.listen(...e)}_upgradeTls(t){let n=t._connection;if(!n){let e=Error(`Cannot upgrade socket: no underlying connection`);this.emit(`tlsClientError`,e,t),t.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,t),t.destroy();return}let r=n,s=r.get_input_stream(),l=r.get_output_stream(),u=a.BufferedInputStream.new_sized(s,4096);u.fill_async(4096,o.PRIORITY_DEFAULT,null,(n,r)=>{let s=null;try{u.fill_finish(r),s=i(u.peek_buffer())}catch{}this._resolveSniContext(s,n=>{let r=n.certificate??this._tlsCertificate;if(!r){let e=Error(`SNI resolution returned no certificate`);this.emit(`tlsClientError`,e,t),t.destroy();return}try{let i=new a.SimpleIOStream({inputStream:u,outputStream:l}),s=a.TlsServerConnection.new(i,r);this._tlsOptions.requestCert?s.authenticationMode=this._tlsOptions.rejectUnauthorized===!1?a.TlsAuthenticationMode.REQUESTED:a.TlsAuthenticationMode.REQUIRED:s.authenticationMode=a.TlsAuthenticationMode.NONE;let d=!!this._tlsOptions.requestCert&&this._tlsOptions.rejectUnauthorized!==!1,f=this._secureContext.caCertificates;if(s.connect(`accept-certificate`,(e,t,n)=>{if(!d)return!0;if(f.length===0)return!1;for(let e of f)try{if(t.verify(null,e)===a.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 p=new a.Cancellable;s.handshake_async(o.PRIORITY_DEFAULT,p,(r,i)=>{try{s.handshake_finish(i);let t=new e;t.encrypted=!0,t.authorized=!0,t._secureContext=n,t._setupTlsStreams(s),t.alpnProtocol=t.getAlpnProtocol(),t._startReading(),this.emit(`secureConnection`,t)}catch(e){let n=c(e,`handshake`,{});this.emit(`tlsClientError`,n,t),t.destroy()}})}catch(e){let n=c(e,`tls_wrap`,{});this.emit(`tlsClientError`,n,t),t.destroy()}})})}};function createServer(e,t){return typeof e==`function`?new TLSServer(void 0,e):new TLSServer(e,t)}export{TLSServer,createServer};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import{tlsCertToPeerCert as e}from"./internal/cert-utils.js";import t from"@girs/gio-2.0";import{Socket as n}from"node:net";var TLSSocket=class extends n{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(t=!1){if(!this._tlsConnection)return{};try{let n=this._tlsConnection.get_peer_certificate();return n?e(n,t):{}}catch{return{}}}getProtocol(){if(!this._tlsConnection)return null;try{switch(this._tlsConnection.get_protocol_version()){case t.TlsProtocolVersion.TLS_1_0:return`TLSv1`;case t.TlsProtocolVersion.TLS_1_1:return`TLSv1.1`;case t.TlsProtocolVersion.TLS_1_2:return`TLSv1.2`;case t.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}}};export{TLSSocket};
@@ -0,0 +1,8 @@
1
+ import { TLSSocket, type TlsConnectOptions } from './tls-socket.js';
2
+ /**
3
+ * Create a TLS client connection.
4
+ *
5
+ * Connects via TCP first (using net.Socket.connect), then upgrades
6
+ * the connection to TLS using Gio.TlsClientConnection.
7
+ */
8
+ export declare function connect(options: TlsConnectOptions, callback?: () => void): TLSSocket;
@@ -1,176 +1,54 @@
1
- import Gio from '@girs/gio-2.0';
2
- import { Socket, Server } from 'node:net';
3
1
  export declare const DEFAULT_MIN_VERSION = "TLSv1.2";
4
2
  export declare const DEFAULT_MAX_VERSION = "TLSv1.3";
5
3
  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
4
  /** Returns a list of supported TLS cipher names (subset; implementation-defined). */
7
5
  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
- }
13
- export interface PeerCertificate {
14
- subject?: CertSubject;
15
- issuer?: CertSubject;
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;
24
- [key: string]: unknown;
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
- }
33
- /**
34
- * Verifies that the certificate `cert` is valid for `hostname`.
35
- * Returns an Error (with code 'ERR_TLS_CERT_ALTNAME_INVALID') if the check
36
- * fails, or `undefined` on success.
37
- *
38
- * Reference: Node.js lib/tls.js exports.checkServerIdentity (RFC 6125 §6.4.3).
39
- */
40
- export declare function checkServerIdentity(hostname: string, cert: PeerCertificate): CertAltNameError | undefined;
41
- export interface SecureContextOptions {
42
- ca?: PemInput;
43
- cert?: PemInput;
44
- key?: PemInput;
45
- passphrase?: string;
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;
63
- }
64
- /** Build a SecureContext from PEM material. Buffer/Uint8Array/string all accepted. */
65
- export declare function createSecureContext(options?: SecureContextOptions): SecureContext;
66
- export interface TlsConnectOptions extends SecureContextOptions {
67
- host?: string;
68
- port?: number;
69
- socket?: Socket;
70
- servername?: string;
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;
76
- }
77
- /**
78
- * TLSSocket wraps a net.Socket with TLS via Gio.TlsConnection.
79
- */
80
- export declare class TLSSocket extends Socket {
81
- encrypted: boolean;
82
- authorized: boolean;
83
- authorizationError?: string;
84
- alpnProtocol: string | false;
85
- servername: string | undefined;
86
- /** @internal */
87
- _tlsConnection: Gio.TlsConnection | null;
88
- /** @internal — preserved for diagnostics + future cert-chain verification. */
89
- _secureContext: SecureContext | null;
90
- constructor(_socket?: Socket, _options?: SecureContextOptions);
91
- /**
92
- * @internal Wire the TLS connection's I/O streams into this socket
93
- * so that read/write operations go through the encrypted channel.
94
- */
95
- _setupTlsStreams(tlsConn: Gio.TlsConnection): void;
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;
102
- /** Get the negotiated TLS protocol version. */
103
- getProtocol(): string | null;
104
- /** Get the negotiated cipher suite name + version. */
105
- getCipher(): {
106
- name: string;
107
- version: string;
108
- } | null;
109
- /** Get the negotiated ALPN protocol (or false if none). */
110
- getAlpnProtocol(): string | false;
111
- }
112
- /**
113
- * Create a TLS client connection.
114
- *
115
- * Connects via TCP first (using net.Socket.connect), then upgrades
116
- * the connection to TLS using Gio.TlsClientConnection.
117
- */
118
- export declare function connect(options: TlsConnectOptions, callback?: () => void): TLSSocket;
119
6
  export declare const rootCertificates: string[];
120
- export type SNICallback = (servername: string, cb: (err: Error | null, ctx?: SecureContext) => void) => void;
121
- export interface TlsServerOptions extends SecureContextOptions {
122
- requestCert?: boolean;
123
- rejectUnauthorized?: boolean;
124
- ALPNProtocols?: string[];
125
- SNICallback?: SNICallback;
126
- }
127
- /**
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.
131
- */
132
- export declare class TLSServer extends Server {
133
- private _tlsCertificate;
134
- private _tlsOptions;
135
- private _sniContexts;
136
- /** @internal exposed for tests. */
137
- _secureContext: SecureContext;
138
- constructor(options?: TlsServerOptions, secureConnectionListener?: (socket: TLSSocket) => void);
139
- /**
140
- * Add an additional context for SNI (Server Name Indication). Uses RFC 6125
141
- * matching against the requested server name.
142
- */
143
- addContext(hostname: string, context: SecureContextOptions): void;
144
- /**
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
150
- */
151
- private _resolveSniContext;
152
- listen(...args: unknown[]): this;
153
- /** Upgrade a raw TCP socket to TLS using Gio.TlsServerConnection. */
154
- private _upgradeTls;
155
- }
156
- /**
157
- * Create a TLS server.
158
- */
159
- export declare function createServer(options?: TlsServerOptions, secureConnectionListener?: (socket: TLSSocket) => void): TLSServer;
160
- export declare function createServer(secureConnectionListener?: (socket: TLSSocket) => void): TLSServer;
161
- export { TLSServer as Server };
7
+ export type { CertSubject, PeerCertificate } from './internal/cert-utils.js';
8
+ export type { CertAltNameError } from './internal/hostname.js';
9
+ export { checkServerIdentity } from './internal/hostname.js';
10
+ export type { SecureContext, SecureContextOptions } from './secure-context.js';
11
+ export { createSecureContext } from './secure-context.js';
12
+ export type { TlsConnectOptions } from './tls-socket.js';
13
+ export { TLSSocket } from './tls-socket.js';
14
+ export { connect } from './connect.js';
15
+ export type { SNICallback, TlsServerOptions } from './tls-server.js';
16
+ export { TLSServer, createServer } from './tls-server.js';
17
+ export { TLSServer as Server } from './tls-server.js';
18
+ export { parseOcspResponse, hasOcspSupport, OcspCertStatus, OcspResponseStatus, type OcspResponseInfo, } from './ocsp.js';
19
+ import { checkServerIdentity as _checkServerIdentity } from './internal/hostname.js';
20
+ import { createSecureContext as _createSecureContext } from './secure-context.js';
21
+ import { TLSSocket as _TLSSocket } from './tls-socket.js';
22
+ import { connect as _connect } from './connect.js';
23
+ import { TLSServer as _TLSServer, createServer as _createServer } from './tls-server.js';
24
+ import { parseOcspResponse as _parseOcspResponse, hasOcspSupport as _hasOcspSupport } from './ocsp.js';
162
25
  declare const tlsExports: {
163
- TLSSocket: typeof TLSSocket;
164
- TLSServer: typeof TLSServer;
165
- Server: typeof TLSServer;
166
- connect: typeof connect;
167
- createServer: typeof createServer;
168
- createSecureContext: typeof createSecureContext;
169
- checkServerIdentity: typeof checkServerIdentity;
26
+ TLSSocket: typeof _TLSSocket;
27
+ TLSServer: typeof _TLSServer;
28
+ Server: typeof _TLSServer;
29
+ connect: typeof _connect;
30
+ createServer: typeof _createServer;
31
+ createSecureContext: typeof _createSecureContext;
32
+ checkServerIdentity: typeof _checkServerIdentity;
170
33
  getCiphers: typeof getCiphers;
171
34
  rootCertificates: string[];
172
35
  DEFAULT_MIN_VERSION: string;
173
36
  DEFAULT_MAX_VERSION: string;
174
37
  DEFAULT_CIPHERS: string;
38
+ parseOcspResponse: typeof _parseOcspResponse;
39
+ hasOcspSupport: typeof _hasOcspSupport;
40
+ OcspCertStatus: {
41
+ readonly GOOD: 0;
42
+ readonly REVOKED: 1;
43
+ readonly UNKNOWN: 2;
44
+ };
45
+ OcspResponseStatus: {
46
+ readonly SUCCESSFUL: 0;
47
+ readonly MALFORMED_REQUEST: 1;
48
+ readonly INTERNAL_ERROR: 2;
49
+ readonly TRY_LATER: 3;
50
+ readonly SIG_REQUIRED: 5;
51
+ readonly UNAUTHORIZED: 6;
52
+ };
175
53
  };
176
54
  export default tlsExports;
@@ -0,0 +1,29 @@
1
+ import Gio from '@girs/gio-2.0';
2
+ import GLib from '@girs/glib-2.0';
3
+ export interface CertSubject {
4
+ CN?: string | string[];
5
+ [key: string]: unknown;
6
+ }
7
+ export interface PeerCertificate {
8
+ subject?: CertSubject;
9
+ issuer?: CertSubject;
10
+ subjectaltname?: string;
11
+ valid_from?: string;
12
+ valid_to?: string;
13
+ fingerprint?: string;
14
+ fingerprint256?: string;
15
+ serialNumber?: string;
16
+ raw?: Uint8Array;
17
+ issuerCertificate?: PeerCertificate;
18
+ [key: string]: unknown;
19
+ }
20
+ /** Parse a distinguished name string (e.g. "CN=example.com,O=Foo") into a key→value object. */
21
+ export declare function parseDistinguishedName(dn: string | null): CertSubject;
22
+ /** Format a GLib.DateTime as an OpenSSL-style validity string. */
23
+ export declare function formatCertDate(dt: GLib.DateTime | null): string;
24
+ /** Build the "subjectaltname" string from DNS names + IP addresses (Node format). */
25
+ export declare function formatAltNames(cert: Gio.TlsCertificate): string;
26
+ /** Compute SHA-1 / SHA-256 fingerprint strings from raw DER bytes (`AA:BB:CC:…`). */
27
+ export declare function fingerprintFromBytes(bytes: Uint8Array, algo: GLib.ChecksumType): string;
28
+ /** Convert a single TlsCertificate to the Node `getPeerCertificate()` shape. */
29
+ export declare function tlsCertToPeerCert(cert: Gio.TlsCertificate, detailed: boolean): PeerCertificate;
@@ -0,0 +1,31 @@
1
+ import type { PeerCertificate } from './cert-utils.js';
2
+ /** Removes a trailing dot from a fully-qualified domain name. */
3
+ export declare function unfqdn(host: string): string;
4
+ /** Splits a hostname into parts, lower-cased, after removing trailing dots. */
5
+ export declare function splitHost(host: string): string[];
6
+ /** Reject control / non-ASCII bytes in pattern labels (RFC 6125 sanity). */
7
+ export declare function isPrintableAscii(s: string): boolean;
8
+ /**
9
+ * Match a hostname (already split into labels) against a single pattern from
10
+ * a SAN DNS entry or CN. Implements RFC 6125 §6.4.3:
11
+ * - wildcard valid only in the leftmost label
12
+ * - wildcard label may not contain Punycode A-labels (`xn--`)
13
+ * - `*.tld` (two-label patterns) are rejected
14
+ * - exactly one wildcard per label
15
+ */
16
+ export declare function checkHostMatch(hostParts: string[], pattern: string): boolean;
17
+ /** Error returned by checkServerIdentity, with Node-compatible shape. */
18
+ export interface CertAltNameError extends Error {
19
+ reason: string;
20
+ host: string;
21
+ cert: PeerCertificate;
22
+ code: 'ERR_TLS_CERT_ALTNAME_INVALID';
23
+ }
24
+ /**
25
+ * Verifies that the certificate `cert` is valid for `hostname`.
26
+ * Returns an Error (with code 'ERR_TLS_CERT_ALTNAME_INVALID') if the check
27
+ * fails, or `undefined` on success.
28
+ *
29
+ * Reference: Node.js lib/tls.js exports.checkServerIdentity (RFC 6125 §6.4.3).
30
+ */
31
+ export declare function checkServerIdentity(hostname: string, cert: PeerCertificate): CertAltNameError | undefined;
@@ -0,0 +1,12 @@
1
+ import Gio from '@girs/gio-2.0';
2
+ export type PemInput = string | Buffer | Uint8Array | Array<string | Buffer | Uint8Array>;
3
+ /** Coerce a PEM input (string, Buffer/Uint8Array, or array) to a single PEM string. */
4
+ export declare function pemToString(value: PemInput): string;
5
+ /** Split a concatenated PEM blob into individual `-----BEGIN ...-----...-----END ...-----` blocks. */
6
+ export declare function splitPemBlocks(pem: string): string[];
7
+ /** Build a TlsCertificate (and chain) from PEM strings. The first cert and key are the leaf. */
8
+ export declare function buildGioCertificate(cert: PemInput, key?: PemInput): Gio.TlsCertificate;
9
+ /** Parse a CA bundle (PEM string or array) into a list of TlsCertificate trust anchors. */
10
+ export declare function buildCaCertificates(ca: PemInput): Gio.TlsCertificate[];
11
+ /** Decode a single PEM cert block into raw DER bytes. */
12
+ export declare function pemToDer(pem: string): Uint8Array;
@@ -0,0 +1,20 @@
1
+ export type { OcspResponseInfo, } from '@gjsify/tls-native';
2
+ export { OcspCertStatus, OcspResponseStatus, } from '@gjsify/tls-native';
3
+ import { type OcspResponseInfo as _OcspResponseInfo } from '@gjsify/tls-native';
4
+ /**
5
+ * Returns true when OCSP-response parsing is available — i.e. when the
6
+ * @gjsify/tls-native Vala+GnuTLS bridge is loaded. Always false on Node
7
+ * (the native prebuild is GJS-only).
8
+ */
9
+ export declare function hasOcspSupport(): boolean;
10
+ /**
11
+ * Parse a DER-encoded OCSP response (RFC 6960).
12
+ *
13
+ * Returns null when the bytes don't parse as an OCSPResponse (init / import
14
+ * errors inside GnuTLS). Throws when OCSP support is not available — gate
15
+ * the call with hasOcspSupport() to detect that case at runtime.
16
+ *
17
+ * @param bytes DER-encoded OCSPResponse bytes (typically the body of a
18
+ * POST response from the cert's AIA OCSP responder URL).
19
+ */
20
+ export declare function parseOcspResponse(bytes: Uint8Array): _OcspResponseInfo | null;
@@ -0,0 +1,27 @@
1
+ import type Gio from '@girs/gio-2.0';
2
+ import { type PemInput } from './internal/pem.js';
3
+ export interface SecureContextOptions {
4
+ ca?: PemInput;
5
+ cert?: PemInput;
6
+ key?: PemInput;
7
+ passphrase?: string;
8
+ rejectUnauthorized?: boolean;
9
+ ciphers?: string;
10
+ minVersion?: string;
11
+ maxVersion?: string;
12
+ ALPNProtocols?: string[];
13
+ }
14
+ /** Internal "secure context" — parsed TLS material shared by tls.connect/createServer. */
15
+ export interface SecureContext {
16
+ certificate: Gio.TlsCertificate | null;
17
+ caCertificates: Gio.TlsCertificate[];
18
+ options: SecureContextOptions;
19
+ /**
20
+ * Node-compat handle (Node returns a `SecureContext` with an internal native
21
+ * `context` field). We have no native handle, so this points back at the
22
+ * SecureContext object itself — `ctx.context !== undefined` matches Node.
23
+ */
24
+ context: SecureContext;
25
+ }
26
+ /** Build a SecureContext from PEM material. Buffer/Uint8Array/string all accepted. */
27
+ export declare function createSecureContext(options?: SecureContextOptions): SecureContext;
@@ -0,0 +1,44 @@
1
+ import { Server } from 'node:net';
2
+ import { type SecureContext, type SecureContextOptions } from './secure-context.js';
3
+ import { TLSSocket } from './tls-socket.js';
4
+ export type SNICallback = (servername: string, cb: (err: Error | null, ctx?: SecureContext) => void) => void;
5
+ export interface TlsServerOptions extends SecureContextOptions {
6
+ requestCert?: boolean;
7
+ rejectUnauthorized?: boolean;
8
+ ALPNProtocols?: string[];
9
+ SNICallback?: SNICallback;
10
+ }
11
+ /**
12
+ * TLSServer accepts incoming TCP connections and upgrades each to TLS via
13
+ * `Gio.TlsServerConnection`. Supports mTLS via `requestCert`+`rejectUnauthorized`,
14
+ * SNI selection via `addContext`/`SNICallback`, and ALPN negotiation.
15
+ */
16
+ export declare class TLSServer extends Server {
17
+ private _tlsCertificate;
18
+ private _tlsOptions;
19
+ private _sniContexts;
20
+ /** @internal — exposed for tests. */
21
+ _secureContext: SecureContext;
22
+ constructor(options?: TlsServerOptions, secureConnectionListener?: (socket: TLSSocket) => void);
23
+ /**
24
+ * Add an additional context for SNI (Server Name Indication). Uses RFC 6125
25
+ * matching against the requested server name.
26
+ */
27
+ addContext(hostname: string, context: SecureContextOptions): void;
28
+ /**
29
+ * Resolve a SecureContext for the given server name. Order:
30
+ * 1. exact match in `_sniContexts`
31
+ * 2. RFC 6125 wildcard match in `_sniContexts`
32
+ * 3. SNICallback (if provided)
33
+ * 4. fall through to the server's default context
34
+ */
35
+ private _resolveSniContext;
36
+ listen(...args: unknown[]): this;
37
+ /** Upgrade a raw TCP socket to TLS using Gio.TlsServerConnection. */
38
+ private _upgradeTls;
39
+ }
40
+ /**
41
+ * Create a TLS server.
42
+ */
43
+ export declare function createServer(options?: TlsServerOptions, secureConnectionListener?: (socket: TLSSocket) => void): TLSServer;
44
+ export declare function createServer(secureConnectionListener?: (socket: TLSSocket) => void): TLSServer;
@@ -0,0 +1,64 @@
1
+ import Gio from '@girs/gio-2.0';
2
+ import { Socket } from 'node:net';
3
+ import { type PeerCertificate } from './internal/cert-utils.js';
4
+ import type { SecureContext, SecureContextOptions } from './secure-context.js';
5
+ export interface TlsConnectOptions extends SecureContextOptions {
6
+ host?: string;
7
+ port?: number;
8
+ socket?: Socket;
9
+ servername?: string;
10
+ ALPNProtocols?: string[];
11
+ /** Pre-built secure context from createSecureContext(). */
12
+ secureContext?: SecureContext;
13
+ /** Custom server-identity check (runs after the GnuTLS-level check). */
14
+ checkServerIdentity?: (host: string, cert: PeerCertificate) => Error | undefined;
15
+ }
16
+ /**
17
+ * Internal cast for Socket's private-field shape. We own `node:net`'s
18
+ * implementation (`@gjsify/net`), so reaching into `_connection` etc. is
19
+ * a defined extension, not a private-API break. Exported for the
20
+ * `connect.ts` + `tls-server.ts` modules that need to wire/read these fields.
21
+ */
22
+ export interface SocketInternals {
23
+ _connection: Gio.SocketConnection | null;
24
+ _ioStream: Gio.IOStream | null;
25
+ _inputStream: Gio.InputStream | null;
26
+ _outputStream: Gio.OutputStream | null;
27
+ _reading: boolean;
28
+ _startReading(): void;
29
+ }
30
+ /**
31
+ * TLSSocket wraps a net.Socket with TLS via Gio.TlsConnection.
32
+ */
33
+ export declare class TLSSocket extends Socket {
34
+ encrypted: boolean;
35
+ authorized: boolean;
36
+ authorizationError?: string;
37
+ alpnProtocol: string | false;
38
+ servername: string | undefined;
39
+ /** @internal */
40
+ _tlsConnection: Gio.TlsConnection | null;
41
+ /** @internal — preserved for diagnostics + future cert-chain verification. */
42
+ _secureContext: SecureContext | null;
43
+ constructor(_socket?: Socket, _options?: SecureContextOptions);
44
+ /**
45
+ * @internal Wire the TLS connection's I/O streams into this socket
46
+ * so that read/write operations go through the encrypted channel.
47
+ */
48
+ _setupTlsStreams(tlsConn: Gio.TlsConnection): void;
49
+ /**
50
+ * Get the peer certificate. When `detailed` is true, walks the issuer chain
51
+ * via `Gio.TlsCertificate.get_issuer()` and populates `issuerCertificate`
52
+ * recursively (with a self-reference on the root for compatibility).
53
+ */
54
+ getPeerCertificate(detailed?: boolean): PeerCertificate;
55
+ /** Get the negotiated TLS protocol version. */
56
+ getProtocol(): string | null;
57
+ /** Get the negotiated cipher suite name + version. */
58
+ getCipher(): {
59
+ name: string;
60
+ version: string;
61
+ } | null;
62
+ /** Get the negotiated ALPN protocol (or false if none). */
63
+ getAlpnProtocol(): string | false;
64
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/tls",
3
- "version": "0.4.21",
3
+ "version": "0.4.22",
4
4
  "description": "Node.js tls module for Gjs",
5
5
  "type": "module",
6
6
  "module": "lib/esm/index.js",
@@ -33,15 +33,16 @@
33
33
  "tls"
34
34
  ],
35
35
  "devDependencies": {
36
- "@gjsify/cli": "^0.4.21",
37
- "@gjsify/unit": "^0.4.21",
38
- "@types/node": "^25.6.2",
36
+ "@gjsify/cli": "^0.4.22",
37
+ "@gjsify/unit": "^0.4.22",
38
+ "@types/node": "^25.9.1",
39
39
  "typescript": "^6.0.3"
40
40
  },
41
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": "^0.4.21",
45
- "@gjsify/utils": "^0.4.21"
42
+ "@girs/gio-2.0": "2.88.0-4.0.1",
43
+ "@girs/glib-2.0": "2.88.0-4.0.1",
44
+ "@gjsify/net": "^0.4.22",
45
+ "@gjsify/tls-native": "^0.4.22",
46
+ "@gjsify/utils": "^0.4.22"
46
47
  }
47
48
  }