All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ee.sk.smartid.AuthenticationResponseValidator Maven / Gradle / Ivy

Go to download

Smart-ID Java client is a Java library that can be used for easy integration of the Smart-ID solution to information systems or e-services

There is a newer version: 2.3
Show newest version
package ee.sk.smartid;

/*-
 * #%L
 * Smart ID sample Java client
 * %%
 * Copyright (C) 2018 SK ID Solutions AS
 * %%
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * #L%
 */

import ee.sk.smartid.exception.TechnicalErrorException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.naming.InvalidNameException;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;

/**
 * Class used to validate the authentication
 */
public class AuthenticationResponseValidator {

  private static final Logger logger = LoggerFactory.getLogger(AuthenticationResponseValidator.class);

  private List trustedCACertificates = new ArrayList<>();

  /**
   * Constructs a new {@code AuthenticationResponseValidator}.
   * 

* The constructed instance is initialized with default trusted * CA certificates. * * @throws TechnicalErrorException when there was an error initializing trusted CA certificates */ public AuthenticationResponseValidator() { initializeTrustedCACertificatesFromKeyStore(); } /** * Validates the authentication response and returns the its result * * @throws TechnicalErrorException when there was an error validating the response * * @param authenticationResponse authentication response to be validated * @return authentication result */ public SmartIdAuthenticationResult validate(SmartIdAuthenticationResponse authenticationResponse) { validateAuthenticationResponse(authenticationResponse); SmartIdAuthenticationResult authenticationResult = new SmartIdAuthenticationResult(); AuthenticationIdentity identity = constructAuthenticationIdentity(authenticationResponse.getCertificate()); authenticationResult.setAuthenticationIdentity(identity); if (!verifyResponseEndResult(authenticationResponse)) { authenticationResult.setValid(false); authenticationResult.addError(SmartIdAuthenticationResult.Error.INVALID_END_RESULT); } if (!verifySignature(authenticationResponse)) { authenticationResult.setValid(false); authenticationResult.addError(SmartIdAuthenticationResult.Error.SIGNATURE_VERIFICATION_FAILURE); } if (!verifyCertificateExpiry(authenticationResponse.getCertificate())) { authenticationResult.setValid(false); authenticationResult.addError(SmartIdAuthenticationResult.Error.CERTIFICATE_EXPIRED); } if (!isCertificateTrusted(authenticationResponse.getCertificate())) { authenticationResult.setValid(false); authenticationResult.addError(SmartIdAuthenticationResult.Error.CERTIFICATE_NOT_TRUSTED); } if (!verifyCertificateLevel(authenticationResponse)) { authenticationResult.setValid(false); authenticationResult.addError(SmartIdAuthenticationResult.Error.CERTIFICATE_LEVEL_MISMATCH); } return authenticationResult; } /** * Gets the list of trusted CA certificates *

* Authenticating person's certificate has to be issued by * one of the trusted CA certificates. Otherwise the person's * authentication is deemed untrusted and therefore not valid. * * @return list of trusted CA certificates */ public List getTrustedCACertificates() { return trustedCACertificates; } /** * Adds a certificate to the list of trusted CA certificates *

* Authenticating person's certificate has to be issued by * one of the trusted CA certificates. Otherwise the person's * authentication is deemed untrusted and therefore not valid. * * @param certificate trusted CA certificate */ public void addTrustedCACertificate(X509Certificate certificate) { trustedCACertificates.add(certificate); } /** * Constructs a certificate from the byte array and * adds it into the list of trusted CA certificates *

* Authenticating person's certificate has to be issued by * one of the trusted CA certificates. Otherwise the person's * authentication is deemed untrusted and therefore not valid. * * @throws CertificateException when there was an error constructing the certificate from bytes * * @param certificateBytes trusted CA certificate */ public void addTrustedCACertificate(byte[] certificateBytes) throws CertificateException { CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); X509Certificate caCertificate = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(certificateBytes)); addTrustedCACertificate(caCertificate); } /** * Constructs a certificate from the file * and adds it into the list of trusted CA certificates *

* Authenticating person's certificate has to be issued by * one of the trusted CA certificates. Otherwise the person's * authentication is deemed untrusted and therefore not valid. * * @throws IOException when there is an error reading the file * @throws CertificateException when there is an error constructing the certificate from the bytes of the file * * @param certificateFile trusted CA certificate */ public void addTrustedCACertificate(File certificateFile) throws IOException, CertificateException { addTrustedCACertificate(Files.readAllBytes(certificateFile.toPath())); } /** * Clears the list of trusted CA certificates *

* PS! When clearing the trusted CA certificates * make sure it is not left empty. In that case * there is impossible to verify the trust of the * authenticating person. */ public void clearTrustedCACertificates() { trustedCACertificates.clear(); } private void initializeTrustedCACertificatesFromKeyStore() { try (InputStream is = AuthenticationResponseValidator.class.getResourceAsStream("/trusted_certificates.jks")) { KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); keystore.load(is, "changeit".toCharArray()); Enumeration aliases = keystore.aliases(); while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); X509Certificate certificate = (X509Certificate) keystore.getCertificate(alias); addTrustedCACertificate(certificate); } } catch (IOException | CertificateException | KeyStoreException | NoSuchAlgorithmException e) { logger.error("Error initializing trusted CA certificates", e); throw new TechnicalErrorException("Error initializing trusted CA certificates", e); } } private void validateAuthenticationResponse(SmartIdAuthenticationResponse authenticationResponse) { if (authenticationResponse.getCertificate() == null) { logger.error("Certificate is not present in the authentication response"); throw new TechnicalErrorException("Certificate is not present in the authentication response"); } if (StringUtils.isEmpty(authenticationResponse.getSignatureValueInBase64())) { logger.error("Signature is not present in the authentication response"); throw new TechnicalErrorException("Signature is not present in the authentication response"); } if (authenticationResponse.getHashType() == null) { logger.error("Hash type is not present in the authentication response"); throw new TechnicalErrorException("Hash type is not present in the authentication response"); } } private boolean verifyResponseEndResult(SmartIdAuthenticationResponse authenticationResponse) { return "OK".equalsIgnoreCase(authenticationResponse.getEndResult()); } private boolean verifySignature(SmartIdAuthenticationResponse authenticationResponse) { try { PublicKey signersPublicKey = authenticationResponse.getCertificate().getPublicKey(); Signature signature = Signature.getInstance("NONEwith" + signersPublicKey.getAlgorithm()); signature.initVerify(signersPublicKey); byte[] signedHash = Base64.decodeBase64(authenticationResponse.getSignedHashInBase64()); byte[] signedDigestWithPadding = addPadding(authenticationResponse.getHashType().getDigestInfoPrefix(), signedHash); signature.update(signedDigestWithPadding); return signature.verify(authenticationResponse.getSignatureValue()); } catch (GeneralSecurityException e) { logger.error("Signature verification failed"); throw new TechnicalErrorException("Signature verification failed", e); } } private boolean verifyCertificateExpiry(X509Certificate certificate) { return !certificate.getNotAfter().before(new Date()); } private boolean isCertificateTrusted(X509Certificate certificate) { for (X509Certificate trustedCACertificate : trustedCACertificates) { try { certificate.verify(trustedCACertificate.getPublicKey()); return true; } catch (SignatureException e) { continue; } catch (GeneralSecurityException e) { logger.warn("Error verifying signer's certificate: " + certificate.getSubjectDN() + " against CA certificate: " + trustedCACertificate.getSubjectDN(), e); continue; } } return false; } private boolean verifyCertificateLevel(SmartIdAuthenticationResponse authenticationResponse) { CertificateLevel certLevel = new CertificateLevel(authenticationResponse.getCertificateLevel()); String requestedCertificateLevel = authenticationResponse.getRequestedCertificateLevel(); return StringUtils.isEmpty(requestedCertificateLevel) || certLevel.isEqualOrAbove(requestedCertificateLevel); } private static byte[] addPadding(byte[] digestInfoPrefix, byte[] digest) { return ArrayUtils.addAll(digestInfoPrefix, digest); } AuthenticationIdentity constructAuthenticationIdentity(X509Certificate certificate) { AuthenticationIdentity identity = new AuthenticationIdentity(); try { LdapName ln = new LdapName(certificate.getSubjectDN().getName()); for(Rdn rdn : ln.getRdns()) { if(rdn.getType().equalsIgnoreCase("GIVENNAME")) { identity.setGivenName(rdn.getValue().toString()); } else if(rdn.getType().equalsIgnoreCase("SURNAME")) { identity.setSurName(rdn.getValue().toString()); } else if(rdn.getType().equalsIgnoreCase("SERIALNUMBER")) { identity.setIdentityCode(rdn.getValue().toString().split("-", 2)[1]); } else if(rdn.getType().equalsIgnoreCase("C")) { identity.setCountry(rdn.getValue().toString()); } } return identity; } catch (InvalidNameException e) { logger.error("Error getting authentication identity from the certificate", e); throw new TechnicalErrorException("Error getting authentication identity from the certificate", e); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy