DEV Community

Tech Believers
Tech Believers

Posted on

Building an SSL Certificate Checker: TLS Validation and Security Audit

Introduction

SSL/TLS certificates are critical for web security and SEO, but they expire, get misconfigured, and develop vulnerabilities. In this guide, we'll build a comprehensive SSL certificate checker that validates certificates, checks security configuration, and monitors expiry.

Understanding SSL/TLS Certificates

SSL (Secure Sockets Layer) and its successor TLS (Transport Layer Security) encrypt data between client and server. Certificates authenticate the server's identity.

Key Components:

  • Subject: Domain name(s) the certificate covers
  • Issuer: Certificate Authority that issued it
  • Validity Period: Not valid before / Not valid after dates
  • Public Key: Used for encryption
  • Signature: Cryptographic proof of authenticity
  • Certificate Chain: Root + Intermediate certificates

Building the Checker

Step 1: Fetching Certificate Information

const tls = require('tls');
const https = require('https');

function checkSSLCertificate(hostname, port = 443) {
    return new Promise((resolve, reject) => {
        const options = {
            host: hostname,
            port: port,
            method: 'GET',
            rejectUnauthorized: false, // Allow checking invalid certs
            agent: false
        };

        const req = https.get(options, (res) => {
            const cert = res.socket.getPeerCertificate();

            if (!cert || Object.keys(cert).length === 0) {
                reject(new Error('No certificate found'));
                return;
            }

            resolve({
                subject: cert.subject,
                issuer: cert.issuer,
                validFrom: cert.valid_from,
                validTo: cert.valid_to,
                daysRemaining: getDaysRemaining(cert.valid_to),
                serialNumber: cert.serialNumber,
                fingerprint: cert.fingerprint,
                subjectAltNames: cert.subjectaltname,
                protocol: res.socket.getProtocol(),
                cipher: res.socket.getCipher()
            });
        });

        req.on('error', reject);
        req.end();
    });
}

function getDaysRemaining(validTo) {
    const expiry = new Date(validTo);
    const now = new Date();
    const diffTime = expiry - now;
    const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
    return diffDays;
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Certificate Validation

function validateCertificate(certInfo, hostname) {
    const validation = {
        valid: true,
        errors: [],
        warnings: []
    };

    // Check expiry
    if (certInfo.daysRemaining < 0) {
        validation.valid = false;
        validation.errors.push({
            type: 'expired',
            message: `Certificate expired ${Math.abs(certInfo.daysRemaining)} days ago`
        });
    } else if (certInfo.daysRemaining < 30) {
        validation.warnings.push({
            type: 'expiring_soon',
            message: `Certificate expires in ${certInfo.daysRemaining} days`
        });
    }

    // Check domain match
    const domainMatch = checkDomainMatch(certInfo, hostname);
    if (!domainMatch) {
        validation.valid = false;
        validation.errors.push({
            type: 'domain_mismatch',
            message: `Certificate not valid for ${hostname}`
        });
    }

    // Check protocol strength
    if (certInfo.protocol && certInfo.protocol.includes('TLSv1.0')) {
        validation.warnings.push({
            type: 'weak_protocol',
            message: 'TLS 1.0 is deprecated. Use TLS 1.2 or 1.3'
        });
    }

    // Check cipher strength
    if (certInfo.cipher && certInfo.cipher.name.includes('RC4')) {
        validation.warnings.push({
            type: 'weak_cipher',
            message: 'Weak cipher suite detected. Update server configuration'
        });
    }

    return validation;
}

function checkDomainMatch(certInfo, hostname) {
    // Check subject CN
    if (certInfo.subject.CN === hostname) {
        return true;
    }

    // Check Subject Alternative Names
    if (certInfo.subjectAltNames) {
        const altNames = certInfo.subjectAltNames.split(',').map(s => s.trim().replace('DNS:', ''));

        for (const altName of altNames) {
            if (altName === hostname) {
                return true;
            }

            // Check wildcard
            if (altName.startsWith('*.')) {
                const wildcardDomain = altName.substring(2);
                if (hostname.endsWith(wildcardDomain)) {
                    return true;
                }
            }
        }
    }

    return false;
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Security Audit

function auditSecurity(certInfo) {
    const audit = {
        score: 100,
        issues: []
    };

    // Protocol check
    if (certInfo.protocol) {
        if (certInfo.protocol.includes('TLSv1.3')) {
            audit.issues.push({ type: 'protocol', status: 'excellent', message: 'TLS 1.3 - Latest and most secure' });
        } else if (certInfo.protocol.includes('TLSv1.2')) {
            audit.issues.push({ type: 'protocol', status: 'good', message: 'TLS 1.2 - Secure' });
        } else if (certInfo.protocol.includes('TLSv1.1') || certInfo.protocol.includes('TLSv1.0')) {
            audit.score -= 30;
            audit.issues.push({ type: 'protocol', status: 'poor', message: 'TLS 1.0/1.1 deprecated. Upgrade to TLS 1.2+' });
        }
    }

    // Key strength check
    if (certInfo.cipher) {
        const keyBits = certInfo.cipher.bits || 0;

        if (keyBits >= 256) {
            audit.issues.push({ type: 'encryption', status: 'excellent', message: `${keyBits}-bit encryption - Very strong` });
        } else if (keyBits >= 128) {
            audit.issues.push({ type: 'encryption', status: 'good', message: `${keyBits}-bit encryption - Adequate` });
        } else {
            audit.score -= 40;
            audit.issues.push({ type: 'encryption', status: 'poor', message: `${keyBits}-bit encryption - Too weak` });
        }
    }

    // Expiry check
    if (certInfo.daysRemaining < 0) {
        audit.score = 0;
        audit.issues.push({ type: 'expiry', status: 'critical', message: 'Certificate expired!' });
    } else if (certInfo.daysRemaining < 7) {
        audit.score -= 20;
        audit.issues.push({ type: 'expiry', status: 'urgent', message: `Expires in ${certInfo.daysRemaining} days` });
    } else if (certInfo.daysRemaining < 30) {
        audit.score -= 10;
        audit.issues.push({ type: 'expiry', status: 'warning', message: `Expires in ${certInfo.daysRemaining} days - renewal recommended` });
    }

    audit.score = Math.max(0, audit.score);

    return audit;
}
Enter fullscreen mode Exit fullscreen mode

Complete Checker Class

class SSLCertificateChecker {
    constructor(hostname, port = 443) {
        this.hostname = hostname;
        this.port = port;
    }

    async check() {
        try {
            const certInfo = await checkSSLCertificate(this.hostname, this.port);
            const validation = validateCertificate(certInfo, this.hostname);
            const securityAudit = auditSecurity(certInfo);

            return {
                hostname: this.hostname,
                port: this.port,
                certificate: certInfo,
                validation,
                securityAudit,
                timestamp: new Date().toISOString()
            };
        } catch (error) {
            return {
                hostname: this.hostname,
                port: this.port,
                error: error.message,
                timestamp: new Date().toISOString()
            };
        }
    }
}

// Usage
const checker = new SSLCertificateChecker('example.com');
checker.check().then(result => {
    console.log('Certificate Info:', result.certificate);
    console.log('Validation:', result.validation);
    console.log('Security Audit:', result.securityAudit);
    console.log('Security Score:', result.securityAudit.score);
});
Enter fullscreen mode Exit fullscreen mode

Conclusion

Building an SSL certificate checker requires understanding TLS protocols, certificate validation, and security best practices. The checker we've built validates certificates, audits security configuration, and monitors expiry dates.

Try it yourself: https://techbelievers.com/tools/ssl-certificate-checker

Top comments (0)