Merge remote-tracking branch 'upstream/dev' into bluetooth-manager-crash

# Conflicts:
#	app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.kt
#	app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.kt
This commit is contained in:
Morgan Pretty 2023-05-30 12:25:13 +10:00
commit c77d465438
10 changed files with 90 additions and 123 deletions

View File

@ -1,13 +1,13 @@
package org.thoughtcrime.securesms.crypto; package org.thoughtcrime.securesms.crypto;
import android.os.Build; import static org.session.libsignal.crypto.CipherUtil.CIPHER_LOCK;
import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties; import android.security.keystore.KeyProperties;
import android.util.Base64; import android.util.Base64;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonGenerator;
@ -45,8 +45,6 @@ public final class KeyStoreHelper {
private static final String ANDROID_KEY_STORE = "AndroidKeyStore"; private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
private static final String KEY_ALIAS = "SignalSecret"; private static final String KEY_ALIAS = "SignalSecret";
private static final Object lock = new Object();
public static SealedData seal(@NonNull byte[] input) { public static SealedData seal(@NonNull byte[] input) {
SecretKey secretKey = getOrCreateKeyStoreEntry(); SecretKey secretKey = getOrCreateKeyStoreEntry();
@ -54,7 +52,7 @@ public final class KeyStoreHelper {
// Cipher operations are not thread-safe so we synchronize over them through doFinal to // Cipher operations are not thread-safe so we synchronize over them through doFinal to
// prevent crashes with quickly repeated encrypt/decrypt operations // prevent crashes with quickly repeated encrypt/decrypt operations
// https://github.com/mozilla-mobile/android-components/issues/5342 // https://github.com/mozilla-mobile/android-components/issues/5342
synchronized (lock) { synchronized (CIPHER_LOCK) {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey); cipher.init(Cipher.ENCRYPT_MODE, secretKey);
@ -75,7 +73,7 @@ public final class KeyStoreHelper {
// Cipher operations are not thread-safe so we synchronize over them through doFinal to // Cipher operations are not thread-safe so we synchronize over them through doFinal to
// prevent crashes with quickly repeated encrypt/decrypt operations // prevent crashes with quickly repeated encrypt/decrypt operations
// https://github.com/mozilla-mobile/android-components/issues/5342 // https://github.com/mozilla-mobile/android-components/issues/5342
synchronized (lock) { synchronized (CIPHER_LOCK) {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, sealedData.iv)); cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, sealedData.iv));
@ -208,7 +206,5 @@ public final class KeyStoreHelper {
return Base64.decode(p.getValueAsString(), Base64.NO_WRAP | Base64.NO_PADDING); return Base64.decode(p.getValueAsString(), Base64.NO_WRAP | Base64.NO_PADDING);
} }
} }
} }
} }

View File

@ -1,5 +1,7 @@
package org.thoughtcrime.securesms.logging; package org.thoughtcrime.securesms.logging;
import static org.session.libsignal.crypto.CipherUtil.CIPHER_LOCK;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.session.libsession.utilities.Conversions; import org.session.libsession.utilities.Conversions;
@ -66,6 +68,7 @@ class LogFile {
byte[] plaintext = entry.getBytes(); byte[] plaintext = entry.getBytes();
try { try {
synchronized (CIPHER_LOCK) {
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(secret, "AES"), new IvParameterSpec(ivBuffer)); cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(secret, "AES"), new IvParameterSpec(ivBuffer));
int cipherLength = cipher.getOutputSize(plaintext.length); int cipherLength = cipher.getOutputSize(plaintext.length);
@ -75,6 +78,7 @@ class LogFile {
outputStream.write(ivBuffer); outputStream.write(ivBuffer);
outputStream.write(Conversions.intToByteArray(cipherLength)); outputStream.write(Conversions.intToByteArray(cipherLength));
outputStream.write(ciphertext, 0, cipherLength); outputStream.write(ciphertext, 0, cipherLength);
}
outputStream.flush(); outputStream.flush();
} catch (ShortBufferException | InvalidAlgorithmParameterException | InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) { } catch (ShortBufferException | InvalidAlgorithmParameterException | InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) {
@ -134,10 +138,11 @@ class LogFile {
Util.readFully(inputStream, ciphertext, length); Util.readFully(inputStream, ciphertext, length);
try { try {
synchronized (CIPHER_LOCK) {
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(secret, "AES"), new IvParameterSpec(ivBuffer)); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(secret, "AES"), new IvParameterSpec(ivBuffer));
byte[] plaintext = cipher.doFinal(ciphertext, 0, length); byte[] plaintext = cipher.doFinal(ciphertext, 0, length);
return new String(plaintext); return new String(plaintext);
}
} catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) { } catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
throw new AssertionError(e); throw new AssertionError(e);
} }

View File

@ -1,6 +1,7 @@
package org.session.libsession.utilities package org.session.libsession.utilities
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import org.session.libsignal.crypto.CipherUtil.CIPHER_LOCK
import org.session.libsignal.utilities.ByteUtil import org.session.libsignal.utilities.ByteUtil
import org.session.libsignal.utilities.Util import org.session.libsignal.utilities.Util
import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.Hex
@ -27,10 +28,12 @@ internal object AESGCM {
internal fun decrypt(ivAndCiphertext: ByteArray, symmetricKey: ByteArray): ByteArray { internal fun decrypt(ivAndCiphertext: ByteArray, symmetricKey: ByteArray): ByteArray {
val iv = ivAndCiphertext.sliceArray(0 until ivSize) val iv = ivAndCiphertext.sliceArray(0 until ivSize)
val ciphertext = ivAndCiphertext.sliceArray(ivSize until ivAndCiphertext.count()) val ciphertext = ivAndCiphertext.sliceArray(ivSize until ivAndCiphertext.count())
synchronized(CIPHER_LOCK) {
val cipher = Cipher.getInstance("AES/GCM/NoPadding") val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(symmetricKey, "AES"), GCMParameterSpec(gcmTagSize, iv)) cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(symmetricKey, "AES"), GCMParameterSpec(gcmTagSize, iv))
return cipher.doFinal(ciphertext) return cipher.doFinal(ciphertext)
} }
}
/** /**
* Sync. Don't call from the main thread. * Sync. Don't call from the main thread.
@ -47,10 +50,12 @@ internal object AESGCM {
*/ */
internal fun encrypt(plaintext: ByteArray, symmetricKey: ByteArray): ByteArray { internal fun encrypt(plaintext: ByteArray, symmetricKey: ByteArray): ByteArray {
val iv = Util.getSecretBytes(ivSize) val iv = Util.getSecretBytes(ivSize)
synchronized(CIPHER_LOCK) {
val cipher = Cipher.getInstance("AES/GCM/NoPadding") val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(symmetricKey, "AES"), GCMParameterSpec(gcmTagSize, iv)) cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(symmetricKey, "AES"), GCMParameterSpec(gcmTagSize, iv))
return ByteUtil.combine(iv, cipher.doFinal(plaintext)) return ByteUtil.combine(iv, cipher.doFinal(plaintext))
} }
}
/** /**
* Sync. Don't call from the main thread. * Sync. Don't call from the main thread.

View File

@ -0,0 +1,8 @@
package org.session.libsignal.crypto;
public class CipherUtil {
// Cipher operations are not thread-safe so we synchronize over them through doFinal to
// prevent crashes with quickly repeated encrypt/decrypt operations
// https://github.com/mozilla-mobile/android-components/issues/5342
public static final Object CIPHER_LOCK = new Object();
}

View File

@ -1,45 +0,0 @@
package org.session.libsignal.crypto
import org.whispersystems.curve25519.Curve25519
import org.session.libsignal.utilities.Util
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
object DiffieHellman {
private val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
private val curve = Curve25519.getInstance(Curve25519.BEST)
private val ivSize = 16
@JvmStatic @Throws
fun encrypt(plaintext: ByteArray, symmetricKey: ByteArray): ByteArray {
val iv = Util.getSecretBytes(ivSize)
val ivSpec = IvParameterSpec(iv)
val secretKeySpec = SecretKeySpec(symmetricKey, "AES")
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivSpec)
val ciphertext = cipher.doFinal(plaintext)
return iv + ciphertext
}
@JvmStatic @Throws
fun encrypt(plaintext: ByteArray, publicKey: ByteArray, privateKey: ByteArray): ByteArray {
val symmetricKey = curve.calculateAgreement(publicKey, privateKey)
return encrypt(plaintext, symmetricKey)
}
@JvmStatic @Throws
fun decrypt(ivAndCiphertext: ByteArray, symmetricKey: ByteArray): ByteArray {
val iv = ivAndCiphertext.sliceArray(0 until ivSize)
val ciphertext = ivAndCiphertext.sliceArray(ivSize until ivAndCiphertext.size)
val ivSpec = IvParameterSpec(iv)
val secretKeySpec = SecretKeySpec(symmetricKey, "AES")
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivSpec)
return cipher.doFinal(ciphertext)
}
@JvmStatic @Throws
fun decrypt(ivAndCiphertext: ByteArray, publicKey: ByteArray, privateKey: ByteArray): ByteArray {
val symmetricKey = curve.calculateAgreement(publicKey, privateKey)
return decrypt(ivAndCiphertext, symmetricKey)
}
}

View File

@ -39,9 +39,7 @@ public abstract class HKDF {
Mac mac = Mac.getInstance("HmacSHA256"); Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(salt, "HmacSHA256")); mac.init(new SecretKeySpec(salt, "HmacSHA256"));
return mac.doFinal(inputKeyMaterial); return mac.doFinal(inputKeyMaterial);
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new AssertionError(e);
} catch (InvalidKeyException e) {
throw new AssertionError(e); throw new AssertionError(e);
} }
} }
@ -73,9 +71,7 @@ public abstract class HKDF {
} }
return results.toByteArray(); return results.toByteArray();
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new AssertionError(e);
} catch (InvalidKeyException e) {
throw new AssertionError(e); throw new AssertionError(e);
} }
} }

View File

@ -6,6 +6,8 @@
package org.session.libsignal.streams; package org.session.libsignal.streams;
import static org.session.libsignal.crypto.CipherUtil.CIPHER_LOCK;
import org.session.libsignal.exceptions.InvalidMacException; import org.session.libsignal.exceptions.InvalidMacException;
import org.session.libsignal.exceptions.InvalidMessageException; import org.session.libsignal.exceptions.InvalidMessageException;
import org.session.libsignal.utilities.Util; import org.session.libsignal.utilities.Util;
@ -92,19 +94,15 @@ public class AttachmentCipherInputStream extends FilterInputStream {
byte[] iv = new byte[BLOCK_SIZE]; byte[] iv = new byte[BLOCK_SIZE];
readFully(iv); readFully(iv);
synchronized (CIPHER_LOCK) {
this.cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); this.cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
this.cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(cipherKey, "AES"), new IvParameterSpec(iv)); this.cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(cipherKey, "AES"), new IvParameterSpec(iv));
}
this.done = false; this.done = false;
this.totalRead = 0; this.totalRead = 0;
this.totalDataSize = totalDataSize; this.totalDataSize = totalDataSize;
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException | InvalidAlgorithmParameterException e) {
throw new AssertionError(e);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
} catch (NoSuchPaddingException e) {
throw new AssertionError(e);
} catch (InvalidAlgorithmParameterException e) {
throw new AssertionError(e); throw new AssertionError(e);
} }
} }
@ -141,15 +139,12 @@ public class AttachmentCipherInputStream extends FilterInputStream {
private int readFinal(byte[] buffer, int offset, int length) throws IOException { private int readFinal(byte[] buffer, int offset, int length) throws IOException {
try { try {
synchronized (CIPHER_LOCK) {
int flourish = cipher.doFinal(buffer, offset); int flourish = cipher.doFinal(buffer, offset);
done = true; done = true;
return flourish; return flourish;
} catch (IllegalBlockSizeException e) { }
throw new IOException(e); } catch (IllegalBlockSizeException | ShortBufferException | BadPaddingException e) {
} catch (BadPaddingException e) {
throw new IOException(e);
} catch (ShortBufferException e) {
throw new IOException(e); throw new IOException(e);
} }
} }
@ -234,9 +229,7 @@ public class AttachmentCipherInputStream extends FilterInputStream {
throw new InvalidMacException("Digest doesn't match!"); throw new InvalidMacException("Digest doesn't match!");
} }
} catch (IOException e) { } catch (IOException | ArithmeticException e) {
throw new InvalidMacException(e);
} catch (ArithmeticException e) {
throw new InvalidMacException(e); throw new InvalidMacException(e);
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
throw new AssertionError(e); throw new AssertionError(e);

View File

@ -6,6 +6,8 @@
package org.session.libsignal.streams; package org.session.libsignal.streams;
import static org.session.libsignal.crypto.CipherUtil.CIPHER_LOCK;
import org.session.libsignal.utilities.Util; import org.session.libsignal.utilities.Util;
import java.io.IOException; import java.io.IOException;
@ -68,16 +70,17 @@ public class AttachmentCipherOutputStream extends DigestingOutputStream {
@Override @Override
public void flush() throws IOException { public void flush() throws IOException {
try { try {
byte[] ciphertext = cipher.doFinal(); byte[] ciphertext;
synchronized (CIPHER_LOCK) {
ciphertext = cipher.doFinal();
}
byte[] auth = mac.doFinal(ciphertext); byte[] auth = mac.doFinal(ciphertext);
super.write(ciphertext); super.write(ciphertext);
super.write(auth); super.write(auth);
super.flush(); super.flush();
} catch (IllegalBlockSizeException e) { } catch (IllegalBlockSizeException | BadPaddingException e) {
throw new AssertionError(e);
} catch (BadPaddingException e) {
throw new AssertionError(e); throw new AssertionError(e);
} }
} }
@ -97,9 +100,7 @@ public class AttachmentCipherOutputStream extends DigestingOutputStream {
private Cipher initializeCipher() { private Cipher initializeCipher() {
try { try {
return Cipher.getInstance("AES/CBC/PKCS5Padding"); return Cipher.getInstance("AES/CBC/PKCS5Padding");
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new AssertionError(e);
} catch (NoSuchPaddingException e) {
throw new AssertionError(e); throw new AssertionError(e);
} }
} }

View File

@ -1,5 +1,7 @@
package org.session.libsignal.streams; package org.session.libsignal.streams;
import static org.session.libsignal.crypto.CipherUtil.CIPHER_LOCK;
import org.session.libsignal.utilities.Util; import org.session.libsignal.utilities.Util;
import java.io.FilterInputStream; import java.io.FilterInputStream;
@ -62,6 +64,7 @@ public class ProfileCipherInputStream extends FilterInputStream {
byte[] ciphertext = new byte[outputLength / 2]; byte[] ciphertext = new byte[outputLength / 2];
int read = in.read(ciphertext, 0, ciphertext.length); int read = in.read(ciphertext, 0, ciphertext.length);
synchronized (CIPHER_LOCK) {
if (read == -1) { if (read == -1) {
if (cipher.getOutputSize(0) > outputLength) { if (cipher.getOutputSize(0) > outputLength) {
throw new AssertionError("Need: " + cipher.getOutputSize(0) + " but only have: " + outputLength); throw new AssertionError("Need: " + cipher.getOutputSize(0) + " but only have: " + outputLength);
@ -76,9 +79,8 @@ public class ProfileCipherInputStream extends FilterInputStream {
return cipher.update(ciphertext, 0, read, output, outputOffset); return cipher.update(ciphertext, 0, read, output, outputOffset);
} }
} catch (IllegalBlockSizeException e) { }
throw new AssertionError(e); } catch (IllegalBlockSizeException | ShortBufferException e) {
} catch(ShortBufferException e) {
throw new AssertionError(e); throw new AssertionError(e);
} catch (BadPaddingException e) { } catch (BadPaddingException e) {
throw new IOException(e); throw new IOException(e);

View File

@ -1,5 +1,7 @@
package org.session.libsignal.streams; package org.session.libsignal.streams;
import static org.session.libsignal.crypto.CipherUtil.CIPHER_LOCK;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
@ -54,20 +56,24 @@ public class ProfileCipherOutputStream extends DigestingOutputStream {
byte[] input = new byte[1]; byte[] input = new byte[1];
input[0] = (byte)b; input[0] = (byte)b;
byte[] output = cipher.update(input); byte[] output;
synchronized (CIPHER_LOCK) {
output = cipher.update(input);
}
super.write(output); super.write(output);
} }
@Override @Override
public void flush() throws IOException { public void flush() throws IOException {
try { try {
byte[] output = cipher.doFinal(); byte[] output;
synchronized (CIPHER_LOCK) {
output = cipher.doFinal();
}
super.write(output); super.write(output);
super.flush(); super.flush();
} catch (BadPaddingException e) { } catch (BadPaddingException | IllegalBlockSizeException e) {
throw new AssertionError(e);
} catch (IllegalBlockSizeException e) {
throw new AssertionError(e); throw new AssertionError(e);
} }
} }