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

Java tutorial

Introduction

Here is the source code for mitm.djigzo.web.pages.certificate.CertificateImport.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.NoSuchProviderException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.LinkedList;

import mitm.application.djigzo.admin.FactoryRoles;
import mitm.application.djigzo.ws.CertificateStore;
import mitm.application.djigzo.ws.KeyAndCertStoreWS;
import mitm.application.djigzo.ws.X509CertStoreWS;
import mitm.common.security.certificate.CertificateUtils;
import mitm.common.security.certificate.X509CertificateInspector;
import mitm.common.util.SizeUtils;
import mitm.common.ws.WebServiceCheckedException;
import mitm.djigzo.web.pages.Certificates;
import mitm.djigzo.web.pages.Roots;
import mitm.djigzo.web.services.CertificateStoreMarker;
import mitm.djigzo.web.services.RootStoreMarker;

import org.apache.commons.fileupload.FileUploadException;
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.Mixins;
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.Select;
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/certificateImport.css")
@Secured({ FactoryRoles.ROLE_ADMIN, FactoryRoles.ROLE_PKI_MANAGER })
public class CertificateImport {
    private final static Logger logger = LoggerFactory.getLogger(CertificateImport.class);

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

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

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

    @Inject
    @Property
    @CertificateStoreMarker
    private KeyAndCertStoreWS keyAndCertStoreWS;

    @Inject
    @Property
    @RootStoreMarker
    private KeyAndCertStoreWS rootWS;

    /*
     * The store to store the certificates in
     */
    @Property
    private CertificateStore store = CertificateStore.CERTIFICATES;

    /*
     * If true self signed certificates are ignored
     */
    @Property
    @Persist(PersistenceConstants.FLASH)
    private Boolean ignoreSelfSigned;

    /*
     * If true only self signed certificates are imported
     */
    @Property
    @Persist(PersistenceConstants.FLASH)
    private Boolean checkSelfSigned;

    /*
     * If true expired certificates are ignored
     */
    @Property
    @Persist(PersistenceConstants.FLASH)
    private Boolean ignoreExpired;

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

    /*
     * True when uploading a file resulted in an error (mostly when file size exceeds maximum)
     */
    @Persist(PersistenceConstants.FLASH)
    private boolean importError;

    /*
     * The error message accompanying the uploadError property.
     */
    @Persist(PersistenceConstants.FLASH)
    private String importErrorMessage;

    /*
     * The total number of certificates found in the uploaded file
     */
    @SuppressWarnings("unused")
    @Property
    @Persist(PersistenceConstants.FLASH)
    private int totalCount;

    @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
     */
    @Component(id = "importForm", parameters = { "clientValidation=false" })
    private Form form;

    @SuppressWarnings("unused")
    @Component(id = "store", parameters = { "blankOption=NEVER", "value=store",
            "script=literal:syncCheckboxes();" })
    @Mixins({ "AddScript" })
    private Select storeSelect;

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

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

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

    @Inject
    private Block infoBlockPlural;

    @Inject
    private Block infoBlockSingular;

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

        if (checkSelfSigned == null) {
            checkSelfSigned = true;
        }

        if (ignoreExpired == null) {
            ignoreExpired = true;
        }
    }

    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 onActivate(CertificateStore store) {
        if (store != null) {
            this.store = store;
        }
    }

    public CertificateStore onPassivate() {
        return store;
    }

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

            return;
        }

        /*
         * We need to check if the uploaded file is really a certificate file.
         */
        try {
            Collection<X509Certificate> certificates = CertificateUtils.readX509Certificates(file.getStream());

            if (certificates.size() == 0) {
                form.recordError("The uploaded file does not contain valid certificates.");
            }
        } catch (CertificateException e) {
            logger.error("Error validating uploaded file.", e);

            form.recordError("The uploaded file is not a valid certificate file.");
        }
    }

    /*
     * 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 CertificateImport.class;
    }

    private X509CertStoreWS getCertStoreWS() {
        X509CertStoreWS certStore;

        switch (store) {
        case CERTIFICATES:
            certStore = keyAndCertStoreWS;
            break;
        case ROOTS:
            certStore = rootWS;
            break;
        default:
            throw new IllegalArgumentException("Unknown store.");
        }

        return certStore;
    }

    private void addCertificates(Collection<X509Certificate> certificates)
            throws CertificateEncodingException, IOException, WebServiceCheckedException {
        /*
         * Create some initial buffer space. Assume certificate is 2K on average.
         */
        ByteArrayOutputStream bos = new ByteArrayOutputStream(maxBatchSize * 2048);

        CertificateUtils.writeCertificates(certificates, bos);

        int added = getCertStoreWS().addCertificates(bos.toByteArray());

        importCount = importCount + added;
    }

    /*
     * Returns true if the certificate is to be accepted for import.
     */
    private boolean accept(X509Certificate certificate) {
        if (ignoreExpired && X509CertificateInspector.isExpired(certificate)) {
            return false;
        }

        boolean isSelfSigned = X509CertificateInspector.isSelfSigned(certificate);

        if (checkSelfSigned && !isSelfSigned) {
            return false;
        }

        if (ignoreSelfSigned && isSelfSigned) {
            return false;
        }

        return true;
    }

    /*
     * Called when the form is submitted.
     */
    public void onSuccess()
            throws NoSuchProviderException, IOException, WebServiceCheckedException, CertificateException {
        importCount = new Integer(0);

        /*
         * It should already be checked that the file contains certificates.
         */
        Collection<X509Certificate> certificates = CertificateUtils.readX509Certificates(file.getStream());

        totalCount = certificates.size();

        Collection<X509Certificate> batch = new LinkedList<X509Certificate>();

        for (X509Certificate certificate : certificates) {
            if (accept(certificate)) {
                batch.add(certificate);

                if (batch.size() >= maxBatchSize) {
                    addCertificates(batch);

                    batch.clear();
                }
            }
        }

        /*
         * Check if there are still some certificates left to add (happens when the number
         * of certificates is not a multiple of maxBatchSize)
         */
        if (batch.size() > 0) {
            addCertificates(batch);
        }
    }

    /*
     * Event handler called when the cancel button is pressed.
     */
    public Object onCancel() {
        return store == CertificateStore.CERTIFICATES ? Certificates.class : Roots.class;
    }

    public boolean isImportError() {
        return importError;
    }

    public String getImportErrorMessage() {
        return importErrorMessage;
    }
}