Skip to content

Commit

Permalink
Merge pull request #170 from apptentive/branch_5.3.1
Browse files Browse the repository at this point in the history
Release 5.3.1
  • Loading branch information
weeeBox authored Oct 19, 2018
2 parents f3e8898 + 471fc9a commit 9b5d6e7
Show file tree
Hide file tree
Showing 18 changed files with 287 additions and 149 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# 2018-08-30 - v5.3.1

#### Fixes

* Fixed SDK state restoration logic.
* Fixed 422 http errors.
* Fixed corrupted payloads if encryption key could not be resolved from the KeyStore.

# 2018-08-30 - v5.3.0

#### Improvements
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use your app, to talk to them at the right time, and in the right way.

##### [Release Notes](https://learn.apptentive.com/knowledge-base/android-sdk-release-notes/)

##### Binary releases are hosted for Maven [here](http://search.maven.org/#artifactdetails|com.apptentive|apptentive-android|5.3.0|aar)
##### Binary releases are hosted for Maven [here](http://search.maven.org/#artifactdetails|com.apptentive|apptentive-android|5.3.1|aar)

#### Reporting Bugs

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ public class ConversationLoadException extends Exception {
public ConversationLoadException(String message) {
super(message);
}
public ConversationLoadException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ public class ConversationManager {
private Conversation activeConversation;
private ConversationProxy activeConversationProxy;

// TODO: this is a temporary solution until we restore conversation state
private boolean activeConversationLoaded;

public ConversationManager(@NonNull Context context, @NonNull File conversationsStorageDir, @NonNull EncryptionKey encryptionKey) {
if (context == null) {
throw new IllegalArgumentException("Context is null");
Expand Down Expand Up @@ -173,6 +176,7 @@ public boolean loadActiveConversation(Context context) {

activeConversation.startListeningForChanges();
activeConversation.scheduleSaveConversationData();
activeConversationLoaded = true;

handleConversationStateChange(activeConversation);
return true;
Expand All @@ -189,7 +193,7 @@ public boolean loadActiveConversation(Context context) {
return false;
}

private @Nullable Conversation loadActiveConversationGuarded() {
private @Nullable Conversation loadActiveConversationGuarded() throws ConversationLoadException {
// try to load an active conversation from metadata first
try {
if (conversationMetadata.hasItems()) {
Expand All @@ -203,6 +207,9 @@ public boolean loadActiveConversation(Context context) {
}
} catch (Exception e) {
ApptentiveLog.e(e, "Exception while loading conversation");

// do not re-create a conversation if the last loading was unsuccessful
throw new ConversationLoadException("Unable to load conversation", e);
}

// no active conversations: create a new one
Expand Down Expand Up @@ -691,7 +698,7 @@ private void updateMetadataItems(Conversation conversation) {

//region Metadata

private ConversationMetadata resolveMetadata() {
private ConversationMetadata resolveMetadata() throws ConversationMetadataLoadException {
checkConversationQueue();

try {
Expand All @@ -718,6 +725,9 @@ private ConversationMetadata resolveMetadata() {
ApptentiveLog.v(CONVERSATION, "No metadata files");
} catch (Exception e) {
ApptentiveLog.e(CONVERSATION, e, "Exception while loading conversation metadata");

// if we fail to load the metadata - we would not create a new one - just throw an exception
throw new ConversationMetadataLoadException("Unable to load metadata", e);
}

return new ConversationMetadata();
Expand Down Expand Up @@ -780,6 +790,13 @@ private void requestLoggedInConversation(final String token, final LoginCallback
return;
}

// Check if we have metadata
if (!activeConversationLoaded) {
ApptentiveLog.e(CONVERSATION, "Unable to login: active conversation was not loaded");
callback.onLoginFail("Unable to login: active conversation was not loaded");
return;
}

// Check if there is an active conversation
if (activeConversation == null) {
ApptentiveLog.d(CONVERSATION, "No active conversation. Performing login...");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.apptentive.android.sdk.conversation;

public class ConversationMetadataLoadException extends Exception {
public ConversationMetadataLoadException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import com.apptentive.android.sdk.ApptentiveLog;
import com.apptentive.android.sdk.debug.Assert;
import com.apptentive.android.sdk.encryption.EncryptionException;
import com.apptentive.android.sdk.encryption.EncryptionKey;
import com.apptentive.android.sdk.encryption.Encryptor;
import com.apptentive.android.sdk.model.ApptentiveMessage;
Expand Down Expand Up @@ -222,7 +223,14 @@ private synchronized void readFromFile() {
}
}

private List<MessageEntry> readFromFileGuarded() throws IOException, NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
private List<MessageEntry> readFromFileGuarded() throws IOException,
NoSuchPaddingException,
InvalidAlgorithmParameterException,
NoSuchAlgorithmException,
IllegalBlockSizeException,
BadPaddingException,
InvalidKeyException,
EncryptionException {
byte[] bytes = Encryptor.readFromEncryptedFile(encryptionKey, file);
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);

Expand All @@ -248,7 +256,14 @@ private synchronized void writeToFile() {
shouldFetchFromFile = false; // mark it as not shouldFetchFromFile to keep a memory version
}

private void writeToFileGuarded() throws IOException, NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
private void writeToFileGuarded() throws IOException,
NoSuchPaddingException,
InvalidKeyException,
NoSuchAlgorithmException,
IllegalBlockSizeException,
BadPaddingException,
InvalidAlgorithmParameterException,
EncryptionException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeByte(VERSION);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.apptentive.android.sdk.encryption;

public class EncryptionException extends Exception {
public EncryptionException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,16 @@
import javax.crypto.spec.SecretKeySpec;

public class EncryptionKey {
public static final EncryptionKey NULL = new EncryptionKey();
/**
* A no-op encryption key for API versions without key chain access (17 and below)
*/
public static final EncryptionKey NULL = new EncryptionKey(false);

/**
* A special instance of the encryption key which will fail every attempt to encrypt and decrypt data.
* Used for the cases when the original encryption key cannot be loaded for the key store.
*/
static final EncryptionKey CORRUPTED = new EncryptionKey(true);

static final String DEFAULT_TRANSFORMATION = "AES/CBC/PKCS5Padding";
private static final String ALGORITHM = "AES";
Expand All @@ -19,6 +28,15 @@ public class EncryptionKey {
private final String hexKey;
private final String transformation;

private boolean corrupted;

private EncryptionKey(boolean corrupted) {
this.corrupted = corrupted;
this.key = null;
this.hexKey = null;
this.transformation = "";
}

public EncryptionKey(@NonNull Key key, @NonNull String transformation) {
if (key == null) {
throw new IllegalArgumentException("Key is null");
Expand All @@ -41,14 +59,12 @@ public EncryptionKey(@NonNull String hexKey) {
this.hexKey = hexKey;
}

private EncryptionKey() {
this.key = null;
this.hexKey = null;
this.transformation = "";
boolean isNull() {
return key == null;
}

public boolean isNull() {
return key == null;
boolean isCorrupted() {
return corrupted;
}

@Nullable Key getSecretKey() {
Expand All @@ -59,7 +75,7 @@ public boolean isNull() {
return hexKey;
}

public @NonNull String getTransformation() {
@NonNull String getTransformation() {
return transformation;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,27 @@ public class Encryptor {
InvalidKeyException,
NoSuchAlgorithmException,
IllegalBlockSizeException,
BadPaddingException,
InvalidAlgorithmParameterException {
BadPaddingException,
InvalidAlgorithmParameterException,
EncryptionException {
return value != null ? encrypt(encryptionKey, value.getBytes()) : null;
}

public static @Nullable byte[] encrypt(EncryptionKey key, @Nullable byte[] plainText) throws NoSuchPaddingException,
NoSuchAlgorithmException,
IllegalBlockSizeException,
IllegalBlockSizeException,
BadPaddingException,
InvalidAlgorithmParameterException,
InvalidKeyException {
InvalidAlgorithmParameterException,
InvalidKeyException,
EncryptionException {
if (key == null) {
throw new IllegalArgumentException("Encryption key is null");
}

if (key.isCorrupted()) {
throw new EncryptionException("Can't encrypt data: key is corrupted");
}

if (plainText == null || key.isNull()) {
return plainText;
}
Expand Down Expand Up @@ -83,21 +89,32 @@ private static byte[] encrypt(EncryptionKey key, byte[] plainText, byte[] iv) th

//region Decrypt

public static @Nullable String decryptString(EncryptionKey encryptionKey, @Nullable byte[] encryptedBytes) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
public static @Nullable String decryptString(EncryptionKey encryptionKey, @Nullable byte[] encryptedBytes) throws NoSuchPaddingException,
InvalidAlgorithmParameterException,
NoSuchAlgorithmException,
IllegalBlockSizeException,
BadPaddingException,
InvalidKeyException,
EncryptionException {
byte[] decrypted = decrypt(encryptionKey, encryptedBytes);
return decrypted != null ? new String(decrypted) : null;
}

public static @Nullable byte[] decrypt(EncryptionKey key, @Nullable byte[] ivAndCipherText) throws NoSuchPaddingException,
InvalidKeyException,
InvalidKeyException,
NoSuchAlgorithmException,
IllegalBlockSizeException,
IllegalBlockSizeException,
BadPaddingException,
InvalidAlgorithmParameterException {
InvalidAlgorithmParameterException,
EncryptionException {
if (key == null) {
throw new IllegalArgumentException("Encryption key is null");
}

if (key.isCorrupted()) {
throw new EncryptionException("Can't decrypt data: key is corrupted");
}

if (ivAndCipherText == null || key.isNull()) {
return ivAndCipherText;
}
Expand Down Expand Up @@ -126,7 +143,14 @@ private static byte[] decrypt(EncryptionKey key, byte[] cipherText, byte[] iv) t

//region File IO

public static void writeToEncryptedFile(EncryptionKey encryptionKey, File file, byte[] data) throws IOException, NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
public static void writeToEncryptedFile(EncryptionKey encryptionKey, File file, byte[] data) throws IOException,
NoSuchPaddingException,
InvalidAlgorithmParameterException,
NoSuchAlgorithmException,
IllegalBlockSizeException,
BadPaddingException,
InvalidKeyException,
EncryptionException {
AtomicFile atomicFile = new AtomicFile(file);
FileOutputStream stream = null;
boolean successful = false;
Expand All @@ -142,7 +166,14 @@ public static void writeToEncryptedFile(EncryptionKey encryptionKey, File file,
}
}

public static byte[] readFromEncryptedFile(EncryptionKey encryptionKey, File file) throws IOException, NoSuchPaddingException, InvalidKeyException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {
public static byte[] readFromEncryptedFile(EncryptionKey encryptionKey, File file) throws IOException,
NoSuchPaddingException,
InvalidKeyException,
NoSuchAlgorithmException,
IllegalBlockSizeException,
BadPaddingException,
InvalidAlgorithmParameterException,
EncryptionException {
final byte[] bytes = Util.readBytes(file);
return decrypt(encryptionKey, bytes);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,8 @@ private static KeyInfo resolveKeyInfo(Context context) {
return keyResolver.resolveKey(context, keyInfo.alias);
} catch (Exception e) {
ApptentiveLog.e(SECURITY, e, "Exception while resolving secret key for alias '%s'. Encryption might not work correctly!", hideIfSanitized(keyInfo.alias));
return EncryptionKey.CORRUPTED;
}

return EncryptionKey.NULL;
}

//endregion
Expand Down
Loading

0 comments on commit 9b5d6e7

Please sign in to comment.