be.fedict.eid.dss.protocol.simple.client.SignatureResponseProcessor.java Source code

Java tutorial

Introduction

Here is the source code for be.fedict.eid.dss.protocol.simple.client.SignatureResponseProcessor.java

Source

/*
 * eID Digital Signature Service Project.
 * Copyright (C) 2009-2010 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.dss.protocol.simple.client;

import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.SignatureException;
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.StringTokenizer;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bouncycastle.util.encoders.Base64;

/**
 * Processor for signature response messages.
 * 
 * @author Frank Cornelis
 */
public class SignatureResponseProcessor {

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

    public static final String SIGNATURE_RESPONSE_PARAMETER = "SignatureResponse";

    public static final String SIGNATURE_RESPONSE_ID_PARAMETER = "SignatureResponseId";

    public static final String SIGNATURE_STATUS_PARAMETER = "SignatureStatus";

    public static final String SIGNATURE_CERTIFICATE_PARAMETER = "SignatureCertificate";

    public static final String SERVICE_SIGNED_PARAMETER = "ServiceSigned";
    public static final String SERVICE_SIGNATURE_PARAMETER = "ServiceSignature";
    public static final String SERVICE_CERTIFICATE_CHAIN_SIZE_PARAMETER = "ServiceCertificateChainSize";
    public static final String SERVICE_CERTIFICATE_PARAMETER_PREFIX = "ServiceCertificate.";

    public static final String RELAY_STATE_PARAMETER = "RelayState";

    private final CertificateFactory certificateFactory;

    private final byte[] serviceFingerprint;

    /**
     * Main constructor.
     * 
     * @param serviceFingerprint
     *            the service X509 certificate fingerprint (SHA1) used to
     *            validate the signatory of the service signature.
     */
    public SignatureResponseProcessor(byte[] serviceFingerprint) {
        try {
            this.certificateFactory = CertificateFactory.getInstance("X.509");
        } catch (CertificateException e) {
            throw new RuntimeException("could not create certificate factory instance: " + e.getMessage(), e);
        }
        this.serviceFingerprint = serviceFingerprint;
    }

    /**
     * Default constructor. No service certificate fingerprint checking enabled.
     */
    public SignatureResponseProcessor() {
        this(null);
    }

    /**
     * Process the incoming DSS response.
     * 
     * @param request
     *            the HTTP servlet request that holds the DSS response.
     * @param target
     *            our target URL used for validation of the service signature.
     * @param base64encodedSignatureRequest
     *            optional base64 encoded signature request used for validation
     *            of the service signature. If <code>null</code> meaning
     *            artifact binding was used, signatureRequestId becomes
     *            required.
     * @param signatureRequestId
     *            optional signature request ID case artifact binding was used.
     * @param relayState
     *            optional relayState param
     * @return the signature response DTO.
     * @throws SignatureResponseProcessorException
     *             in case something went wrong.
     */
    public SignatureResponse process(HttpServletRequest request, String target,
            String base64encodedSignatureRequest, String signatureRequestId, String relayState)
            throws SignatureResponseProcessorException {
        /*
         * Decode all incoming parameters.
         */
        String signatureStatus = request.getParameter(SIGNATURE_STATUS_PARAMETER);
        if (null == signatureStatus) {
            String msg = SIGNATURE_STATUS_PARAMETER + " parameter not present";
            LOG.error(msg);
            throw new SignatureResponseProcessorException(msg);
        }
        LOG.debug("signature status: " + signatureStatus);
        if (!"OK".equals(signatureStatus)) {
            String msg = "invalid signature status: " + signatureStatus;
            LOG.error(msg);
            if ("USER_CANCELLED".equals(signatureStatus)) {
                throw new UserCancelledSignatureResponseProcessorException("user cancelled");
            }
            throw new SignatureResponseProcessorException(msg);
        }

        String signatureResponse = request.getParameter(SIGNATURE_RESPONSE_PARAMETER);
        String signatureResponseId = request.getParameter(SIGNATURE_RESPONSE_ID_PARAMETER);

        if (null == signatureResponse && null == signatureResponseId) {
            String msg = "No " + SIGNATURE_RESPONSE_PARAMETER + " or " + SIGNATURE_RESPONSE_ID_PARAMETER
                    + " parameter found!";
            LOG.error(msg);
            throw new SignatureResponseProcessorException(msg);
        }

        String encodedSignatureCertificate = request.getParameter(SIGNATURE_CERTIFICATE_PARAMETER);
        if (null == encodedSignatureCertificate) {
            String msg = SIGNATURE_CERTIFICATE_PARAMETER + " parameter not present";
            LOG.error(msg);
            throw new SignatureResponseProcessorException(msg);
        }

        /*
         * Validate RelayState if needed.
         */
        String responseRelayState = request.getParameter(RELAY_STATE_PARAMETER);
        if (null != relayState) {
            if (!relayState.equals(responseRelayState)) {
                String msg = "Returned relayState \"" + responseRelayState + "\" "
                        + "does not match expected RelayState: \"" + relayState + "\"";
                LOG.error(msg);
                throw new SignatureResponseProcessorException(msg);
            }
        }

        /*
         * Check service signature.
         */
        String encodedServiceSigned = request.getParameter(SERVICE_SIGNED_PARAMETER);
        if (null != encodedServiceSigned) {
            LOG.debug("service signature present");
            String serviceSigned;
            try {
                serviceSigned = URLDecoder.decode(encodedServiceSigned, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                throw new SignatureResponseProcessorException("URL decoder error: " + e.getMessage());
            }
            String encodedServiceSignature = request.getParameter(SERVICE_SIGNATURE_PARAMETER);
            if (null == encodedServiceSignature) {
                throw new SignatureResponseProcessorException("missing " + SERVICE_SIGNATURE_PARAMETER);
            }
            byte[] serviceSignatureValue = Base64.decode(encodedServiceSignature);

            /*
             * Parse the service certificate chain.
             */
            int serviceCertificateChainSize = Integer
                    .parseInt(request.getParameter(SERVICE_CERTIFICATE_CHAIN_SIZE_PARAMETER));
            List<X509Certificate> serviceCertificateChain = new LinkedList<X509Certificate>();
            for (int idx = 1; idx <= serviceCertificateChainSize; idx++) {
                String encodedCertificate = request.getParameter(SERVICE_CERTIFICATE_PARAMETER_PREFIX + idx);
                byte[] certificateData = Base64.decode(encodedCertificate);
                X509Certificate certificate;
                try {
                    certificate = (X509Certificate) this.certificateFactory
                            .generateCertificate(new ByteArrayInputStream(certificateData));
                } catch (CertificateException e) {
                    throw new SignatureResponseProcessorException("cert decoding error: " + e.getMessage());
                }
                serviceCertificateChain.add(certificate);
            }

            if (null == target) {
                throw new SignatureResponseProcessorException(
                        "target parameter required for validation of service signature");
            }

            if (null == base64encodedSignatureRequest && null == signatureRequestId) {
                throw new SignatureResponseProcessorException("base64encodedSignatureRequest or signatureRequestId "
                        + "required for validation of service signature");
            }
            try {
                verifyServiceSignature(serviceSigned, target, base64encodedSignatureRequest, signatureRequestId,
                        signatureResponse, signatureResponseId, encodedSignatureCertificate, serviceSignatureValue,
                        serviceCertificateChain);
            } catch (Exception e) {
                String msg = "service signature invalid: " + e.getMessage();
                LOG.error(msg, e);
                throw new SignatureResponseProcessorException(msg);
            }
        } else {
            if (null != this.serviceFingerprint) {
                /*
                 * In case of a service fingerprint being available, we really
                 * require the eID DSS to send us a service signature.
                 */
                throw new SignatureResponseProcessorException(
                        "Service fingerprint available but service signature is missing");
            }
        }

        /*
         * Parse all incoming data.
         */
        byte[] decodedSignatureResponse = null;
        if (null != signatureResponse) {
            decodedSignatureResponse = Base64.decode(signatureResponse);
            LOG.debug("decoded signature response size: " + decodedSignatureResponse.length);
        }

        byte[] decodedSignatureCertificate = Base64.decode(encodedSignatureCertificate);
        X509Certificate signatureCertificate;
        try {
            signatureCertificate = (X509Certificate) this.certificateFactory
                    .generateCertificate(new ByteArrayInputStream(decodedSignatureCertificate));
        } catch (CertificateException e) {
            String msg = SIGNATURE_CERTIFICATE_PARAMETER + " is not an X509 certificate";
            LOG.error(msg, e);
            throw new SignatureResponseProcessorException(msg);
        }

        /*
         * Construct result DTO.
         */
        return new SignatureResponse(decodedSignatureResponse, signatureResponseId, signatureCertificate);
    }

    private void verifyServiceSignature(String serviceSigned, String target, String signatureRequest,
            String signatureRequestId, String signatureResponse, String signatureResponseId,
            String encodedSignatureCertificate, byte[] serviceSignatureValue,
            List<X509Certificate> serviceCertificateChain)
            throws CertificateException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {

        LOG.debug("verifying service signature");
        X509Certificate serviceCertificate = serviceCertificateChain.get(0);
        LOG.debug("service identity: " + serviceCertificate.getSubjectX500Principal());
        Signature serviceSignature = Signature.getInstance("SHA1withRSA");
        serviceSignature.initVerify(serviceCertificate);

        StringTokenizer serviceSignedStringTokenizer = new StringTokenizer(serviceSigned, ",");
        while (serviceSignedStringTokenizer.hasMoreTokens()) {
            String serviceSignedElement = serviceSignedStringTokenizer.nextToken();
            LOG.debug("service signed: " + serviceSignedElement);
            byte[] data;
            if ("target".equals(serviceSignedElement)) {
                data = target.getBytes();
            } else if ("SignatureRequest".equals(serviceSignedElement)) {
                data = signatureRequest.getBytes();
            } else if ("SignatureRequestId".equals(serviceSignedElement)) {
                data = signatureRequestId.getBytes();
            } else if ("SignatureResponse".equals(serviceSignedElement)) {
                data = signatureResponse.getBytes();
            } else if ("SignatureResponseId".equals(serviceSignedElement)) {
                data = signatureResponseId.getBytes();
            } else if ("SignatureCertificate".equals(serviceSignedElement)) {
                data = encodedSignatureCertificate.getBytes();
            } else {
                throw new SecurityException("service signed unknown element: " + serviceSignedElement);
            }
            serviceSignature.update(data);
        }

        boolean valid = serviceSignature.verify(serviceSignatureValue);
        if (!valid) {
            throw new SecurityException("service signature not valid");
        }

        if (null != this.serviceFingerprint) {
            LOG.debug("checking service fingerprint");
            byte[] actualServiceFingerprint = DigestUtils.sha(serviceCertificate.getEncoded());
            if (!Arrays.equals(this.serviceFingerprint, actualServiceFingerprint)) {
                throw new SecurityException("service certificate fingerprint mismatch");
            }
        }
    }
}