mirror of
https://github.com/MarginaliaSearch/MarginaliaSearch.git
synced 2025-10-06 07:32:38 +02:00
Compare commits
2 Commits
deploy-025
...
deploy-025
Author | SHA1 | Date | |
---|---|---|---|
|
e7fa558954 | ||
|
720685bf3f |
@@ -0,0 +1,7 @@
|
|||||||
|
-- Add additional summary columns to DOMAIN_SECURITY_INFORMATION table
|
||||||
|
-- to make it easier to get more information about the SSL certificate's validity
|
||||||
|
|
||||||
|
ALTER TABLE DOMAIN_SECURITY_INFORMATION ADD COLUMN SSL_CHAIN_VALID BOOLEAN DEFAULT NULL;
|
||||||
|
ALTER TABLE DOMAIN_SECURITY_INFORMATION ADD COLUMN SSL_HOST_VALID BOOLEAN DEFAULT NULL;
|
||||||
|
ALTER TABLE DOMAIN_SECURITY_INFORMATION ADD COLUMN SSL_DATE_VALID BOOLEAN DEFAULT NULL;
|
||||||
|
OPTIMIZE TABLE DOMAIN_SECURITY_INFORMATION;
|
@@ -43,7 +43,10 @@ public record DomainSecurityRecord(
|
|||||||
@Nullable String headerXXssProtection,
|
@Nullable String headerXXssProtection,
|
||||||
@Nullable String headerServer,
|
@Nullable String headerServer,
|
||||||
@Nullable String headerXPoweredBy,
|
@Nullable String headerXPoweredBy,
|
||||||
@Nullable Instant tsLastUpdate
|
@Nullable Instant tsLastUpdate,
|
||||||
|
@Nullable Boolean sslChainValid,
|
||||||
|
@Nullable Boolean sslHostValid,
|
||||||
|
@Nullable Boolean sslDateValid
|
||||||
)
|
)
|
||||||
implements WritableModel
|
implements WritableModel
|
||||||
{
|
{
|
||||||
@@ -103,7 +106,11 @@ public record DomainSecurityRecord(
|
|||||||
rs.getString("DOMAIN_SECURITY_INFORMATION.HEADER_X_XSS_PROTECTION"),
|
rs.getString("DOMAIN_SECURITY_INFORMATION.HEADER_X_XSS_PROTECTION"),
|
||||||
rs.getString("DOMAIN_SECURITY_INFORMATION.HEADER_SERVER"),
|
rs.getString("DOMAIN_SECURITY_INFORMATION.HEADER_SERVER"),
|
||||||
rs.getString("DOMAIN_SECURITY_INFORMATION.HEADER_X_POWERED_BY"),
|
rs.getString("DOMAIN_SECURITY_INFORMATION.HEADER_X_POWERED_BY"),
|
||||||
rs.getObject("DOMAIN_SECURITY_INFORMATION.TS_LAST_UPDATE", Instant.class));
|
rs.getObject("DOMAIN_SECURITY_INFORMATION.TS_LAST_UPDATE", Instant.class),
|
||||||
|
rs.getObject("SSL_CHAIN_VALID", Boolean.class),
|
||||||
|
rs.getObject("SSL_HOST_VALID", Boolean.class),
|
||||||
|
rs.getObject("SSL_DATE_VALID", Boolean.class)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static HttpSchema httpSchemaFromString(@Nullable String schema) {
|
private static HttpSchema httpSchemaFromString(@Nullable String schema) {
|
||||||
@@ -150,8 +157,11 @@ public record DomainSecurityRecord(
|
|||||||
header_x_powered_by,
|
header_x_powered_by,
|
||||||
ssl_cert_public_key_hash,
|
ssl_cert_public_key_hash,
|
||||||
asn,
|
asn,
|
||||||
ts_last_update)
|
ts_last_update,
|
||||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
ssl_chain_valid,
|
||||||
|
ssl_host_valid,
|
||||||
|
ssl_date_valid)
|
||||||
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||||
"""))
|
"""))
|
||||||
{
|
{
|
||||||
ps.setInt(1, domainId());
|
ps.setInt(1, domainId());
|
||||||
@@ -295,6 +305,25 @@ public record DomainSecurityRecord(
|
|||||||
} else {
|
} else {
|
||||||
ps.setTimestamp(32, java.sql.Timestamp.from(tsLastUpdate()));
|
ps.setTimestamp(32, java.sql.Timestamp.from(tsLastUpdate()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sslChainValid() == null) {
|
||||||
|
ps.setNull(33, java.sql.Types.BOOLEAN);
|
||||||
|
} else {
|
||||||
|
ps.setBoolean(33, sslChainValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sslHostValid() == null) {
|
||||||
|
ps.setNull(34, java.sql.Types.BOOLEAN);
|
||||||
|
} else {
|
||||||
|
ps.setBoolean(34, sslHostValid());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sslDateValid() == null) {
|
||||||
|
ps.setNull(35, java.sql.Types.BOOLEAN);
|
||||||
|
} else {
|
||||||
|
ps.setBoolean(35, sslDateValid());
|
||||||
|
}
|
||||||
|
|
||||||
ps.executeUpdate();
|
ps.executeUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -333,6 +362,11 @@ public record DomainSecurityRecord(
|
|||||||
private String headerXPoweredBy;
|
private String headerXPoweredBy;
|
||||||
private Instant tsLastUpdate;
|
private Instant tsLastUpdate;
|
||||||
|
|
||||||
|
private Boolean isCertChainValid;
|
||||||
|
private Boolean isCertHostValid;
|
||||||
|
private Boolean isCertDateValid;
|
||||||
|
|
||||||
|
|
||||||
private static Instant MAX_UNIX_TIMESTAMP = Instant.ofEpochSecond(Integer.MAX_VALUE);
|
private static Instant MAX_UNIX_TIMESTAMP = Instant.ofEpochSecond(Integer.MAX_VALUE);
|
||||||
|
|
||||||
public Builder() {
|
public Builder() {
|
||||||
@@ -508,6 +542,21 @@ public record DomainSecurityRecord(
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder sslChainValid(@Nullable Boolean isCertChainValid) {
|
||||||
|
this.isCertChainValid = isCertChainValid;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder sslHostValid(@Nullable Boolean isCertHostValid) {
|
||||||
|
this.isCertHostValid = isCertHostValid;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder sslDateValid(@Nullable Boolean isCertDateValid) {
|
||||||
|
this.isCertDateValid = isCertDateValid;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public DomainSecurityRecord build() {
|
public DomainSecurityRecord build() {
|
||||||
return new DomainSecurityRecord(
|
return new DomainSecurityRecord(
|
||||||
domainId,
|
domainId,
|
||||||
@@ -541,7 +590,10 @@ public record DomainSecurityRecord(
|
|||||||
headerXXssProtection,
|
headerXXssProtection,
|
||||||
headerServer,
|
headerServer,
|
||||||
headerXPoweredBy,
|
headerXPoweredBy,
|
||||||
tsLastUpdate
|
tsLastUpdate,
|
||||||
|
isCertChainValid,
|
||||||
|
isCertHostValid,
|
||||||
|
isCertDateValid
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,16 +12,24 @@ import java.util.*;
|
|||||||
/** Utility class for validating X.509 certificates.
|
/** Utility class for validating X.509 certificates.
|
||||||
* This class provides methods to validate certificate chains, check expiration,
|
* This class provides methods to validate certificate chains, check expiration,
|
||||||
* hostname validity, and revocation status.
|
* hostname validity, and revocation status.
|
||||||
|
* <p></p>
|
||||||
|
* This is extremely unsuitable for actual SSL/TLS validation,
|
||||||
|
* and is only to be used in analyzing certificates for fingerprinting
|
||||||
|
* and diagnosing servers!
|
||||||
*/
|
*/
|
||||||
public class CertificateValidator {
|
public class CertificateValidator {
|
||||||
|
// If true, will attempt to fetch missing intermediate certificates via AIA urls.
|
||||||
|
private static final boolean TRY_FETCH_MISSING_CERTS = false;
|
||||||
|
|
||||||
public static class ValidationResult {
|
public static class ValidationResult {
|
||||||
public boolean chainValid = false;
|
public boolean chainValid = false;
|
||||||
public boolean certificateExpired = false;
|
public boolean certificateExpired = false;
|
||||||
public boolean certificateRevoked = false;
|
public boolean certificateRevoked = false;
|
||||||
|
public boolean selfSigned = false;
|
||||||
public boolean hostnameValid = false;
|
public boolean hostnameValid = false;
|
||||||
|
|
||||||
public boolean isValid() {
|
public boolean isValid() {
|
||||||
return chainValid && !certificateExpired && !certificateRevoked && hostnameValid;
|
return !selfSigned && !certificateExpired && !certificateRevoked && hostnameValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> errors = new ArrayList<>();
|
public List<String> errors = new ArrayList<>();
|
||||||
@@ -36,6 +44,7 @@ public class CertificateValidator {
|
|||||||
sb.append("Not Expired: ").append(!certificateExpired ? "✓" : "✗").append("\n");
|
sb.append("Not Expired: ").append(!certificateExpired ? "✓" : "✗").append("\n");
|
||||||
sb.append("Not Revoked: ").append(!certificateRevoked ? "✓" : "✗").append("\n");
|
sb.append("Not Revoked: ").append(!certificateRevoked ? "✓" : "✗").append("\n");
|
||||||
sb.append("Hostname Valid: ").append(hostnameValid ? "✓" : "✗").append("\n");
|
sb.append("Hostname Valid: ").append(hostnameValid ? "✓" : "✗").append("\n");
|
||||||
|
sb.append("Self-Signed: ").append(selfSigned ? "✓" : "✗").append("\n");
|
||||||
|
|
||||||
if (!errors.isEmpty()) {
|
if (!errors.isEmpty()) {
|
||||||
sb.append("\nErrors:\n");
|
sb.append("\nErrors:\n");
|
||||||
@@ -85,11 +94,15 @@ public class CertificateValidator {
|
|||||||
// 2. Check hostname validity
|
// 2. Check hostname validity
|
||||||
result.hostnameValid = checkHostname(leafCert, hostname, result);
|
result.hostnameValid = checkHostname(leafCert, hostname, result);
|
||||||
|
|
||||||
// 3. Check certificate chain validity (with AIA fetching)
|
// 3. Not really checking if it's self-signed, but if the chain is incomplete (and likely self-signed)
|
||||||
|
result.selfSigned = certChain.length <= 1;
|
||||||
|
|
||||||
|
// 4. Check certificate chain validity (optionally with AIA fetching)
|
||||||
result.chainValid = checkChainValidity(certChain, RootCerts.getTrustAnchors(), result, autoTrustFetchedRoots);
|
result.chainValid = checkChainValidity(certChain, RootCerts.getTrustAnchors(), result, autoTrustFetchedRoots);
|
||||||
|
|
||||||
// 4. Check revocation status
|
// 5. Check revocation status
|
||||||
result.certificateRevoked = checkRevocation(leafCert, result);
|
result.certificateRevoked = false; // not implemented
|
||||||
|
// checkRevocation(leafCert, result);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -169,6 +182,13 @@ public class CertificateValidator {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else if (!TRY_FETCH_MISSING_CERTS) {
|
||||||
|
result.errors.addAll(originalResult.issues);
|
||||||
|
result.details.put("chainLength", originalChain.length);
|
||||||
|
result.details.put("chainExtended", false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
List<X509Certificate> repairedChain = CertificateFetcher.buildCompleteChain(originalChain[0]);
|
List<X509Certificate> repairedChain = CertificateFetcher.buildCompleteChain(originalChain[0]);
|
||||||
|
|
||||||
|
@@ -126,6 +126,9 @@ public class DomainSecurityInformationFactory {
|
|||||||
.sslCertWildcard(isWildcard)
|
.sslCertWildcard(isWildcard)
|
||||||
.sslCertificateChainLength(sslCertificates.length)
|
.sslCertificateChainLength(sslCertificates.length)
|
||||||
.sslCertificateValid(validationResult.isValid())
|
.sslCertificateValid(validationResult.isValid())
|
||||||
|
.sslHostValid(validationResult.hostnameValid)
|
||||||
|
.sslChainValid(validationResult.chainValid)
|
||||||
|
.sslDateValid(!validationResult.certificateExpired)
|
||||||
.httpVersion(httpResponse.version())
|
.httpVersion(httpResponse.version())
|
||||||
.tsLastUpdate(Instant.now())
|
.tsLastUpdate(Instant.now())
|
||||||
.build();
|
.build();
|
||||||
|
@@ -243,6 +243,7 @@ class PingDaoTest {
|
|||||||
.headerServer("Apache/2.4.41 (Ubuntu)")
|
.headerServer("Apache/2.4.41 (Ubuntu)")
|
||||||
.headerXPoweredBy("PHP/7.4.3")
|
.headerXPoweredBy("PHP/7.4.3")
|
||||||
.tsLastUpdate(Instant.now())
|
.tsLastUpdate(Instant.now())
|
||||||
|
.sslHostValid(true)
|
||||||
.build();
|
.build();
|
||||||
var svc = new PingDao(dataSource);
|
var svc = new PingDao(dataSource);
|
||||||
svc.write(foo);
|
svc.write(foo);
|
||||||
|
Reference in New Issue
Block a user