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 0a7c69cdeecb263bffda33a6b227952914bbd518..cf93d58307b1347bc10fff1cb63e8b0371647bfc 100644 --- a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudHttpClient.java +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/CTCloudHttpClient.java @@ -158,16 +158,23 @@ public <O> O doPost(String urlPath, Map<String, String> urlReqParams, Object jso // Parse Response if (classType != null) { HttpEntity entity = response.getEntity(); - String entityStr = EntityUtils.toString(entity); - O respObj = mapper.readValue(entityStr, classType); - return respObj; - } else { - return null; + if (entity != null) { + String entityStr = EntityUtils.toString(entity); + if (StringUtils.isNotBlank(entityStr)) { + O respObj = mapper.readValue(entityStr, classType); + return respObj; + } + } } + + return null; } else { LOGGER.log(Level.WARNING, "Response Received. - Status Error {}", response.getStatusLine()); handleNonOKResponse(response, ""); } + // transform all non-CTCloudException's into a CTCloudException + } catch (CTCloudException ex) { + throw ex; } catch (Exception ex) { LOGGER.log(Level.WARNING, "Error when parsing response from CyberTriage Cloud", ex); throw new CTCloudException(CTCloudException.parseUnknownException(ex), ex); @@ -191,7 +198,7 @@ public void doFileUploadPut(FileUploadRequest fileUploadRequest) throws CTCloudE if (fileUploadRequest == null) { throw new CTCloudException(ErrorCode.BAD_REQUEST, new IllegalArgumentException("fileUploadRequest cannot be null")); } - + String fullUrlPath = fileUploadRequest.getFullUrlPath(); String fileName = fileUploadRequest.getFileName(); InputStream fileInputStream = fileUploadRequest.getFileInputStream(); @@ -200,7 +207,7 @@ public void doFileUploadPut(FileUploadRequest fileUploadRequest) throws CTCloudE if (StringUtils.isBlank(fullUrlPath) || fileInputStream == null || contentLength == null || contentLength <= 0) { throw new CTCloudException(ErrorCode.BAD_REQUEST, new IllegalArgumentException("fullUrlPath, fileInputStream, contentLength must not be empty, null or less than 0")); } - + URI putUri; try { putUri = new URI(fullUrlPath); diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/LicenseResponse.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/LicenseResponse.java index a3a824788400e033cdf0776fbe6fcda2616bad0c..5a85778b60ec8c84375afd42b3514a6c69b5576c 100644 --- a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/LicenseResponse.java +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/json/LicenseResponse.java @@ -32,18 +32,21 @@ public class LicenseResponse { private final Boolean hostChanged; private final Long hostChangesRemaining; private final BoostLicenseResponse boostLicense; + private final String errorMsg; @JsonCreator public LicenseResponse( @JsonProperty("success") Boolean success, @JsonProperty("hostChanged") Boolean hostChanged, @JsonProperty("hostChangesRemaining") Long hostChangesRemaining, - @JsonProperty("boostLicense") BoostLicenseResponse boostLicense + @JsonProperty("boostLicense") BoostLicenseResponse boostLicense, + @JsonProperty("errorMsg") String errorMsg ) { this.success = success; this.hostChanged = hostChanged; this.hostChangesRemaining = hostChangesRemaining; this.boostLicense = boostLicense; + this.errorMsg = errorMsg; } public Boolean isSuccess() { @@ -61,4 +64,8 @@ public Long getHostChangesRemaining() { public BoostLicenseResponse getBoostLicense() { return boostLicense; } + + public String getErrorMsg() { + return errorMsg; + } } diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/LicenseDecryptorUtil.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/LicenseDecryptorUtil.java index f62b57d795559275f0329182904dc867fc9c1b83..26ebe793a4a93b0462dfb59b1cf67df849adf48d 100644 --- a/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/LicenseDecryptorUtil.java +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctapi/util/LicenseDecryptorUtil.java @@ -34,6 +34,7 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import java.security.spec.X509EncodedKeySpec; +import java.text.MessageFormat; import java.util.Base64; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; @@ -42,6 +43,7 @@ import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; +import org.apache.commons.lang3.ObjectUtils; /** * Decrypts the payload of boost license. @@ -58,12 +60,12 @@ public static LicenseDecryptorUtil getInstance() { private LicenseDecryptorUtil() { } - + public LicenseInfo createLicenseInfo(LicenseResponse licenseResponse) throws JsonProcessingException, InvalidLicenseException { - if (licenseResponse == null || licenseResponse.getBoostLicense() == null) { - throw new InvalidLicenseException("License or boost license are null"); + if (licenseResponse == null) { + throw new InvalidLicenseException("License is null"); } - + DecryptedLicenseResponse decrypted = parseLicenseJSON(licenseResponse.getBoostLicense()); return new LicenseInfo(licenseResponse, decrypted); } @@ -78,6 +80,9 @@ public LicenseInfo createLicenseInfo(LicenseResponse licenseResponse) throws Jso * com.basistech.df.cybertriage.autopsy.ctapi.util.LicenseDecryptorUtil.InvalidLicenseException */ public DecryptedLicenseResponse parseLicenseJSON(BoostLicenseResponse licenseResponse) throws JsonProcessingException, InvalidLicenseException { + if (licenseResponse == null) { + throw new InvalidLicenseException("Boost license is null"); + } String decryptedJsonResponse; try { @@ -101,6 +106,12 @@ public DecryptedLicenseResponse parseLicenseJSON(BoostLicenseResponse licenseRes } private String decryptLicenseString(String encryptedJson, String ivBase64, String encryptedKey, String version) throws IOException, GeneralSecurityException, InvalidLicenseException { + if (ObjectUtils.anyNull(encryptedJson, ivBase64, encryptedKey, version)) { + throw new InvalidLicenseException(MessageFormat.format( + "encryptedJson: {0}, iv: {1}, encryptedKey: {2}, version: {3} must all be non-null", + encryptedJson, ivBase64, encryptedKey, version)); + } + if (!"1.0".equals(version)) { throw new InvalidLicenseException("Unexpected file version: " + version); } diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties index 3c86b5446dc4c97322b2d9a9847caa1694de8bba..44e71a4513faf8e99b7bb26b37a3aa624f9f7d91 100644 --- a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties @@ -4,7 +4,7 @@ CTLicenseDialog.title=Add a License... CTLicenseDialog.licenseNumberLabel.text=License Number: -CTLicenseDialog.licenseNumberTextField.text=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX +CTLicenseDialog.licenseNumberTextField.text= CTLicenseDialog.cancelButton.text=Cancel CTLicenseDialog.okButton.text=Ok CTLicenseDialog.warningLabel.text= @@ -25,3 +25,4 @@ EULADialog.title=Cyber Triage End User License Agreement CTMalwareScannerOptionsPanel.licenseInfoMessageLabel.text= CTMalwareScannerOptionsPanel.disclaimer.text=<html>The Cyber Triage Malware Scanner module uses 40+ malware scanning engines to identify if Windows executables are malicious. It requires a paid subscription to use.</html> CTMalwareScannerOptionsPanel.purchaseFromLabel.text=For licensing information, visit +CTLicenseDialog.licenseNumberTextField.toolTipText=AUT-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties-MERGED b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties-MERGED index 0e888c7facf1375c12a0f927e486e3b38032b34b..d9876a8ed7fb131d2b7163a2007a49b81726bba9 100644 --- a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties-MERGED +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties-MERGED @@ -4,11 +4,11 @@ CTLicenseDialog.title=Add a License... CTLicenseDialog.licenseNumberLabel.text=License Number: -CTLicenseDialog.licenseNumberTextField.text=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX +CTLicenseDialog.licenseNumberTextField.text= CTLicenseDialog.cancelButton.text=Cancel CTLicenseDialog.okButton.text=Ok CTLicenseDialog.warningLabel.text= -CTLicenseDialog_verifyInput_licenseNumberError=<html>Please verify license number format of 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'</html> +CTLicenseDialog_verifyInput_licenseNumberError=<html>Please enter a license number</html> CTMalwareScannerOptionsPanel.hashLookupsRemainingLabel.text= CTMalwareScannerOptionsPanel.countersResetLabel.text= CTMalwareScannerOptionsPanel.maxFileUploadsLabel.text= @@ -31,6 +31,8 @@ CTMalwareScannerOptionsPanel_licenseAddDialogEnteredErr_title=License Number Alr CTMalwareScannerOptionsPanel_licenseAddDialogPatternErr_desc=Please verify that license number is of format 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' CTMalwareScannerOptionsPanel_licenseAddDialogPatternErr_title=Invalid License Number CTMalwareScannerOptionsPanel_LicenseFetcher_apiErr_title=Server Error +# {0} - licenseCode +CTMalwareScannerOptionsPanel_LicenseFetcher_defaultErrMsg_desc=Error activating boost license {0} CTMalwareScannerOptionsPanel_LicenseFetcher_localErr_desc=A general error occurred while fetching license information. Please try again later. CTMalwareScannerOptionsPanel_LicenseFetcher_localErr_title=General Error # {0} - expiresDate @@ -63,3 +65,4 @@ EULADialog.title=Cyber Triage End User License Agreement CTMalwareScannerOptionsPanel.licenseInfoMessageLabel.text= CTMalwareScannerOptionsPanel.disclaimer.text=<html>The Cyber Triage Malware Scanner module uses 40+ malware scanning engines to identify if Windows executables are malicious. It requires a paid subscription to use.</html> CTMalwareScannerOptionsPanel.purchaseFromLabel.text=For licensing information, visit +CTLicenseDialog.licenseNumberTextField.toolTipText=AUT-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.form b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.form index e7cd2743a0764cbcbf22a4cfe04867d489ebe5ae..2ea57d43a8abce9be2971e3a6e9dfce09942d473 100644 --- a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.form +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.form @@ -127,6 +127,9 @@ <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties" key="CTLicenseDialog.licenseNumberTextField.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> </Property> + <Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/Bundle.properties" key="CTLicenseDialog.licenseNumberTextField.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> </Properties> <Constraints> <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> diff --git a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.java b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.java index 3bff998901d0fc071686150e58c3eedf4c8d57df..99fc749dd739bb0923293896c70eb6ca24d09b44 100644 --- a/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.java +++ b/Core/src/com/basistech/df/cybertriage/autopsy/ctoptions/ctcloud/CTLicenseDialog.java @@ -18,18 +18,20 @@ */ package com.basistech.df.cybertriage.autopsy.ctoptions.ctcloud; +import java.awt.Color; import java.util.regex.Pattern; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import org.apache.commons.lang3.StringUtils; import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.corecomponents.TextPrompt; /** * License dialog */ class CTLicenseDialog extends javax.swing.JDialog { - private static final Pattern LICENSE_PATTERN = Pattern.compile("^\\s*[a-zA-Z0-9\\-]+?\\s*$"); + private static final Pattern LICENSE_PATTERN = Pattern.compile("^\\s*[a-zA-Z0-9-_]+?\\s*$"); private String licenseString = null; /** @@ -38,6 +40,7 @@ class CTLicenseDialog extends javax.swing.JDialog { public CTLicenseDialog(java.awt.Frame parent, boolean modal) { super(parent, modal); initComponents(); + configureHintText(); this.licenseNumberTextField.getDocument().putProperty("filterNewlines", Boolean.TRUE); this.licenseNumberTextField.getDocument().addDocumentListener(new DocumentListener() { @Override @@ -56,13 +59,23 @@ public void removeUpdate(DocumentEvent e) { } }); } - + + private void configureHintText() { + TextPrompt textPrompt = new TextPrompt( + StringUtils.defaultString(this.licenseNumberTextField.getToolTipText()), + this.licenseNumberTextField); + + textPrompt.setForeground(Color.LIGHT_GRAY); + float alpha = 0.9f; // Mostly opaque + textPrompt.changeAlpha(alpha); + } + String getValue() { return licenseString; } @Messages({ - "CTLicenseDialog_verifyInput_licenseNumberError=<html>Please verify license number format of 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'</html>" + "CTLicenseDialog_verifyInput_licenseNumberError=<html>Please enter a license number</html>" }) private void verifyInput() { String licenseInput = StringUtils.defaultString(this.licenseNumberTextField.getText()); @@ -165,6 +178,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { getContentPane().add(cancelButton, gridBagConstraints); licenseNumberTextField.setText(org.openide.util.NbBundle.getMessage(CTLicenseDialog.class, "CTLicenseDialog.licenseNumberTextField.text")); // NOI18N + licenseNumberTextField.setToolTipText(org.openide.util.NbBundle.getMessage(CTLicenseDialog.class, "CTLicenseDialog.licenseNumberTextField.toolTipText")); // NOI18N gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 1; @@ -177,7 +191,8 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { }// </editor-fold>//GEN-END:initComponents private void okButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_okButtonActionPerformed - this.licenseString = this.licenseNumberTextField.getText(); + String inputText = this.licenseNumberTextField.getText(); + this.licenseString = inputText == null ? null : inputText.trim(); this.dispose(); }//GEN-LAST:event_okButtonActionPerformed 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 3c71bd3cbacb53462d1b0325398c5e0b1f768a8f..deff21d4230b27ec14282dac45ed4b06ce39f5c2 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 @@ -608,6 +608,8 @@ private void acceptEula(LicenseResponse licenseResponse) { @NbBundle.Messages({ "CTMalwareScannerOptionsPanel_LicenseFetcher_apiErr_title=Server Error", "CTMalwareScannerOptionsPanel_LicenseFetcher_localErr_title=General Error", + "# {0} - licenseCode", + "CTMalwareScannerOptionsPanel_LicenseFetcher_defaultErrMsg_desc=Error activating boost license {0}", "CTMalwareScannerOptionsPanel_LicenseFetcher_localErr_desc=A general error occurred while fetching license information. Please try again later.",}) private class LicenseFetcher extends SwingWorker<LicenseResponse, Void> { @@ -629,10 +631,9 @@ protected LicenseResponse doInBackground() throws Exception { protected void done() { try { LicenseResponse licenseResponse = get(); - if (licenseResponse != null && licenseResponse.isSuccess()) { - SwingUtilities.invokeLater(() -> acceptEula(licenseResponse)); - } else { - logger.log(Level.WARNING, "An API error occurred while fetching license information. License fetch was not successful"); + // if no result, show unauthorized + if (licenseResponse == null) { + logger.log(Level.WARNING, "An API error occurred while fetching license information. License fetch returned no result."); JOptionPane.showMessageDialog( CTMalwareScannerOptionsPanel.this, CTCloudException.ErrorCode.UN_AUTHORIZED.getDescription(), @@ -640,7 +641,30 @@ protected void done() { JOptionPane.ERROR_MESSAGE); setLicenseDisplay(licenseInfo, null); loadMalwareScansInfo(licenseInfo); + return; + } + + // if not successful response + if (!Boolean.TRUE.equals(licenseResponse.isSuccess())) { + logger.log(Level.WARNING, "An API error occurred while fetching license information. License fetch was not successful"); + // use default message unless error message specified + String message = Bundle.CTMalwareScannerOptionsPanel_LicenseFetcher_defaultErrMsg_desc(licenseText); + if (!StringUtils.isBlank(licenseResponse.getErrorMsg())) { + message = licenseResponse.getErrorMsg(); + } + JOptionPane.showMessageDialog( + CTMalwareScannerOptionsPanel.this, + message, + Bundle.CTMalwareScannerOptionsPanel_LicenseFetcher_apiErr_title(), + JOptionPane.ERROR_MESSAGE); + setLicenseDisplay(licenseInfo, null); + loadMalwareScansInfo(licenseInfo); + return; } + + // otherwise, load + SwingUtilities.invokeLater(() -> acceptEula(licenseResponse)); + } catch (InterruptedException | CancellationException ex) { // ignore cancellation; just load current license setLicenseDisplay(licenseInfo, null);