diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicensePersistence.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicensePersistence.java index 67f19201108c7fa305a9cbcfb2128196570c18eb..e95d9e72795c57d1a37d206fc19864681ada9b3e 100644 --- a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicensePersistence.java +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicensePersistence.java @@ -92,19 +92,15 @@ public synchronized Optional<LicenseInfo> loadLicenseInfo() { }); } - public synchronized boolean saveMalwareSettings(MalwareIngestSettings malwareIngestSettings) { + public synchronized boolean saveMalwareSettings(MalwareIngestSettings malwareIngestSettings) { if (malwareIngestSettings != null) { File settingsFile = getMalwareIngestFile(); try { settingsFile.getParentFile().mkdirs(); - if (licenseResponse != null) { - objectMapper.writeValue(licenseFile, licenseResponse); - } else if (licenseFile.exists()) { - Files.delete(licenseFile.toPath()); - } + objectMapper.writeValue(settingsFile, malwareIngestSettings); return true; } catch (IOException ex) { - logger.log(Level.WARNING, "There was an error writing CyberTriage license to file: " + licenseFile.getAbsolutePath(), ex); + logger.log(Level.WARNING, "There was an error writing malware ingest settings to file: " + settingsFile.getAbsolutePath(), ex); } } @@ -112,19 +108,23 @@ public synchronized boolean saveMalwareSettings(MalwareIngestSettings malwareIng } public synchronized MalwareIngestSettings loadMalwareIngestSettings() { - Optional<LicenseResponse> toRet = Optional.empty(); - File licenseFile = getCTLicenseFile(); - if (licenseFile.exists() && licenseFile.isFile()) { + MalwareIngestSettings settings = null; + File settingsFile = getMalwareIngestFile(); + if (settingsFile.exists() && settingsFile.isFile()) { try { - toRet = Optional.ofNullable(objectMapper.readValue(licenseFile, LicenseResponse.class)); + settings = objectMapper.readValue(settingsFile, MalwareIngestSettings.class); } catch (IOException ex) { - logger.log(Level.WARNING, "There was an error reading CyberTriage license to file: " + licenseFile.getAbsolutePath(), ex); + logger.log(Level.WARNING, "There was an error reading malware ingest settings from file: " + settingsFile.getAbsolutePath(), ex); } } + + if (settings == null) { + settings = new MalwareIngestSettings(); + } - return toRet; + return settings; } - + private File getCTLicenseFile() { return Paths.get(PlatformUtil.getModuleConfigDirectory(), CT_SETTINGS_DIR, CT_LICENSE_FILENAME).toFile(); } diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTMalwareScannerOptionsPanel.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTMalwareScannerOptionsPanel.java index 94ba8fb948336695776cd42b1d8801b71506a743..b380dded422c5b61f8bde48d57c7d3b3deaac7d3 100644 --- a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTMalwareScannerOptionsPanel.java +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTMalwareScannerOptionsPanel.java @@ -111,6 +111,7 @@ public void componentShown(ComponentEvent e) { @Override public synchronized void saveSettings() { ctPersistence.saveLicenseResponse(getLicenseInfo()); + ctPersistence.saveMalwareSettings(getIngestSettings()); } @Override @@ -127,11 +128,26 @@ public synchronized void loadSettings() { if (licenseInfo != null) { loadMalwareScansInfo(licenseInfo); } + + MalwareIngestSettings ingestSettings = ctPersistence.loadMalwareIngestSettings(); + setIngestSettings(ingestSettings); } private synchronized LicenseResponse getLicenseInfo() { return this.licenseInfo == null ? null : this.licenseInfo.getLicenseResponse(); } + + private MalwareIngestSettings getIngestSettings() { + return new MalwareIngestSettings() + .setUploadFiles(this.fileUploadCheckbox.isSelected()); + } + + private void setIngestSettings(MalwareIngestSettings ingestSettings) { + if (ingestSettings == null) { + ingestSettings = new MalwareIngestSettings(); + } + this.fileUploadCheckbox.setSelected(ingestSettings.isUploadFiles()); + } private synchronized void setLicenseDisplay(LicenseInfo licenseInfo, String licenseMessage) { this.licenseInfo = licenseInfo; @@ -411,7 +427,7 @@ private void licenseInfoAddButtonActionPerformed(java.awt.event.ActionEvent evt) }//GEN-LAST:event_licenseInfoAddButtonActionPerformed private void fileUploadCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fileUploadCheckboxActionPerformed - // TODO add your handling code here: + this.firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); }//GEN-LAST:event_fileUploadCheckboxActionPerformed @NbBundle.Messages({ diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java index dd8e45a085f2250dbdd42bd8197644f2503e7fe3..a509310391789c7bfd9648a2e71b848ce4150ac9 100644 --- a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java +++ b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/MalwareScanIngestModule.java @@ -40,6 +40,7 @@ import java.util.stream.Stream; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.curator.shaded.com.google.common.collect.Lists; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; @@ -96,6 +97,9 @@ private static class SharedProcessing { private static final long MIN_UPLOAD_SIZE = 1; private static final long MAX_UPLOAD_SIZE = 1_000_000_000; + private static final int NUM_FILE_UPLOAD_RETRIES = 60 * 5; + private static final long FILE_UPLOAD_RETRY_SLEEP_MILLIS = 60 * 1000; + private static final Set<String> EXECUTABLE_MIME_TYPES = Stream.of( "application/x-bat",//NON-NLS @@ -128,6 +132,7 @@ private static class SharedProcessing { private BlackboardArtifact.Type malwareType = null; private long dsId = 0; private long ingestJobId = 0; + private boolean uploadUnknownFiles = false; @Messages({ "MalwareScanIngestModule_ShareProcessing_lowLimitWarning_title=Hash Lookups Low", @@ -186,6 +191,7 @@ synchronized void startUp(IngestJobContext context) throws IngestModuleException dsId = context.getDataSource().getId(); ingestJobId = context.getJobId(); licenseInfo = licenseInfoOpt.get(); + uploadUnknownFiles = ctSettingsPersistence.loadMalwareIngestSettings().isUploadFiles(); // set run state to initialized runState = RunState.STARTED_UP; @@ -201,55 +207,6 @@ private static long remaining(Long limit, Long used) { return limit - used; } - private boolean isUnknown(CTCloudBean cloudBean) { - return cloudBean != null - && cloudBean.getMalwareResult() != null - && cloudBean.getMalwareResult().getStatus() == MalwareResultBean.Status.NOT_FOUND; - } - - private boolean isUploadable(AbstractFile af) { - long size = af.getSize(); - return size >= MIN_UPLOAD_SIZE && size <= MAX_UPLOAD_SIZE; - } - - private boolean uploadFile(SleuthkitCase skCase, DecryptedLicenseResponse decrypted, CTCloudBean cloudBean, long objId) throws CTCloudException, TskCoreException { - if (!isUnknown(cloudBean)) { - return false; - } - - AbstractFile af = skCase.getAbstractFileById(objId); - if (af == null) { - return false; - } - - if (!isUploadable(af)) { - return false; - } - - // get auth token / file upload url - AuthTokenResponse authTokenResponse = ctApiDAO.getAuthToken(decrypted, true); - if (StringUtils.isBlank(authTokenResponse.getFileUploadUrl())) { - throw new CTCloudException(CTCloudException.ErrorCode.NETWORK_ERROR); - } - - // upload bytes - ReadContentInputStream fileInputStream = new ReadContentInputStream(af); - ctApiDAO.uploadFile(authTokenResponse.getFileUploadUrl(), af.getName(), fileInputStream); - - // upload metadata - MetadataUploadRequest metaRequest = new MetadataUploadRequest() - .setCreatedDate(af.getCrtime()) - .setFilePath(af.getUniquePath()) - .setFileSizeBytes(af.getSize()) - .setFileUploadUrl(authTokenResponse.getFileUploadUrl()) - .setMd5(af.getMd5Hash()) - .setSha1(af.getSha1Hash()) - .setSha256(af.getSha256Hash()); - - ctApiDAO.uploadMeta(new AuthenticatedRequestData(decrypted, authTokenResponse), metaRequest); - return true; - } - @Messages({ "MalwareScanIngestModule_ShareProcessing_batchTimeout_title=Batch Processing Timeout", "MalwareScanIngestModule_ShareProcessing_batchTimeout_desc=Batch processing timed out" @@ -391,6 +348,82 @@ private String sanitizedMd5(String orig) { return StringUtils.defaultString(orig).trim().toLowerCase(); } + private boolean isUnknown(CTCloudBean cloudBean) { + return cloudBean != null + && cloudBean.getMalwareResult() != null + && cloudBean.getMalwareResult().getStatus() == MalwareResultBean.Status.NOT_FOUND; + } + + private boolean isUploadable(AbstractFile af) { + long size = af.getSize(); + return size >= MIN_UPLOAD_SIZE && size <= MAX_UPLOAD_SIZE; + } + + private boolean uploadFile(CTCloudBean cloudBean, long objId) throws CTCloudException, TskCoreException { + if (!uploadUnknownFiles) { + return false; + } + + if (!isUnknown(cloudBean)) { + return false; + } + + AbstractFile af = skCase.getAbstractFileById(objId); + if (af == null) { + return false; + } + + if (!isUploadable(af)) { + return false; + } + + // get auth token / file upload url + AuthTokenResponse authTokenResponse = ctApiDAO.getAuthToken(decrypted, true); + if (StringUtils.isBlank(authTokenResponse.getFileUploadUrl())) { + throw new CTCloudException(CTCloudException.ErrorCode.NETWORK_ERROR); + } else if (remaining(authTokenResponse.getFileUploadLimit(), authTokenResponse.getFileUploadCount()) <= 0) { + // don't proceed with upload if reached limit + uploadUnknownFiles = false; + return false; + } + + // upload bytes + ReadContentInputStream fileInputStream = new ReadContentInputStream(af); + ctApiDAO.uploadFile(authTokenResponse.getFileUploadUrl(), af.getName(), fileInputStream); + + // upload metadata + MetadataUploadRequest metaRequest = new MetadataUploadRequest() + .setCreatedDate(af.getCrtime()) + .setFilePath(af.getUniquePath()) + .setFileSizeBytes(af.getSize()) + .setFileUploadUrl(authTokenResponse.getFileUploadUrl()) + .setMd5(af.getMd5Hash()) + .setSha1(af.getSha1Hash()) + .setSha256(af.getSha256Hash()); + + ctApiDAO.uploadMeta(new AuthenticatedRequestData(decrypted, authTokenResponse), metaRequest); + return true; + } + + private boolean getUploadedFileResults(Map<String, List<Long>> md5objIdMapping) { + Map<String, List<Long>> remaining = new HashMap<>(md5objIdMapping); + + for (int retry = 0; retry < NUM_FILE_UPLOAD_RETRIES; retry++) { + List<List<String>> md5Batches = Lists.partition(new ArrayList<>(remaining.keySet()), BATCH_SIZE); + for (List<String> batch : md5Batches) { + // TODO query and capture still unknown + } + + if (remaining.isEmpty()) { + return true; + } + + + Thread.sleep(FILE_UPLOAD_RETRY_SLEEP_MILLIS); + } + + } + @Messages({ "MalwareScanIngestModule_SharedProcessing_createAnalysisResult_Yes=YES", "MalwareScanIngestModule_SharedProcessing_createAnalysisResult_No=NO"