@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.
- package/lib/esm/connect.js +1 -0
- package/lib/esm/index.js +1 -2
- package/lib/esm/internal/cert-utils.js +1 -0
- package/lib/esm/internal/hostname.js +1 -0
- package/lib/esm/internal/pem.js +2 -0
- package/lib/esm/ocsp.js +1 -0
- package/lib/esm/secure-context.js +1 -0
- package/lib/esm/tls-server.js +1 -0
- package/lib/esm/tls-socket.js +1 -0
- package/lib/types/connect.d.ts +8 -0
- package/lib/types/index.d.ts +40 -162
- package/lib/types/internal/cert-utils.d.ts +29 -0
- package/lib/types/internal/hostname.d.ts +31 -0
- package/lib/types/internal/pem.d.ts +12 -0
- package/lib/types/ocsp.d.ts +20 -0
- package/lib/types/secure-context.d.ts +27 -0
- package/lib/types/tls-server.d.ts +44 -0
- package/lib/types/tls-socket.d.ts +64 -0
- package/package.json +9 -8
|
@@ -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{
|
|
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};
|
package/lib/esm/ocsp.js
ADDED
|
@@ -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;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -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
|
|
121
|
-
export
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
|
164
|
-
TLSServer: typeof
|
|
165
|
-
Server: typeof
|
|
166
|
-
connect: typeof
|
|
167
|
-
createServer: typeof
|
|
168
|
-
createSecureContext: typeof
|
|
169
|
-
checkServerIdentity: typeof
|
|
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.
|
|
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.
|
|
37
|
-
"@gjsify/unit": "^0.4.
|
|
38
|
-
"@types/node": "^25.
|
|
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.
|
|
43
|
-
"@girs/glib-2.0": "2.88.0-4.0.
|
|
44
|
-
"@gjsify/net": "^0.4.
|
|
45
|
-
"@gjsify/
|
|
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
|
}
|