at.gv.egiz.bku.slcommands.impl.cms.Signature.java Source code

Java tutorial

Introduction

Here is the source code for at.gv.egiz.bku.slcommands.impl.cms.Signature.java

Source

/*
 * Copyright 2013 by Graz University of Technology, Austria
 * MOCCA has been developed by the E-Government Innovation Center EGIZ, a joint
 * initiative of the Federal Chancellery Austria and Graz University of Technology.
 *
 * Licensed under the EUPL, Version 1.1 or - as soon they will be approved by
 * the European Commission - subsequent versions of the EUPL (the "Licence");
 * You may not use this work except in compliance with the Licence.
 * You may obtain a copy of the Licence at:
 * http://www.osor.eu/eupl/
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the Licence is distributed on an "AS IS" basis,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the Licence for the specific language governing permissions and
 * limitations under the Licence.
 *
 * This product combines work with different licenses. See the "NOTICE" text
 * file for details on the various modules and licenses.
 * The "NOTICE" text file is part of the distribution. Any derivative works
 * that you distribute must include a readable copy of the "NOTICE" text file.
 */

package at.gv.egiz.bku.slcommands.impl.cms;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.InvalidParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import javax.xml.crypto.dsig.DigestMethod;

import org.apache.commons.lang.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3._2000._09.xmldsig_.DigestMethodType;

import at.buergerkarte.namespaces.securitylayer._1_2_3.CMSDataObjectOptionalMetaType;
import at.buergerkarte.namespaces.securitylayer._1_2_3.CMSDataObjectRequiredMetaType;
import at.buergerkarte.namespaces.securitylayer._1_2_3.DigestAndRefType;
import at.buergerkarte.namespaces.securitylayer._1_2_3.ExcludedByteRangeType;
import at.buergerkarte.namespaces.securitylayer._1_2_3.MetaInfoType;
import at.gv.egiz.bku.slcommands.impl.xsect.AlgorithmMethodFactory;
import at.gv.egiz.bku.slcommands.impl.xsect.AlgorithmMethodFactoryImpl;
import at.gv.egiz.bku.slcommands.impl.xsect.STALSignatureException;
import at.gv.egiz.bku.slexceptions.SLCommandException;
import at.gv.egiz.bku.utils.urldereferencer.URLDereferencer;
import at.gv.egiz.stal.HashDataInput;
import at.gv.egiz.stal.STAL;
import iaik.asn1.ASN1Object;
import iaik.asn1.CodingException;
import iaik.asn1.ObjectID;
import iaik.asn1.SEQUENCE;
import iaik.asn1.UTF8String;
import iaik.asn1.structures.AlgorithmID;
import iaik.asn1.structures.Attribute;
import iaik.asn1.structures.ChoiceOfTime;
import iaik.cms.CMSException;
import iaik.cms.CMSSignatureException;
import iaik.cms.CertificateIdentifier;
import iaik.cms.ContentInfo;
import iaik.cms.IssuerAndSerialNumber;
import iaik.cms.SignedData;
import iaik.cms.SignerInfo;
import iaik.smime.ess.ESSCertID;
import iaik.smime.ess.ESSCertIDv2;
import iaik.x509.X509ExtensionException;

/**
 * This class represents a CMS-Signature as to be created by the
 * security layer command <code>CreateCMSSignatureRequest</code>.
 * 
 * @author tkellner
 */
public class Signature {

    public final static String ID_AA_ETS_MIMETYPE = "0.4.0.1733.2.1";

    /**
     * Logging facility.
     */
    private final Logger log = LoggerFactory.getLogger(Signature.class);

    protected SignedData signedData;
    protected SignerInfo signerInfo;
    protected byte[] signedDocument;
    protected String mimeType;
    protected AlgorithmID signatureAlgorithm;
    protected AlgorithmID digestAlgorithm;
    protected byte[] digestValue;
    protected String signatureAlgorithmURI;
    protected String digestAlgorithmURI;
    protected ExcludedByteRangeType excludedByteRange;
    private CMSHashDataInput hashDataInput;

    public Signature(CMSDataObjectOptionalMetaType dataObject, String structure, X509Certificate signingCertificate,
            Date signingTime, URLDereferencer urlDereferencer, boolean useStrongHash)
            throws NoSuchAlgorithmException, CertificateEncodingException, CertificateException,
            X509ExtensionException, InvalidParameterException, CodingException, SLCommandException, IOException,
            CMSException {
        int mode = structure.equalsIgnoreCase("enveloping") ? SignedData.IMPLICIT : SignedData.EXPLICIT;
        if (dataObject.getContent() != null) {
            String filename = null;
            byte[] dataToBeSigned = getContent(dataObject, urlDereferencer);
            this.signedData = new SignedData(dataToBeSigned, mode);
            MetaInfoType metaInfo = dataObject.getMetaInfo();
            if (metaInfo != null) {
                this.mimeType = metaInfo.getMimeType();
                filename = metaInfo.getDescription(); // security layer doesn't specify explicit filename property for
                                                      // single signature requests
            }
            hashDataInput = new CMSHashDataInput(signedDocument, mimeType);
            hashDataInput.setFilename(filename);
        } else {
            DigestAndRefType digestAndRef = dataObject.getDigestAndRef();
            DigestMethodType digestMethod = digestAndRef.getDigestMethod();

            hashDataInput = new ReferencedHashDataInput(dataObject.getMetaInfo().getMimeType(), urlDereferencer,
                    digestAndRef.getReference(), dataObject.getExcludedByteRange());

            try {
                digestAlgorithm = getAlgorithmID(digestMethod.getAlgorithm());
            } catch (URISyntaxException e) {
                //TODO: choose proper execption
                throw new NoSuchAlgorithmException(e);
            }
            digestValue = digestAndRef.getDigestValue();
            this.signedData = new SignedData(ObjectID.pkcs7_data);
        }
        setAlgorithmIDs(signingCertificate, useStrongHash);
        createSignerInfo(signingCertificate);
        setSignerCertificate(signingCertificate);
        this.mimeType = dataObject.getMetaInfo().getMimeType();

        setAttributes(this.mimeType, signingCertificate, signingTime);
    }

    public Signature(CMSDataObjectRequiredMetaType dataObject, String structure, X509Certificate signingCertificate,
            URLDereferencer urlDereferencer, boolean useStrongHash)
            throws NoSuchAlgorithmException, CertificateEncodingException, CertificateException,
            X509ExtensionException, InvalidParameterException, CodingException, SLCommandException, IOException {
        byte[] dataToBeSigned = getContent(dataObject, urlDereferencer);
        int mode = structure.equalsIgnoreCase("enveloping") ? SignedData.IMPLICIT : SignedData.EXPLICIT;
        this.signedData = new SignedData(dataToBeSigned, mode);
        setAlgorithmIDs(signingCertificate, useStrongHash);
        createSignerInfo(signingCertificate);
        setSignerCertificate(signingCertificate);

        hashDataInput = new CMSHashDataInput(signedDocument, mimeType);
        MetaInfoType metaInfo = dataObject.getMetaInfo();
        if (metaInfo != null) {
            // security layer doesn't specify explicit filename property for
            // single signature requests
            hashDataInput.setFilename(metaInfo.getDescription());
        }
        setAttributes(signingCertificate);
    }

    private void createSignerInfo(X509Certificate signingCertificate)
            throws CertificateEncodingException, CertificateException {
        iaik.x509.X509Certificate sigcert = new iaik.x509.X509Certificate(signingCertificate.getEncoded());
        CertificateIdentifier signerIdentifier = new IssuerAndSerialNumber(sigcert);
        PrivateKey privateKey = new STALPrivateKey(signatureAlgorithmURI, digestAlgorithmURI);
        signerInfo = new SignerInfo(signerIdentifier, digestAlgorithm, signatureAlgorithm, privateKey);
    }

    private void setSignerCertificate(X509Certificate signingCertificate) {
        X509Certificate[] sigcerts = new X509Certificate[] { signingCertificate };
        signedData.addCertificates(sigcerts);
    }

    private void setAttributes(String mimeType, X509Certificate signingCertificate, Date signingTime)
            throws CertificateException, NoSuchAlgorithmException, CodingException {
        List<Attribute> attributes = new ArrayList<Attribute>();
        setMimeTypeAttrib(attributes, mimeType);
        setContentTypeAttrib(attributes);
        setSigningCertificateAttrib(attributes, signingCertificate);
        if (signingTime != null)
            setSigningTimeAttrib(attributes, signingTime);
        Attribute[] attributeArray = attributes.toArray(new Attribute[attributes.size()]);
        signerInfo.setSignedAttributes(attributeArray);
    }

    private void setAttributes(X509Certificate signingCertificate)
            throws CertificateException, NoSuchAlgorithmException, CodingException {
        List<Attribute> attributes = new ArrayList<Attribute>();
        setContentTypeAttrib(attributes);
        setSigningCertificateAttrib(attributes, signingCertificate);
        Attribute[] attributeArray = attributes.toArray(new Attribute[attributes.size()]);
        signerInfo.setSignedAttributes(attributeArray);
    }

    private void setMimeTypeAttrib(List<Attribute> attributes, String mimeType) {
        String oidStr = ID_AA_ETS_MIMETYPE;
        String name = "mime-type";
        ObjectID mimeTypeOID = new ObjectID(oidStr, name);

        Attribute mimeTypeAtt = new Attribute(mimeTypeOID, new ASN1Object[] { new UTF8String(mimeType) });
        attributes.add(mimeTypeAtt);
    }

    private void setContentTypeAttrib(List<Attribute> attributes) {
        Attribute contentType = new Attribute(ObjectID.contentType, new ASN1Object[] { ObjectID.cms_data });
        attributes.add(contentType);
    }

    private void setSigningCertificateAttrib(List<Attribute> attributes, X509Certificate signingCertificate)
            throws CertificateException, NoSuchAlgorithmException, CodingException {
        ObjectID id;
        ASN1Object value = new SEQUENCE();
        if (digestAlgorithm.equals(AlgorithmID.sha1)) {
            id = ObjectID.signingCertificate;
            value.addComponent(new ESSCertID(signingCertificate, true).toASN1Object());
        } else {
            id = ObjectID.signingCertificateV2;
            value.addComponent(new ESSCertIDv2(digestAlgorithm, signingCertificate, true).toASN1Object());
        }
        ASN1Object signingCert = new SEQUENCE();
        signingCert.addComponent(value);
        Attribute signingCertificateAttrib = new Attribute(id, new ASN1Object[] { signingCert });
        attributes.add(signingCertificateAttrib);
    }

    private void setSigningTimeAttrib(List<Attribute> attributes, Date date) {
        Attribute signingTime = new Attribute(ObjectID.signingTime,
                new ASN1Object[] { new ChoiceOfTime(date).toASN1Object() });
        attributes.add(signingTime);
    }

    private byte[] getContent(CMSDataObjectOptionalMetaType dataObject, URLDereferencer urlDereferencer)
            throws InvalidParameterException, SLCommandException, IOException {
        byte[] data = dataObject.getContent().getBase64Content();
        if (data == null) {
            String reference = dataObject.getContent().getReference();
            if (reference == null)
                throw new SLCommandException(4003);
            InputStream is = urlDereferencer.dereference(reference).getStream();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            for (int i = is.read(buffer); i > -1; i = is.read(buffer)) {
                baos.write(buffer, 0, i);
            }
            data = baos.toByteArray();
            is.close();
        }
        this.signedDocument = data.clone();

        this.excludedByteRange = dataObject.getExcludedByteRange();
        if (this.excludedByteRange == null)
            return data;

        int from = this.excludedByteRange.getFrom().intValue();
        int to = this.excludedByteRange.getTo().intValue();
        if (from > data.length || to > data.length || from > to)
            throw new InvalidParameterException("ExcludedByteRange contains invalid data: [" + from + "-" + to
                    + "], Content length: " + data.length);

        // Fill ExcludedByteRange with 0s for document to display in viewer
        Arrays.fill(this.signedDocument, from, to + 1, (byte) 0);

        // Remove ExcludedByteRange from data to be signed
        byte[] first = null;
        byte[] second = null;
        if (from > 0)
            first = Arrays.copyOfRange(data, 0, from);
        if ((to + 1) < data.length)
            second = Arrays.copyOfRange(data, to + 1, data.length);
        data = ArrayUtils.addAll(first, second);
        log.debug("ExcludedByteRange [" + from + "-" + to + "], Content length: " + data.length);
        return data;
    }

    private void setAlgorithmIDs(X509Certificate signingCertificate, boolean useStrongHash)
            throws NoSuchAlgorithmException {
        AlgorithmMethodFactory amf = new AlgorithmMethodFactoryImpl(signingCertificate, useStrongHash);
        signatureAlgorithmURI = amf.getSignatureAlgorithmURI();
        signatureAlgorithm = amf.getSignatureAlgorithmID();
        if (digestAlgorithm != null) {
            if (AlgorithmID.sha1.equals(digestAlgorithm)) {
                digestAlgorithmURI = DigestMethod.SHA1;
            } else if (AlgorithmID.sha256.equals(digestAlgorithm)) {
                digestAlgorithmURI = DigestMethod.SHA256;
            } else if (AlgorithmID.sha512.equals(digestAlgorithm)) {
                digestAlgorithmURI = DigestMethod.SHA512;
            } else if (AlgorithmID.ripeMd160.equals(digestAlgorithm)) {
                digestAlgorithmURI = DigestMethod.RIPEMD160;
            } else {
                throw new NoSuchAlgorithmException("Algorithm '" + digestAlgorithm + "' not supported.");
            }
        } else {
            digestAlgorithmURI = amf.getDigestAlgorithmURI();
            digestAlgorithm = amf.getDigestAlgorithmID();
        }
    }

    public HashDataInput getHashDataInput() {

        if (hashDataInput != null) {
            return hashDataInput;
        } else {
            return new CMSHashDataInput(signedDocument, mimeType);
        }
    }

    public byte[] sign(STAL stal, String keyboxIdentifier)
            throws CMSException, CMSSignatureException, SLCommandException {
        STALSecurityProvider securityProvider = new STALSecurityProvider(stal, keyboxIdentifier, getHashDataInput(),
                this.excludedByteRange);
        signedData.setSecurityProvider(securityProvider);
        try {
            signedData.addSignerInfo(signerInfo);
        } catch (NoSuchAlgorithmException e) {
            STALSignatureException stalSignatureException = securityProvider.getStalSignatureException();
            if (stalSignatureException != null) {
                throw new SLCommandException(stalSignatureException.getErrorCode());
            }
            throw new CMSSignatureException(e);
        }
        if (digestValue != null) {
            try {
                signedData.setMessageDigest(digestAlgorithm, digestValue);
            } catch (NoSuchAlgorithmException e) {
                throw new CMSSignatureException(e);
            }
        }
        ContentInfo contentInfo = new ContentInfo(signedData);
        return contentInfo.getEncoded();
    }

    protected AlgorithmID getAlgorithmID(String uri) throws URISyntaxException {
        String oid = null;
        URI urn = new URI(uri);
        String scheme = urn.getScheme();
        if ("URN".equalsIgnoreCase(scheme)) {
            String schemeSpecificPart = urn.getSchemeSpecificPart().toLowerCase();
            if (schemeSpecificPart.startsWith("oid:")) {
                oid = schemeSpecificPart.substring(4, schemeSpecificPart.length());
            }
        }
        return new AlgorithmID(new ObjectID(oid));
    }
}