Add SSL certificate checker and tests
This commit is contained in:
+164
@@ -199,3 +199,167 @@ dnsForm.addEventListener("submit", async (event) => {
|
||||
dnsButton.textContent = "Look Up DNS";
|
||||
}
|
||||
});
|
||||
// ---------------------------------------------------------
|
||||
// SSL Certificate Checker
|
||||
// ---------------------------------------------------------
|
||||
|
||||
// Get references to the SSL form and result elements.
|
||||
const sslForm = document.getElementById("ssl-form");
|
||||
const sslHostnameInput = document.getElementById(
|
||||
"ssl-hostname-input"
|
||||
);
|
||||
const sslButton = document.getElementById("ssl-button");
|
||||
const sslMessage = document.getElementById("ssl-message");
|
||||
const sslResultPanel = document.getElementById(
|
||||
"ssl-result-panel"
|
||||
);
|
||||
|
||||
const sslStatusBadge = document.getElementById(
|
||||
"ssl-status-badge"
|
||||
);
|
||||
const sslHostname = document.getElementById("ssl-hostname");
|
||||
const sslDaysRemaining = document.getElementById(
|
||||
"ssl-days-remaining"
|
||||
);
|
||||
const sslValidFrom = document.getElementById("ssl-valid-from");
|
||||
const sslValidTo = document.getElementById("ssl-valid-to");
|
||||
const sslProtocol = document.getElementById("ssl-protocol");
|
||||
const sslCipher = document.getElementById("ssl-cipher");
|
||||
const sslAddress = document.getElementById("ssl-address");
|
||||
const sslCheckedAt = document.getElementById("ssl-checked-at");
|
||||
|
||||
const sslSubject = document.getElementById("ssl-subject");
|
||||
const sslIssuer = document.getElementById("ssl-issuer");
|
||||
const sslFingerprint = document.getElementById(
|
||||
"ssl-fingerprint"
|
||||
);
|
||||
|
||||
const sslAuthorizationErrorBlock = document.getElementById(
|
||||
"ssl-authorization-error-block"
|
||||
);
|
||||
const sslAuthorizationError = document.getElementById(
|
||||
"ssl-authorization-error"
|
||||
);
|
||||
|
||||
// Convert certificate date strings into a readable local date.
|
||||
function formatCertificateDate(value) {
|
||||
if (!value) {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
const date = new Date(value);
|
||||
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return date.toLocaleString();
|
||||
}
|
||||
|
||||
// Choose readable text for the certificate expiration state.
|
||||
function getExpirationText(daysRemaining) {
|
||||
if (daysRemaining === null || daysRemaining === undefined) {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
if (daysRemaining < 0) {
|
||||
return `${Math.abs(daysRemaining)} days expired`;
|
||||
}
|
||||
|
||||
if (daysRemaining === 0) {
|
||||
return "Expires today";
|
||||
}
|
||||
|
||||
return `${daysRemaining} days`;
|
||||
}
|
||||
|
||||
// Handle SSL certificate form submissions.
|
||||
sslForm.addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const hostname = sslHostnameInput.value.trim();
|
||||
|
||||
// Reset the SSL interface before beginning the request.
|
||||
sslMessage.textContent = "Checking certificate...";
|
||||
sslMessage.className = "status-message";
|
||||
sslResultPanel.hidden = true;
|
||||
sslButton.disabled = true;
|
||||
sslButton.textContent = "Checking...";
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/ssl", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ hostname }),
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
result.error || "The SSL certificate check failed."
|
||||
);
|
||||
}
|
||||
|
||||
// Show whether Node trusted the certificate chain and hostname.
|
||||
if (result.authorized) {
|
||||
sslStatusBadge.textContent = "Valid";
|
||||
sslStatusBadge.className = "status-badge online";
|
||||
} else {
|
||||
sslStatusBadge.textContent = "Not trusted";
|
||||
sslStatusBadge.className = "status-badge offline";
|
||||
}
|
||||
|
||||
// Populate the certificate summary.
|
||||
sslHostname.textContent = result.hostname;
|
||||
sslDaysRemaining.textContent = getExpirationText(
|
||||
result.daysRemaining
|
||||
);
|
||||
|
||||
sslValidFrom.textContent = formatCertificateDate(
|
||||
result.validFrom
|
||||
);
|
||||
sslValidTo.textContent = formatCertificateDate(
|
||||
result.validTo
|
||||
);
|
||||
|
||||
sslProtocol.textContent = result.protocol || "Unknown";
|
||||
sslCipher.textContent = result.cipher || "Unknown";
|
||||
|
||||
sslAddress.textContent = result.resolvedAddress
|
||||
? `${result.resolvedAddress} (IPv${result.addressFamily})`
|
||||
: "Unknown";
|
||||
|
||||
sslCheckedAt.textContent = formatCertificateDate(
|
||||
result.checkedAt
|
||||
);
|
||||
|
||||
// Populate the longer certificate values.
|
||||
sslSubject.textContent = result.subject || "Unknown";
|
||||
sslIssuer.textContent = result.issuer || "Unknown";
|
||||
sslFingerprint.textContent =
|
||||
result.fingerprint256 || "Unavailable";
|
||||
|
||||
// Show the authorization problem only when one exists.
|
||||
if (result.authorizationError) {
|
||||
sslAuthorizationError.textContent =
|
||||
result.authorizationError;
|
||||
sslAuthorizationErrorBlock.hidden = false;
|
||||
} else {
|
||||
sslAuthorizationError.textContent = "";
|
||||
sslAuthorizationErrorBlock.hidden = true;
|
||||
}
|
||||
|
||||
sslMessage.textContent =
|
||||
"Certificate check completed.";
|
||||
sslResultPanel.hidden = false;
|
||||
} catch (error) {
|
||||
sslMessage.textContent = error.message;
|
||||
sslMessage.className = "status-message error";
|
||||
} finally {
|
||||
sslButton.disabled = false;
|
||||
sslButton.textContent = "Check Certificate";
|
||||
}
|
||||
});
|
||||
+126
-2
@@ -17,7 +17,8 @@
|
||||
<p class="eyebrow">BYTEGEIST TOOLBOX</p>
|
||||
<h1>Practical Website and Network Tools</h1>
|
||||
<p class="description">
|
||||
Check a website's current status or inspect its public DNS records.
|
||||
Check a website's current status, inspect its public DNS records,
|
||||
or review its SSL certificate.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
@@ -39,6 +40,15 @@
|
||||
>
|
||||
DNS Lookup
|
||||
</button>
|
||||
|
||||
<button
|
||||
id="ssl-tab"
|
||||
class="tool-tab"
|
||||
type="button"
|
||||
data-tool="ssl-tool"
|
||||
>
|
||||
SSL Certificate
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<!-- Website Status Checker -->
|
||||
@@ -153,9 +163,123 @@
|
||||
<p id="dns-checked-at" class="dns-checked-at"></p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- SSL Certificate Checker -->
|
||||
<section id="ssl-tool" class="tool-panel" hidden>
|
||||
<header class="tool-header">
|
||||
<h2>SSL Certificate Checker</h2>
|
||||
|
||||
<p class="tool-description">
|
||||
Inspect the public TLS certificate for a website and view its issuer,
|
||||
expiration date, authorization status, protocol, cipher, and fingerprint.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<form id="ssl-form" class="tool-form">
|
||||
<label for="ssl-hostname-input">Hostname or website URL</label>
|
||||
|
||||
<div class="input-row">
|
||||
<input
|
||||
id="ssl-hostname-input"
|
||||
name="hostname"
|
||||
type="text"
|
||||
placeholder="example.com"
|
||||
autocomplete="off"
|
||||
required
|
||||
>
|
||||
|
||||
<button id="ssl-button" type="submit">
|
||||
Check Certificate
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p
|
||||
id="ssl-message"
|
||||
class="status-message"
|
||||
aria-live="polite"
|
||||
></p>
|
||||
|
||||
<section id="ssl-result-panel" class="result-panel" hidden>
|
||||
<div class="result-heading">
|
||||
<h3>Certificate Details</h3>
|
||||
|
||||
<span
|
||||
id="ssl-status-badge"
|
||||
class="status-badge"
|
||||
></span>
|
||||
</div>
|
||||
|
||||
<dl class="result-grid">
|
||||
<div>
|
||||
<dt>Hostname</dt>
|
||||
<dd id="ssl-hostname">-</dd>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<dt>Days remaining</dt>
|
||||
<dd id="ssl-days-remaining">-</dd>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<dt>Valid from</dt>
|
||||
<dd id="ssl-valid-from">-</dd>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<dt>Expires</dt>
|
||||
<dd id="ssl-valid-to">-</dd>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<dt>TLS protocol</dt>
|
||||
<dd id="ssl-protocol">-</dd>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<dt>Cipher</dt>
|
||||
<dd id="ssl-cipher">-</dd>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<dt>Resolved address</dt>
|
||||
<dd id="ssl-address">-</dd>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<dt>Checked at</dt>
|
||||
<dd id="ssl-checked-at">-</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
<div class="certificate-detail-block">
|
||||
<span>Subject</span>
|
||||
<p id="ssl-subject">-</p>
|
||||
</div>
|
||||
|
||||
<div class="certificate-detail-block">
|
||||
<span>Issuer</span>
|
||||
<p id="ssl-issuer">-</p>
|
||||
</div>
|
||||
|
||||
<div class="certificate-detail-block">
|
||||
<span>SHA-256 fingerprint</span>
|
||||
<code id="ssl-fingerprint">-</code>
|
||||
</div>
|
||||
|
||||
<div
|
||||
id="ssl-authorization-error-block"
|
||||
class="certificate-detail-block certificate-error"
|
||||
hidden
|
||||
>
|
||||
<span>Authorization error</span>
|
||||
<p id="ssl-authorization-error">-</p>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- Load browser JavaScript after the page elements exist. -->
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@@ -342,3 +342,49 @@ button:disabled {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.certificate-detail-block {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
margin-top: 14px;
|
||||
padding: 14px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: #0d1523;
|
||||
}
|
||||
|
||||
.certificate-detail-block span {
|
||||
color: var(--muted);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.certificate-detail-block p {
|
||||
margin: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.certificate-detail-block code {
|
||||
color: var(--accent);
|
||||
font-family:
|
||||
"Cascadia Code",
|
||||
"Courier New",
|
||||
monospace;
|
||||
line-height: 1.5;
|
||||
overflow-wrap: anywhere;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.certificate-error {
|
||||
border-color: rgba(255, 107, 115, 0.5);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.tool-tabs {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Always hide elements that use the HTML hidden attribute. */
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
Reference in New Issue
Block a user