mitm.djigzo.web.pages.certificate.CertificateImportKey.java Source code

Java tutorial

Introduction

Here is the source code for mitm.djigzo.web.pages.certificate.CertificateImportKey.java

Source

/*
 * Copyright (c) 2008-2011, Martijn Brinkers, Djigzo.
 * 
 * This file is part of Djigzo email encryption.
 *
 * Djigzo is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License 
 * version 3, 19 November 2007 as published by the Free Software 
 * Foundation.
 *
 * Djigzo 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public 
 * License along with Djigzo. If not, see <http://www.gnu.org/licenses/>
 *
 * Additional permission under GNU AGPL version 3 section 7
 * 
 * If you modify this Program, or any covered work, by linking or 
 * combining it with saaj-api-1.3.jar, saaj-impl-1.3.jar, 
 * wsdl4j-1.6.1.jar (or modified versions of these libraries), 
 * containing parts covered by the terms of Common Development and 
 * Distribution License (CDDL), Common Public License (CPL) the 
 * licensors of this Program grant you additional permission to 
 * convey the resulting work.
 */
package mitm.djigzo.web.pages.certificate;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableEntryException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.util.Enumeration;

import mitm.application.djigzo.admin.FactoryRoles;
import mitm.application.djigzo.workflow.KeyAndCertificateWorkflow;
import mitm.application.djigzo.ws.KeyAndCertificateWorkflowWS;
import mitm.common.security.SecurityFactoryFactory;
import mitm.common.security.SecurityFactoryFactoryException;
import mitm.common.util.SizeUtils;
import mitm.common.ws.WebServiceCheckedException;
import mitm.djigzo.web.pages.Certificates;

import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.tapestry5.Block;
import org.apache.tapestry5.PersistenceConstants;
import org.apache.tapestry5.annotations.Component;
import org.apache.tapestry5.annotations.IncludeStylesheet;
import org.apache.tapestry5.annotations.OnEvent;
import org.apache.tapestry5.annotations.Persist;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.annotations.SetupRender;
import org.apache.tapestry5.corelib.components.Checkbox;
import org.apache.tapestry5.corelib.components.Form;
import org.apache.tapestry5.corelib.components.PasswordField;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.ioc.annotations.Value;
import org.apache.tapestry5.upload.components.Upload;
import org.apache.tapestry5.upload.services.UploadEvents;
import org.apache.tapestry5.upload.services.UploadedFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.annotation.Secured;

@IncludeStylesheet("context:styles/pages/certificate/certificateImportKey.css")
@Secured({ FactoryRoles.ROLE_ADMIN, FactoryRoles.ROLE_PKI_MANAGER })
public class CertificateImportKey {
    private final static Logger logger = LoggerFactory.getLogger(CertificateImportKey.class);

    /*
     * Maximum size of the file to upload
     */
    private static final int MAX_UPLOAD_SIZE = 100 * SizeUtils.MB;

    /*
     * 'Handle' to the file that's uploaded
     */
    private UploadedFile file;

    /*
     * Maximum number of certificates that are uploaded in one call.
     */
    @Inject
    @Value("${certificateImportKey.maxBatchSize}")
    private int maxBatchSize;

    @Inject
    @Property
    private KeyAndCertificateWorkflowWS keyAndCertificateWorkflowWS;

    /*
     * The number of entries imported
     */
    @Property
    @Persist(PersistenceConstants.FLASH)
    private Integer importCount;

    /*
     * True if an error occurred during private key file upload
     */
    @Persist(PersistenceConstants.FLASH)
    private boolean importError;

    /*
     * The error message (only valid when error is true)
     */
    @Persist(PersistenceConstants.FLASH)
    private String importErrorMessage;

    /*
     * If true self entries without a key are ignored
     */
    @Property
    @Persist
    private Boolean ignoreMissingKey;

    /*
     * The password for the private key file
     */
    @Property
    private String password;

    @SuppressWarnings("unused")
    @Component(id = "upload", parameters = { "value=file", "validate=required" })
    private Upload upload;

    /*
     * I disabled clientValidation because it kept on popping up a warning when I opened the file
     * browser. Could be a bug.
     */
    @Component(id = "importForm", parameters = { "clientValidation=false" })
    private Form form;

    @SuppressWarnings("unused")
    @Component(id = "ignoreMissingKey", parameters = { "value=ignoreMissingKey" })
    private Checkbox ignoreMissingKeyCheckbox;

    @SuppressWarnings("unused")
    @Component(id = "password", parameters = { "value=password" })
    private PasswordField passwordField;

    @Inject
    private Block infoBlockPlural;

    @Inject
    private Block infoBlockSingular;

    @SetupRender
    @Secured({ FactoryRoles.ROLE_ADMIN, FactoryRoles.ROLE_PKI_MANAGER })
    protected void setupRender() {
        if (ignoreMissingKey == null) {
            ignoreMissingKey = false;
        }
    }

    public UploadedFile getFile() {
        return file;
    }

    public void setFile(UploadedFile file) {
        this.file = file;
    }

    public Block getInfoBlock() {
        if (importCount == null) {
            return null;
        }

        return importCount == 1 ? infoBlockSingular : infoBlockPlural;
    }

    public void onValidateFromUpload(UploadedFile file) {
        /*
         * Sanity check
         */
        if (file.getSize() > MAX_UPLOAD_SIZE) {
            form.recordError("The uploaded file exceeds the maximum size of " + MAX_UPLOAD_SIZE);
        }
    }

    /*
     * Event handler that gets called when the uploaded file exceeds the maximum size
     */
    @OnEvent(UploadEvents.UPLOAD_EXCEPTION)
    protected Object onUploadException(FileUploadException uploadException) {
        logger.error("Error uploading file", uploadException);

        importError = true;
        importErrorMessage = uploadException.getMessage();

        return CertificateImportKey.class;
    }

    private int uploadKeyStore(KeyStore keyStore, KeyAndCertificateWorkflow.MissingKey missingKey, String password)
            throws WebServiceCheckedException, KeyStoreException, NoSuchAlgorithmException, CertificateException,
            IOException {
        ByteArrayOutputStream pfx = new ByteArrayOutputStream();

        keyStore.store(pfx, password.toCharArray());

        return keyAndCertificateWorkflowWS.addPFX(pfx.toByteArray(), password, missingKey);
    }

    private void importPfx() throws KeyStoreException, NoSuchProviderException, SecurityFactoryFactoryException,
            NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableEntryException,
            WebServiceCheckedException {
        /*
         * To prevent timeouts on the SOAP connection we should upload the PFX file in batches if the PFX file
         * contains a large number of entries. The PFX file should therefore be opened. 
         */
        KeyStore allKeys = SecurityFactoryFactory.getSecurityFactory().createKeyStore("PKCS12");

        if (password == null) {
            password = "";
        }

        allKeys.load(file.getStream(), password.toCharArray());

        KeyAndCertificateWorkflow.MissingKey missingKey = ignoreMissingKey
                ? KeyAndCertificateWorkflow.MissingKey.SKIP_CERTIFICATE
                : KeyAndCertificateWorkflow.MissingKey.ADD_CERTIFICATE;

        int imported = 0;

        KeyStore batchKeys = SecurityFactoryFactory.getSecurityFactory().createKeyStore("PKCS12");
        batchKeys.load(null, password.toCharArray());

        Enumeration<String> aliases = allKeys.aliases();

        KeyStore.PasswordProtection passwordProtection = new KeyStore.PasswordProtection(password.toCharArray());

        while (aliases.hasMoreElements()) {
            String alias = aliases.nextElement();

            if (allKeys.isKeyEntry(alias)) {
                KeyStore.Entry entry = allKeys.getEntry(alias, passwordProtection);

                batchKeys.setEntry(alias, entry, passwordProtection);
            } else {
                Certificate certificate = allKeys.getCertificate(alias);

                batchKeys.setCertificateEntry(alias, certificate);
            }

            if (batchKeys.size() >= maxBatchSize) {
                imported += uploadKeyStore(batchKeys, missingKey, password);

                batchKeys = SecurityFactoryFactory.getSecurityFactory().createKeyStore("PKCS12");
                batchKeys.load(null, password.toCharArray());
            }
        }

        /*
         * Check if there are still some entries left to add (happens when the number
         * of entries is not a multiple of maxBatchSize)
         */
        if (batchKeys.size() > 0) {
            imported += uploadKeyStore(batchKeys, missingKey, password);
        }

        this.importCount = imported;
    }

    /*
     * Called when the form is submitted.
     */
    public void onSuccess() {
        try {
            importPfx();
        } catch (Exception e) {
            logger.error("Error importing the private key file. Message: ", e);

            importError = true;

            importErrorMessage = ExceptionUtils.getRootCauseMessage(e);
        }
    }

    public boolean isSingular() {
        return importCount == 1;
    }

    public boolean isImportError() {
        return importError;
    }

    public String getImportErrorMessage() {
        return importErrorMessage;
    }

    /*
     * Event handler called when the cancel button is pressed.
     */
    public Object onCancel() {
        return Certificates.class;
    }
}