From ea15c7e5b689320709f1a13f4574e2cf6310016f Mon Sep 17 00:00:00 2001 From: MrUnknownDE Date: Sat, 29 Mar 2025 19:37:27 +0100 Subject: [PATCH] start with ssl_check --- backend/routes/sslCheck.js | 126 ++++++++++++++++++++++++++++++++++++ backend/server.js | 3 + frontend/app/index.html | 1 + frontend/app/ssl-check.html | 87 +++++++++++++++++++++++++ frontend/app/ssl-check.js | 101 +++++++++++++++++++++++++++++ 5 files changed, 318 insertions(+) create mode 100644 backend/routes/sslCheck.js create mode 100644 frontend/app/ssl-check.html create mode 100644 frontend/app/ssl-check.js diff --git a/backend/routes/sslCheck.js b/backend/routes/sslCheck.js new file mode 100644 index 0000000..40ab22e --- /dev/null +++ b/backend/routes/sslCheck.js @@ -0,0 +1,126 @@ +const express = require('express'); +const { exec } = require('child_process'); +const router = express.Router(); + +// Funktion zum Parsen der openssl s_client Ausgabe +function parseSslOutput(output) { + const result = { + issuer: null, + subject: null, + validFrom: null, + validTo: null, + error: null, + details: output // Rohausgabe für Debugging + }; + + try { + const issuerMatch = output.match(/issuer=([^\n]+)/); + if (issuerMatch) result.issuer = issuerMatch[1].trim(); + + const subjectMatch = output.match(/subject=([^\n]+)/); + if (subjectMatch) result.subject = subjectMatch[1].trim(); + + // Gültigkeitsdaten extrahieren (Beispielformat: notBefore=..., notAfter=...) + // openssl Datumsformate können variieren, dies ist ein einfacher Ansatz + const validFromMatch = output.match(/notBefore=([^\n]+)/); + if (validFromMatch) result.validFrom = new Date(validFromMatch[1].trim()).toISOString(); + + const validToMatch = output.match(/notAfter=([^\n]+)/); + if (validToMatch) result.validTo = new Date(validToMatch[1].trim()).toISOString(); + + // Einfache Bewertung: Ist das Zertifikat noch gültig? + if (result.validFrom && result.validTo) { + const now = new Date(); + const validFromDate = new Date(result.validFrom); + const validToDate = new Date(result.validTo); + if (now < validFromDate || now > validToDate) { + result.validity = "Invalid (Expired or Not Yet Valid)"; + } else { + result.validity = "Valid"; + } + } else { + result.validity = "Could not determine validity"; + } + + + } catch (e) { + console.error("Error parsing openssl output:", e); + result.error = "Error parsing certificate details."; + } + + return result; +} + + +router.get('/', async (req, res) => { + const domain = req.query.domain; + + if (!domain) { + return res.status(400).json({ error: 'Domain parameter is required' }); + } + + // Verwende Port 443 für HTTPS + const command = `echo | openssl s_client -servername ${domain} -connect ${domain}:443 -showcerts 2>/dev/null | openssl x509 -noout -text`; + + exec(command, (error, stdout, stderr) => { + if (error) { + console.error(`exec error: ${error}`); + // Versuche, spezifischere Fehler zu erkennen + if (stderr.includes("connect:errno=") || error.message.includes("getaddrinfo ENOTFOUND")) { + return res.status(500).json({ error: `Could not connect to domain: ${domain}`, details: stderr || error.message }); + } + if (stderr.includes("SSL alert number 40")) { + return res.status(500).json({ error: `No SSL certificate found or SSL handshake failed for domain: ${domain}`, details: stderr }); + } + return res.status(500).json({ error: 'Failed to execute openssl command', details: stderr || error.message }); + } + + if (stderr) { + console.warn(`openssl stderr: ${stderr}`); // Warnung, aber fahre fort, wenn stdout vorhanden ist + } + + if (!stdout) { + return res.status(500).json({ error: 'No certificate information received from openssl', details: stderr }); + } + + const certInfo = parseSslOutput(stdout); + if (certInfo.error) { + // Wenn beim Parsen ein Fehler aufgetreten ist, aber stdout vorhanden war + return res.status(500).json({ error: certInfo.error, raw_output: stdout }); + } + + + // Einfache Bewertung hinzufügen (Beispiel) + let score = 0; + let evaluation = []; + if (certInfo.validity === "Valid") { + score += 5; + evaluation.push("Certificate is currently valid."); + + // Prüfe die verbleibende Gültigkeitsdauer + const daysRemaining = Math.floor((new Date(certInfo.validTo) - new Date()) / (1000 * 60 * 60 * 24)); + if (daysRemaining < 30) { + score -= 2; + evaluation.push(`Warning: Certificate expires in ${daysRemaining} days.`); + } else { + score += 2; + evaluation.push(`Certificate expires in ${daysRemaining} days.`); + } + } else { + evaluation.push("Certificate is not valid."); + } + + // Weitere Prüfungen könnten hier hinzugefügt werden (z.B. auf schwache Signaturalgorithmen, Schlüssellänge etc.) + + res.json({ + domain: domain, + certificate: certInfo, + evaluation: { + score: Math.max(0, Math.min(10, score)), // Score zwischen 0 und 10 + summary: evaluation.join(' ') + } + }); + }); +}); + +module.exports = router; \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index c542616..68121b4 100644 --- a/backend/server.js +++ b/backend/server.js @@ -33,6 +33,7 @@ const lookupRoutes = require('./routes/lookup'); const dnsLookupRoutes = require('./routes/dnsLookup'); const whoisLookupRoutes = require('./routes/whoisLookup'); const versionRoutes = require('./routes/version'); +const sslCheckRoutes = require('./routes/sslCheck'); // <-- NEUE ROUTE IMPORTIERT // --- Logger Initialisierung --- const logger = pino({ @@ -94,6 +95,7 @@ app.use('/api/traceroute', generalLimiter); app.use('/api/lookup', generalLimiter); app.use('/api/dns-lookup', generalLimiter); app.use('/api/whois-lookup', generalLimiter); +app.use('/api/ssl-check', generalLimiter); // <-- RATE LIMITER FÜR NEUE ROUTE // --- API Routes --- @@ -105,6 +107,7 @@ app.use('/api/lookup', lookupRoutes); app.use('/api/dns-lookup', dnsLookupRoutes); app.use('/api/whois-lookup', whoisLookupRoutes); app.use('/api/version', versionRoutes); +app.use('/api/ssl-check', sslCheckRoutes); // <-- NEUE ROUTE REGISTRIERT // --- Sentry Error Handler --- diff --git a/frontend/app/index.html b/frontend/app/index.html index b9da1b7..2f0e77d 100644 --- a/frontend/app/index.html +++ b/frontend/app/index.html @@ -92,6 +92,7 @@
  • Subnetz Rechner
  • DNS Lookup
  • WHOIS Lookup
  • +
  • SSL Check
  • diff --git a/frontend/app/ssl-check.html b/frontend/app/ssl-check.html new file mode 100644 index 0000000..8429420 --- /dev/null +++ b/frontend/app/ssl-check.html @@ -0,0 +1,87 @@ + + + + + + SSL Certificate Check + + + + + + + +
    +

    SSL Certificate Check

    +

    Enter a domain name to check its SSL/TLS certificate details and validity.

    + +
    +
    + + +
    +
    +
    + Loading... +
    +
    +
    + + +
    + + + + + \ No newline at end of file diff --git a/frontend/app/ssl-check.js b/frontend/app/ssl-check.js new file mode 100644 index 0000000..b65bdde --- /dev/null +++ b/frontend/app/ssl-check.js @@ -0,0 +1,101 @@ +document.addEventListener('DOMContentLoaded', () => { + const form = document.getElementById('ssl-check-form'); + const domainInput = document.getElementById('domain-input'); + const resultDiv = document.getElementById('result'); + const resultDomainSpan = document.getElementById('result-domain'); + const evaluationDiv = document.getElementById('evaluation'); + const scoreValueSpan = document.getElementById('score-value'); + const scoreBarInner = document.getElementById('score-bar-inner'); + const evaluationSummaryP = document.getElementById('evaluation-summary'); + const certOutputPre = document.getElementById('cert-output'); + const errorMessageDiv = document.getElementById('error-message'); + const loadingSpinner = document.querySelector('.loading-spinner'); + const submitButton = document.getElementById('submit-button'); + + form.addEventListener('submit', async (event) => { + event.preventDefault(); + const domain = domainInput.value.trim(); + + if (!domain) { + showError('Please enter a domain name.'); + return; + } + + // Reset UI + hideError(); + resultDiv.style.display = 'none'; + loadingSpinner.style.display = 'block'; + submitButton.disabled = true; + submitButton.innerHTML = ' Checking...'; + + + try { + const response = await fetch(`/api/ssl-check?domain=${encodeURIComponent(domain)}`); + const data = await response.json(); + + resultDiv.style.display = 'block'; + resultDomainSpan.textContent = domain; + + if (!response.ok || data.error) { + // Handle API errors (including connection errors, no cert found etc.) + const errorDetail = data.details ? ` Details: ${data.details}` : ''; + showError(data.error || `HTTP error! status: ${response.status}${errorDetail}`); + evaluationDiv.style.display = 'none'; // Hide evaluation section on error + document.getElementById('certificate-details').style.display = 'none'; // Hide details section + } else { + // Display successful result + evaluationDiv.style.display = 'block'; + document.getElementById('certificate-details').style.display = 'block'; + + // Evaluation + scoreValueSpan.textContent = data.evaluation.score; + evaluationSummaryP.textContent = data.evaluation.summary; + updateScoreBar(data.evaluation.score); + + // Certificate Details (Format for readability) + let formattedDetails = `Issuer: ${data.certificate.issuer || 'N/A'}\n`; + formattedDetails += `Subject: ${data.certificate.subject || 'N/A'}\n`; + formattedDetails += `Valid From: ${data.certificate.validFrom ? new Date(data.certificate.validFrom).toLocaleString() : 'N/A'}\n`; + formattedDetails += `Valid To: ${data.certificate.validTo ? new Date(data.certificate.validTo).toLocaleString() : 'N/A'}\n`; + formattedDetails += `Validity Status: ${data.certificate.validity || 'N/A'}\n\n`; + formattedDetails += `--- Raw OpenSSL Output ---\n${data.certificate.details || 'N/A'}`; + certOutputPre.textContent = formattedDetails; + + } + } catch (error) { + console.error('Fetch error:', error); + showError(`An error occurred while fetching the certificate details. Check the browser console. Error: ${error.message}`); + evaluationDiv.style.display = 'none'; + document.getElementById('certificate-details').style.display = 'none'; + } finally { + loadingSpinner.style.display = 'none'; + submitButton.disabled = false; + submitButton.innerHTML = 'Check Certificate'; + } + }); + + function showError(message) { + errorMessageDiv.textContent = message; + errorMessageDiv.style.display = 'block'; + resultDiv.style.display = 'block'; // Show the result box to display the error within it + } + + function hideError() { + errorMessageDiv.style.display = 'none'; + errorMessageDiv.textContent = ''; + } + + function updateScoreBar(score) { + const percentage = score * 10; // Score is out of 10 + scoreBarInner.style.width = `${percentage}%`; + + // Change color based on score + if (score >= 8) { + scoreBarInner.style.backgroundColor = '#198754'; // Green + } else if (score >= 5) { + scoreBarInner.style.backgroundColor = '#ffc107'; // Yellow + } else { + scoreBarInner.style.backgroundColor = '#dc3545'; // Red + } + } +}); \ No newline at end of file