mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-11 18:54:21 +00:00
acb40c6133
Added a new logger that persists logs for a longer duration to the user's cache directory. Logs are encrypted. The new logs are sent in addition to the user's logcat output.
181 lines
6.2 KiB
Java
181 lines
6.2 KiB
Java
package org.thoughtcrime.securesms.crypto;
|
|
|
|
|
|
import android.os.Build;
|
|
import android.security.keystore.KeyGenParameterSpec;
|
|
import android.security.keystore.KeyProperties;
|
|
import android.support.annotation.NonNull;
|
|
import android.support.annotation.RequiresApi;
|
|
import android.util.Base64;
|
|
|
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
|
import com.fasterxml.jackson.core.JsonGenerator;
|
|
import com.fasterxml.jackson.core.JsonParser;
|
|
import com.fasterxml.jackson.databind.DeserializationContext;
|
|
import com.fasterxml.jackson.databind.JsonDeserializer;
|
|
import com.fasterxml.jackson.databind.JsonSerializer;
|
|
import com.fasterxml.jackson.databind.SerializerProvider;
|
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
|
|
|
import org.thoughtcrime.securesms.util.JsonUtils;
|
|
|
|
import java.io.IOException;
|
|
import java.security.InvalidAlgorithmParameterException;
|
|
import java.security.InvalidKeyException;
|
|
import java.security.KeyStore;
|
|
import java.security.KeyStoreException;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.security.NoSuchProviderException;
|
|
import java.security.UnrecoverableEntryException;
|
|
import java.security.cert.CertificateException;
|
|
|
|
import javax.crypto.BadPaddingException;
|
|
import javax.crypto.Cipher;
|
|
import javax.crypto.IllegalBlockSizeException;
|
|
import javax.crypto.KeyGenerator;
|
|
import javax.crypto.NoSuchPaddingException;
|
|
import javax.crypto.SecretKey;
|
|
import javax.crypto.spec.GCMParameterSpec;
|
|
|
|
public class KeyStoreHelper {
|
|
|
|
private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
|
|
private static final String KEY_ALIAS = "SignalSecret";
|
|
|
|
@RequiresApi(Build.VERSION_CODES.M)
|
|
public static SealedData seal(@NonNull byte[] input) {
|
|
SecretKey secretKey = getOrCreateKeyStoreEntry();
|
|
|
|
try {
|
|
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
|
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
|
|
|
|
byte[] iv = cipher.getIV();
|
|
byte[] data = cipher.doFinal(input);
|
|
|
|
return new SealedData(iv, data);
|
|
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
|
|
throw new AssertionError(e);
|
|
}
|
|
}
|
|
|
|
@RequiresApi(Build.VERSION_CODES.M)
|
|
public static byte[] unseal(@NonNull SealedData sealedData) {
|
|
SecretKey secretKey = getKeyStoreEntry();
|
|
|
|
try {
|
|
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
|
cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, sealedData.iv));
|
|
|
|
return cipher.doFinal(sealedData.data);
|
|
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
|
|
throw new AssertionError(e);
|
|
}
|
|
}
|
|
|
|
@RequiresApi(Build.VERSION_CODES.M)
|
|
private static SecretKey getOrCreateKeyStoreEntry() {
|
|
if (hasKeyStoreEntry()) return getKeyStoreEntry();
|
|
else return createKeyStoreEntry();
|
|
}
|
|
|
|
@RequiresApi(Build.VERSION_CODES.M)
|
|
private static SecretKey createKeyStoreEntry() {
|
|
try {
|
|
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
|
|
KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
|
|
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
|
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
|
.build();
|
|
|
|
keyGenerator.init(keyGenParameterSpec);
|
|
|
|
return keyGenerator.generateKey();
|
|
} catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
|
|
throw new AssertionError(e);
|
|
}
|
|
}
|
|
|
|
@RequiresApi(Build.VERSION_CODES.M)
|
|
private static SecretKey getKeyStoreEntry() {
|
|
try {
|
|
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
|
|
keyStore.load(null);
|
|
|
|
return ((KeyStore.SecretKeyEntry) keyStore.getEntry(KEY_ALIAS, null)).getSecretKey();
|
|
} catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException | UnrecoverableEntryException e) {
|
|
throw new AssertionError(e);
|
|
}
|
|
}
|
|
|
|
@RequiresApi(Build.VERSION_CODES.M)
|
|
private static boolean hasKeyStoreEntry() {
|
|
try {
|
|
KeyStore ks = KeyStore.getInstance(ANDROID_KEY_STORE);
|
|
ks.load(null);
|
|
|
|
return ks.containsAlias(KEY_ALIAS) && ks.entryInstanceOf(KEY_ALIAS, KeyStore.SecretKeyEntry.class);
|
|
} catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) {
|
|
throw new AssertionError(e);
|
|
}
|
|
}
|
|
|
|
public static class SealedData {
|
|
|
|
@SuppressWarnings("unused")
|
|
private static final String TAG = SealedData.class.getSimpleName();
|
|
|
|
@JsonProperty
|
|
@JsonSerialize(using = ByteArraySerializer.class)
|
|
@JsonDeserialize(using = ByteArrayDeserializer.class)
|
|
private byte[] iv;
|
|
|
|
@JsonProperty
|
|
@JsonSerialize(using = ByteArraySerializer.class)
|
|
@JsonDeserialize(using = ByteArrayDeserializer.class)
|
|
private byte[] data;
|
|
|
|
SealedData(@NonNull byte[] iv, @NonNull byte[] data) {
|
|
this.iv = iv;
|
|
this.data = data;
|
|
}
|
|
|
|
@SuppressWarnings("unused")
|
|
public SealedData() {}
|
|
|
|
public String serialize() {
|
|
try {
|
|
return JsonUtils.toJson(this);
|
|
} catch (IOException e) {
|
|
throw new AssertionError(e);
|
|
}
|
|
}
|
|
|
|
public static SealedData fromString(@NonNull String value) {
|
|
try {
|
|
return JsonUtils.fromJson(value, SealedData.class);
|
|
} catch (IOException e) {
|
|
throw new AssertionError(e);
|
|
}
|
|
}
|
|
|
|
private static class ByteArraySerializer extends JsonSerializer<byte[]> {
|
|
@Override
|
|
public void serialize(byte[] value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
|
gen.writeString(Base64.encodeToString(value, Base64.NO_WRAP | Base64.NO_PADDING));
|
|
}
|
|
}
|
|
|
|
private static class ByteArrayDeserializer extends JsonDeserializer<byte[]> {
|
|
|
|
@Override
|
|
public byte[] deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
|
|
return Base64.decode(p.getValueAsString(), Base64.NO_WRAP | Base64.NO_PADDING);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|