@gjsify/tls 0.52.0 → 0.54.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.
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";import{hasTlsSessionAccess as e}from"./session-access.js";import{TLSSocket as t}from"./tls-socket.js";import{createSecureContext as n}from"./secure-context.js";import r from"@girs/gio-2.0";import i from"@girs/glib-2.0";function connect(a,o){let s=new t(void 0,a);o&&s.once(`secureConnect`,o);let c=a.port||443,l=a.host||`localhost`,u=a.servername||l,d=a.rejectUnauthorized!==!1,f=a.secureContext??n(a);s._secureContext=f,s.servername=u;let p=a.checkServerIdentity;return s.once(`connect`,()=>{let t=s._connection;if(!t){s.destroy(Error(`No underlying connection for TLS upgrade`));return}try{let n=r.NetworkAddress.new(u,c),o=r.TlsClientConnection.new(t,n);if(o.set_server_identity(n),a.session&&e())try{s._tlsConnection=o,s.setSession(a.session)}catch{}if(f.certificate)try{o.set_certificate(f.certificate)}catch(e){console.warn(`[tls] failed to set client certificate:`,e)}a.ALPNProtocols&&a.ALPNProtocols.length>0&&o.set_advertised_protocols(a.ALPNProtocols),o.connect(`accept-certificate`,(e,t,i)=>{if(!d)return!0;if(f.caCertificates.length===0)return!1;for(let e of f.caCertificates)try{if(t.verify(n,e)===r.TlsCertificateFlags.NO_FLAGS)return!0}catch{}return!1});let l=new r.Cancellable;o.handshake_async(i.PRIORITY_DEFAULT,l,(t,n)=>{try{if(o.handshake_finish(n),s.authorized=!0,s._setupTlsStreams(o),s.alpnProtocol=s.getAlpnProtocol(),p){let e=s.getPeerCertificate(),t=p(u,e);if(t&&(s.authorized=!1,s.authorizationError=t.message,d)){s.destroy(t);return}}let t=s;if(t._reading=!1,t._startReading(),e()){let e=s.getSession();e&&s.emit(`session`,e)}s.emit(`secureConnect`)}catch(e){s.authorized=!1,s.authorizationError=e instanceof Error?e.message:String(e),d?s.destroy(e instanceof Error?e:Error(String(e))):(s._setupTlsStreams(o),s.emit(`secureConnect`))}})}catch(e){s.destroy(e instanceof Error?e:Error(String(e)))}}),s.connect({port:c,host:l}),s}export{connect};
1
+ import"./_virtual/_rolldown/runtime.js";import{TLSSocket as e}from"./tls-socket.js";function connect(t,n){let r=t.socket,i=new e(r,t);if(n&&i.once(`secureConnect`,n),!r){let e=t.port||443,n=t.host||`localhost`;i.servername=t.servername||n,i._awaitingSecure=!0,i.once(`connect`,()=>i._performHandshake(t)),i.connect({port:e,host:n})}return i}export{connect};
package/lib/esm/index.js CHANGED
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";import{checkServerIdentity as e}from"./internal/hostname.js";import{TlsChannelBindingType as t,hasTlsSessionAccess as n}from"./session-access.js";import{TLSSocket as r}from"./tls-socket.js";import{createSecureContext as i}from"./secure-context.js";import{connect as a}from"./connect.js";import{TLSServer as o,createServer as s}from"./tls-server.js";import{OcspCertStatus as c,OcspResponseStatus as l,hasOcspSupport as u,parseOcspResponse as d}from"./ocsp.js";const f=`TLSv1.2`,p=`TLSv1.3`,m=`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 h=[],g={TLSSocket:r,TLSServer:o,Server:o,connect:a,createServer:s,createSecureContext:i,checkServerIdentity:e,getCiphers,rootCertificates:h,DEFAULT_MIN_VERSION:f,DEFAULT_MAX_VERSION:p,DEFAULT_CIPHERS:m,parseOcspResponse:d,hasOcspSupport:u,OcspCertStatus:c,OcspResponseStatus:l,hasTlsSessionAccess:n,TlsChannelBindingType:t};export{m as DEFAULT_CIPHERS,p as DEFAULT_MAX_VERSION,f as DEFAULT_MIN_VERSION,c as OcspCertStatus,l as OcspResponseStatus,o as Server,o as TLSServer,r as TLSSocket,t as TlsChannelBindingType,e as checkServerIdentity,a as connect,i as createSecureContext,s as createServer,g as default,getCiphers,u as hasOcspSupport,n as hasTlsSessionAccess,d as parseOcspResponse,h as rootCertificates};
1
+ import"./_virtual/_rolldown/runtime.js";import{checkServerIdentity as e}from"./internal/hostname.js";import{createSecureContext as t}from"./secure-context.js";import{TlsChannelBindingType as n,hasTlsSessionAccess as r}from"./session-access.js";import{TLSSocket as i}from"./tls-socket.js";import{connect as a}from"./connect.js";import{TLSServer as o,createServer as s}from"./tls-server.js";import{OcspCertStatus as c,OcspResponseStatus as l,hasOcspSupport as u,parseOcspResponse as d}from"./ocsp.js";const f=`TLSv1.2`,p=`TLSv1.3`,m=`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 h=[],g={TLSSocket:i,TLSServer:o,Server:o,connect:a,createServer:s,createSecureContext:t,checkServerIdentity:e,getCiphers,rootCertificates:h,DEFAULT_MIN_VERSION:f,DEFAULT_MAX_VERSION:p,DEFAULT_CIPHERS:m,parseOcspResponse:d,hasOcspSupport:u,OcspCertStatus:c,OcspResponseStatus:l,hasTlsSessionAccess:r,TlsChannelBindingType:n};export{m as DEFAULT_CIPHERS,p as DEFAULT_MAX_VERSION,f as DEFAULT_MIN_VERSION,c as OcspCertStatus,l as OcspResponseStatus,o as Server,o as TLSServer,i as TLSSocket,n as TlsChannelBindingType,e as checkServerIdentity,a as connect,t as createSecureContext,s as createServer,g as default,getCiphers,u as hasOcspSupport,r as hasTlsSessionAccess,d as parseOcspResponse,h as rootCertificates};
@@ -1 +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};
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(`Hostname/IP does not match certificate's altnames: ${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};
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";import{checkHostMatch as e,splitHost as t}from"./internal/hostname.js";import{TLSSocket as n}from"./tls-socket.js";import{createSecureContext as r}from"./secure-context.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/core";var TLSServer=class extends s{_tlsCertificate=null;_tlsOptions;_sniContexts=new Map;_secureContext;constructor(e,t){super(),this._tlsOptions=e??{},this._secureContext=r(this._tlsOptions),this._tlsCertificate=this._secureContext.certificate,t&&this.on(`secureConnection`,t),this._tlsOptions.cert&&!this._tlsCertificate&&l(this,`error`,c(Error(`Failed to parse TLS certificate`),`createServer`,{}))}addContext(e,t){try{let n=r(t);this._sniContexts.set(e.toLowerCase(),n)}catch(e){this.emit(`error`,c(e,`addContext`,{}))}}_resolveSniContext(n,r){let i=this._secureContext;if(!n){r(i);return}let a=n.toLowerCase(),o=this._sniContexts.get(a);if(o){r(o);return}let s=t(a);for(let[t,n]of this._sniContexts)if(e(s,t)){r(n);return}if(this._tlsOptions.SNICallback)try{this._tlsOptions.SNICallback(n,(e,t)=>{if(e||!t){r(i);return}r(t)});return}catch{r(i);return}r(i)}listen(...e){return this.on(`connection`,e=>{this._upgradeTls(e)}),super.listen(...e)}_upgradeTls(e){let t=e._connection;if(!t){let t=Error(`Cannot upgrade socket: no underlying connection`);this.emit(`tlsClientError`,t,e),e.destroy();return}if(!this._tlsCertificate&&this._sniContexts.size===0&&!this._tlsOptions.SNICallback){let t=Error(`TLS server has no certificate configured`);this.emit(`tlsClientError`,t,e),e.destroy();return}let r=t,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,(t,r)=>{let s=null;try{u.fill_finish(r),s=i(u.peek_buffer())}catch{}this._resolveSniContext(s,t=>{let r=t.certificate??this._tlsCertificate;if(!r){let t=Error(`SNI resolution returned no certificate`);this.emit(`tlsClientError`,t,e),e.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;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&&s.set_advertised_protocols(this._tlsOptions.ALPNProtocols);let p=new a.Cancellable;s.handshake_async(o.PRIORITY_DEFAULT,p,(r,i)=>{try{s.handshake_finish(i);let e=new n;e.encrypted=!0,e.authorized=!0,e._secureContext=t,e._setupTlsStreams(s),e.alpnProtocol=e.getAlpnProtocol(),e._startReading(),this.emit(`secureConnection`,e)}catch(t){let n=c(t,`handshake`,{});this.emit(`tlsClientError`,n,e),e.destroy()}})}catch(t){let n=c(t,`tls_wrap`,{});this.emit(`tlsClientError`,n,e),e.destroy()}})})}};function createServer(e,t){return typeof e==`function`?new TLSServer(void 0,e):new TLSServer(e,t)}export{TLSServer,createServer};
1
+ import"./_virtual/_rolldown/runtime.js";import{checkHostMatch as e,splitHost as t}from"./internal/hostname.js";import{createSecureContext as n}from"./secure-context.js";import{TLSSocket as r,claimGjsifyConnection as i,foreignSocketError as a}from"./tls-socket.js";import{parseClientHelloSni as o}from"./internal/sni-parser.js";import s from"@girs/gio-2.0";import c from"@girs/glib-2.0";import{Server as l}from"node:net";import{createNodeError as u,deferEmit as d}from"@gjsify/utils/core";var TLSServer=class extends l{_tlsCertificate=null;_tlsOptions;_sniContexts=new Map;_secureContext;constructor(e,t){super(),this._tlsOptions=e??{},this._secureContext=n(this._tlsOptions),this._tlsCertificate=this._secureContext.certificate,t&&this.on(`secureConnection`,t),this._tlsOptions.cert&&!this._tlsCertificate&&d(this,`error`,u(Error(`Failed to parse TLS certificate`),`createServer`,{}))}addContext(e,t){try{let r=n(t);this._sniContexts.set(e.toLowerCase(),r)}catch(e){this.emit(`error`,u(e,`addContext`,{}))}}_resolveSniContext(n,r){let i=this._secureContext;if(!n){r(i);return}let a=n.toLowerCase(),o=this._sniContexts.get(a);if(o){r(o);return}let s=t(a);for(let[t,n]of this._sniContexts)if(e(s,t)){r(n);return}if(this._tlsOptions.SNICallback)try{this._tlsOptions.SNICallback(n,(e,t)=>{if(e||!t){r(i);return}r(t)});return}catch{r(i);return}r(i)}listen(...e){return this.on(`connection`,e=>{this._upgradeTls(e)}),super.listen(...e)}_upgradeTls(e){let t=i(e);if(!t){this.emit(`tlsClientError`,a(),e),e.destroy();return}let n=t.connection;if(!n){let t=Error(`Cannot upgrade socket: no underlying connection`);this.emit(`tlsClientError`,t,e),e.destroy();return}let closeClaimedConnection=()=>{try{(t.connection??t.ioStream)?.close(null)}catch{}};if(!this._tlsCertificate&&this._sniContexts.size===0&&!this._tlsOptions.SNICallback){let t=Error(`TLS server has no certificate configured`);this.emit(`tlsClientError`,t,e),closeClaimedConnection(),e.destroy();return}let r=n;if(this._sniContexts.size===0&&!this._tlsOptions.SNICallback){let t=this._tlsCertificate;if(!t){let t=Error(`TLS server has no certificate configured`);this.emit(`tlsClientError`,t,e),closeClaimedConnection(),e.destroy();return}this._completeUpgrade(r,t,this._secureContext,e,closeClaimedConnection);return}let l=r.get_input_stream(),u=r.get_output_stream(),d=s.BufferedInputStream.new_sized(l,4096);d.fill_async(4096,c.PRIORITY_DEFAULT,null,(t,n)=>{let r=null;try{d.fill_finish(n),r=o(d.peek_buffer())}catch{}this._resolveSniContext(r,t=>{let n=t.certificate??this._tlsCertificate;if(!n){let t=Error(`SNI resolution returned no certificate`);this.emit(`tlsClientError`,t,e),closeClaimedConnection(),e.destroy();return}let r=new s.SimpleIOStream({inputStream:d,outputStream:u});this._completeUpgrade(r,n,t,e,closeClaimedConnection)})})}_completeUpgrade(e,t,n,i,a){try{let o=s.TlsServerConnection.new(e,t);this._tlsOptions.requestCert?o.authenticationMode=this._tlsOptions.rejectUnauthorized===!1?s.TlsAuthenticationMode.REQUESTED:s.TlsAuthenticationMode.REQUIRED:o.authenticationMode=s.TlsAuthenticationMode.NONE;let l=!!this._tlsOptions.requestCert&&this._tlsOptions.rejectUnauthorized!==!1,d=this._secureContext.caCertificates;o.connect(`accept-certificate`,(e,t,n)=>{if(!l)return!0;if(d.length===0)return!1;for(let e of d)try{if(t.verify(null,e)===s.TlsCertificateFlags.NO_FLAGS)return!0}catch{}return!1}),this._tlsOptions.ALPNProtocols&&this._tlsOptions.ALPNProtocols.length>0&&o.set_advertised_protocols(this._tlsOptions.ALPNProtocols);let f=new s.Cancellable;o.handshake_async(c.PRIORITY_DEFAULT,f,(e,t)=>{try{o.handshake_finish(t);let e=new r;e.encrypted=!0,e.authorized=!0,e._secureContext=n,e._setupTlsStreams(o),e.alpnProtocol=e.getAlpnProtocol(),e.once(`close`,()=>i.destroy()),e._startReading(),this.emit(`secureConnection`,e)}catch(e){let t=u(e,`handshake`,{});this.emit(`tlsClientError`,t,i),a(),i.destroy()}})}catch(e){let t=u(e,`tls_wrap`,{});this.emit(`tlsClientError`,t,i),a(),i.destroy()}}};function createServer(e,t){return typeof e==`function`?new TLSServer(void 0,e):new TLSServer(e,t)}export{TLSServer,createServer};
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";import{tlsCertToPeerCert as e}from"./internal/cert-utils.js";import{createSessionAccess as t,hasTlsSessionAccess as n}from"./session-access.js";import r from"@girs/gio-2.0";import{Buffer as i}from"node:buffer";import{Socket as a}from"node:net";var TLSSocket=class extends a{encrypted=!0;authorized=!1;authorizationError;alpnProtocol=!1;servername;_tlsConnection=null;_secureContext=null;_sessionAccess=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}_getSessionAccess(){if(this._sessionAccess)return this._sessionAccess;if(!this._tlsConnection)return null;try{this._sessionAccess=t(this._tlsConnection)}catch{this._sessionAccess=null}return this._sessionAccess}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;switch(this._tlsConnection.get_protocol_version()){case r.TlsProtocolVersion.TLS_1_0:return`TLSv1`;case r.TlsProtocolVersion.TLS_1_1:return`TLSv1.1`;case r.TlsProtocolVersion.TLS_1_2:return`TLSv1.2`;case r.TlsProtocolVersion.TLS_1_3:return`TLSv1.3`;default:return null}}getCipher(){return this._tlsConnection?{name:this._tlsConnection.get_ciphersuite_name()||`unknown`,version:this.getProtocol()||`unknown`}:null}getAlpnProtocol(){return this._tlsConnection&&this._tlsConnection.get_negotiated_protocol()||!1}getFinished(){if(!n())return;let e=this._getSessionAccess();if(e)try{return _bytesToBuffer(e.get_finished())}catch{return}}getPeerFinished(){if(!n())return;let e=this._getSessionAccess();if(e)try{return _bytesToBuffer(e.get_peer_finished())}catch{return}}getSession(){if(!n())return;let e=this._getSessionAccess();if(e)try{return _bytesToBuffer(e.get_session_data())}catch{return}}setSession(e){if(!n())return;let t=this._getSessionAccess();if(t)try{t.set_session_data(_bufferToBytes(e))}catch{}}isSessionReused(){if(!n())return!1;let e=this._getSessionAccess();if(!e)return!1;try{return e.is_session_reused()}catch{return!1}}};function _bytesToBuffer(e){if(e==null)return;if(e instanceof Uint8Array)return i.from(e);let t=e;if(typeof t.toArray==`function`)try{return i.from(t.toArray())}catch{return}}function _bufferToBytes(e){return e}export{TLSSocket};
1
+ import"./_virtual/_rolldown/runtime.js";import{tlsCertToPeerCert as e}from"./internal/cert-utils.js";import{createSecureContext as t}from"./secure-context.js";import{createSessionAccess as n,hasTlsSessionAccess as r}from"./session-access.js";import i from"@girs/gio-2.0";import a from"@girs/glib-2.0";import{Buffer as o}from"node:buffer";import{Socket as s}from"node:net";import c from"node:process";function claimGjsifyConnection(e){let t=e;return typeof t?._claimConnection==`function`?t._claimConnection():null}var TLSSocket=class extends s{encrypted=!0;authorized=!1;authorizationError;alpnProtocol=!1;servername;_tlsConnection=null;_secureContext=null;_sessionAccess=null;_adoptedSocket=null;_awaitingSecure=!1;_afterSecure=[];_handshakeClaim=null;_certRejection=null;constructor(e,t){super(),e&&this._startClient(e,t??{})}_claimSocketStreams(e){let t=claimGjsifyConnection(e);return t||this.destroy(foreignSocketError()),t}_startClient(e,t){this.servername=t.servername||t.host||`localhost`,this._adoptedSocket=e,this._awaitingSecure=!0,this.once(`close`,()=>{e.destroyed||e.destroy()});let begin=()=>{this._adoptConnection(e).then(e=>{e&&this._performHandshake(t)})};e.connecting?e.once(`connect`,begin):c.nextTick(begin)}async _adoptConnection(e){if(this.destroyed||e.destroyed)return!1;let t=e;if(typeof t._detachReader!=`function`)return this.destroy(foreignSocketError()),!1;let n=this._claimSocketStreams(t);if(!n)return!1;let r=await t._detachReader();if(this.destroyed){try{(n.connection??n.ioStream)?.close(null)}catch{}return!1}if(r&&r.length>0){this.destroy(_upgradeRaceError());try{(n.connection??n.ioStream)?.close(null)}catch{}return!1}let i=this;i._connection=n.connection,i._ioStream=n.ioStream,i._inputStream=n.inputStream,i._outputStream=n.outputStream;let a=this,o=e;return a.remoteAddress=o.remoteAddress,a.remotePort=o.remotePort,a.remoteFamily=o.remoteFamily,a.localAddress=o.localAddress,a.localPort=o.localPort,a.connecting=!1,a.pending=!1,!0}_performHandshake(e){let n=this.servername||e.servername||e.host||`localhost`;this.servername=n;let o=e.port||this.remotePort||443,s=e.rejectUnauthorized!==!1,c=e.secureContext??t(e);this._secureContext=c;let l=e.checkServerIdentity,u=this,d=this._claimSocketStreams(u);if(!d)return;let f=d.connection;if(!f){this.destroy(Error(`No underlying connection for TLS upgrade`));return}this._handshakeClaim=d;let fail=e=>{this.destroy(e instanceof Error?e:Error(String(e)))};try{let t=i.NetworkAddress.new(n,o),p=i.TlsClientConnection.new(f,t);if(p.set_server_identity(t),e.session&&r())try{this._tlsConnection=p,this.setSession(e.session)}catch{}if(c.certificate)try{p.set_certificate(c.certificate)}catch(e){console.warn(`[tls] failed to set client certificate:`,e)}e.ALPNProtocols&&e.ALPNProtocols.length>0&&p.set_advertised_protocols(e.ALPNProtocols),p.connect(`accept-certificate`,(e,r,a)=>{if(!s)return!0;let o=a;for(let e of c.caCertificates)try{if(o=r.verify(t,e),o===i.TlsCertificateFlags.NO_FLAGS)return!0}catch{}return this._certRejection=_certRejection(r,o,n),!1}),u._pendingIo++,p.handshake_async(a.PRIORITY_DEFAULT,u._cancellable,(e,t)=>{let r=null;try{p.handshake_finish(t)}catch(e){r=e}if(this.destroyed){u._ioSettled();return}if(this._handshakeClaim=null,u._ioSettled(),r){this.authorized=!1;let e=this._certRejection,t=e?Object.assign(Error(e.message),{code:e.code}):r;this.authorizationError=t instanceof Error?t.message:String(t),u._connection=d.connection,u._ioStream=d.ioStream,fail(t);return}this._secureEstablished(p,n,s,l)})}catch(e){fail(e)}}_secureEstablished(e,t,n,i){if(this.authorized=!0,this._setupTlsStreams(e),this.alpnProtocol=this.getAlpnProtocol(),i){let e=i(t,this.getPeerCertificate());if(e&&(this.authorized=!1,this.authorizationError=e.message,n)){this.destroy(e);return}}let a=this;if(a._reading=!1,a._startReading(),r()){let e=this.getSession();e&&this.emit(`session`,e)}this.emit(`secureConnect`),this._flushAfterSecure()}_write(e,t,n){if(this._awaitingSecure){this._afterSecure.push({run:()=>super._write(e,t,n),abort:()=>n(_destroyedError())});return}super._write(e,t,n)}_flushAfterSecure(){let e=this._afterSecure;this._afterSecure=[];for(let t of e)t.run()}_destroy(e,t){let n=this._handshakeClaim;if(n){this._handshakeClaim=null;let e=this;e._connection=n.connection,e._ioStream=n.ioStream}let r=this._afterSecure;this._afterSecure=[];for(let e of r)e.abort();super._destroy(e,t)}_final(e){if(this._awaitingSecure){this._afterSecure.push({run:()=>this._final(e),abort:()=>{}});return}let t=this._tlsConnection;if(!t){super._final(e);return}let n=this,r=t.get_output_stream();n._pendingIo++,r.close_async(a.PRIORITY_DEFAULT,null,(t,i)=>{try{r.close_finish(i)}catch{}n._ioSettled(),e()})}_setupTlsStreams(e){this._awaitingSecure=!1,this._tlsConnection=e;let t=this;t._inputStream=e.get_input_stream(),t._outputStream=e.get_output_stream(),t._connection=e}_getSessionAccess(){if(this._sessionAccess)return this._sessionAccess;if(!this._tlsConnection)return null;try{this._sessionAccess=n(this._tlsConnection)}catch{this._sessionAccess=null}return this._sessionAccess}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;switch(this._tlsConnection.get_protocol_version()){case i.TlsProtocolVersion.TLS_1_0:return`TLSv1`;case i.TlsProtocolVersion.TLS_1_1:return`TLSv1.1`;case i.TlsProtocolVersion.TLS_1_2:return`TLSv1.2`;case i.TlsProtocolVersion.TLS_1_3:return`TLSv1.3`;default:return null}}getCipher(){return this._tlsConnection?{name:this._tlsConnection.get_ciphersuite_name()||`unknown`,version:this.getProtocol()||`unknown`}:null}getAlpnProtocol(){return this._tlsConnection&&this._tlsConnection.get_negotiated_protocol()||!1}getFinished(){if(!r())return;let e=this._getSessionAccess();if(e)try{return _bytesToBuffer(e.get_finished())}catch{return}}getPeerFinished(){if(!r())return;let e=this._getSessionAccess();if(e)try{return _bytesToBuffer(e.get_peer_finished())}catch{return}}getSession(){if(!r())return;let e=this._getSessionAccess();if(e)try{return _bytesToBuffer(e.get_session_data())}catch{return}}setSession(e){if(!r())return;let t=this._getSessionAccess();if(t)try{t.set_session_data(_bufferToBytes(e))}catch{}}isSessionReused(){if(!r())return!1;let e=this._getSessionAccess();if(!e)return!1;try{return e.is_session_reused()}catch{return!1}}};function _destroyedError(){let e=Error(`Cannot call write after a stream was destroyed`);return e.code=`ERR_STREAM_DESTROYED`,e}function _certRejection(e,t,n){return t&i.TlsCertificateFlags.BAD_IDENTITY?{code:`ERR_TLS_CERT_ALTNAME_INVALID`,message:`Hostname/IP does not match certificate's altnames: Host: ${n}. is not in the cert's altnames`}:t&i.TlsCertificateFlags.EXPIRED?{code:`CERT_HAS_EXPIRED`,message:`certificate has expired`}:t&i.TlsCertificateFlags.NOT_ACTIVATED?{code:`CERT_NOT_YET_VALID`,message:`certificate is not yet valid`}:e.subjectName&&e.subjectName===e.issuerName?{code:`DEPTH_ZERO_SELF_SIGNED_CERT`,message:`self-signed certificate`}:{code:`UNABLE_TO_VERIFY_LEAF_SIGNATURE`,message:`unable to verify the first certificate`}}function _upgradeRaceError(){let e=Error(`tls: data arrived on the socket before the TLS handshake could take over reading its input stream — Gio.TlsClientConnection has no API to replay already-read bytes into the handshake. This should not happen for a well-behaved STARTTLS peer.`);return e.code=`ERR_GJSIFY_TLS_UPGRADE_RACE`,e}function foreignSocketError(){let e=Error(`tls.connect({socket}) / new tls.TLSSocket(socket, …) can only upgrade a @gjsify/net Socket today — the given socket doesn't carry the Gio connection needed to build a Gio.TlsClientConnection. Generic Duplex support (Node's real contract) is tracked in status/open-todos/README.md.`);return e.code=`ERR_GJSIFY_TLS_FOREIGN_SOCKET`,e}function _bytesToBuffer(e){if(e==null)return;if(e instanceof Uint8Array)return o.from(e);let t=e;if(typeof t.toArray==`function`)try{return o.from(t.toArray())}catch{return}}function _bufferToBytes(e){return e}export{TLSSocket,claimGjsifyConnection,foreignSocketError};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import{checkServerIdentity as e}from"./internal/hostname.js";import{tlsCertToPeerCert as t}from"./internal/cert-utils.js";import{createSecureContext as n}from"./secure-context.js";import r from"@girs/gio-2.0";import i from"@girs/glib-2.0";function resolveRejectUnauthorized(e){return e===void 0?i.getenv(`NODE_TLS_REJECT_UNAUTHORIZED`)!==`0`:e!==!1}function createPeerVerifier(n){let r=resolveRejectUnauthorized(n.rejectUnauthorized),i=n.checkServerIdentity??e,a=n.caCertificates??[],o=null;return{accept(e){return o=chainError(e,a)??i(n.host,t(e,!0))??null,o===null||!r},get error(){return o},get authorized(){return o===null},get authorizationError(){if(o)return o.code??o.message}}}const a=r.TlsCertificateFlags.VALIDATE_ALL&~r.TlsCertificateFlags.BAD_IDENTITY;function chainFlags(e,t){if(t.length===0)return r.TlsBackend.get_default().get_default_database().verify_chain(e,r.TLS_DATABASE_PURPOSE_AUTHENTICATE_SERVER,null,null,r.TlsDatabaseVerifyFlags.NONE,null);let n=r.TlsCertificateFlags.UNKNOWN_CA;for(let i of t){let t=e.verify(null,i)&a;if(t===r.TlsCertificateFlags.NO_FLAGS)return t;t&r.TlsCertificateFlags.UNKNOWN_CA||(n=t)}return n}function isSelfSigned(e){return e.get_subject_name()===e.get_issuer_name()}function verifyError(e,t){let n=Error(t);return n.code=e,n}function chainError(e,t=[]){let n=chainFlags(e,t)&a,i=r.TlsCertificateFlags;if(n===i.NO_FLAGS)return null;if(n&i.UNKNOWN_CA){if(isSelfSigned(e))return verifyError(`DEPTH_ZERO_SELF_SIGNED_CERT`,`self-signed certificate`);let t=e;for(let e=t.get_issuer();e&&!e.is_same(t);e=t.get_issuer())t=e;return t===e?verifyError(`UNABLE_TO_VERIFY_LEAF_SIGNATURE`,`unable to verify the first certificate`):isSelfSigned(t)?verifyError(`SELF_SIGNED_CERT_IN_CHAIN`,`self-signed certificate in certificate chain`):verifyError(`UNABLE_TO_GET_ISSUER_CERT_LOCALLY`,`unable to get local issuer certificate`)}return n&i.REVOKED?verifyError(`CERT_REVOKED`,`certificate revoked`):n&i.EXPIRED?verifyError(`CERT_HAS_EXPIRED`,`certificate has expired`):n&i.NOT_ACTIVATED?verifyError(`CERT_NOT_YET_VALID`,`certificate is not yet valid`):n&i.INSECURE?verifyError(`CA_MD_TOO_WEAK`,`CA signature digest algorithm too weak`):verifyError(`CERT_REJECTED`,`certificate rejected`)}export{chainError,createPeerVerifier,n as createSecureContext,resolveRejectUnauthorized};
@@ -2,7 +2,13 @@ import { TLSSocket, type TlsConnectOptions } from './tls-socket.js';
2
2
  /**
3
3
  * Create a TLS client connection.
4
4
  *
5
- * Connects via TCP first (using net.Socket.connect), then upgrades
6
- * the connection to TLS using Gio.TlsClientConnection.
5
+ * Without `options.socket`: connects via TCP first (net.Socket.connect on
6
+ * the TLSSocket itself), then upgrades to TLS once connected.
7
+ *
8
+ * With `options.socket`: adopts that already-connected (or still
9
+ * connecting) `net.Socket` and upgrades it in place — the STARTTLS shape
10
+ * (`@xmpp/starttls`, SMTP `STARTTLS`, IMAP `STARTTLS`, …): the caller has
11
+ * already exchanged some plaintext over the socket and is now handing it
12
+ * over for the TLS handshake to take over reading/writing.
7
13
  */
8
14
  export declare function connect(options: TlsConnectOptions, callback?: () => void): TLSSocket;
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
@@ -34,8 +34,27 @@ export declare class TLSServer extends Server {
34
34
  */
35
35
  private _resolveSniContext;
36
36
  listen(...args: unknown[]): this;
37
- /** Upgrade a raw TCP socket to TLS using Gio.TlsServerConnection. */
37
+ /**
38
+ * Upgrade a raw TCP socket to TLS using Gio.TlsServerConnection.
39
+ *
40
+ * Called synchronously from the 'connection' listener `listen()`
41
+ * registers — `Server._handleConnection` defers its own auto-start of
42
+ * `socket`'s plaintext read loop specifically so this can run first.
43
+ * `_claimConnection()` nulls `socket`'s stream fields right away: that
44
+ * makes the deferred auto-start a no-op, pre-empting a second,
45
+ * conflicting reader on the same Gio.InputStream this method's own
46
+ * SNI peek + handshake are about to read from (the incident behind
47
+ * `Server._handleConnection`'s `autoStartReading` parameter).
48
+ */
38
49
  private _upgradeTls;
50
+ /**
51
+ * Configure mTLS/ALPN/accept-certificate on a `Gio.TlsServerConnection`
52
+ * built over `baseIo`, run the handshake, and emit 'secureConnection'
53
+ * on success. Shared by `_upgradeTls`'s SNI-less fast path and its
54
+ * SNI-peek path (they differ only in how `baseIo`/`certificate` were
55
+ * obtained).
56
+ */
57
+ private _completeUpgrade;
39
58
  }
40
59
  /**
41
60
  * Create a TLS server.
@@ -2,7 +2,7 @@ import Gio from '@girs/gio-2.0';
2
2
  import { Buffer } from 'node:buffer';
3
3
  import { Socket } from 'node:net';
4
4
  import { type PeerCertificate } from './internal/cert-utils.js';
5
- import type { SecureContext, SecureContextOptions } from './secure-context.js';
5
+ import { type SecureContext, type SecureContextOptions } from './secure-context.js';
6
6
  import type { NativeSessionAccess } from './session-access.js';
7
7
  export interface TlsConnectOptions extends SecureContextOptions {
8
8
  host?: string;
@@ -37,7 +37,41 @@ export interface SocketInternals {
37
37
  _outputStream: Gio.OutputStream | null;
38
38
  _reading: boolean;
39
39
  _startReading(): void;
40
+ /** In-flight Gio operations `_destroy` waits for before closing the connection. */
41
+ _pendingIo: number;
42
+ _ioSettled(): void;
43
+ /** Cancelled by `destroy()`; guards every Gio operation on this socket. */
44
+ _cancellable: Gio.Cancellable;
45
+ /**
46
+ * Stop the read loop and cancel any in-flight read, resolving once
47
+ * settled — see `@gjsify/net`'s `Socket._detachReader()` for the full
48
+ * contract. Used to hand an already-connected `net.Socket`'s Gio
49
+ * connection off to a `TLSSocket` (`tls.connect({socket})` / STARTTLS).
50
+ */
51
+ _detachReader(): Promise<Buffer | null>;
52
+ /**
53
+ * Atomically null out the four stream fields above and return what
54
+ * they were — see `@gjsify/net`'s `Socket._claimConnection()`. Call
55
+ * SYNCHRONOUSLY within a 'connect'/'connection' listener to pre-empt
56
+ * an auto-start that hasn't run yet (nulling `_inputStream` makes it
57
+ * a no-op); pair with `_detachReader()` when a read may already be
58
+ * running.
59
+ */
60
+ _claimConnection(): {
61
+ connection: Gio.SocketConnection | null;
62
+ ioStream: Gio.IOStream | null;
63
+ inputStream: Gio.InputStream | null;
64
+ outputStream: Gio.OutputStream | null;
65
+ };
40
66
  }
67
+ /** What {@link SocketInternals._claimConnection} hands over. */
68
+ export type ClaimedConnection = ReturnType<SocketInternals['_claimConnection']>;
69
+ /**
70
+ * Claim `socket`'s Gio stream fields, or answer `null` when it is not a
71
+ * `@gjsify/net` Socket (no `_claimConnection`): the caller then fails with
72
+ * {@link foreignSocketError} instead of a bare TypeError.
73
+ */
74
+ export declare function claimGjsifyConnection(socket: unknown): ClaimedConnection | null;
41
75
  /**
42
76
  * TLSSocket wraps a net.Socket with TLS via Gio.TlsConnection.
43
77
  */
@@ -57,7 +91,123 @@ export declare class TLSSocket extends Socket {
57
91
  * (`_getSessionAccess()`), reused across subsequent calls.
58
92
  */
59
93
  _sessionAccess: NativeSessionAccess | null;
60
- constructor(_socket?: Socket, _options?: SecureContextOptions);
94
+ /**
95
+ * @internal Set while a `net.Socket` given to the constructor (or to
96
+ * `tls.connect({socket})`) is being — or has been — adopted, so
97
+ * `destroy()` can tear it down too. See `_startClient`.
98
+ */
99
+ _adoptedSocket: Socket | null;
100
+ /**
101
+ * @internal True from the moment a client handshake is scheduled
102
+ * (`tls.connect()`, `new TLSSocket(socket)`) until `_setupTlsStreams()`
103
+ * wires the encrypted streams. While set, `_write`/`_final` wait in
104
+ * `_afterSecure` instead of reaching the plaintext transport — Node
105
+ * buffers `tls.connect(...).write(req)` the same way.
106
+ */
107
+ _awaitingSecure: boolean;
108
+ /** Writes/end() queued while `_awaitingSecure`; run in order once secure. */
109
+ private _afterSecure;
110
+ /**
111
+ * The raw streams `_performHandshake` claimed, held here (not in a
112
+ * closure) while the handshake runs so `_destroy` can hand them back to
113
+ * `@gjsify/net`'s release path — otherwise a `destroy()` mid-handshake
114
+ * leaked the descriptor.
115
+ */
116
+ private _handshakeClaim;
117
+ /** Why 'accept-certificate' refused the peer, as Node's error code + message. */
118
+ private _certRejection;
119
+ constructor(socket?: Socket, options?: TlsConnectOptions);
120
+ /**
121
+ * @internal Adopt `providedSocket` — already connected, or still
122
+ * connecting — as this TLSSocket's transport, then run the client TLS
123
+ * handshake over it. Backs both `new tls.TLSSocket(socket, options)`
124
+ * and `tls.connect({socket, ...})`: Node's `tls.connect` is a thin
125
+ * wrapper over the TLSSocket constructor plus starting the handshake,
126
+ * so we fold the two entry points together here instead of keeping
127
+ * the handshake logic twice (see `connect.ts`).
128
+ */
129
+ /**
130
+ * Claim a socket's stream fields, or destroy `this` and answer `null`.
131
+ *
132
+ * Node's real `tls.connect({socket})` accepts ANY Duplex; we can only
133
+ * adopt a `@gjsify/net` Socket today — its Gio connection is what gets
134
+ * handed to Gio.TlsClientConnection, and a foreign Duplex has none.
135
+ * Feature-detect rather than let a bare `_claimConnection is not a
136
+ * function` TypeError surface three calls deep: reached in practice when
137
+ * a build aliases `node:tls` to this polyfill but leaves `node:net` on a
138
+ * runtime's own native module (e.g. `@gjsify/node-gi`'s consumer harness,
139
+ * which forces `runtimes.node === "native"` deps onto their polyfill body
140
+ * but `@gjsify/net` declares `"none"`, so it stays native — see
141
+ * status/open-todos/README.md). Generic-Duplex support is tracked there too.
142
+ *
143
+ * ONE guard for every claim site: this method for the two client
144
+ * ones, {@link claimGjsifyConnection} directly for `TLSServer`'s
145
+ * accepted sockets. `_adoptConnection` had a guard; `_performHandshake`
146
+ * and `TLSServer._upgradeTls` called `_claimConnection()` bare. The
147
+ * server one is what kept four `given-socket.spec.ts` tests red on the
148
+ * `node-gi` consumer harness since #1837 added them: `withServer` is a
149
+ * `tls.createServer` whose accepted sockets are native there too, so
150
+ * every connection threw the TypeError out of the `'connection'`
151
+ * listener, uncaught, into whichever test was running.
152
+ */
153
+ private _claimSocketStreams;
154
+ private _startClient;
155
+ /**
156
+ * @internal Detach `providedSocket`'s read loop (see
157
+ * `SocketInternals._detachReader`) and steal its Gio connection/streams
158
+ * onto `this`. Returns `false` — having already cleaned up — when
159
+ * either socket was destroyed meanwhile, or when the detach lost the
160
+ * race to data that had already arrived (see `_detachReader`'s doc):
161
+ * `Gio.TlsClientConnection` has no API to replay bytes already pulled
162
+ * off the wire, unlike Node's OpenSSL BIO, so that case is surfaced as
163
+ * a destroy() error rather than silently dropped.
164
+ *
165
+ * Claims the streams (`_claimConnection`, nulling them on
166
+ * `providedSocket`) SYNCHRONOUSLY, before the `_detachReader` await:
167
+ * when `_startClient` runs this from `providedSocket`'s OWN 'connect'
168
+ * listener (the still-connecting case), `Socket.connect()`'s
169
+ * `_setupConnection()` is about to auto-start reading right after
170
+ * 'connect' listeners finish — nulling `_inputStream` now makes that a
171
+ * no-op instead of a second reader racing the handshake this method
172
+ * goes on to start. For the already-connected case, a read may
173
+ * already be running; `_detachReader()` (order relative to the claim
174
+ * doesn't matter — the running loop holds its own local stream
175
+ * reference, not `providedSocket`'s field) settles that.
176
+ */
177
+ private _adoptConnection;
178
+ /**
179
+ * @internal Perform the client TLS handshake over this socket's Gio
180
+ * connection — either self-connected via `tls.connect()` (no
181
+ * `options.socket`), or just adopted from a caller-supplied
182
+ * `net.Socket` via `_startClient()`. Moved here from `connect.ts` so
183
+ * both entry points share one implementation.
184
+ *
185
+ * Reference: Node.js `lib/internal/tls/wrap.js` `TLSSocket.prototype._init`.
186
+ */
187
+ _performHandshake(options: TlsConnectOptions): void;
188
+ private _secureEstablished;
189
+ _write(chunk: unknown, encoding: BufferEncoding, callback: (error?: Error | null) => void): void;
190
+ private _flushAfterSecure;
191
+ /**
192
+ * Hands a mid-handshake connection back to `@gjsify/net`'s release path:
193
+ * `super._destroy` cancels `_cancellable` (aborting `handshake_async`)
194
+ * and, since the handshake counts as pending I/O, closes the restored
195
+ * raw connection only once that callback has settled.
196
+ */
197
+ _destroy(err: Error | null, callback: (error?: Error | null) => void): void;
198
+ /**
199
+ * `end()` on a TLS socket must send TLS close_notify — the peer's TLS
200
+ * layer only reports EOF on that alert, never on a bare TCP half-close.
201
+ * `@gjsify/net`'s `_final` half-closes via `get_socket().shutdown()`,
202
+ * which a `Gio.TlsConnection` (stored in `_connection`) doesn't have:
203
+ * the call threw, was swallowed, and nothing reached the peer, so an
204
+ * `end()` with the default `allowHalfOpen: false` on both sides waited
205
+ * forever for the other side's 'end'. Closing the TLS output stream
206
+ * sends close_notify and closes the base stream's write side
207
+ * (glib-networking's `g_tls_connection_base_close_internal`), while
208
+ * the read side stays open for the peer's reply.
209
+ */
210
+ _final(callback: (error?: Error | null) => void): void;
61
211
  /**
62
212
  * @internal Wire the TLS connection's I/O streams into this socket
63
213
  * so that read/write operations go through the encrypted channel.
@@ -138,3 +288,14 @@ export declare class TLSSocket extends Socket {
138
288
  */
139
289
  isSessionReused(): boolean;
140
290
  }
291
+ /**
292
+ * Error surfaced by `TLSSocket._adoptConnection()` when `options.socket` /
293
+ * the constructor's `socket` argument doesn't carry a `@gjsify/net` Gio
294
+ * connection (`_claimConnection`/`_detachReader` missing). Node's real
295
+ * `tls.connect({socket})` accepts any Duplex; adopting a foreign one needs
296
+ * a Duplex→Gio.IOStream adapter this package doesn't have yet — tracked in
297
+ * status/open-todos/README.md, next to the SNI-peek entry.
298
+ */
299
+ export declare function foreignSocketError(): Error & {
300
+ code: string;
301
+ };
@@ -0,0 +1,37 @@
1
+ import Gio from '@girs/gio-2.0';
2
+ import { type PeerCertificate } from './internal/cert-utils.js';
3
+ export { createSecureContext, type SecureContext, type SecureContextOptions } from './secure-context.js';
4
+ export type { PeerCertificate } from './internal/cert-utils.js';
5
+ /** Error carrying Node's OpenSSL-derived `code` (e.g. `DEPTH_ZERO_SELF_SIGNED_CERT`). */
6
+ export interface TlsVerifyError extends Error {
7
+ code: string;
8
+ }
9
+ export interface PeerVerifierOptions {
10
+ /** Trust anchors (`SecureContext.caCertificates`). Empty ⇒ the system trust store. */
11
+ caCertificates?: Gio.TlsCertificate[];
12
+ /** Unset ⇒ verify, unless `NODE_TLS_REJECT_UNAUTHORIZED=0` (Node's rule: explicit wins). */
13
+ rejectUnauthorized?: boolean;
14
+ /** Name the certificate must be valid for — Node's `servername || host`. */
15
+ host: string;
16
+ checkServerIdentity?: (host: string, cert: PeerCertificate) => Error | undefined;
17
+ }
18
+ export interface PeerVerifier {
19
+ /** `accept-certificate` handler: true accepts the peer. */
20
+ accept(peer: Gio.TlsCertificate): boolean;
21
+ /** Why the peer is not authorized (null when it is) — the error a rejected request reports. */
22
+ readonly error: Error | null;
23
+ readonly authorized: boolean;
24
+ /** Node's `TLSSocket.authorizationError`: the error's code, else its message. */
25
+ readonly authorizationError: string | undefined;
26
+ }
27
+ /** Resolve `rejectUnauthorized` the way Node's tls.connect does. */
28
+ export declare function resolveRejectUnauthorized(rejectUnauthorized: boolean | undefined): boolean;
29
+ /** Build a verifier for one connection attempt. */
30
+ export declare function createPeerVerifier(options: PeerVerifierOptions): PeerVerifier;
31
+ /**
32
+ * The chain's verdict as the error Node (OpenSSL) reports for it, or null.
33
+ * An unknown issuer splits four ways by the shape of the presented chain,
34
+ * matching X509_V_ERR_{DEPTH_ZERO_SELF_SIGNED_CERT,SELF_SIGNED_CERT_IN_CHAIN,
35
+ * UNABLE_TO_VERIFY_LEAF_SIGNATURE,UNABLE_TO_GET_ISSUER_CERT_LOCALLY}.
36
+ */
37
+ export declare function chainError(peer: Gio.TlsCertificate, anchors?: Gio.TlsCertificate[]): TlsVerifyError | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/tls",
3
- "version": "0.52.0",
3
+ "version": "0.54.0",
4
4
  "description": "Node.js tls module for Gjs",
5
5
  "type": "module",
6
6
  "module": "lib/esm/index.js",
@@ -10,6 +10,10 @@
10
10
  "types": "./lib/types/index.d.ts",
11
11
  "default": "./lib/esm/index.js"
12
12
  },
13
+ "./verify": {
14
+ "types": "./lib/types/verify.d.ts",
15
+ "default": "./lib/esm/verify.js"
16
+ },
13
17
  "./browser": {
14
18
  "types": "./lib/types/browser.d.ts",
15
19
  "default": "./lib/esm/browser.js"
@@ -37,17 +41,17 @@
37
41
  "tls"
38
42
  ],
39
43
  "devDependencies": {
40
- "@gjsify/cli": "^0.52.0",
41
- "@gjsify/unit": "^0.52.0",
44
+ "@gjsify/cli": "^0.54.0",
45
+ "@gjsify/unit": "^0.54.0",
42
46
  "@types/node": "^25.9.2",
43
47
  "typescript": "^6.0.3"
44
48
  },
45
49
  "dependencies": {
46
50
  "@girs/gio-2.0": "5.4.0",
47
51
  "@girs/glib-2.0": "5.4.0",
48
- "@gjsify/net": "^0.52.0",
49
- "@gjsify/tls-native": "^0.52.0",
50
- "@gjsify/utils": "^0.52.0"
52
+ "@gjsify/net": "^0.54.0",
53
+ "@gjsify/tls-native": "^0.54.0",
54
+ "@gjsify/utils": "^0.54.0"
51
55
  },
52
56
  "gjsify": {
53
57
  "runtimes": {
@@ -56,7 +60,10 @@
56
60
  "browser": "none",
57
61
  "nativescript": "none"
58
62
  },
59
- "tier": 1
63
+ "tier": 1,
64
+ "polyfillPeers": {
65
+ "net": "tls.connect({ socket }) and new TLSSocket(socket) adopt a @gjsify/net Socket by claiming its Gio connection; a runtime's own net.Socket has none to hand over"
66
+ }
60
67
  },
61
68
  "license": "MIT",
62
69
  "repository": {