diff --git a/app/requirements-web.txt b/app/requirements-web.txt index f2625f2..c20da59 100644 --- a/app/requirements-web.txt +++ b/app/requirements-web.txt @@ -12,7 +12,7 @@ alembic==1.13.0 # Authentication & Security Flask-Login==0.6.3 bcrypt==4.1.2 -cryptography==41.0.7 +cryptography>=42.0.0 # API & Serialization Flask-CORS==4.0.0 @@ -35,3 +35,6 @@ python-dotenv==1.0.0 # Development & Testing pytest==7.4.3 pytest-flask==1.3.0 + +# Cert Testing +sslyze==6.0.0 \ No newline at end of file diff --git a/app/web/services/scan_service.py b/app/web/services/scan_service.py index 4564986..2cc996e 100644 --- a/app/web/services/scan_service.py +++ b/app/web/services/scan_service.py @@ -449,9 +449,10 @@ class ScanService: # Process certificate and TLS info if present http_info = service_data.get('http_info', {}) - if http_info.get('certificate'): + ssl_tls = http_info.get('ssl_tls', {}) + if ssl_tls.get('certificate'): self._process_certificate( - http_info['certificate'], + ssl_tls, scan_obj.id, service.id ) @@ -489,16 +490,19 @@ class ScanService: return service return None - def _process_certificate(self, cert_data: Dict[str, Any], scan_id: int, + def _process_certificate(self, ssl_tls_data: Dict[str, Any], scan_id: int, service_id: int) -> None: """ Process certificate and TLS version data. Args: - cert_data: Certificate data dictionary + ssl_tls_data: SSL/TLS data dictionary containing 'certificate' and 'tls_versions' scan_id: Scan ID service_id: Service ID """ + # Extract certificate data from ssl_tls structure + cert_data = ssl_tls_data.get('certificate', {}) + # Create ScanCertificate record cert = ScanCertificate( scan_id=scan_id, @@ -516,7 +520,7 @@ class ScanService: self.db.flush() # Process TLS versions - tls_versions = cert_data.get('tls_versions', {}) + tls_versions = ssl_tls_data.get('tls_versions', {}) for version, version_data in tls_versions.items(): tls = ScanTLSVersion( scan_id=scan_id, diff --git a/app/web/templates/scan_detail.html b/app/web/templates/scan_detail.html index 041445e..a0e9096 100644 --- a/app/web/templates/scan_detail.html +++ b/app/web/templates/scan_detail.html @@ -154,6 +154,67 @@ + + + {% endblock %} {% block scripts %} @@ -332,6 +393,7 @@ Version Status Screenshot + Certificate @@ -345,11 +407,12 @@ const ports = ip.ports || []; if (ports.length === 0) { - portsContainer.innerHTML = 'No ports found'; + portsContainer.innerHTML = 'No ports found'; } else { ports.forEach(port => { const service = port.services && port.services.length > 0 ? port.services[0] : null; const screenshotPath = service && service.screenshot_path ? service.screenshot_path : null; + const certificate = service && service.certificates && service.certificates.length > 0 ? service.certificates[0] : null; const row = document.createElement('tr'); row.classList.add('scan-row'); // Fix white row bug @@ -362,6 +425,7 @@ ${service ? service.version || '-' : '-'} ${port.expected ? 'Expected' : 'Unexpected'} ${screenshotPath ? `` : '-'} + ${certificate ? `` : '-'} `; portsContainer.appendChild(row); }); @@ -614,5 +678,97 @@ console.error('Error loading historical chart:', error); } } + + // Show certificate details modal + function showCertificateModal(cert) { + // Populate modal fields + document.getElementById('cert-subject').textContent = cert.subject || '-'; + document.getElementById('cert-issuer').textContent = cert.issuer || '-'; + document.getElementById('cert-serial').textContent = cert.serial_number || '-'; + + // Format dates + document.getElementById('cert-valid-from').textContent = cert.not_valid_before + ? new Date(cert.not_valid_before).toLocaleString() + : '-'; + document.getElementById('cert-valid-until').textContent = cert.not_valid_after + ? new Date(cert.not_valid_after).toLocaleString() + : '-'; + + // Days until expiry with color coding + if (cert.days_until_expiry !== null && cert.days_until_expiry !== undefined) { + let badgeClass = 'badge-success'; + if (cert.days_until_expiry < 0) { + badgeClass = 'badge-danger'; + } else if (cert.days_until_expiry < 30) { + badgeClass = 'badge-warning'; + } + document.getElementById('cert-days-expiry').innerHTML = + `${cert.days_until_expiry} days`; + } else { + document.getElementById('cert-days-expiry').textContent = '-'; + } + + // Self-signed indicator + document.getElementById('cert-self-signed').innerHTML = cert.is_self_signed + ? 'Yes' + : 'No'; + + // SANs + if (cert.sans && cert.sans.length > 0) { + document.getElementById('cert-sans').innerHTML = cert.sans + .map(san => `${san}`) + .join(''); + } else { + document.getElementById('cert-sans').textContent = 'None'; + } + + // TLS versions + if (cert.tls_versions && cert.tls_versions.length > 0) { + let tlsHtml = '
'; + tlsHtml += ''; + + cert.tls_versions.forEach(tls => { + const statusBadge = tls.supported + ? 'Supported' + : 'Not Supported'; + + let ciphers = '-'; + if (tls.cipher_suites && tls.cipher_suites.length > 0) { + ciphers = `${tls.cipher_suites.length} cipher(s) + + `; + } + + tlsHtml += ``; + }); + + tlsHtml += '
VersionStatusCipher Suites
${tls.tls_version}${statusBadge}${ciphers}
'; + document.getElementById('cert-tls-versions').innerHTML = tlsHtml; + } else { + document.getElementById('cert-tls-versions').textContent = 'No TLS information available'; + } + + // Show modal + const modal = new bootstrap.Modal(document.getElementById('certificateModal')); + modal.show(); + } + + // Toggle cipher suites display + function toggleCiphers(btn, version) { + const cipherList = btn.nextElementSibling; + const icon = btn.querySelector('i'); + + if (cipherList.style.display === 'none') { + const ciphers = JSON.parse(btn.dataset.ciphers); + cipherList.innerHTML = ciphers.map(c => `
${c}
`).join(''); + cipherList.style.display = 'block'; + icon.className = 'bi bi-chevron-up'; + } else { + cipherList.style.display = 'none'; + icon.className = 'bi bi-chevron-down'; + } + } {% endblock %}