Search in sources :

Example 1 with CryptoException

use of org.opendatakit.briefcase.model.CryptoException in project briefcase by opendatakit.

the class ExportToCsv method processInstance.

private boolean processInstance(File instanceDir) {
    File submission = new File(instanceDir, "submission.xml");
    if (!submission.exists() || !submission.isFile()) {
        EventBus.publish(new ExportProgressEvent("Submission not found for instance directory: " + instanceDir.getPath(), briefcaseLfd));
        return false;
    }
    processedInstances++;
    EventBus.publish(new ExportProgressEvent("Processing instance: " + instanceDir.getName(), briefcaseLfd));
    EventBus.publish(new ExportProgressPercentageEvent((processedInstances * 100.0) / totalInstances, briefcaseLfd));
    // If we are encrypted, be sure the temporary directory
    // that will hold the unencrypted files is created.
    // If we aren't encrypted, the temporary directory
    // is the same as the instance directory.
    File unEncryptedDir;
    if (briefcaseLfd.isFileEncryptedForm()) {
        // create the temp directory that will hold the unencrypted
        // files. Do this in the outputDir so that the briefcase storage location
        // can be a read-only network mount. issue 676.
        Path path;
        try {
            path = Files.createTempDirectory(Paths.get(outputDir.toURI()), ".temp");
        } catch (IOException e) {
            String msg = "Unable to create temp directory.";
            log.error(msg, e);
            EventBus.publish(new ExportProgressEvent(msg + " Cause : " + e.toString(), briefcaseLfd));
            return false;
        }
        unEncryptedDir = path.toFile();
    } else {
        unEncryptedDir = instanceDir;
    }
    // parse the xml document (this is the manifest if encrypted)...
    Document doc;
    boolean isValidated = false;
    try {
        doc = XmlManipulationUtils.parseXml(submission);
    } catch (ParsingException | FileSystemException e) {
        log.error("Error parsing submission", e);
        EventBus.publish(new ExportProgressEvent(("Error parsing submission " + instanceDir.getName()) + " Cause: " + e.toString(), briefcaseLfd));
        return false;
    }
    String submissionDate = null;
    // extract the submissionDate, if present, from the attributes
    // of the root element of the submission or submission manifest (if encrypted).
    submissionDate = doc.getRootElement().getAttributeValue(null, "submissionDate");
    if (submissionDate == null || submissionDate.length() == 0) {
        submissionDate = null;
    } else {
        Date theDate = WebUtils.parseDate(submissionDate);
        DateFormat formatter = DateFormat.getDateTimeInstance();
        submissionDate = formatter.format(theDate);
        // just return true to skip records out of range
        if (startDate != null && theDate.before(startDate)) {
            log.info("Submission date is before specified, skipping: " + instanceDir.getName());
            return true;
        }
        if (endDate != null && theDate.after(endDate)) {
            log.info("Submission date is after specified, skipping: " + instanceDir.getName());
            return true;
        }
        // don't export records without dates if either date is set
        if ((startDate != null || endDate != null) && submissionDate == null) {
            log.info("No submission date found, skipping: " + instanceDir.getName());
            return true;
        }
    }
    // failure.
    try {
        if (briefcaseLfd.isFileEncryptedForm()) {
            // NOTE: this changes the value of 'doc'
            try {
                FileSystemUtils.DecryptOutcome outcome = FileSystemUtils.decryptAndValidateSubmission(doc, briefcaseLfd.getPrivateKey(), instanceDir, unEncryptedDir);
                doc = outcome.submission;
                isValidated = outcome.isValidated;
            } catch (ParsingException | CryptoException | FileSystemException e) {
                // Was unable to parse file or decrypt file or a file system error occurred
                // Hence skip this instance
                EventBus.publish(new ExportProgressEvent("Error decrypting submission " + instanceDir.getName() + " Cause: " + e.toString() + " skipping....", briefcaseLfd));
                log.info("Error decrypting submission " + instanceDir.getName() + " Cause: " + e.toString());
                // update total number of files skipped
                totalFilesSkipped++;
                return true;
            }
        }
        String instanceId = null;
        String base64EncryptedFieldKey = null;
        // find an instanceId to use...
        try {
            FormInstanceMetadata sim = XmlManipulationUtils.getFormInstanceMetadata(doc.getRootElement());
            instanceId = sim.instanceId;
            base64EncryptedFieldKey = sim.base64EncryptedFieldKey;
        } catch (ParsingException e) {
            log.error("Could not extract metadata from submission", e);
            EventBus.publish(new ExportProgressEvent("Could not extract metadata from submission " + submission.getAbsolutePath(), briefcaseLfd));
            return false;
        }
        if (instanceId == null || instanceId.length() == 0) {
            // if we have no instanceID, and there isn't any in the file,
            // use the checksum as the id.
            // NOTE: encrypted submissions always have instanceIDs.
            // This is for legacy non-OpenRosa forms.
            long checksum;
            try {
                checksum = FileUtils.checksumCRC32(submission);
            } catch (IOException e1) {
                String msg = "Failed during computing of crc";
                log.error(msg, e1);
                EventBus.publish(new ExportProgressEvent(msg + ": " + e1.getMessage(), briefcaseLfd));
                return false;
            }
            instanceId = "crc32:" + Long.toString(checksum);
        }
        if (terminationFuture.isCancelled()) {
            EventBus.publish(new ExportProgressEvent("Aborted", briefcaseLfd));
            return false;
        }
        EncryptionInformation ei = null;
        if (base64EncryptedFieldKey != null) {
            try {
                ei = new EncryptionInformation(base64EncryptedFieldKey, instanceId, briefcaseLfd.getPrivateKey());
            } catch (CryptoException e) {
                log.error("Error establishing field decryption", e);
                EventBus.publish(new ExportProgressEvent("Error establishing field decryption for submission " + instanceDir.getName() + " Cause: " + e.toString(), briefcaseLfd));
                return false;
            }
        }
        // emit the csv record...
        try {
            OutputStreamWriter osw = fileMap.get(briefcaseLfd.getSubmissionElement());
            emitString(osw, true, submissionDate);
            emitSubmissionCsv(osw, ei, doc.getRootElement(), briefcaseLfd.getSubmissionElement(), briefcaseLfd.getSubmissionElement(), false, instanceId, unEncryptedDir);
            emitString(osw, false, instanceId);
            if (briefcaseLfd.isFileEncryptedForm()) {
                emitString(osw, false, Boolean.toString(isValidated));
                if (!isValidated) {
                    EventBus.publish(new ExportProgressEvent("Decrypted submission " + instanceDir.getName() + " may be missing attachments and could not be validated.", briefcaseLfd));
                }
            }
            osw.append("\n");
            return true;
        } catch (IOException e) {
            String msg = "Failed writing csv";
            log.error(msg, e);
            EventBus.publish(new ExportProgressEvent(msg + ": " + e.getMessage(), briefcaseLfd));
            return false;
        }
    } finally {
        if (briefcaseLfd.isFileEncryptedForm()) {
            // destroy the temp directory and its contents...
            try {
                FileUtils.deleteDirectory(unEncryptedDir);
            } catch (IOException e) {
                String msg = "Unable to remove decrypted files";
                log.error(msg, e);
                EventBus.publish(new ExportProgressEvent(msg + ": " + e.getMessage(), briefcaseLfd));
                return false;
            }
        }
    }
}
Also used : Path(java.nio.file.Path) FormInstanceMetadata(org.opendatakit.briefcase.util.XmlManipulationUtils.FormInstanceMetadata) IOException(java.io.IOException) Document(org.kxml2.kdom.Document) ExportProgressEvent(org.opendatakit.briefcase.model.ExportProgressEvent) Date(java.util.Date) LocalDate(java.time.LocalDate) FileSystemException(org.opendatakit.briefcase.model.FileSystemException) ParsingException(org.opendatakit.briefcase.model.ParsingException) DateFormat(java.text.DateFormat) ExportProgressPercentageEvent(org.opendatakit.briefcase.model.ExportProgressPercentageEvent) OutputStreamWriter(java.io.OutputStreamWriter) CryptoException(org.opendatakit.briefcase.model.CryptoException) File(java.io.File)

Example 2 with CryptoException

use of org.opendatakit.briefcase.model.CryptoException in project briefcase by opendatakit.

the class FileSystemUtils method decryptSubmissionFiles.

private static boolean decryptSubmissionFiles(String base64EncryptedSymmetricKey, FormInstanceMetadata fim, List<String> mediaNames, String encryptedSubmissionFile, String base64EncryptedElementSignature, PrivateKey rsaPrivateKey, File instanceDir, File unencryptedDir) throws FileSystemException, CryptoException, ParsingException {
    EncryptionInformation ei = new EncryptionInformation(base64EncryptedSymmetricKey, fim.instanceId, rsaPrivateKey);
    byte[] elementDigest;
    try {
        // construct the base64-encoded RSA-encrypted symmetric key
        Cipher pkCipher;
        pkCipher = Cipher.getInstance(ASYMMETRIC_ALGORITHM);
        // extract digest
        pkCipher.init(Cipher.DECRYPT_MODE, rsaPrivateKey);
        byte[] encryptedElementSignature = Base64.decodeBase64(base64EncryptedElementSignature);
        elementDigest = pkCipher.doFinal(encryptedElementSignature);
    } catch (NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException | InvalidKeyException | NoSuchPaddingException e) {
        String msg = "Error decrypting base64EncryptedElementSignature";
        log.error(msg, e);
        throw new CryptoException(msg + " Cause: " + e.toString());
    }
    // NOTE: will decrypt only the files in the media list, plus the encryptedSubmissionFile
    File[] allFiles = instanceDir.listFiles();
    List<File> filesToProcess = new ArrayList<>();
    for (File f : allFiles) {
        if (mediaNames.contains(f.getName())) {
            filesToProcess.add(f);
        } else if (encryptedSubmissionFile.equals(f.getName())) {
            filesToProcess.add(f);
        }
    }
    // should have all media files plus one submission.xml.enc file
    if (filesToProcess.size() != mediaNames.size() + 1) {
        // figure out what we're missing...
        int lostFileCount = 0;
        List<String> missing = new ArrayList<>();
        for (String name : mediaNames) {
            if (name == null) {
                // this was lost due to an pre-ODK Aggregate 1.4.5 mark-as-complete action
                ++lostFileCount;
                continue;
            }
            File f = new File(instanceDir, name);
            if (!filesToProcess.contains(f)) {
                missing.add(name);
            }
        }
        StringBuilder b = new StringBuilder();
        for (String name : missing) {
            b.append(" ").append(name);
        }
        if (!filesToProcess.contains(new File(instanceDir, encryptedSubmissionFile))) {
            b.append(" ").append(encryptedSubmissionFile);
            throw new FileSystemException("Error decrypting: " + instanceDir.getName() + " Missing files:" + b.toString());
        } else {
            // ignore the fact that we don't have the lost files
            if (filesToProcess.size() + lostFileCount != mediaNames.size() + 1) {
                throw new FileSystemException("Error decrypting: " + instanceDir.getName() + " Missing files:" + b.toString());
            }
        }
    }
    // decrypt the media files IN ORDER.
    for (String mediaName : mediaNames) {
        String displayedName = (mediaName == null) ? "<missing .enc file>" : mediaName;
        File f = (mediaName == null) ? null : new File(instanceDir, mediaName);
        try {
            decryptFile(ei, f, unencryptedDir);
        } catch (InvalidKeyException | NoSuchPaddingException | InvalidAlgorithmParameterException | NoSuchAlgorithmException e) {
            String msg = "Error decrypting:" + displayedName;
            log.error(msg, e);
            throw new CryptoException(msg + " Cause: " + e.toString());
        } catch (IOException e) {
            String msg = "Error decrypting:" + displayedName;
            log.error(msg, e);
            throw new FileSystemException(msg + " Cause: " + e.toString());
        }
    }
    // decrypt the submission file
    File f = new File(instanceDir, encryptedSubmissionFile);
    try {
        decryptFile(ei, f, unencryptedDir);
    } catch (InvalidKeyException | NoSuchPaddingException | InvalidAlgorithmParameterException | NoSuchAlgorithmException e) {
        log.error("Error decrypting file", e);
        throw new CryptoException("Error decrypting:" + f.getName() + " Cause: " + e.toString());
    } catch (IOException e) {
        log.error("Error decrypting file", e);
        throw new FileSystemException("Error decrypting:" + f.getName() + " Cause: " + e.toString());
    }
    // get the FIM for the decrypted submission file
    File submissionFile = new File(unencryptedDir, encryptedSubmissionFile.substring(0, encryptedSubmissionFile.lastIndexOf(".enc")));
    FormInstanceMetadata submissionFim;
    try {
        Document subDoc = XmlManipulationUtils.parseXml(submissionFile);
        submissionFim = XmlManipulationUtils.getFormInstanceMetadata(subDoc.getRootElement());
    } catch (ParsingException | FileSystemException e) {
        log.error("Error decrypting submission", e);
        throw new FileSystemException("Error decrypting: " + submissionFile.getName() + " Cause: " + e);
    }
    boolean same = submissionFim.xparam.formId.equals(fim.xparam.formId);
    if (!same) {
        throw new FileSystemException("Error decrypting:" + unencryptedDir.getName() + " Cause: form instance metadata differs from that in manifest");
    }
    // Construct the element signature string
    StringBuilder b = new StringBuilder();
    appendElementSignatureSource(b, fim.xparam.formId);
    if (fim.xparam.modelVersion != null) {
        appendElementSignatureSource(b, fim.xparam.modelVersion);
    }
    appendElementSignatureSource(b, base64EncryptedSymmetricKey);
    appendElementSignatureSource(b, fim.instanceId);
    boolean missingFile = false;
    for (String encFilename : mediaNames) {
        if (encFilename == null) {
            missingFile = true;
            continue;
        }
        File decryptedFile = new File(unencryptedDir, encFilename.substring(0, encFilename.lastIndexOf(".enc")));
        if (decryptedFile.getName().endsWith(".missing")) {
            // this is a missing file -- we will not be able to
            // confirm the signature of the submission.
            missingFile = true;
            continue;
        }
        String md5 = FileSystemUtils.getMd5Hash(decryptedFile);
        appendElementSignatureSource(b, decryptedFile.getName() + "::" + md5);
    }
    String md5 = FileSystemUtils.getMd5Hash(submissionFile);
    appendElementSignatureSource(b, submissionFile.getName() + "::" + md5);
    // compute the digest of the element signature string
    byte[] messageDigest;
    try {
        MessageDigest md = MessageDigest.getInstance("MD5");
        md.update(b.toString().getBytes("UTF-8"));
        messageDigest = md.digest();
    } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
        String msg = "Error computing xml signature";
        log.error(msg, e);
        throw new CryptoException(msg + " Cause: " + e);
    }
    same = true;
    for (int i = 0; i < messageDigest.length; ++i) {
        if (messageDigest[i] != elementDigest[i]) {
            same = false;
            break;
        }
    }
    return same;
}
Also used : ArrayList(java.util.ArrayList) IllegalBlockSizeException(javax.crypto.IllegalBlockSizeException) NoSuchAlgorithmException(java.security.NoSuchAlgorithmException) BadPaddingException(javax.crypto.BadPaddingException) Document(org.kxml2.kdom.Document) FileSystemException(org.opendatakit.briefcase.model.FileSystemException) ParsingException(org.opendatakit.briefcase.model.ParsingException) MessageDigest(java.security.MessageDigest) InvalidAlgorithmParameterException(java.security.InvalidAlgorithmParameterException) FormInstanceMetadata(org.opendatakit.briefcase.util.XmlManipulationUtils.FormInstanceMetadata) NoSuchPaddingException(javax.crypto.NoSuchPaddingException) UnsupportedEncodingException(java.io.UnsupportedEncodingException) IOException(java.io.IOException) InvalidKeyException(java.security.InvalidKeyException) Cipher(javax.crypto.Cipher) CryptoException(org.opendatakit.briefcase.model.CryptoException) File(java.io.File)

Aggregations

File (java.io.File)2 IOException (java.io.IOException)2 Document (org.kxml2.kdom.Document)2 CryptoException (org.opendatakit.briefcase.model.CryptoException)2 FileSystemException (org.opendatakit.briefcase.model.FileSystemException)2 ParsingException (org.opendatakit.briefcase.model.ParsingException)2 FormInstanceMetadata (org.opendatakit.briefcase.util.XmlManipulationUtils.FormInstanceMetadata)2 OutputStreamWriter (java.io.OutputStreamWriter)1 UnsupportedEncodingException (java.io.UnsupportedEncodingException)1 Path (java.nio.file.Path)1 InvalidAlgorithmParameterException (java.security.InvalidAlgorithmParameterException)1 InvalidKeyException (java.security.InvalidKeyException)1 MessageDigest (java.security.MessageDigest)1 NoSuchAlgorithmException (java.security.NoSuchAlgorithmException)1 DateFormat (java.text.DateFormat)1 LocalDate (java.time.LocalDate)1 ArrayList (java.util.ArrayList)1 Date (java.util.Date)1 BadPaddingException (javax.crypto.BadPaddingException)1 Cipher (javax.crypto.Cipher)1