diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudHttpClient.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudHttpClient.java index d774ff1661..0fa9feda54 100644 --- a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudHttpClient.java +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudHttpClient.java @@ -21,17 +21,13 @@ package com.basistech.df.cybertriage.autopsy.ctapi; import com.basistech.df.cybertriage.autopsy.ctapi.util.ObjectMapperUtil; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; +import java.io.InputStream; import java.net.Authenticator; import java.net.InetAddress; import java.net.PasswordAuthentication; import java.net.URI; import java.net.URISyntaxException; import java.net.UnknownHostException; -import java.security.KeyManagementException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.security.UnrecoverableKeyException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; @@ -41,13 +37,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import java.util.logging.Level; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; -import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpEntity; @@ -64,7 +54,9 @@ import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; import org.apache.http.client.utils.URIBuilder; +import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; +import org.apache.http.entity.mime.MultipartEntityBuilder; import org.sleuthkit.autopsy.coreutils.Logger; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; @@ -175,10 +167,14 @@ public class CTCloudHttpClient { if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { LOGGER.log(Level.INFO, "Response Received. - Status OK"); // Parse Response - HttpEntity entity = response.getEntity(); - String entityStr = EntityUtils.toString(entity); - O respObj = mapper.readValue(entityStr, classType); - return respObj; + if (classType != null) { + HttpEntity entity = response.getEntity(); + String entityStr = EntityUtils.toString(entity); + O respObj = mapper.readValue(entityStr, classType); + return respObj; + } else { + return null; + } } else { LOGGER.log(Level.WARNING, "Response Received. - Status Error {}", response.getStatusLine()); handleNonOKResponse(response, ""); @@ -198,6 +194,40 @@ public class CTCloudHttpClient { return null; } + + public void doFileUploadPost(String urlPath, String fileName, InputStream fileIs) throws CTCloudException { + + try (CloseableHttpClient httpclient = createConnection(getProxySettings(), sslContext)) { + HttpPost post = new HttpPost(urlPath); + configureRequestTimeout(post); + + post.addHeader("Connection", "keep-alive"); + + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.addBinaryBody( + "file", + fileIs, + ContentType.APPLICATION_OCTET_STREAM, + fileName + ); + + HttpEntity multipart = builder.build(); + post.setEntity(multipart); + + try (CloseableHttpResponse response = httpclient.execute(post)) { + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_NO_CONTENT) { + LOGGER.log(Level.INFO, "Response Received. - Status OK"); + } else { + LOGGER.log(Level.WARNING, MessageFormat.format("Response Received. - Status Error {0}", response.getStatusLine())); + handleNonOKResponse(response, fileName); + } + } + } catch (IOException ex) { + LOGGER.log(Level.WARNING, "IO Exception raised when connecting to Reversing Labs for file content upload ", ex); + throw new CTCloudException(CTCloudException.ErrorCode.NETWORK_ERROR, ex); + } + } /** * A generic way to handle the HTTP response - when the response code is NOT diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CtApiDAO.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CtApiDAO.java index 899af5545b..927cc10cbe 100644 --- a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CtApiDAO.java +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CtApiDAO.java @@ -27,7 +27,9 @@ import com.basistech.df.cybertriage.autopsy.ctapi.json.DecryptedLicenseResponse; import com.basistech.df.cybertriage.autopsy.ctapi.json.FileReputationRequest; import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseRequest; import com.basistech.df.cybertriage.autopsy.ctapi.json.LicenseResponse; +import com.basistech.df.cybertriage.autopsy.ctapi.json.MetadataUploadRequest; import com.basistech.df.cybertriage.autopsy.ctapi.util.CTHostIDGenerationUtil; +import java.io.InputStream; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -44,6 +46,8 @@ public class CTApiDAO { private static final String LICENSE_REQUEST_PATH = "/_ah/api/license/v1/activate"; private static final String AUTH_TOKEN_REQUEST_PATH = "/_ah/api/auth/v2/generate_token"; private static final String CTCLOUD_SERVER_HASH_PATH = "/_ah/api/reputation/v1/query/file/hash/md5?query_types=CORRELATION,MALWARE"; + private static final String CTCLOUD_UPLOAD_FILE_METADATA_PATH = "/_ah/api/reputation/v1/upload/meta"; + private static final String AUTOPSY_PRODUCT = "AUTOPSY"; private static final CTApiDAO instance = new CTApiDAO(); @@ -72,15 +76,27 @@ public class CTApiDAO { } public AuthTokenResponse getAuthToken(DecryptedLicenseResponse decrypted) throws CTCloudException { + return getAuthToken(decrypted, false); + } + + public AuthTokenResponse getAuthToken(DecryptedLicenseResponse decrypted, boolean fileUpload) throws CTCloudException { AuthTokenRequest authTokenRequest = new AuthTokenRequest() .setAutopsyVersion(getAppVersion()) - .setRequestFileUpload(false) + .setRequestFileUpload(fileUpload) .setBoostLicenseId(decrypted.getBoostLicenseId()) .setHostId(decrypted.getLicenseHostId()); return httpClient.doPost(AUTH_TOKEN_REQUEST_PATH, authTokenRequest, AuthTokenResponse.class); } + public void uploadFile(String url, String fileName, InputStream fileIs) throws CTCloudException { + httpClient.doFileUploadPost(url, fileName, fileIs); + } + + public void uploadMeta(AuthenticatedRequestData authenticatedRequestData, MetadataUploadRequest metaRequest) throws CTCloudException { + httpClient.doPost(AUTH_TOKEN_REQUEST_PATH, getAuthParams(authenticatedRequestData), metaRequest, null); + } + private static Map getAuthParams(AuthenticatedRequestData authenticatedRequestData) { return new HashMap() { { diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/MetadataUploadRequest.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/MetadataUploadRequest.java new file mode 100644 index 0000000000..57d59c3faa --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/MetadataUploadRequest.java @@ -0,0 +1,109 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * 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 com.basistech.df.cybertriage.autopsy.ctapi.json; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class MetadataUploadRequest { + + @JsonProperty("file_upload_url") + private String fileUploadUrl; + + @JsonProperty("sha1") + private String sha1; + + @JsonProperty("sha256") + private String sha256; + + @JsonProperty("md5") + private String md5; + + @JsonProperty("filePath") + private String filePath; + + @JsonProperty("fileSize") + private long fileSizeBytes; + + @JsonProperty("createdDate") + private long createdDate; + + public String getFileUploadUrl() { + return fileUploadUrl; + } + + public MetadataUploadRequest setFileUploadUrl(String fileUploadUrl) { + this.fileUploadUrl = fileUploadUrl; + return this; + } + + public String getSha1() { + return sha1; + } + + public MetadataUploadRequest setSha1(String sha1) { + this.sha1 = sha1; + return this; + } + + public String getSha256() { + return sha256; + } + + public MetadataUploadRequest setSha256(String sha256) { + this.sha256 = sha256; + return this; + } + + public String getMd5() { + return md5; + } + + public MetadataUploadRequest setMd5(String md5) { + this.md5 = md5; + return this; + } + + public String getFilePath() { + return filePath; + } + + public MetadataUploadRequest setFilePath(String filePath) { + this.filePath = filePath; + return this; + } + + public long getFileSizeBytes() { + return fileSizeBytes; + } + + public MetadataUploadRequest setFileSizeBytes(long fileSizeBytes) { + this.fileSizeBytes = fileSizeBytes; + return this; + } + + public long getCreatedDate() { + return createdDate; + } + + public MetadataUploadRequest setCreatedDate(long createdDate) { + this.createdDate = createdDate; + return this; + } + +} diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/FileUpload.java b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/FileUpload.java new file mode 100644 index 0000000000..64fb36f2a1 --- /dev/null +++ b/Core/src/com/basistech/df/cybertriage/autopsy/malwarescan/FileUpload.java @@ -0,0 +1,78 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2023 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * 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 com.basistech.df.cybertriage.autopsy.malwarescan; + +import com.basistech.df.cybertriage.autopsy.ctapi.CTApiDAO; +import com.basistech.df.cybertriage.autopsy.ctapi.CTCloudException; +import com.basistech.df.cybertriage.autopsy.ctapi.json.AuthTokenResponse; +import com.basistech.df.cybertriage.autopsy.ctapi.json.AuthenticatedRequestData; +import com.basistech.df.cybertriage.autopsy.ctapi.json.CTCloudBean; +import com.basistech.df.cybertriage.autopsy.ctapi.json.DecryptedLicenseResponse; +import com.basistech.df.cybertriage.autopsy.ctapi.json.MetadataUploadRequest; +import org.apache.commons.lang3.StringUtils; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.ReadContentInputStream; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Handles uploading of files that are unknown. + */ +public class FileUpload { + + private final CTApiDAO ctApiDAO = CTApiDAO.getInstance(); + + private boolean isUnknown(CTCloudBean cloudBean) { + + } + + private boolean isUploadable(AbstractFile af) { + + } + + public boolean tryUpload(SleuthkitCase skCase, CTCloudBean cloudBean, long objId) { + + } + + private boolean upload(DecryptedLicenseResponse decrypted, AbstractFile af) throws CTCloudException, TskCoreException { + // 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; + } + +}