@@ -1580,9 +1580,9 @@
|
|||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p class="modal-desc">Assign a hostname to an hs:// tunnel URL. The host will be accessible at <code style="font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--cyan);">https://<hostname></code> via the proxy.</p>
|
<p class="modal-desc">Assign a hostname to an hs:// tunnel URL. The host will be accessible at <code style="font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--cyan);">https://<hostname></code> via the proxy.</p>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label" for="addVhostHostname">Hostname <span class="form-hint">(e.g. myapp.hole.sail or api.my.internal — no real TLDs)</span></label>
|
<label class="form-label" for="addVhostHostname">Hostname <span class="form-hint">(e.g. myapp.hole.sail or i.love.hole.sail — no real TLDs)</span></label>
|
||||||
<input type="text" id="addVhostHostname" class="input mono" placeholder="myapp.hole.sail" autocomplete="off" spellcheck="false">
|
<input type="text" id="addVhostHostname" class="input mono" placeholder="myapp.hole.sail" autocomplete="off" spellcheck="false">
|
||||||
<div style="font-size:11px;color:var(--text4);margin-top:4px;line-height:1.5;">Must be <code style="font-size:10px;">host.second.tld</code> format with a private TLD (3 labels minimum). Real TLDs like <code style="font-size:10px;">.com</code>, <code style="font-size:10px;">.co.uk</code> are not allowed.</div>
|
<div style="font-size:11px;color:var(--text4);margin-top:4px;line-height:1.5;">Use any depth: <code style="font-size:10px;">app.hole.sail</code>, <code style="font-size:10px;">i.love.hole.sail</code>, <code style="font-size:10px;">api.v2.my.internal</code> — private TLDs only, real TLDs like <code style="font-size:10px;">.com</code> are not allowed.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label" for="addVhostHsUrl">hs:// URL</label>
|
<label class="form-label" for="addVhostHsUrl">hs:// URL</label>
|
||||||
|
|||||||
@@ -436,18 +436,19 @@ function isRootCAInstalled(callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get or create a wildcard certificate for a two-label base domain.
|
* Get or create a wildcard certificate for a parent domain of any depth.
|
||||||
* e.g. getOrCreateWildcardCert('hole.sail') -> cert covering *.hole.sail + hole.sail
|
* e.g. getOrCreateWildcardCert('hole.sail') -> cert covering *.hole.sail
|
||||||
|
* getOrCreateWildcardCert('love.hole.sail') -> cert covering *.love.hole.sail
|
||||||
*
|
*
|
||||||
* This is the preferred API for the SNI-aware HTTPS proxy — one cert per TLD,
|
* This is the preferred API for the SNI-aware HTTPS proxy — one cert per
|
||||||
* selected at handshake time based on the SNI hostname.
|
* wildcard parent, selected at handshake time based on the SNI hostname.
|
||||||
*/
|
*/
|
||||||
function getOrCreateWildcardCert (baseDomain) {
|
function getOrCreateWildcardCert (parentDomain) {
|
||||||
if (!baseDomain || baseDomain.split('.').length < 2) return null;
|
if (!parentDomain || parentDomain.split('.').length < 2) return null;
|
||||||
const certKey = 'wildcard.' + baseDomain;
|
const certKey = 'wildcard.' + parentDomain;
|
||||||
const altNames = [
|
const altNames = [
|
||||||
{ type: 2, value: '*.' + baseDomain },
|
{ type: 2, value: '*.' + parentDomain },
|
||||||
{ type: 2, value: baseDomain }
|
{ type: 2, value: parentDomain }
|
||||||
];
|
];
|
||||||
return getOrCreateDomainCert(certKey, altNames);
|
return getOrCreateDomainCert(certKey, altNames);
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-13
@@ -128,16 +128,22 @@ function extractSNI (buf) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract the two-label base domain from a hostname.
|
* Derive the wildcard parent domain for a hostname — i.e. everything except
|
||||||
* e.g. "test.haha.wooo" -> "haha.wooo"
|
* the leftmost label. This is what the cert's SAN wildcard must cover.
|
||||||
* "myapp.hole.sail" -> "hole.sail"
|
*
|
||||||
|
* Examples:
|
||||||
|
* "myapp.hole.sail" -> "hole.sail" (cert: *.hole.sail)
|
||||||
|
* "test.haha.wooo" -> "haha.wooo" (cert: *.haha.wooo)
|
||||||
|
* "i.love.hole.sail" -> "love.hole.sail" (cert: *.love.hole.sail)
|
||||||
|
* "a.b.c.my.internal" -> "b.c.my.internal"
|
||||||
|
*
|
||||||
* Returns null if the hostname has fewer than 2 labels.
|
* Returns null if the hostname has fewer than 2 labels.
|
||||||
*/
|
*/
|
||||||
function getBaseDomain (hostname) {
|
function getWildcardParent (hostname) {
|
||||||
if (!hostname) return null;
|
if (!hostname) return null;
|
||||||
const parts = hostname.split('.');
|
const parts = hostname.split('.');
|
||||||
if (parts.length < 2) return null;
|
if (parts.length < 2) return null;
|
||||||
return parts.slice(-2).join('.');
|
return parts.slice(1).join('.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -366,25 +372,28 @@ function handleRawConnection (rawSocket) {
|
|||||||
const sni = extractSNI(peekedBuf);
|
const sni = extractSNI(peekedBuf);
|
||||||
debugLog('SNI extracted:', sni);
|
debugLog('SNI extracted:', sni);
|
||||||
|
|
||||||
const baseDomain = getBaseDomain(sni) || 'hole.sail';
|
// Derive the wildcard parent: strip the leftmost label so the cert's
|
||||||
|
// *.parent SAN exactly covers the SNI hostname at any depth.
|
||||||
|
// e.g. "myapp.hole.sail" -> parent "hole.sail" -> cert *.hole.sail
|
||||||
|
// "i.love.hole.sail" -> parent "love.hole.sail" -> cert *.love.hole.sail
|
||||||
|
const wildcardParent = getWildcardParent(sni) || 'hole.sail';
|
||||||
|
|
||||||
// Get or create a wildcard cert for this base domain
|
// Get or create a wildcard cert for this parent domain
|
||||||
let certResult = null;
|
let certResult = null;
|
||||||
if (proxyCertsDirOrCA && typeof proxyCertsDirOrCA.getOrCreateWildcardCert === 'function') {
|
if (proxyCertsDirOrCA && typeof proxyCertsDirOrCA.getOrCreateWildcardCert === 'function') {
|
||||||
certResult = proxyCertsDirOrCA.getOrCreateWildcardCert(baseDomain);
|
certResult = proxyCertsDirOrCA.getOrCreateWildcardCert(wildcardParent);
|
||||||
} else if (proxyCertsDirOrCA && typeof proxyCertsDirOrCA.getOrCreateDomainCert === 'function') {
|
} else if (proxyCertsDirOrCA && typeof proxyCertsDirOrCA.getOrCreateDomainCert === 'function') {
|
||||||
// Fallback: use the old multi-SAN approach with just this domain
|
|
||||||
certResult = proxyCertsDirOrCA.getOrCreateDomainCert(
|
certResult = proxyCertsDirOrCA.getOrCreateDomainCert(
|
||||||
'wildcard.' + baseDomain,
|
'wildcard.' + wildcardParent,
|
||||||
[
|
[
|
||||||
{ type: 2, value: '*.' + baseDomain },
|
{ type: 2, value: '*.' + wildcardParent },
|
||||||
{ type: 2, value: baseDomain }
|
{ type: 2, value: wildcardParent }
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!certResult) {
|
if (!certResult) {
|
||||||
if (process.stderr) process.stderr.write('[https-proxy] no cert for baseDomain=' + baseDomain + ', dropping connection\n');
|
if (process.stderr) process.stderr.write('[https-proxy] no cert for wildcardParent=' + wildcardParent + ', dropping connection\n');
|
||||||
rawSocket.destroy();
|
rawSocket.destroy();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user