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 += '| Version | Status | Cipher Suites |
';
+
+ 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 += `| ${tls.tls_version} | ${statusBadge} | ${ciphers} |
`;
+ });
+
+ tlsHtml += '
';
+ 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 %}