org.opendatakit.services.utilities.EncryptionUtils.java Source code

Java tutorial

Introduction

Here is the source code for org.opendatakit.services.utilities.EncryptionUtils.java

Source

/*
 * Copyright (C) 2011-2013 University of Washington
 *
 * 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.opendatakit.services.utilities;

import java.io.ByteArrayInputStream;
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.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.lang3.CharEncoding;
import org.opendatakit.logging.WebLogger;
import org.opendatakit.utilities.FileSet;
import org.opendatakit.utilities.FileSet.MimeFile;
import org.opendatakit.utilities.ODKFileUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;

/**
 * Utility class for encrypting submissions during the SaveToDiskTask.
 *
 * @author mitchellsundt@gmail.com
 *
 */
public class EncryptionUtils {
    private static final String APPLICATION_OCTET_STREAM = "application/octet-stream";
    private static final String t = "EncryptionUtils";
    public static final String RSA_ALGORITHM = "RSA";
    // the symmetric key we are encrypting with RSA is only 256 bits... use
    // SHA-256
    public static final String ASYMMETRIC_ALGORITHM = "RSA/NONE/OAEPWithSHA256AndMGF1Padding";
    public static final String SYMMETRIC_ALGORITHM = "AES/CFB/PKCS5Padding";
    public static final int SYMMETRIC_KEY_LENGTH = 256;
    public static final int IV_BYTE_LENGTH = 16;

    // tags in the submission manifest

    private static final String XML_ENCRYPTED_TAG_NAMESPACE = "http://www.opendatakit.org/xforms/encrypted";
    private static final String XML_OPENROSA_NAMESPACE = "http://openrosa.org/xforms";
    private static final String DATA = "data";
    private static final String ID = "id";
    private static final String VERSION = "version";
    private static final String ENCRYPTED = "encrypted";
    private static final String BASE64_ENCRYPTED_KEY = "base64EncryptedKey";
    private static final String ENCRYPTED_XML_FILE = "encryptedXmlFile";
    private static final String META = "meta";
    private static final String INSTANCE_ID = "instanceID";
    private static final String MEDIA = "media";
    private static final String FILE = "file";
    private static final String BASE64_ENCRYPTED_ELEMENT_SIGNATURE = "base64EncryptedElementSignature";
    private static final String NEW_LINE = "\n";

    private EncryptionUtils() {
    }

    public static final class EncryptedFormInformation {
        public final String appName;
        public final String tableId;
        public final String instanceId;
        public final String base64EncryptedFileRsaPublicKey;
        public final PublicKey rsaPublicKey;
        public final String base64RsaEncryptedSymmetricKey;
        public final SecretKeySpec symmetricKey;
        public final byte[] ivSeedArray;
        private int ivCounter = 0;
        public final StringBuilder elementSignatureSource = new StringBuilder();
        public final Base64Wrapper wrapper;

        EncryptedFormInformation(String appName, String tableId, String xmlBase64RsaPublicKey, String instanceId,
                PublicKey rsaPublicKey, Base64Wrapper wrapper) {
            this.appName = appName;
            this.tableId = tableId;
            this.instanceId = instanceId;
            this.base64EncryptedFileRsaPublicKey = xmlBase64RsaPublicKey;
            this.rsaPublicKey = rsaPublicKey;
            this.wrapper = wrapper;

            // generate the symmetric key from random bits...

            SecureRandom r = new SecureRandom();
            byte[] key = new byte[SYMMETRIC_KEY_LENGTH / 8];
            r.nextBytes(key);
            SecretKeySpec sk = new SecretKeySpec(key, SYMMETRIC_ALGORITHM);
            symmetricKey = sk;

            // construct the fixed portion of the iv -- the ivSeedArray
            // this is the md5 hash of the instanceID and the symmetric key
            try {
                MessageDigest md = MessageDigest.getInstance("MD5");
                md.update(instanceId.getBytes(CharEncoding.UTF_8));
                md.update(key);
                byte[] messageDigest = md.digest();
                ivSeedArray = new byte[IV_BYTE_LENGTH];
                for (int i = 0; i < IV_BYTE_LENGTH; ++i) {
                    ivSeedArray[i] = messageDigest[(i % messageDigest.length)];
                }
            } catch (NoSuchAlgorithmException e) {
                WebLogger.getLogger(appName).e(t, e.toString());
                WebLogger.getLogger(appName).printStackTrace(e);
                throw new IllegalArgumentException(e.getMessage());
            } catch (UnsupportedEncodingException e) {
                WebLogger.getLogger(appName).e(t, e.toString());
                WebLogger.getLogger(appName).printStackTrace(e);
                throw new IllegalArgumentException(e.getMessage());
            }

            // construct the base64-encoded RSA-encrypted symmetric key
            try {
                Cipher pkCipher;
                pkCipher = Cipher.getInstance(ASYMMETRIC_ALGORITHM);
                // write AES key
                pkCipher.init(Cipher.ENCRYPT_MODE, rsaPublicKey);
                byte[] pkEncryptedKey = pkCipher.doFinal(key);
                String alg = pkCipher.getAlgorithm();
                WebLogger.getLogger(appName).i(t, "AlgorithmUsed: " + alg);
                base64RsaEncryptedSymmetricKey = wrapper.encodeToString(pkEncryptedKey);

            } catch (NoSuchAlgorithmException e) {
                WebLogger.getLogger(appName).e(t, "Unable to encrypt the symmetric key");
                WebLogger.getLogger(appName).printStackTrace(e);
                throw new IllegalArgumentException(e.getMessage());
            } catch (NoSuchPaddingException e) {
                WebLogger.getLogger(appName).e(t, "Unable to encrypt the symmetric key");
                WebLogger.getLogger(appName).printStackTrace(e);
                throw new IllegalArgumentException(e.getMessage());
            } catch (InvalidKeyException e) {
                WebLogger.getLogger(appName).e(t, "Unable to encrypt the symmetric key");
                WebLogger.getLogger(appName).printStackTrace(e);
                throw new IllegalArgumentException(e.getMessage());
            } catch (IllegalBlockSizeException e) {
                WebLogger.getLogger(appName).e(t, "Unable to encrypt the symmetric key");
                WebLogger.getLogger(appName).printStackTrace(e);
                throw new IllegalArgumentException(e.getMessage());
            } catch (BadPaddingException e) {
                WebLogger.getLogger(appName).e(t, "Unable to encrypt the symmetric key");
                WebLogger.getLogger(appName).printStackTrace(e);
                throw new IllegalArgumentException(e.getMessage());
            }

            // start building elementSignatureSource...
            appendElementSignatureSource(tableId);
            appendElementSignatureSource(base64RsaEncryptedSymmetricKey);

            appendElementSignatureSource(instanceId);
        }

        public void appendElementSignatureSource(String value) {
            elementSignatureSource.append(value).append("\n");
        }

        public void appendSubmissionFileSignatureSource(String contents, File file) {
            String md5Hash = ODKFileUtils.getNakedMd5Hash(appName, contents);
            appendElementSignatureSource(file.getName() + "::" + md5Hash);
        }

        public void appendFileSignatureSource(File file) {
            String md5Hash = ODKFileUtils.getNakedMd5Hash(appName, file);
            appendElementSignatureSource(file.getName() + "::" + md5Hash);
        }

        public String getBase64EncryptedElementSignature() {
            // Step 0: construct the text of the elements in
            // elementSignatureSource (done)
            // Where...
            // * Elements are separated by newline characters.
            // * Filename is the unencrypted filename (no .enc suffix).
            // * Md5 hashes of the unencrypted files' contents are converted
            // to zero-padded 32-character strings before concatenation.
            // Assumes this is in the order:
            // formId
            // version (omitted if null)
            // base64RsaEncryptedSymmetricKey
            // instanceId
            // for each media file { filename "::" md5Hash }
            // submission.xml "::" md5Hash

            // Step 1: construct the (raw) md5 hash of Step 0.
            byte[] messageDigest;
            try {
                MessageDigest md = MessageDigest.getInstance("MD5");
                md.update(elementSignatureSource.toString().getBytes(CharEncoding.UTF_8));
                messageDigest = md.digest();
            } catch (NoSuchAlgorithmException e) {
                WebLogger.getLogger(appName).e(t, e.toString());
                WebLogger.getLogger(appName).printStackTrace(e);
                throw new IllegalArgumentException(e.getMessage());
            } catch (UnsupportedEncodingException e) {
                WebLogger.getLogger(appName).e(t, e.toString());
                WebLogger.getLogger(appName).printStackTrace(e);
                throw new IllegalArgumentException(e.getMessage());
            }

            // Step 2: construct the base64-encoded RSA-encrypted md5
            try {
                Cipher pkCipher;
                pkCipher = Cipher.getInstance(ASYMMETRIC_ALGORITHM);
                // write AES key
                pkCipher.init(Cipher.ENCRYPT_MODE, rsaPublicKey);
                byte[] pkEncryptedKey = pkCipher.doFinal(messageDigest);
                return wrapper.encodeToString(pkEncryptedKey);

            } catch (NoSuchAlgorithmException e) {
                WebLogger.getLogger(appName).e(t, "Unable to encrypt the symmetric key");
                WebLogger.getLogger(appName).printStackTrace(e);
                throw new IllegalArgumentException(e.getMessage());
            } catch (NoSuchPaddingException e) {
                WebLogger.getLogger(appName).e(t, "Unable to encrypt the symmetric key");
                WebLogger.getLogger(appName).printStackTrace(e);
                throw new IllegalArgumentException(e.getMessage());
            } catch (InvalidKeyException e) {
                WebLogger.getLogger(appName).e(t, "Unable to encrypt the symmetric key");
                WebLogger.getLogger(appName).printStackTrace(e);
                throw new IllegalArgumentException(e.getMessage());
            } catch (IllegalBlockSizeException e) {
                WebLogger.getLogger(appName).e(t, "Unable to encrypt the symmetric key");
                WebLogger.getLogger(appName).printStackTrace(e);
                throw new IllegalArgumentException(e.getMessage());
            } catch (BadPaddingException e) {
                WebLogger.getLogger(appName).e(t, "Unable to encrypt the symmetric key");
                WebLogger.getLogger(appName).printStackTrace(e);
                throw new IllegalArgumentException(e.getMessage());
            }
        }

        public Cipher getCipher() throws InvalidKeyException, InvalidAlgorithmParameterException,
                NoSuchAlgorithmException, NoSuchPaddingException {
            ++ivSeedArray[ivCounter % ivSeedArray.length];
            ++ivCounter;
            IvParameterSpec baseIv = new IvParameterSpec(ivSeedArray);
            Cipher c = Cipher.getInstance(EncryptionUtils.SYMMETRIC_ALGORITHM);
            c.init(Cipher.ENCRYPT_MODE, symmetricKey, baseIv);
            return c;
        }
    }

    /**
     * Retrieve the encryption information for this row.
     * 
     * @param appName
     * @param tableId
     * @param xmlBase64RsaPublicKey
     * @param instanceId
     * @return
     */
    public static EncryptedFormInformation getEncryptedFormInformation(String appName, String tableId,
            String xmlBase64RsaPublicKey, String instanceId) {

        // fetch the form information
        String base64RsaPublicKey = xmlBase64RsaPublicKey;
        PublicKey pk;
        Base64Wrapper wrapper;

        if (base64RsaPublicKey == null || base64RsaPublicKey.length() == 0) {
            return null; // this is legitimately not an encrypted form
        }

        // submission must have an OpenRosa metadata block with a non-null
        // instanceID value.
        if (instanceId == null) {
            WebLogger.getLogger(appName).e(t, "No OpenRosa metadata block or no instanceId defined in that block");
            return null;
        }

        int version = android.os.Build.VERSION.SDK_INT;
        if (version < 8) {
            WebLogger.getLogger(appName).e(t, "Phone does not support encryption.");
            return null; // save unencrypted
        }

        // this constructor will throw an exception if we are not
        // running on version 8 or above (if Base64 is not found).
        try {
            wrapper = new Base64Wrapper(appName);
        } catch (ClassNotFoundException e) {
            WebLogger.getLogger(appName).e(t, "Phone does not have Base64 class but API level is " + version);
            WebLogger.getLogger(appName).printStackTrace(e);
            return null; // save unencrypted
        }

        // OK -- Base64 decode (requires API Version 8 or higher)
        byte[] publicKey = wrapper.decode(base64RsaPublicKey);
        X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKey);
        KeyFactory kf;
        try {
            kf = KeyFactory.getInstance(RSA_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            WebLogger.getLogger(appName).e(t, "Phone does not support RSA encryption.");
            WebLogger.getLogger(appName).printStackTrace(e);
            return null;
        }
        try {
            pk = kf.generatePublic(publicKeySpec);
        } catch (InvalidKeySpecException e) {
            WebLogger.getLogger(appName).printStackTrace(e);
            WebLogger.getLogger(appName).e(t, "Invalid RSA public key.");
            return null;
        }
        return new EncryptedFormInformation(appName, tableId, xmlBase64RsaPublicKey, instanceId, pk, wrapper);
    }

    private static void encryptFile(File file, File encryptedFile, EncryptedFormInformation formInfo)
            throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
            InvalidAlgorithmParameterException {

        // add elementSignatureSource for this file...
        formInfo.appendFileSignatureSource(file);

        try {
            Cipher c = formInfo.getCipher();

            OutputStream fout;
            fout = new FileOutputStream(encryptedFile);
            fout = new CipherOutputStream(fout, c);
            InputStream fin;
            fin = new FileInputStream(file);
            byte[] buffer = new byte[2048];
            int len = fin.read(buffer);
            while (len != -1) {
                fout.write(buffer, 0, len);
                len = fin.read(buffer);
            }
            fin.close();
            fout.flush();
            fout.close();
            WebLogger.getLogger(formInfo.appName).i(t,
                    "Encrpyted:" + file.getName() + " -> " + encryptedFile.getName());
        } catch (IOException e) {
            WebLogger.getLogger(formInfo.appName).e(t,
                    "Error encrypting: " + file.getName() + " -> " + encryptedFile.getName());
            WebLogger.getLogger(formInfo.appName).printStackTrace(e);
            throw e;
        } catch (NoSuchAlgorithmException e) {
            WebLogger.getLogger(formInfo.appName).e(t,
                    "Error encrypting: " + file.getName() + " -> " + encryptedFile.getName());
            WebLogger.getLogger(formInfo.appName).printStackTrace(e);
            throw e;
        } catch (NoSuchPaddingException e) {
            WebLogger.getLogger(formInfo.appName).e(t,
                    "Error encrypting: " + file.getName() + " -> " + encryptedFile.getName());
            WebLogger.getLogger(formInfo.appName).printStackTrace(e);
            throw e;
        } catch (InvalidKeyException e) {
            WebLogger.getLogger(formInfo.appName).e(t,
                    "Error encrypting: " + file.getName() + " -> " + encryptedFile.getName());
            WebLogger.getLogger(formInfo.appName).printStackTrace(e);
            throw e;
        } catch (InvalidAlgorithmParameterException e) {
            WebLogger.getLogger(formInfo.appName).e(t,
                    "Error encrypting: " + file.getName() + " -> " + encryptedFile.getName());
            WebLogger.getLogger(formInfo.appName).printStackTrace(e);
            throw e;
        }
    }

    private static void encryptIntoFile(String contents, File submissionFile, File encryptedFile,
            EncryptedFormInformation formInfo) throws IOException, NoSuchAlgorithmException, NoSuchPaddingException,
            InvalidKeyException, InvalidAlgorithmParameterException {

        // add elementSignatureSource for this file...
        formInfo.appendSubmissionFileSignatureSource(contents, submissionFile);

        try {
            Cipher c = formInfo.getCipher();

            OutputStream fout;
            fout = new FileOutputStream(encryptedFile);
            fout = new CipherOutputStream(fout, c);
            InputStream fin;
            fin = new ByteArrayInputStream(contents.getBytes(CharEncoding.UTF_8));
            byte[] buffer = new byte[2048];
            int len = fin.read(buffer);
            while (len != -1) {
                fout.write(buffer, 0, len);
                len = fin.read(buffer);
            }
            fin.close();
            fout.flush();
            fout.close();
            WebLogger.getLogger(formInfo.appName).i(t, "Encrpyted: content -> " + encryptedFile.getName());
        } catch (IOException e) {
            WebLogger.getLogger(formInfo.appName).e(t, "Error encrypting: content -> " + encryptedFile.getName());
            WebLogger.getLogger(formInfo.appName).printStackTrace(e);
            throw e;
        } catch (NoSuchAlgorithmException e) {
            WebLogger.getLogger(formInfo.appName).e(t, "Error encrypting: content -> " + encryptedFile.getName());
            WebLogger.getLogger(formInfo.appName).printStackTrace(e);
            throw e;
        } catch (NoSuchPaddingException e) {
            WebLogger.getLogger(formInfo.appName).e(t, "Error encrypting: content -> " + encryptedFile.getName());
            WebLogger.getLogger(formInfo.appName).printStackTrace(e);
            throw e;
        } catch (InvalidKeyException e) {
            WebLogger.getLogger(formInfo.appName).e(t, "Error encrypting: content -> " + encryptedFile.getName());
            WebLogger.getLogger(formInfo.appName).printStackTrace(e);
            throw e;
        } catch (InvalidAlgorithmParameterException e) {
            WebLogger.getLogger(formInfo.appName).e(t, "Error encrypting: content -> " + encryptedFile.getName());
            WebLogger.getLogger(formInfo.appName).printStackTrace(e);
            throw e;
        }
    }

    public static boolean deletePlaintextFiles(File instanceXml) {
        // NOTE: assume the directory containing the instanceXml contains ONLY
        // files related to this one instance.
        File instanceDir = instanceXml.getParentFile();

        boolean allSuccessful = true;
        // encrypt files that do not end with ".enc", and do not start with ".";
        // ignore directories
        File[] allFiles = instanceDir.listFiles();
        for (File f : allFiles) {
            if (f.equals(instanceXml))
                continue; // don't touch instance file
            if (f.isDirectory())
                continue; // don't handle directories
            if (!f.getName().endsWith(".enc")) {
                // not an encrypted file -- delete it!
                allSuccessful = allSuccessful & f.delete(); // DO NOT
                // short-circuit
            }
        }
        return allSuccessful;
    }

    private static List<MimeFile> encryptSubmissionFiles(FileSet fileSet, String submission, File submissionXml,
            File submissionXmlEnc, EncryptedFormInformation formInfo) {

        // encrypt files that do not end with ".enc"
        List<MimeFile> filesToProcess = new ArrayList<MimeFile>();
        for (MimeFile f : fileSet.attachmentFiles) {
            if (f.file.getName().endsWith(".enc")) {
                f.file.delete(); // try to delete this (leftover junk)
            } else {
                filesToProcess.add(f);
            }
        }
        // encrypt here...
        for (MimeFile f : filesToProcess) {
            try {
                File encryptedFile = new File(f.file.getParentFile(), f.file.getName() + ".enc");
                encryptFile(f.file, encryptedFile, formInfo);
                f.file = encryptedFile;
                f.contentType = APPLICATION_OCTET_STREAM;
            } catch (IOException e) {
                return null;
            } catch (InvalidKeyException e) {
                return null;
            } catch (NoSuchAlgorithmException e) {
                return null;
            } catch (NoSuchPaddingException e) {
                return null;
            } catch (InvalidAlgorithmParameterException e) {
                return null;
            }
        }

        // encrypt the submission.xml as the last file...
        try {
            encryptIntoFile(submission, submissionXml, submissionXmlEnc, formInfo);
            // TODO: attachments remain in plaintext on the sdcard until
            // instance is deleted
            fileSet.addAttachmentFile(submissionXmlEnc, APPLICATION_OCTET_STREAM);
        } catch (IOException e) {
            return null;
        } catch (InvalidKeyException e) {
            return null;
        } catch (NoSuchAlgorithmException e) {
            return null;
        } catch (NoSuchPaddingException e) {
            return null;
        } catch (InvalidAlgorithmParameterException e) {
            return null;
        }

        return filesToProcess;
    }

    /**
     * Constructs the encrypted attachments, encrypted form xml, and the plaintext
     * submission manifest (with signature) for the form submission.
     *
     * Does not delete any of the original files.
     *
     * @parma fileSet
     * @param submission
     * @param submissionXml
     * @param submissionXmlEnc
     * @param formInfo
     * @return
     */
    public static boolean generateEncryptedSubmission(FileSet fileSet, String submission, File submissionXml,
            File submissionXmlEnc, EncryptedFormInformation formInfo) {

        // Step 1: encrypt the submission and all the media files...
        List<MimeFile> mediaFiles = encryptSubmissionFiles(fileSet, submission, submissionXml, submissionXmlEnc,
                formInfo);
        if (mediaFiles == null) {
            return false; // something failed...
        }

        // Step 2: build the encrypted-submission manifest (overwrites
        // submission.xml)...
        if (!writeSubmissionManifest(formInfo, submissionXml, submissionXmlEnc, mediaFiles)) {
            return false;
        }
        fileSet.instanceFile = submissionXml;
        return true;
    }

    private static boolean writeSubmissionManifest(EncryptedFormInformation formInfo, File submissionXml,
            File submissionXmlEnc, List<MimeFile> mediaFiles) {

        FileOutputStream out = null;
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            Document d = db.newDocument();
            d.setXmlStandalone(true);
            Element e = d.createElementNS(XML_ENCRYPTED_TAG_NAMESPACE, DATA);
            e.setPrefix(null);
            e.setAttribute(ID, formInfo.tableId);
            e.setAttribute(ENCRYPTED, "yes");
            d.appendChild(e);

            Element c;
            c = d.createElementNS(XML_ENCRYPTED_TAG_NAMESPACE, BASE64_ENCRYPTED_KEY);
            Text txtNode;
            txtNode = d.createTextNode(formInfo.base64RsaEncryptedSymmetricKey);
            c.appendChild(txtNode);
            e.appendChild(c);

            c = d.createElementNS(XML_OPENROSA_NAMESPACE, META);
            c.setPrefix("orx");
            {
                Element instanceTag = d.createElementNS(XML_OPENROSA_NAMESPACE, INSTANCE_ID);
                txtNode = d.createTextNode(formInfo.instanceId);
                instanceTag.appendChild(txtNode);
                c.appendChild(instanceTag);
            }
            e.appendChild(c);

            for (MimeFile file : mediaFiles) {
                c = d.createElementNS(XML_ENCRYPTED_TAG_NAMESPACE, MEDIA);
                Element fileTag = d.createElementNS(XML_ENCRYPTED_TAG_NAMESPACE, FILE);
                txtNode = d.createTextNode(file.file.getName());
                fileTag.appendChild(txtNode);
                c.appendChild(fileTag);
                e.appendChild(c);
            }

            c = d.createElementNS(XML_ENCRYPTED_TAG_NAMESPACE, ENCRYPTED_XML_FILE);
            txtNode = d.createTextNode(submissionXmlEnc.getName());
            c.appendChild(txtNode);
            e.appendChild(c);

            c = d.createElementNS(XML_ENCRYPTED_TAG_NAMESPACE, BASE64_ENCRYPTED_ELEMENT_SIGNATURE);
            txtNode = d.createTextNode(formInfo.getBase64EncryptedElementSignature());
            c.appendChild(txtNode);
            e.appendChild(c);

            out = new FileOutputStream(submissionXml);

            TransformerFactory factory = TransformerFactory.newInstance();
            Transformer transformer = factory.newTransformer();
            Properties outFormat = new Properties();
            outFormat.setProperty(OutputKeys.INDENT, "no");
            outFormat.setProperty(OutputKeys.METHOD, "xml");
            outFormat.setProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            outFormat.setProperty(OutputKeys.VERSION, "1.0");
            outFormat.setProperty(OutputKeys.ENCODING, "UTF-8");
            transformer.setOutputProperties(outFormat);

            DOMSource domSource = new DOMSource(d.getDocumentElement());
            StreamResult result = new StreamResult(out);
            transformer.transform(domSource, result);

            out.flush();
            out.close();
        } catch (FileNotFoundException ex) {
            WebLogger.getLogger(formInfo.appName).printStackTrace(ex);
            WebLogger.getLogger(formInfo.appName).e(t, "Error writing submission.xml for encrypted submission: "
                    + submissionXml.getParentFile().getName());
            return false;
        } catch (UnsupportedEncodingException ex) {
            WebLogger.getLogger(formInfo.appName).printStackTrace(ex);
            WebLogger.getLogger(formInfo.appName).e(t, "Error writing submission.xml for encrypted submission: "
                    + submissionXml.getParentFile().getName());
            return false;
        } catch (IOException ex) {
            WebLogger.getLogger(formInfo.appName).printStackTrace(ex);
            WebLogger.getLogger(formInfo.appName).e(t, "Error writing submission.xml for encrypted submission: "
                    + submissionXml.getParentFile().getName());
            return false;
        } catch (TransformerConfigurationException ex) {
            WebLogger.getLogger(formInfo.appName).printStackTrace(ex);
            WebLogger.getLogger(formInfo.appName).e(t, "Error writing submission.xml for encrypted submission: "
                    + submissionXml.getParentFile().getName());
            return false;
        } catch (TransformerException ex) {
            WebLogger.getLogger(formInfo.appName).printStackTrace(ex);
            WebLogger.getLogger(formInfo.appName).e(t, "Error writing submission.xml for encrypted submission: "
                    + submissionXml.getParentFile().getName());
            return false;
        } catch (ParserConfigurationException ex) {
            WebLogger.getLogger(formInfo.appName).printStackTrace(ex);
            WebLogger.getLogger(formInfo.appName).e(t, "Error writing submission.xml for encrypted submission: "
                    + submissionXml.getParentFile().getName());
            return false;
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    // ignore
                }
            }
        }

        return true;
    }
}