sslko
Version:
A simple tool to check SSL/TLS certificate information for a given domain.
94 lines (93 loc) • 3.68 kB
JavaScript
import * as tls from "node:tls";
import { CertificateError, CertificateErrorCode } from "./certificate-error.js";
import { DEFAULT_PORT, DEFAULT_TIMEOUT, MAX_PORT, MIN_PORT, } from "./constants.js";
/**
* Default options for the getCertificate function.
*/
const DefaultOptions = {
port: DEFAULT_PORT, // Default port for HTTPS
timeout: DEFAULT_TIMEOUT, // Default timeout of 10 seconds
rejectUnauthorized: false, // We'll do our own verification
detailed: true, // Return a DetailedPeerCertificate by default
};
/**
* Fetches the SSL/TLS certificate from a given host and port.
*
* @param host The hostname to connect to.
* @param options Optional parameters to configure the connection.
* @throws {CertificateError} If the connection times out or if there is an error retrieving the certificate.
*
* @example Return a PeerCertificateDetailed object:
* ```typescript
* import { getCertificate } from 'sslko';
* const cert = await getCertificate('example.com');
* console.log(cert);
* ```
*
* @example Let Node.js getCertificateInfo the certificate for you:
* ```typescript
* import { getCertificate } from 'sslko';
* const cert = await getCertificate('example.com', { rejectUnauthorized: true });
* console.log(cert);
* ```
*
* @example Return a PeerCertificateDetailed object with a custom port:
* ```typescript
* import { getCertificate } from 'sslko';
* const cert = await getCertificate('example.com', { port: 8443 });
* console.log(cert);
* ```
*
* @example Return a PeerCertificate object:
* ```typescript
* import { getCertificate } from 'sslko';
* const cert = await getCertificate('example.com', { detailed: false });
* console.log(cert);
* ```
*
* @example Enable trace - useful for debugging:
*
* When enabled, TLS packet trace information is written to `stderr`. This can be used to debug TLS connection problems.
*
* ```typescript
* import { getCertificate } from 'sslko';
* const cert = await getCertificate('example.com', { enableTrace: true });
* ```
*/
export async function getCertificate(host, options = {}) {
const { timeout, detailed, port, ...rest } = {
...{ host, servername: host },
...DefaultOptions,
...options,
};
if (port && (port < MIN_PORT || port > MAX_PORT)) {
throw new CertificateError(`Invalid port number. Port must be between ${MIN_PORT} and ${MAX_PORT}.`, CertificateErrorCode.INVALID_PORT);
}
const socket = tls.connect({ ...rest, port, timeout });
return await new Promise((resolve, reject) => {
if (timeout) {
socket.setTimeout(timeout, () => {
socket.destroy();
reject(new CertificateError(`Failed to connect to ${host}:${port} within ${timeout}ms`, CertificateErrorCode.TIMEOUT));
});
}
socket.on("secureConnect", () => {
// @see https://github.com/oven-sh/bun/issues/21902 - Bun always failed when detailed = false
const cert = detailed
? socket.getPeerCertificate(true)
: socket.getPeerCertificate();
socket.end();
// empty certificate check
if (!cert || Object.keys(cert).length === 0) {
return reject(new CertificateError("No certificate information available", CertificateErrorCode.MISSING_CERTIFICATE));
}
resolve(cert);
});
socket.on("error", (error) => {
const message = error?.message || "Unknown error";
const code = error?.code ||
CertificateErrorCode.CERT_ERROR;
reject(new CertificateError(message, code));
});
});
}