From a522ffa2dc8c717c9861448d83abffc124ed0f38 Mon Sep 17 00:00:00 2001 From: Rune Flobakk Date: Wed, 11 Oct 2023 15:27:15 +0200 Subject: [PATCH] More informative, less dramatic exception message Includes some more information on what to check (request URL for API, certificate from server, align with configured ServiceEnvionment) when a server certificate is evaluated as untrusted when intending to use the API. Also removes the overly dramatic reference to a possible "man-in-the-middle attack". Fixes #199 --- .../http/SignatureApiTrustStrategy.java | 24 ++++++++++++++++--- .../OrganizationNumberValidation.java | 2 +- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/lib/src/main/java/no/digipost/signature/client/core/internal/http/SignatureApiTrustStrategy.java b/lib/src/main/java/no/digipost/signature/client/core/internal/http/SignatureApiTrustStrategy.java index 3ce6501a..4319ae80 100644 --- a/lib/src/main/java/no/digipost/signature/client/core/internal/http/SignatureApiTrustStrategy.java +++ b/lib/src/main/java/no/digipost/signature/client/core/internal/http/SignatureApiTrustStrategy.java @@ -5,7 +5,13 @@ import no.digipost.signature.client.security.CertificateChainValidation.Result; import org.apache.hc.core5.ssl.TrustStrategy; +import java.math.BigInteger; import java.security.cert.X509Certificate; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Optional; + +import static javax.security.auth.x500.X500Principal.RFC1779; public final class SignatureApiTrustStrategy implements TrustStrategy { @@ -35,11 +41,23 @@ public boolean isTrusted(X509Certificate[] chain, String authType) { case TRUSTED_AND_SKIP_FURTHER_VALIDATION: return true; case TRUSTED: return false; case UNTRUSTED: default: - String subjectDN = chain[0].getSubjectX500Principal().getName(); + String certificateDescription = Optional.ofNullable(chain) + .filter(certs -> certs.length > 0) + .map(certs -> certs[0]) + .map(cert -> { + String subjectDN = cert.getSubjectX500Principal().getName(RFC1779); + BigInteger serialNumber = cert.getSerialNumber(); + String issuerDN = cert.getIssuerX500Principal().getName(RFC1779); + ZonedDateTime expires = cert.getNotAfter().toInstant().atZone(ZoneId.systemDefault()); + return subjectDN + " (serial number " + serialNumber + ", expires " + expires + "), issued by " + issuerDN; + }) + .orElse(""); throw new SecurityException( "Untrusted server certificate, according to " + certificateChainValidation + ". " + - "Make sure the server URI is correct. Actual certificate: " + subjectDN + ". " + - "This could indicate a misconfiguration of the client or server, or potentially a man-in-the-middle attack."); + "Actual certificate from server response: " + certificateDescription + ". " + + "This normally indicates either a misconfiguration of this client library, or a mixup of URLs used to communicate with the API. " + + "Make sure the request URL is correct, is actually for the API, and it aligns with the configured ServiceEnvironment. " + + "It should e.g. not be a URL that is to be accessed by a user from a web browser."); } } diff --git a/lib/src/main/java/no/digipost/signature/client/security/OrganizationNumberValidation.java b/lib/src/main/java/no/digipost/signature/client/security/OrganizationNumberValidation.java index 723edc93..087ead44 100644 --- a/lib/src/main/java/no/digipost/signature/client/security/OrganizationNumberValidation.java +++ b/lib/src/main/java/no/digipost/signature/client/security/OrganizationNumberValidation.java @@ -28,7 +28,7 @@ public Result validate(X509Certificate[] certChain) { @Override public String toString() { - return getClass().getSimpleName() + " trusting '" + trustedOrganizationNumber + "'"; + return getClass().getSimpleName() + " trusting organization number '" + trustedOrganizationNumber + "'"; } }