- Updated all component headers and documentation
- Changed navbar and footer branding
- Updated homepage hero badge
- Modified page title in index.html
- Simplified footer text to 'Built with ❤️'
- Consistent V2 capitalization across all references
219 lines
8.9 KiB
JavaScript
219 lines
8.9 KiB
JavaScript
"use strict";
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.SSLAnalyzerService = void 0;
|
|
const tls_1 = __importDefault(require("tls"));
|
|
const url_1 = require("url");
|
|
const logger_1 = require("../lib/logger");
|
|
class SSLAnalyzerService {
|
|
async analyzeSSL(url) {
|
|
const parsedUrl = new url_1.URL(url);
|
|
const host = parsedUrl.hostname;
|
|
const port = parsedUrl.port ? parseInt(parsedUrl.port) : 443;
|
|
logger_1.logger.info(`Starting SSL analysis for: ${host}:${port}`);
|
|
const result = {
|
|
host,
|
|
port,
|
|
warnings: [],
|
|
errors: [],
|
|
securityScore: 0,
|
|
recommendations: [],
|
|
};
|
|
if (parsedUrl.protocol !== 'https:') {
|
|
result.errors.push('URL is not HTTPS');
|
|
result.recommendations.push('Use HTTPS to secure communications');
|
|
return result;
|
|
}
|
|
try {
|
|
const certificate = await this.getCertificateInfo(host, port);
|
|
result.certificate = certificate;
|
|
this.analyzeCertificateSecurity(result);
|
|
logger_1.logger.info(`SSL analysis completed for: ${host}:${port}`, {
|
|
valid: certificate.valid,
|
|
daysToExpiry: certificate.daysToExpiry,
|
|
securityScore: result.securityScore
|
|
});
|
|
}
|
|
catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : 'Unknown SSL error';
|
|
result.errors.push(`SSL analysis failed: ${errorMessage}`);
|
|
logger_1.logger.error(`SSL analysis failed for ${host}:${port}:`, error);
|
|
}
|
|
return result;
|
|
}
|
|
async getCertificateInfo(host, port) {
|
|
return new Promise((resolve, reject) => {
|
|
const options = {
|
|
host,
|
|
port,
|
|
servername: host,
|
|
rejectUnauthorized: false,
|
|
timeout: 10000,
|
|
};
|
|
const socket = tls_1.default.connect(options, () => {
|
|
try {
|
|
const cert = socket.getPeerCertificate(true);
|
|
const cipher = socket.getCipher();
|
|
const protocol = socket.getProtocol();
|
|
if (!cert || Object.keys(cert).length === 0) {
|
|
socket.end();
|
|
reject(new Error('No certificate found'));
|
|
return;
|
|
}
|
|
const now = new Date();
|
|
const validFrom = new Date(cert.valid_from);
|
|
const validTo = new Date(cert.valid_to);
|
|
const daysToExpiry = Math.floor((validTo.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
|
|
const certificateInfo = {
|
|
valid: socket.authorized,
|
|
subject: this.parseCertificateField(cert.subject),
|
|
issuer: this.parseCertificateField(cert.issuer),
|
|
validFrom: validFrom.toISOString(),
|
|
validTo: validTo.toISOString(),
|
|
daysToExpiry,
|
|
serialNumber: cert.serialNumber,
|
|
fingerprint: cert.fingerprint,
|
|
signatureAlgorithm: cert.signatureAlgorithm,
|
|
keySize: cert.bits,
|
|
protocol,
|
|
cipher: cipher ? {
|
|
name: cipher.name,
|
|
version: cipher.version,
|
|
} : undefined,
|
|
};
|
|
socket.end();
|
|
resolve(certificateInfo);
|
|
}
|
|
catch (error) {
|
|
socket.end();
|
|
reject(error);
|
|
}
|
|
});
|
|
socket.on('error', (error) => {
|
|
reject(error);
|
|
});
|
|
socket.on('timeout', () => {
|
|
socket.destroy();
|
|
reject(new Error('SSL connection timeout'));
|
|
});
|
|
socket.setTimeout(10000);
|
|
});
|
|
}
|
|
parseCertificateField(field) {
|
|
if (!field)
|
|
return {};
|
|
return {
|
|
commonName: field.CN,
|
|
organization: field.O,
|
|
organizationalUnit: field.OU,
|
|
locality: field.L,
|
|
state: field.ST,
|
|
country: field.C,
|
|
};
|
|
}
|
|
analyzeCertificateSecurity(result) {
|
|
const cert = result.certificate;
|
|
if (!cert)
|
|
return;
|
|
let score = 100;
|
|
if (!cert.valid) {
|
|
result.warnings.push('Certificate is not valid or trusted');
|
|
result.recommendations.push('Install a valid SSL certificate from a trusted CA');
|
|
score -= 30;
|
|
}
|
|
if (cert.daysToExpiry < 0) {
|
|
result.errors.push('Certificate has expired');
|
|
result.recommendations.push('Renew the SSL certificate immediately');
|
|
score -= 40;
|
|
}
|
|
else if (cert.daysToExpiry < 30) {
|
|
result.warnings.push(`Certificate expires in ${cert.daysToExpiry} days`);
|
|
result.recommendations.push('Renew the SSL certificate soon');
|
|
score -= 10;
|
|
}
|
|
else if (cert.daysToExpiry < 90) {
|
|
result.warnings.push(`Certificate expires in ${cert.daysToExpiry} days`);
|
|
score -= 5;
|
|
}
|
|
if (cert.keySize && cert.keySize < 2048) {
|
|
result.warnings.push(`Weak key size: ${cert.keySize} bits`);
|
|
result.recommendations.push('Use at least 2048-bit RSA keys or 256-bit ECC keys');
|
|
score -= 20;
|
|
}
|
|
if (cert.signatureAlgorithm) {
|
|
if (cert.signatureAlgorithm.toLowerCase().includes('sha1')) {
|
|
result.warnings.push('Using deprecated SHA-1 signature algorithm');
|
|
result.recommendations.push('Upgrade to SHA-256 or better signature algorithm');
|
|
score -= 15;
|
|
}
|
|
else if (cert.signatureAlgorithm.toLowerCase().includes('md5')) {
|
|
result.errors.push('Using insecure MD5 signature algorithm');
|
|
result.recommendations.push('Immediately upgrade to SHA-256 or better');
|
|
score -= 30;
|
|
}
|
|
}
|
|
if (cert.protocol) {
|
|
if (cert.protocol.includes('TLSv1.0') || cert.protocol.includes('TLSv1.1')) {
|
|
result.warnings.push(`Using deprecated protocol: ${cert.protocol}`);
|
|
result.recommendations.push('Upgrade to TLS 1.2 or TLS 1.3');
|
|
score -= 15;
|
|
}
|
|
else if (cert.protocol.includes('SSLv')) {
|
|
result.errors.push(`Using insecure protocol: ${cert.protocol}`);
|
|
result.recommendations.push('Immediately upgrade to TLS 1.2 or TLS 1.3');
|
|
score -= 35;
|
|
}
|
|
}
|
|
if (cert.cipher?.name) {
|
|
const cipherName = cert.cipher.name.toLowerCase();
|
|
if (cipherName.includes('rc4') || cipherName.includes('des')) {
|
|
result.errors.push(`Weak cipher suite: ${cert.cipher.name}`);
|
|
result.recommendations.push('Use strong cipher suites like AES-GCM');
|
|
score -= 25;
|
|
}
|
|
else if (cipherName.includes('cbc')) {
|
|
result.warnings.push(`CBC cipher mode detected: ${cert.cipher.name}`);
|
|
result.recommendations.push('Prefer GCM or ChaCha20-Poly1305 cipher modes');
|
|
score -= 5;
|
|
}
|
|
}
|
|
if (!cert.subject.commonName) {
|
|
result.warnings.push('Certificate missing Common Name');
|
|
score -= 5;
|
|
}
|
|
result.securityScore = Math.max(0, score);
|
|
if (result.securityScore >= 90) {
|
|
result.recommendations.push('SSL configuration looks excellent!');
|
|
}
|
|
else if (result.securityScore >= 70) {
|
|
result.recommendations.push('SSL configuration is good with minor improvements possible');
|
|
}
|
|
else if (result.securityScore >= 50) {
|
|
result.recommendations.push('SSL configuration needs improvement for better security');
|
|
}
|
|
else {
|
|
result.recommendations.push('SSL configuration requires immediate attention');
|
|
}
|
|
}
|
|
async quickSSLCheck(url) {
|
|
try {
|
|
const analysis = await this.analyzeSSL(url);
|
|
return {
|
|
valid: analysis.certificate?.valid ?? false,
|
|
daysToExpiry: analysis.certificate?.daysToExpiry,
|
|
warnings: analysis.warnings,
|
|
};
|
|
}
|
|
catch (error) {
|
|
logger_1.logger.warn(`Quick SSL check failed for ${url}:`, error);
|
|
return {
|
|
valid: false,
|
|
warnings: ['SSL check failed'],
|
|
};
|
|
}
|
|
}
|
|
}
|
|
exports.SSLAnalyzerService = SSLAnalyzerService;
|
|
//# sourceMappingURL=ssl-analyzer.service.js.map
|