Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;

import javax.naming.InvalidNameException;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
import javax.security.auth.x500.X500Principal;

import org.slf4j.Logger;

Expand Down Expand Up @@ -191,20 +197,73 @@ private void validateCertificateLevel(AuthenticationResponse authenticationRespo
}
}

private record CertDnDetails(String country, String organization, String commonName) {

private static CertDnDetails from(X500Principal principal) {
String country = null;
String organization = null;
String commonName = null;

LdapName ldapName;
try {
ldapName = new LdapName(principal.getName());
} catch (InvalidNameException e) {
String errorMessage = "Error getting certificate distinguished name";
logger.error(errorMessage, e);
throw new SmartIdClientException(errorMessage, e);
}

for (Rdn rdn : ldapName.getRdns()) {
if ("C".equalsIgnoreCase(rdn.getType())) {
country = rdn.getValue().toString();
} else if ("O".equalsIgnoreCase(rdn.getType())) {
organization = rdn.getValue().toString();
} else if ("CN".equalsIgnoreCase(rdn.getType())) {
commonName = rdn.getValue().toString();
}
}
return new CertDnDetails(country, organization, commonName);
}

private static boolean equal(CertDnDetails first, CertDnDetails second) {
return Objects.equals(first.country, second.country) &&
Objects.equals(first.organization, second.organization) &&
Objects.equals(first.commonName, second.commonName);
}
}

private void validateCertificateIsTrusted(X509Certificate responseCertificate) {
CertDnDetails issuerDn = CertDnDetails.from(responseCertificate.getIssuerX500Principal());

for (X509Certificate trustedCACertificate : trustedCACertificates) {
logger.debug("Verifying signer's certificate '{}' against CA certificate '{}'",
responseCertificate.getSubjectX500Principal(),
trustedCACertificate.getSubjectX500Principal());

CertDnDetails caCertDn = CertDnDetails.from(trustedCACertificate.getSubjectX500Principal());

if (!CertDnDetails.equal(issuerDn, caCertDn)) {
logger.debug("Skipped trusted CA certificate '{}', no match with signer's certificate issuer '{}'",
trustedCACertificate.getSubjectX500Principal(),
responseCertificate.getIssuerX500Principal());
continue;
}

try {
responseCertificate.verify(trustedCACertificate.getPublicKey());
logger.info("Certificate verification passed for '{}' against CA responseCertificate '{}' ",
logger.info("Certificate verification passed for '{}' against CA certificate '{}'",
responseCertificate.getSubjectX500Principal(),
trustedCACertificate.getSubjectX500Principal());
return;
} catch (GeneralSecurityException ex) {
logger.debug("Error verifying signer's responseCertificate: {} against CA responseCertificate: {}",
logger.debug("Error verifying signer's certificate: {} against CA certificate: {}",
responseCertificate.getSubjectX500Principal(),
trustedCACertificate.getSubjectX500Principal(), ex);
}
}

logger.error("No suitable trusted CA certificate found: '{}'. Ensure that this CA certificate is present in the trusted CA certificate list",
responseCertificate.getIssuerX500Principal());
throw new UnprocessableSmartIdResponseException("Signer's certificate is not trusted");
}

Expand Down Expand Up @@ -237,4 +296,4 @@ private static String createSignatureData(AuthenticationResponse authenticationR
authenticationResponse.getServerRandom(),
randomChallenge);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,38 @@ void toAuthenticationIdentity_requestedCertificateLevelIsSetToNull_doNotValidate
assertEquals(Optional.of(LocalDate.of(1905, 4, 4)), authenticationIdentity.getDateOfBirth());
}

@Test
void toAuthenticationIdentity_certificateHasMatchingIssuerDnAndValidSignature_ok() {
var validator = new AuthenticationResponseValidator(new X509Certificate[]{toX509Certificate(CA_CERT)});
var dynamicLinkAuthenticationResponse = new AuthenticationResponse();

dynamicLinkAuthenticationResponse.setCertificate(toX509Certificate(AUTH_CERT));
dynamicLinkAuthenticationResponse.setCertificateLevel(AuthenticationCertificateLevel.QUALIFIED);
dynamicLinkAuthenticationResponse.setAlgorithmName("sha256WithRSA");

dynamicLinkAuthenticationResponse.setSignatureValueInBase64(toBase64("validSignatureForAuthCert"));
dynamicLinkAuthenticationResponse.setServerRandom("serverRandom");
dynamicLinkAuthenticationResponse.setEndResult("OK");

assertThrows(SmartIdClientException.class, () -> validator.toAuthenticationIdentity(dynamicLinkAuthenticationResponse, "randomChallenge"));
}

@Test
void toAuthenticationIdentity_certificateHasMatchingKeyButDifferentDN_throwException() {
var dynamicLinkAuthenticationResponse = new AuthenticationResponse();
dynamicLinkAuthenticationResponse.setCertificate(toX509Certificate(UNTRUSTED_CERT));
dynamicLinkAuthenticationResponse.setCertificateLevel(AuthenticationCertificateLevel.QUALIFIED);
dynamicLinkAuthenticationResponse.setAlgorithmName("sha256WithRSA");
dynamicLinkAuthenticationResponse.setSignatureValueInBase64(toBase64("validSignatureForFakeCert"));
dynamicLinkAuthenticationResponse.setServerRandom("serverRandom");
dynamicLinkAuthenticationResponse.setEndResult("OK");

var exception = assertThrows(UnprocessableSmartIdResponseException.class, () ->
authenticationResponseValidator.toAuthenticationIdentity(dynamicLinkAuthenticationResponse, "randomChallenge"));

assertEquals("Signer's certificate is not trusted", exception.getMessage());
}

@Test
void toAuthenticationIdentity_dynamicLinkAuthenticationResponseIsMissing_throwException() {
var exception = assertThrows(SmartIdClientException.class, () -> authenticationResponseValidator.toAuthenticationIdentity(null, null));
Expand Down