org.neociclo.odetteftp.util.EnvelopingUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.neociclo.odetteftp.util.EnvelopingUtil.java

Source

/**
 * The Accord Project, http://accordproject.org
 * Copyright (C) 2005-2013 Rafael Marins, http://rafaelmarins.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.neociclo.odetteftp.util;

import static org.neociclo.odetteftp.protocol.CommandExchangeBuffer.DEFAULT_PROTOCOL_CHARSET;
import static org.neociclo.odetteftp.protocol.CommandExchangeBuffer.formatAttribute;
import static org.neociclo.odetteftp.protocol.v20.CommandBuilderVer20.formatDate;
import static org.neociclo.odetteftp.protocol.v20.CommandBuilderVer20.formatTime;
import static org.neociclo.odetteftp.protocol.v20.ReleaseFormatVer20.EERP_V20;
import static org.neociclo.odetteftp.util.SecurityUtil.*;
import static org.neociclo.odetteftp.util.ProtocolUtil.padd;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import org.bouncycastle.asn1.cms.KeyTransRecipientInfo;
import org.bouncycastle.asn1.cms.SignerIdentifier;
import org.bouncycastle.asn1.x500.X500Name;

import org.bouncycastle.cms.CMSCompressedDataGenerator;
import org.bouncycastle.cms.CMSCompressedDataParser;
import org.bouncycastle.cms.CMSCompressedDataStreamGenerator;
import org.bouncycastle.cms.CMSEnvelopedData;
import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
import org.bouncycastle.cms.CMSEnvelopedDataParser;
import org.bouncycastle.cms.CMSEnvelopedDataStreamGenerator;
import org.bouncycastle.cms.CMSEnvelopedGenerator;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSProcessable;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.CMSSignedDataParser;
import org.bouncycastle.cms.CMSSignedDataStreamGenerator;
import org.bouncycastle.cms.CMSSignedGenerator;
import org.bouncycastle.cms.RecipientId;
import org.bouncycastle.cms.RecipientInformation;
import org.bouncycastle.cms.RecipientInformationStore;
import org.bouncycastle.cms.SignerId;
import org.bouncycastle.cms.SignerInfoGenerator;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
import org.neociclo.odetteftp.protocol.v20.CipherSuite;
import org.neociclo.odetteftp.protocol.v20.DefaultSignedDeliveryNotification;
import org.neociclo.odetteftp.protocol.v20.SignedDeliveryNotification;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Rafael Marins
 */
public class EnvelopingUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(EnvelopingUtil.class);

    /**
     * Generate an EnvelopedData object by encrypting the content using the
     * partner's public certificate with the specified CipherSuite.
     * 
     * @param content
     *            the data to be encrypted
     * @param cipherSel
     *            ODETTE-FTP like cipher suite selection
     * @param cert
     *            partner's public certificate used to produce encrypted data
     * @return
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws CMSException
     * @throws IOException
     */
    public static byte[] createEnvelopedData(byte[] content, CipherSuite cipherSel, X509Certificate cert)
            throws NoSuchAlgorithmException, NoSuchProviderException, CMSException, IOException {

        installBouncyCastleProviderIfNecessary();

        byte[] encoded = null;

        // set up the generator
        CMSEnvelopedDataGenerator gen = new CMSEnvelopedDataGenerator();

        gen.addKeyTransRecipient(cert);

        // create the enveloped-data object
        CMSProcessable data = new CMSProcessableByteArray(content);

        String algorithm = asEncryptionAlgorithm(cipherSel);
        CMSEnvelopedData enveloped = gen.generate(data, algorithm, BC_PROVIDER);
        encoded = enveloped.getEncoded();

        return encoded;
    }

    public static void createEnvelopedData(InputStream dataStream, OutputStream outStream, CipherSuite cipherSel,
            X509Certificate cert)
            throws NoSuchAlgorithmException, NoSuchProviderException, CMSException, IOException {

        OutputStream enveloped = openEnvelopedDataStreamGenerator(outStream, cipherSel, cert);
        IoUtil.copyStream(dataStream, enveloped);

    }

    public static OutputStream openEnvelopedDataStreamGenerator(OutputStream outStream, CipherSuite cipherSel,
            X509Certificate cert)
            throws NoSuchAlgorithmException, NoSuchProviderException, CMSException, IOException {

        installBouncyCastleProviderIfNecessary();

        // set up the generator
        CMSEnvelopedDataStreamGenerator gen = new CMSEnvelopedDataStreamGenerator();

        gen.addKeyTransRecipient(cert);

        String algorithm = asEncryptionAlgorithm(cipherSel);

        // create the enveloped-data stream
        OutputStream enveloped = gen.open(outStream, algorithm, BC_PROVIDER);

        return enveloped;
    }

    /**
     * 
     * @param cryptData
     *            InputStream of encapsulated encrypted data
     * @param cert
     *            user secure certificate used to match the recipient identifier
     * @param key
     *            user private key used to decrypt the encapsulated data
     * @return InputStream the original data stream (decrypted)
     * @throws CMSException
     * @throws IOException
     * @throws NoSuchProviderException
     */
    public static InputStream openEnvelopedDataParser(InputStream cryptData, X509Certificate cert, PrivateKey key)
            throws CMSException, IOException, NoSuchProviderException {

        installBouncyCastleProviderIfNecessary();

        // set up the parser
        CMSEnvelopedDataParser ep = new CMSEnvelopedDataParser(cryptData);

        // TODO validate the receiving enveloped-data against supported
        // algorithms

        // look for our recipient identifier
        RecipientId recId = new org.bouncycastle.cms.KeyTransRecipientId(
                new X500Name(cert.getIssuerX500Principal().getName()), cert.getSerialNumber());

        RecipientInformationStore recipients = ep.getRecipientInfos();
        RecipientInformation recipient = recipients.get(recId);

        if (recipient != null) {
            // return the decrypting parser InputStream
            InputStream parserStream = recipient.getContentStream(key, BC_PROVIDER).getContentStream();
            return parserStream;
        }

        // TODO raise a kind of invalid certificate exception instead of null
        // or recipient not found

        return null;

    }

    /**
     * 
     * @param compressedData
     *            InputStream of encapsulated compressed data
     * @return InputStream the original (uncompressed) readable data stream
     * @throws CMSException
     */
    public static InputStream openCompressedDataParser(InputStream compressedData) throws CMSException {

        // set up the parser and retrieve the original data stream
        CMSCompressedDataParser cp = new CMSCompressedDataParser(compressedData);
        InputStream contentStream = cp.getContent().getContentStream();

        return contentStream;
    }

    public static InputStream openSignedDataParser(InputStream sigData, final X509Certificate checkCert)
            throws CMSException {
        return openSignedDataParser(sigData, checkCert, null);
    }

    public static InputStream openSignedDataParser(InputStream sigData, final X509Certificate checkCert,
            final SignatureVerifyResult checkResult) throws CMSException {

        installBouncyCastleProviderIfNecessary();

        // set up the parser
        final CMSSignedDataParser sp = new CMSSignedDataParser(sigData);

        // TODO what to do? the validity of the certificate isn't verified here

        //
        // Perform signature verification.
        //
        // Create a runnable block which is executed after the returned
        // input stream is completely read (end of stream is reached). This is
        // strictly important, because we are in a streaming mode the order of
        // the operations is important.
        // 

        final Runnable signatureChecker = new Runnable() {
            public void run() {
                try {
                    SignerInformationStore signers = sp.getSignerInfos();

                    // lookup signer by matching with the given certificate

                    SignerId sigId = new SignerId(new X500Name(checkCert.getIssuerX500Principal().getName()),
                            checkCert.getSerialNumber());

                    SignerInformation signer = signers.get(sigId);

                    // perform signature verification
                    if (signer != null) {

                        //
                        // verify that the signature is correct and that it was generated
                        // when the certificate was current
                        //
                        if (signer.verify(checkCert, BC_PROVIDER)) {
                            // signature verified
                            if (checkResult != null) {
                                checkResult.setSuccess();
                            }
                        } else {
                            // signature failed!!!
                            if (checkResult != null) {
                                checkResult.setFailure();
                            }
                        }

                    } else {

                        // signer not found
                        if (checkResult != null) {
                            checkResult.setError(new Exception("Provided check certificate doesn't match."));
                        }
                    }

                } catch (Exception e) {
                    if (checkResult != null) {
                        checkResult.setError(e);
                    }
                }

            }
        };

        //
        // Return content stream from the encapsulated data.
        //
        // A simple input stream is returned, where readable bytes represents
        // the original content data (without signatures) from the encapsulated
        // signed envelope. But a wrapping InputStream is created to execute the
        // signature verification after the buffer is completely read.
        //

        final InputStream contentStream = sp.getSignedContent().getContentStream();

        InputStream endOfStreamSignatureCheckInputStream = new InputStream() {

            /**
             * Used to avoid running the signature checker above multiple times. 
             */
            private boolean alreadyReachedEof = false;

            @Override
            public int read() throws IOException {
                int b = contentStream.read();
                if (b == -1 && !alreadyReachedEof) {
                    alreadyReachedEof = true;
                    signatureChecker.run();
                }
                return b;
            }
        };

        return endOfStreamSignatureCheckInputStream;

    }

    public static void createSignedData(File data, File output, CipherSuite cipherSuite, X509Certificate cert,
            PrivateKey key) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException,
            CMSException, IOException {

        // open compressed data stream
        FileOutputStream outStream = null;
        try {
            outStream = new FileOutputStream(output);
        } catch (FileNotFoundException e) {
            throw new FileNotFoundException("Failed to create SignedData. Cannot open output signing data file: "
                    + output.getAbsolutePath() + ". " + e.getMessage());
        }

        // generate compressed data stream
        FileInputStream dataStream = null;
        try {
            dataStream = new FileInputStream(data);
            createSignedData(dataStream, cipherSuite, outStream, cert, key);
        } catch (FileNotFoundException e) {
            throw new FileNotFoundException("Failed to create SignedData. Cannot to open input file to sign data: "
                    + data.getAbsolutePath() + ". " + e.getMessage());
        } finally {
            try {
                dataStream.close();
            } catch (Throwable t) {
                // ignore
            }
            try {
                outStream.close();
            } catch (Throwable t) {
                // ignore
            }
        }

    }

    public static void createSignedData(InputStream dataStream, CipherSuite cipherSuite, OutputStream outStream,
            X509Certificate cert, PrivateKey key) throws InvalidKeyException, NoSuchAlgorithmException,
            NoSuchProviderException, CMSException, IOException {

        OutputStream signed = openSignedDataStreamGenerator(outStream, cipherSuite, cert, key);
        IoUtil.copyStream(dataStream, signed);

    }

    public static OutputStream openSignedDataStreamGenerator(OutputStream outStream, CipherSuite cipherSuite,
            X509Certificate cert, PrivateKey key) throws NoSuchAlgorithmException, NoSuchProviderException,
            CMSException, IOException, InvalidKeyException {

        installBouncyCastleProviderIfNecessary();

        // set up the generator
        CMSSignedDataStreamGenerator gen = new CMSSignedDataStreamGenerator();

        gen.addSigner(key, cert, asDigestAlgorithm(cipherSuite), BC_PROVIDER);

        // create the signed-data stream
        OutputStream signed = gen.open(outStream, true);

        return signed;
    }

    public static void createEnvelopedData(File data, File output, CipherSuite cipherSel, X509Certificate cert)
            throws NoSuchAlgorithmException, NoSuchProviderException, CMSException, IOException {

        // open compressed data stream
        FileOutputStream outStream = null;
        try {
            outStream = new FileOutputStream(output);
        } catch (FileNotFoundException e) {
            throw new FileNotFoundException(
                    "Failed to create EnvelopedData. Cannot open output enveloping data file: "
                            + output.getAbsolutePath() + ". " + e.getMessage());
        }

        // generate compressed data stream
        FileInputStream dataStream = null;
        try {
            dataStream = new FileInputStream(data);
            createEnvelopedData(dataStream, outStream, cipherSel, cert);
        } catch (FileNotFoundException e) {
            throw new FileNotFoundException(
                    "Failed to create EnvelopedData. Cannot to open input file to envelope data: "
                            + data.getAbsolutePath() + ". " + e.getMessage());
        } finally {
            try {
                dataStream.close();
            } catch (Throwable t) {
                // ignore
            }
            try {
                outStream.close();
            } catch (Throwable t) {
                // ignore
            }
        }

    }

    public static void createEnvelopedData(String dataPath, String outputPath, CipherSuite cipherSel,
            X509Certificate cert)
            throws NoSuchAlgorithmException, NoSuchProviderException, CMSException, IOException {

        // create input and output file objects
        File input = new File(dataPath);
        File output = new File(outputPath);

        // generate enveloped data stream
        createEnvelopedData(input, output, cipherSel, cert);
    }

    /**
     * Generate a SignedData object using SHA-1 digest.
     * 
     * @param content
     *            the data to be signed
     * @param cipherSuite
     * @param cert
     *            private certificate used in conjunction with private key
     * @param key
     *            private key used to produce the signed-data object
     * @return the encoded signed-data object
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws CMSException
     * @throws IOException
     */
    public static byte[] createSignedData(byte[] content, CipherSuite cipherSuite, X509Certificate cert,
            PrivateKey key) throws NoSuchAlgorithmException, NoSuchProviderException, CMSException, IOException {

        installBouncyCastleProviderIfNecessary();

        // set up the generator
        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();

        gen.addSigner(key, cert, asDigestAlgorithm(cipherSuite));

        // create the signed-data object
        CMSProcessable data = new CMSProcessableByteArray(content);
        CMSSignedData signed = gen.generate(data, BC_PROVIDER);

        return signed.getEncoded();
    }

    /**
     * Return null if certificate's recipientId could not be found within the
     * encoded envelope - typically when using a bad certificate to decrypt the
     * authentication challenge encrypted using other public certificate.
     * 
     * @param encoded
     * @param cert
     * @param key
     * @return
     * @throws NoSuchProviderException
     * @throws CMSException
     * @throws IOException
     */
    public static byte[] parseEnvelopedData(byte[] encoded, X509Certificate cert, PrivateKey key)
            throws NoSuchProviderException, CMSException, IOException {

        installBouncyCastleProviderIfNecessary();

        byte[] data = null;

        CMSEnvelopedData enveloped = new CMSEnvelopedData(encoded);

        // TODO validate the receiving enveloped-data against supported
        // algorithms

        // look for our recipient identifier
        RecipientId recId = new org.bouncycastle.cms.KeyTransRecipientId(
                new X500Name(cert.getIssuerX500Principal().getName()), cert.getSerialNumber());

        RecipientInformationStore recipients = enveloped.getRecipientInfos();
        RecipientInformation recipient = recipients.get(recId);

        if (recipient != null) {
            // decrypt the data
            data = recipient.getContent(key, BC_PROVIDER);
        }

        return data;

    }

    public static void parseEnvelopedDataContentStream(InputStream envelopedStream, OutputStream outStream,
            X509Certificate cert, PrivateKey key) throws NoSuchProviderException, CMSException, IOException {

        installBouncyCastleProviderIfNecessary();

        // use the CMS parser to decrypt the EnvelopedData
        CMSEnvelopedDataParser parser = new CMSEnvelopedDataParser(envelopedStream);

        // TODO validate the receiving enveloped-data against supported
        // algorithms

        // look for our recipient identifier
        RecipientId recId = new org.bouncycastle.cms.KeyTransRecipientId(
                new X500Name(cert.getIssuerX500Principal().getName()), cert.getSerialNumber());

        RecipientInformationStore recipients = parser.getRecipientInfos();
        RecipientInformation recipient = recipients.get(recId);

        if (recipient != null) {
            // decrypt the data
            InputStream unenveloped = recipient.getContentStream(key, BC_PROVIDER).getContentStream();
            IoUtil.copyStream(unenveloped, outStream);
        }

    }

    /**
     * Retrieve the signed content from a SignedData object. Signature MUST BE
     * VERIFIED apart since it's the original data without the signature
     * information.
     * 
     * @param encoded
     *            the SignedData object
     * @return the original data from signed content
     * @throws CMSException
     */
    public static byte[] parseSignedData(byte[] encoded) throws CMSException {

        installBouncyCastleProviderIfNecessary();

        CMSSignedData signed = new CMSSignedData(encoded);

        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        try {
            signed.getSignedContent().write(bout);
        } catch (IOException e) {
            // ignore in a hope it won't happen with ByteArrayOutputStream
            LOGGER.error("parseSignedData() - Failed to retrieve SignedData content.", e);
            return null;
        }
        byte[] content = bout.toByteArray();

        return content;
    }

    public static byte[] parseSignedData(byte[] encoded, X509Certificate checkCert,
            SignatureVerifyResult checkResult) throws CMSException, IOException {

        installBouncyCastleProviderIfNecessary();

        ByteArrayInputStream sigData = new ByteArrayInputStream(encoded);
        InputStream contentStream = openSignedDataParser(sigData, checkCert, checkResult);

        ByteArrayOutputStream content = new ByteArrayOutputStream();
        IoUtil.copyStream(contentStream, content);

        return content.toByteArray();
    }

    public static void parseSignedDataContentStream(InputStream signedStream, OutputStream outStream,
            X509Certificate cert) throws CMSException, IOException {

        installBouncyCastleProviderIfNecessary();

        // use the CMS parser to unwrap signature from the SignedData
        CMSSignedDataParser parser = new CMSSignedDataParser(signedStream);

        // TODO do verify the signature

        InputStream contentStream = parser.getSignedContent().getContentStream();
        IoUtil.copyStream(contentStream, outStream);

    }

    public static void createCompressedData(String dataPath, String outputPath) throws IOException {

        // create input and output file objects
        File input = new File(dataPath);
        File output = new File(outputPath);

        // generate compressed data stream
        createCompressedData(input, output);
    }

    public static void createCompressedData(File data, File output) throws IOException {

        // open compressed data stream
        FileOutputStream outStream = null;
        try {
            outStream = new FileOutputStream(output);
        } catch (FileNotFoundException e) {
            throw new FileNotFoundException(
                    "Failed to create CompressedData. Cannot open output compressed data file: "
                            + output.getAbsolutePath() + ". " + e.getMessage());
        }

        // generate compressed data stream
        FileInputStream dataStream = null;
        try {
            dataStream = new FileInputStream(data);
            createCompressedData(dataStream, outStream);
        } catch (FileNotFoundException e) {
            throw new FileNotFoundException(
                    "Failed to create CompressedData. Cannot to open input file to generated compressed data: "
                            + data.getAbsolutePath() + ". " + e.getMessage());
        } finally {
            try {
                dataStream.close();
            } catch (Throwable t) {
                // ignore
            }
            try {
                outStream.close();
            } catch (Throwable t) {
                // ignore
            }
        }

    }

    public static void createCompressedData(InputStream dataStream, OutputStream outStream) throws IOException {

        OutputStream compressed = openCompressedDataStreamGenerator(outStream);
        IoUtil.copyStream(dataStream, compressed);

    }

    public static OutputStream openCompressedDataStreamGenerator(OutputStream outStream) throws IOException {

        CMSCompressedDataStreamGenerator gen = new CMSCompressedDataStreamGenerator();

        OutputStream compressed = gen.open(outStream, CMSCompressedDataGenerator.ZLIB);
        return compressed;

    }

    public static void createFileFromCompressedData(String compressedDataPath, String outputPath)
            throws CMSException, IOException {

        // create input and output file objects
        File input = new File(compressedDataPath);
        File output = new File(outputPath);

        // generate compressed data stream
        createFileFromCompressedData(input, output);
    }

    public static void createFileFromCompressedData(File compressedData, File output)
            throws CMSException, IOException {

        // open compressed data input stream
        FileInputStream in = null;
        try {
            in = new FileInputStream(compressedData);
        } catch (FileNotFoundException e) {
            throw new FileNotFoundException(
                    "Failed to create file from CompressedData. Cannot open output compressed data file: "
                            + compressedData.getAbsolutePath() + ". " + e.getMessage());
        }

        // create data file from CompressedData
        FileOutputStream outStream = null;
        try {
            outStream = new FileOutputStream(output);
            parseCompressedDataContentStream(in, outStream);
        } catch (FileNotFoundException e) {
            throw new FileNotFoundException("Failed to create file from CompressedData. Cannot open output file: "
                    + output.getAbsolutePath() + ". " + e.getMessage());
        } finally {
            try {
                in.close();
            } catch (Throwable t) {
                // ignore
            }
            try {
                outStream.close();
            } catch (Throwable t) {
                // ignore
            }
        }

    }

    public static void createFileFromEnvelopedData(String envelopedDataPath, String outputPath,
            X509Certificate cert, PrivateKey key) throws NoSuchProviderException, CMSException, IOException {

        // create input and output file objects
        File input = new File(envelopedDataPath);
        File output = new File(outputPath);

        // generate compressed data stream
        createFileFromEnvelopedData(input, output, cert, key);

    }

    public static void createFileFromEnvelopedData(File envelopedData, File output, X509Certificate cert,
            PrivateKey key) throws NoSuchProviderException, CMSException, IOException {

        // open compressed data input stream
        FileInputStream in = null;
        try {
            in = new FileInputStream(envelopedData);
        } catch (FileNotFoundException e) {
            throw new FileNotFoundException(
                    "Failed to create file from EnvelopedData. Cannot open output enveloped data file: "
                            + envelopedData.getAbsolutePath() + ". " + e.getMessage());
        }

        // create data file from CompressedData
        FileOutputStream outStream = null;
        try {
            outStream = new FileOutputStream(output);
            parseEnvelopedDataContentStream(in, outStream, cert, key);
        } catch (FileNotFoundException e) {
            throw new FileNotFoundException("Failed to create file from EnvelopedData. Cannot open output file: "
                    + output.getAbsolutePath() + ". " + e.getMessage());
        } finally {
            try {
                in.close();
            } catch (Throwable t) {
                // ignore
            }
            try {
                outStream.close();
            } catch (Throwable t) {
                // ignore
            }
        }

    }

    public static void parseCompressedDataContentStream(InputStream compressedData, OutputStream outStream)
            throws CMSException, IOException {

        // use the CMS parser to uncompress the CompressedData
        CMSCompressedDataParser cp = new CMSCompressedDataParser(compressedData);
        InputStream uncompressed = cp.getContent().getContentStream();

        IoUtil.copyStream(uncompressed, outStream);

    }

    public static void createFileFromSignedData(File signedData, File output, X509Certificate cert)
            throws CMSException, IOException {

        // open compressed data input stream
        FileInputStream in = null;
        try {
            in = new FileInputStream(signedData);
        } catch (FileNotFoundException e) {
            throw new FileNotFoundException(
                    "Failed to create file from SignedData. Cannot open output signed data file: "
                            + signedData.getAbsolutePath() + ". " + e.getMessage());
        }

        // create data file from CompressedData
        FileOutputStream outStream = null;
        try {
            outStream = new FileOutputStream(output);
            parseSignedDataContentStream(in, outStream, cert);
        } catch (FileNotFoundException e) {
            throw new FileNotFoundException("Failed to create file from SignedData. Cannot open output file: "
                    + output.getAbsolutePath() + ". " + e.getMessage());
        } finally {
            try {
                in.close();
            } catch (Throwable t) {
                // ignore
            }
            try {
                outStream.close();
            } catch (Throwable t) {
                // ignore
            }
        }

    }

    public static void addNotifSignature(DefaultSignedDeliveryNotification notif, CipherSuite cipherSuite,
            X509Certificate userCert, PrivateKey userPrivateKey)
            throws NoSuchAlgorithmException, NoSuchProviderException, IOException, CMSException {

        if (notif == null)
            throw new NullPointerException("notif");
        if (notif.getDatasetName() == null)
            throw new IllegalArgumentException("Delivery Notification object's DatasetName is null.");
        if (notif.getDateTime() == null)
            throw new IllegalArgumentException("Delivery Notification object's DateTime is null.");
        if (notif.getDestination() == null)
            throw new IllegalArgumentException("Delivery Notification object's Destination is null.");
        if (notif.getOriginator() == null)
            throw new IllegalArgumentException("Delivery Notification object's Originator is null.");
        if (notif.getCreator() == null)
            throw new IllegalArgumentException("Delivery Notification object's Creator is null.");
        if (notif.getVirtualFileHash() == null)
            throw new IllegalArgumentException("Delivery Notification object's Virtual File Hash is null.");
        if (cipherSuite == null)
            throw new NullPointerException("cipherSuite");
        if (userCert == null)
            throw new NullPointerException("userCert");
        if (userPrivateKey == null)
            throw new NullPointerException("userPrivateKey");

        // prepare the signing data
        byte[] data = getNotifSigningData(notif);

        // perform signing and set into the acknowledge object
        byte[] signature = createSignedData(data, cipherSuite, userCert, userPrivateKey);
        notif.setNotificationSignature(signature);

    }

    /**
     * Prepare the data buffer for signing from the acknowledge object.
     * 
     * @param info
     * @return
     * @throws UnsupportedEncodingException
     */
    public static byte[] getNotifSigningData(SignedDeliveryNotification info) throws UnsupportedEncodingException {

        // use EERP_V20 fields as properties are the same for NERP formatting

        short ticker = info.getTicker();
        String timeWithCounter = formatTime(info.getDateTime()) + padd(Short.toString(ticker), 4, true, '0');

        StringBuffer sb = new StringBuffer();
        sb.append(formatAttribute(EERP_V20.getField("EERPDSN"), info.getDatasetName()));
        sb.append(formatDate(info.getDateTime()));
        sb.append(timeWithCounter);
        sb.append(formatAttribute(EERP_V20.getField("EERPDEST"), info.getDestination()));
        sb.append(formatAttribute(EERP_V20.getField("EERPORIG"), info.getOriginator()));

        byte[] text = sb.toString().getBytes(DEFAULT_PROTOCOL_CHARSET);
        sb = null;

        byte[] hash = info.getVirtualFileHash();

        if (text == null || hash == null)
            return null;

        byte[] data = new byte[text.length + hash.length];
        System.arraycopy(text, 0, data, 0, text.length);
        System.arraycopy(hash, 0, data, text.length, hash.length);

        return data;
    }

    public static String asEncryptionAlgorithm(CipherSuite cipherSuite) {
        if (cipherSuite == CipherSuite.TRIPLEDES_RSA_SHA1)
            return CMSEnvelopedGenerator.DES_EDE3_CBC;
        else if (cipherSuite == CipherSuite.AES_RSA_SHA1)
            return CMSEnvelopedGenerator.AES256_CBC;
        else
            return null;
    }

    public static String asDigestAlgorithm(CipherSuite cs) {
        if (cs == CipherSuite.TRIPLEDES_RSA_SHA1 || cs == CipherSuite.AES_RSA_SHA1) {
            return CMSSignedGenerator.DIGEST_SHA1;
        } else {
            return null;
        }
    }

}