be.fedict.eid.applet.service.impl.handler.AuthenticationDataMessageHandler.java Source code

Java tutorial

Introduction

Here is the source code for be.fedict.eid.applet.service.impl.handler.AuthenticationDataMessageHandler.java

Source

/*
 * eID Applet Project.
 * Copyright (C) 2008-2013 FedICT.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version
 * 3.0 as published by the Free Software Foundation.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, see 
 * http://www.gnu.org/licenses/.
 */

package be.fedict.eid.applet.service.impl.handler;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.crypto.Cipher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.x509.DigestInfo;
import org.bouncycastle.util.encoders.Hex;

import be.fedict.eid.applet.service.Address;
import be.fedict.eid.applet.service.EIdCertsData;
import be.fedict.eid.applet.service.EIdData;
import be.fedict.eid.applet.service.Identity;
import be.fedict.eid.applet.service.impl.AuthenticationChallenge;
import be.fedict.eid.applet.service.impl.AuthenticationSignatureContextImpl;
import be.fedict.eid.applet.service.impl.RequestContext;
import be.fedict.eid.applet.service.impl.ServiceLocator;
import be.fedict.eid.applet.service.impl.UserIdentifierUtil;
import be.fedict.eid.applet.service.impl.tlv.TlvParser;
import be.fedict.eid.applet.service.spi.AuditService;
import be.fedict.eid.applet.service.spi.AuthenticationService;
import be.fedict.eid.applet.service.spi.AuthenticationSignatureContext;
import be.fedict.eid.applet.service.spi.AuthenticationSignatureService;
import be.fedict.eid.applet.service.spi.CertificateSecurityException;
import be.fedict.eid.applet.service.spi.ChannelBindingService;
import be.fedict.eid.applet.service.spi.ExpiredCertificateSecurityException;
import be.fedict.eid.applet.service.spi.IdentityIntegrityService;
import be.fedict.eid.applet.service.spi.PreSignResult;
import be.fedict.eid.applet.service.spi.RevokedCertificateSecurityException;
import be.fedict.eid.applet.service.spi.TrustCertificateSecurityException;
import be.fedict.eid.applet.shared.AuthSignRequestMessage;
import be.fedict.eid.applet.shared.AuthenticationContract;
import be.fedict.eid.applet.shared.AuthenticationDataMessage;
import be.fedict.eid.applet.shared.ErrorCode;
import be.fedict.eid.applet.shared.FinishedMessage;

/**
 * Authentication data message protocol handler.
 * 
 * @author Frank Cornelis
 * 
 */
@HandlesMessage(AuthenticationDataMessage.class)
public class AuthenticationDataMessageHandler implements MessageHandler<AuthenticationDataMessage> {

    public static final String AUTHENTICATED_USER_IDENTIFIER_SESSION_ATTRIBUTE = "eid.identifier";

    public static String PLAIN_TEXT_DIGEST_ALGO_OID = "2.16.56.1.2.1.3.1";

    private static final Log LOG = LogFactory.getLog(AuthenticationDataMessageHandler.class);

    @InitParam(AuthenticationDataMessageHandler.AUTHN_SERVICE_INIT_PARAM_NAME)
    private ServiceLocator<AuthenticationService> authenticationServiceLocator;

    @InitParam(AuthenticationDataMessageHandler.AUDIT_SERVICE_INIT_PARAM_NAME)
    private ServiceLocator<AuditService> auditServiceLocator;

    @InitParam(HelloMessageHandler.CHANNEL_BINDING_SERVICE)
    private ServiceLocator<ChannelBindingService> channelBindingServiceLocator;

    @InitParam(HelloMessageHandler.HOSTNAME_INIT_PARAM_NAME)
    private String hostname;

    @InitParam(HelloMessageHandler.INET_ADDRESS_INIT_PARAM_NAME)
    private InetAddress inetAddress;

    @InitParam(CHALLENGE_MAX_MATURITY_INIT_PARAM_NAME)
    private Long maxMaturity;

    private X509Certificate serverCertificate;

    @InitParam(HelloMessageHandler.SESSION_ID_CHANNEL_BINDING_INIT_PARAM_NAME)
    private boolean sessionIdChannelBinding;

    public static final String AUTHN_SERVICE_INIT_PARAM_NAME = "AuthenticationService";

    public static final String AUTHN_SIGNATURE_SERVICE_INIT_PARAM_NAME = "AuthenticationSignatureService";

    public static final String AUDIT_SERVICE_INIT_PARAM_NAME = "AuditService";

    public static final String CHALLENGE_MAX_MATURITY_INIT_PARAM_NAME = "ChallengeMaxMaturity";

    public static final String NRCID_SECRET_INIT_PARAM_NAME = "NRCIDSecret";

    public static final String NRCID_ORG_ID_INIT_PARAM_NAME = "NRCIDOrgId";

    public static final String NRCID_APP_ID_INIT_PARAM_NAME = "NRCIDAppId";

    @InitParam(NRCID_SECRET_INIT_PARAM_NAME)
    private String nrcidSecret;

    @InitParam(NRCID_ORG_ID_INIT_PARAM_NAME)
    private String nrcidOrgId;

    @InitParam(NRCID_APP_ID_INIT_PARAM_NAME)
    private String nrcidAppId;

    @InitParam(HelloMessageHandler.IDENTITY_INTEGRITY_SERVICE_INIT_PARAM_NAME)
    private ServiceLocator<IdentityIntegrityService> identityIntegrityServiceLocator;

    @InitParam(IdentityDataMessageHandler.INCLUDE_DATA_FILES)
    private boolean includeDataFiles;

    @InitParam(AUTHN_SIGNATURE_SERVICE_INIT_PARAM_NAME)
    private ServiceLocator<AuthenticationSignatureService> authenticationSignatureServiceLocator;

    public Object handleMessage(AuthenticationDataMessage message, Map<String, String> httpHeaders,
            HttpServletRequest request, HttpSession session) throws ServletException {
        LOG.debug("authentication data message received");

        if (null == message.authnCert) {
            /*
             * Can be the case for future (Kids) eID cards that have some
             * certificates missing.
             */
            String msg = "authentication certificate not present";
            LOG.warn(msg);
            throw new ServletException(msg);
        }
        byte[] signatureValue = message.signatureValue;
        LOG.debug("authn signing certificate subject: " + message.authnCert.getSubjectX500Principal());
        PublicKey signingKey = message.authnCert.getPublicKey();

        if (this.sessionIdChannelBinding) {
            checkSessionIdChannelBinding(message, request);
            if (null == this.serverCertificate) {
                LOG.warn("adviced to use in combination with server certificate channel binding");
            }
        }

        ChannelBindingService channelBindingService = this.channelBindingServiceLocator.locateService();
        if (null != this.serverCertificate || null != channelBindingService) {
            LOG.debug("using server certificate channel binding");
        }

        if (false == this.sessionIdChannelBinding && null == this.serverCertificate
                && null == channelBindingService) {
            LOG.warn("not using any secure channel binding");
        }

        byte[] challenge;
        try {
            challenge = AuthenticationChallenge.getAuthnChallenge(session, this.maxMaturity);
        } catch (SecurityException e) {
            AuditService auditService = this.auditServiceLocator.locateService();
            if (null != auditService) {
                String remoteAddress = request.getRemoteAddr();
                auditService.authenticationError(remoteAddress, message.authnCert);
            }
            throw new ServletException("security error: " + e.getMessage(), e);
        }

        byte[] serverCertificateClientPOV = null;
        try {
            if (null != message.serverCertificate) {
                serverCertificateClientPOV = message.serverCertificate.getEncoded();
            }
        } catch (CertificateEncodingException e) {
            throw new ServletException("server cert decoding error: " + e.getMessage(), e);
        }
        /*
         * We validate the authentication contract using the client-side
         * communicated server SSL certificate in case of secure channel
         * binding.
         */
        AuthenticationContract authenticationContract = new AuthenticationContract(message.saltValue, this.hostname,
                this.inetAddress, message.sessionId, serverCertificateClientPOV, challenge);
        byte[] toBeSigned;
        try {
            toBeSigned = authenticationContract.calculateToBeSigned();
        } catch (IOException e) {
            throw new ServletException("IO error: " + e.getMessage(), e);
        }

        try {
            Signature signature = Signature.getInstance("SHA1withRSA");
            signature.initVerify(signingKey);
            signature.update(toBeSigned);
            boolean result = signature.verify(signatureValue);
            if (false == result) {
                AuditService auditService = this.auditServiceLocator.locateService();
                if (null != auditService) {
                    String remoteAddress = request.getRemoteAddr();
                    auditService.authenticationError(remoteAddress, message.authnCert);
                }
                throw new SecurityException("authn signature incorrect");
            }
        } catch (NoSuchAlgorithmException e) {
            throw new SecurityException("algo error");
        } catch (InvalidKeyException e) {
            throw new SecurityException("authn key error");
        } catch (SignatureException e) {
            throw new SecurityException("signature error");
        }

        RequestContext requestContext = new RequestContext(session);
        String transactionMessage = requestContext.getTransactionMessage();
        if (null != transactionMessage) {
            LOG.debug("verifying TransactionMessage signature");
            byte[] transactionMessageSignature = message.transactionMessageSignature;
            if (null == transactionMessageSignature) {
                throw new SecurityException("missing TransactionMessage signature");
            }
            try {
                Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
                cipher.init(Cipher.DECRYPT_MODE, signingKey);
                byte[] signatureDigestInfoValue = cipher.doFinal(transactionMessageSignature);
                ASN1InputStream aIn = new ASN1InputStream(signatureDigestInfoValue);
                DigestInfo signatureDigestInfo = new DigestInfo((ASN1Sequence) aIn.readObject());
                if (false == PLAIN_TEXT_DIGEST_ALGO_OID
                        .equals(signatureDigestInfo.getAlgorithmId().getObjectId().getId())) {
                    throw new SecurityException("TransactionMessage signature algo OID incorrect");
                }
                if (false == Arrays.equals(transactionMessage.getBytes(), signatureDigestInfo.getDigest())) {
                    throw new SecurityException("signed TransactionMessage incorrect");
                }
                LOG.debug("TransactionMessage signature validated");
            } catch (Exception e) {
                LOG.error("error verifying TransactionMessage signature", e);
                AuditService auditService = this.auditServiceLocator.locateService();
                if (null != auditService) {
                    String remoteAddress = request.getRemoteAddr();
                    auditService.authenticationError(remoteAddress, message.authnCert);
                }
                throw new SecurityException("error verifying TransactionMessage signature: " + e.getMessage());
            }
        }

        /*
         * Secure channel binding verification.
         */
        if (null != channelBindingService) {
            X509Certificate serverCertificate = channelBindingService.getServerCertificate();
            if (null == serverCertificate) {
                LOG.warn("could not verify secure channel binding as the server does not know its identity yet");
            } else {
                if (false == serverCertificate.equals(message.serverCertificate)) {
                    AuditService auditService = this.auditServiceLocator.locateService();
                    if (null != auditService) {
                        String remoteAddress = request.getRemoteAddr();
                        auditService.authenticationError(remoteAddress, message.authnCert);
                    }
                    throw new SecurityException("secure channel binding identity mismatch");
                }
                LOG.debug("secure channel binding verified");
            }
        } else {
            if (null != this.serverCertificate) {
                if (false == this.serverCertificate.equals(message.serverCertificate)) {
                    AuditService auditService = this.auditServiceLocator.locateService();
                    if (null != auditService) {
                        String remoteAddress = request.getRemoteAddr();
                        auditService.authenticationError(remoteAddress, message.authnCert);
                    }
                    throw new SecurityException("secure channel binding identity mismatch");
                }
                LOG.debug("secure channel binding verified");
            }
        }

        AuthenticationService authenticationService = this.authenticationServiceLocator.locateService();
        List<X509Certificate> certificateChain = new LinkedList<X509Certificate>();
        certificateChain.add(message.authnCert);
        certificateChain.add(message.citizenCaCert);
        certificateChain.add(message.rootCaCert);
        certificateChain.add(message.rrnCertificate);
        try {
            authenticationService.setHttpSessionObject(request.getSession());
            authenticationService.validateCertificateChain(certificateChain);
        } catch (ExpiredCertificateSecurityException e) {
            return new FinishedMessage(ErrorCode.CERTIFICATE_EXPIRED);
        } catch (RevokedCertificateSecurityException e) {
            return new FinishedMessage(ErrorCode.CERTIFICATE_REVOKED);
        } catch (TrustCertificateSecurityException e) {
            return new FinishedMessage(ErrorCode.CERTIFICATE_NOT_TRUSTED);
        } catch (CertificateSecurityException e) {
            return new FinishedMessage(ErrorCode.CERTIFICATE);
        } catch (Exception e) {
            /*
             * We don't want to depend on the full JavaEE profile in this
             * artifact.
             */
            if ("javax.ejb.EJBException".equals(e.getClass().getName())) {
                Exception exception;
                try {
                    Method getCausedByExceptionMethod = e.getClass().getMethod("getCausedByException",
                            new Class[] {});
                    exception = (Exception) getCausedByExceptionMethod.invoke(e, new Object[] {});
                } catch (Exception e2) {
                    LOG.debug("error: " + e.getMessage(), e);
                    throw new SecurityException("error retrieving the root cause: " + e2.getMessage());
                }
                if (exception instanceof ExpiredCertificateSecurityException) {
                    return new FinishedMessage(ErrorCode.CERTIFICATE_EXPIRED);
                }
                if (exception instanceof RevokedCertificateSecurityException) {
                    return new FinishedMessage(ErrorCode.CERTIFICATE_REVOKED);
                }
                if (exception instanceof TrustCertificateSecurityException) {
                    return new FinishedMessage(ErrorCode.CERTIFICATE_NOT_TRUSTED);
                }
                if (exception instanceof CertificateSecurityException) {
                    return new FinishedMessage(ErrorCode.CERTIFICATE);
                }
            }
            throw new SecurityException("authn service error: " + e.getMessage());
        }

        String userId = UserIdentifierUtil.getUserId(message.authnCert);
        LOG.info("authenticated: " + userId + " @ " + request.getRemoteAddr());
        if (null != this.nrcidSecret) {
            userId = UserIdentifierUtil.getNonReversibleCitizenIdentifier(userId, this.nrcidOrgId, this.nrcidAppId,
                    this.nrcidSecret);
        }
        /*
         * Some people state that you cannot use the national register number
         * without hashing. Problem is that hashing introduces hash collision
         * problems. The probability is very low, but what if it's your leg
         * they're cutting of because of a patient mismatch based on the SHA1 of
         * your national register number?
         */

        /*
         * Push authenticated used Id into the HTTP session.
         */
        session.setAttribute(AUTHENTICATED_USER_IDENTIFIER_SESSION_ATTRIBUTE, userId);

        EIdData eidData = (EIdData) session.getAttribute(IdentityDataMessageHandler.EID_SESSION_ATTRIBUTE);
        if (null == eidData) {
            eidData = new EIdData();
            session.setAttribute(IdentityDataMessageHandler.EID_SESSION_ATTRIBUTE, eidData);
        }
        eidData.identifier = userId;

        AuditService auditService = this.auditServiceLocator.locateService();
        if (null != auditService) {
            auditService.authenticated(userId);
        }

        boolean includeIdentity = requestContext.includeIdentity();
        boolean includeAddress = requestContext.includeAddress();
        boolean includeCertificates = requestContext.includeCertificates();
        boolean includePhoto = requestContext.includePhoto();

        /*
         * Also process the identity data in case it was requested.
         */
        if (includeIdentity) {
            if (null == message.identityData) {
                throw new ServletException("identity data not included while requested");
            }
        }
        if (includeAddress) {
            if (null == message.addressData) {
                throw new ServletException("address data not included while requested");
            }
        }
        if (includePhoto) {
            if (null == message.photoData) {
                throw new ServletException("photo data not included while requested");
            }
        }
        IdentityIntegrityService identityIntegrityService = this.identityIntegrityServiceLocator.locateService();
        if (null != identityIntegrityService) {
            if (null == message.rrnCertificate) {
                throw new ServletException("national registry certificate not included while requested");
            }
            List<X509Certificate> rrnCertificateChain = new LinkedList<X509Certificate>();
            rrnCertificateChain.add(message.rrnCertificate);
            rrnCertificateChain.add(message.rootCaCert);

            try {
                identityIntegrityService.checkNationalRegistrationCertificate(rrnCertificateChain);
            } catch (ExpiredCertificateSecurityException e) {
                return new FinishedMessage(ErrorCode.CERTIFICATE_EXPIRED);
            } catch (RevokedCertificateSecurityException e) {
                return new FinishedMessage(ErrorCode.CERTIFICATE_REVOKED);
            } catch (TrustCertificateSecurityException e) {
                return new FinishedMessage(ErrorCode.CERTIFICATE_NOT_TRUSTED);
            } catch (CertificateSecurityException e) {
                return new FinishedMessage(ErrorCode.CERTIFICATE);
            } catch (Exception e) {
                if ("javax.ejb.EJBException".equals(e.getClass().getName())) {
                    Exception exception;
                    try {
                        Method getCausedByExceptionMethod = e.getClass().getMethod("getCausedByException",
                                new Class[] {});
                        exception = (Exception) getCausedByExceptionMethod.invoke(e, new Object[] {});
                    } catch (Exception e2) {
                        LOG.debug("error: " + e.getMessage(), e);
                        throw new SecurityException("error retrieving the root cause: " + e2.getMessage());
                    }
                    if (exception instanceof ExpiredCertificateSecurityException) {
                        return new FinishedMessage(ErrorCode.CERTIFICATE_EXPIRED);
                    }
                    if (exception instanceof RevokedCertificateSecurityException) {
                        return new FinishedMessage(ErrorCode.CERTIFICATE_REVOKED);
                    }
                    if (exception instanceof TrustCertificateSecurityException) {
                        return new FinishedMessage(ErrorCode.CERTIFICATE_NOT_TRUSTED);
                    }
                    if (exception instanceof CertificateSecurityException) {
                        return new FinishedMessage(ErrorCode.CERTIFICATE);
                    }
                }
                throw new SecurityException("error checking the NRN certificate: " + e.getMessage(), e);
            }

            PublicKey rrnPublicKey = message.rrnCertificate.getPublicKey();
            if (includeIdentity) {
                if (null == message.identitySignatureData) {
                    throw new ServletException("identity signature data not included while requested");
                }
                verifySignature(message.rrnCertificate.getSigAlgName(), message.identitySignatureData, rrnPublicKey,
                        request, message.identityData);
            }
            if (includeAddress) {
                if (null == message.addressSignatureData) {
                    throw new ServletException("address signature data not included while requested");
                }
                byte[] addressFile = trimRight(message.addressData);
                verifySignature(message.rrnCertificate.getSigAlgName(), message.addressSignatureData, rrnPublicKey,
                        request, addressFile, message.identitySignatureData);
            }
        }
        if (includeIdentity) {
            Identity identity = TlvParser.parse(message.identityData, Identity.class);
            if (false == UserIdentifierUtil.getUserId(message.authnCert).equals(identity.nationalNumber)) {
                throw new ServletException("national number mismatch");
            }
            session.setAttribute(IdentityDataMessageHandler.IDENTITY_SESSION_ATTRIBUTE, identity);
            eidData.identity = identity;
            auditService = this.auditServiceLocator.locateService();
            if (null != auditService) {
                auditService.identified(identity.nationalNumber);
            }
        }
        if (includeAddress) {
            Address address = TlvParser.parse(message.addressData, Address.class);
            session.setAttribute(IdentityDataMessageHandler.ADDRESS_SESSION_ATTRIBUTE, address);
            eidData.address = address;
        }
        if (includePhoto) {
            if (includeIdentity) {
                byte[] expectedPhotoDigest = eidData.identity.photoDigest;
                byte[] actualPhotoDigest = digestPhoto(getDigestAlgo(expectedPhotoDigest.length),
                        message.photoData);
                if (false == Arrays.equals(expectedPhotoDigest, actualPhotoDigest)) {
                    throw new ServletException("photo digest incorrect");
                }
            }
            session.setAttribute(IdentityDataMessageHandler.PHOTO_SESSION_ATTRIBUTE, message.photoData);
            eidData.photo = message.photoData;
        }
        if (includeCertificates) {
            if (includeIdentity) {
                eidData.certs = new EIdCertsData();
                eidData.certs.authn = message.authnCert;
                eidData.certs.ca = message.citizenCaCert;
                eidData.certs.root = message.rootCaCert;
                eidData.certs.sign = message.signCert;
            }
            session.setAttribute(IdentityDataMessageHandler.AUTHN_CERT_SESSION_ATTRIBUTE, message.authnCert);
            session.setAttribute(IdentityDataMessageHandler.CA_CERT_SESSION_ATTRIBUTE, message.citizenCaCert);
            session.setAttribute(IdentityDataMessageHandler.ROOT_CERT_SESSION_ATTRIBTUE, message.rootCaCert);
            session.setAttribute(IdentityDataMessageHandler.SIGN_CERT_SESSION_ATTRIBUTE, message.signCert);
        }

        if (this.includeDataFiles) {
            session.setAttribute(IdentityDataMessageHandler.EID_DATA_IDENTITY_SESSION_ATTRIBUTE,
                    message.identityData);
            session.setAttribute(IdentityDataMessageHandler.EID_DATA_ADDRESS_SESSION_ATTRIBUTE,
                    message.addressData);
        }

        AuthenticationSignatureService authenticationSignatureService = this.authenticationSignatureServiceLocator
                .locateService();
        if (null != authenticationSignatureService) {
            List<X509Certificate> authnCertificateChain;
            if (null != message.authnCert) {
                authnCertificateChain = new LinkedList<X509Certificate>();
                authnCertificateChain.add(message.authnCert);
                authnCertificateChain.add(message.citizenCaCert);
                authnCertificateChain.add(message.rootCaCert);
                authnCertificateChain.add(message.rrnCertificate);
            } else {
                authnCertificateChain = null;
            }
            AuthenticationSignatureContext authenticationSignatureContext = new AuthenticationSignatureContextImpl(
                    session);
            PreSignResult preSignResult = authenticationSignatureService.preSign(authnCertificateChain,
                    authenticationSignatureContext);
            if (null == preSignResult) {
                return new FinishedMessage();
            }
            boolean logoff = preSignResult.getLogoff();
            byte[] computedDigestValue = preSignResult.getDigestInfo().digestValue;
            String digestAlgo = preSignResult.getDigestInfo().digestAlgo;
            String authnMessage = preSignResult.getDigestInfo().description;
            AuthSignRequestMessage authSignRequestMessage = new AuthSignRequestMessage(computedDigestValue,
                    digestAlgo, authnMessage, logoff);
            return authSignRequestMessage;
        }
        return new FinishedMessage();
    }

    private byte[] trimRight(byte[] addressFile) {
        int idx;
        for (idx = 0; idx < addressFile.length; idx++) {
            if (0 == addressFile[idx]) {
                break;
            }
        }
        byte[] result = new byte[idx];
        System.arraycopy(addressFile, 0, result, 0, idx);
        return result;
    }

    private byte[] digestPhoto(String digestAlgoName, byte[] photoFile) {
        MessageDigest messageDigest;
        try {
            messageDigest = MessageDigest.getInstance(digestAlgoName);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("SHA error: " + e.getMessage(), e);
        }
        byte[] photoDigest = messageDigest.digest(photoFile);
        return photoDigest;
    }

    private String getDigestAlgo(final int hashSize) throws RuntimeException {
        switch (hashSize) {
        case 20:
            return "SHA-1";
        case 28:
            return "SHA-224";
        case 32:
            return "SHA-256";
        case 48:
            return "SHA-384";
        case 64:
            return "SHA-512";
        }
        throw new RuntimeException("Failed to find guess algorithm for hash size of " + hashSize + " bytes");
    }

    private void verifySignature(String signatureAlgo, byte[] signatureData, PublicKey publicKey,
            HttpServletRequest request, byte[]... data) throws ServletException {
        Signature signature;
        try {
            signature = Signature.getInstance(signatureAlgo);
        } catch (NoSuchAlgorithmException e) {
            throw new ServletException("algo error: " + e.getMessage(), e);
        }
        try {
            signature.initVerify(publicKey);
        } catch (InvalidKeyException e) {
            throw new ServletException("key error: " + e.getMessage(), e);
        }
        try {
            for (byte[] dataItem : data) {
                signature.update(dataItem);
            }
            boolean result = signature.verify(signatureData);
            if (false == result) {
                AuditService auditService = this.auditServiceLocator.locateService();
                if (null != auditService) {
                    String remoteAddress = request.getRemoteAddr();
                    auditService.identityIntegrityError(remoteAddress);
                }
                throw new ServletException("signature incorrect");
            }
        } catch (SignatureException e) {
            throw new ServletException("signature error: " + e.getMessage(), e);
        }
    }

    private void checkSessionIdChannelBinding(AuthenticationDataMessage message, HttpServletRequest request) {
        LOG.debug("using TLS session Id channel binding");
        byte[] sessionId = message.sessionId;
        /*
         * Next is Tomcat specific.
         */
        String actualSessionId = (String) request.getAttribute("javax.servlet.request.ssl_session");
        if (null == actualSessionId) {
            /*
             * Servlet specs v3.0
             */
            actualSessionId = (String) request.getAttribute("javax.servlet.request.ssl_session_id");
        }
        if (null == actualSessionId) {
            LOG.warn("could not verify the SSL session identifier");
            return;
        }
        if (false == Arrays.equals(sessionId, Hex.decode(actualSessionId))) {
            LOG.warn("SSL session Id mismatch");
            LOG.debug("signed SSL session Id: " + new String(Hex.encode(sessionId)));
            LOG.debug("actual SSL session Id: " + actualSessionId);
            throw new SecurityException("SSL session Id mismatch");
        }
        LOG.debug("SSL session identifier checked");
    }

    public void init(ServletConfig config) throws ServletException {
        String channelBindingServerCertificate = config
                .getInitParameter(HelloMessageHandler.CHANNEL_BINDING_SERVER_CERTIFICATE);
        if (null != channelBindingServerCertificate) {
            File serverCertificateFile = new File(channelBindingServerCertificate);
            if (false == serverCertificateFile.exists()) {
                throw new ServletException("server certificate not found: " + serverCertificateFile);
            }
            byte[] encodedServerCertificate;
            try {
                encodedServerCertificate = FileUtils.readFileToByteArray(serverCertificateFile);
            } catch (IOException e) {
                throw new ServletException("error reading server certificate: " + e.getMessage(), e);
            }
            this.serverCertificate = getCertificate(encodedServerCertificate);
        }
    }

    private X509Certificate getCertificate(byte[] certData) {
        CertificateFactory certificateFactory;
        try {
            certificateFactory = CertificateFactory.getInstance("X.509");
        } catch (CertificateException e) {
            throw new RuntimeException("cert factory error: " + e.getMessage(), e);
        }
        try {
            X509Certificate certificate = (X509Certificate) certificateFactory
                    .generateCertificate(new ByteArrayInputStream(certData));
            return certificate;
        } catch (CertificateException e) {
            throw new RuntimeException("certificate decoding error: " + e.getMessage(), e);
        }
    }
}