Skip to content

Commit c62f540

Browse files
Merge pull request #276 from smswithoutborders/dev
Dev
2 parents 38af31b + 951625c commit c62f540

11 files changed

Lines changed: 315 additions & 64 deletions

File tree

app/build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,5 +363,9 @@ dependencies {
363363
implementation(libs.coil.network.okhttp)
364364

365365
coreLibraryDesugaring libs.desugar.jdk.libs
366+
367+
implementation 'net.zetetic:sqlcipher-android:4.12.0@aar'
368+
implementation 'androidx.sqlite:sqlite:2.6.2'
369+
implementation "androidx.security:security-crypto:1.1.0"
366370
}
367371

app/src/main/java/com/example/sw0b_001/data/Composers.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,28 @@ object Composers {
9797
return buffer.array()
9898
}
9999

100+
fun decomposeBridgeMessage(
101+
message: String
102+
): EmailContent {
103+
val splitMessage = message.split("\n");
104+
val alias = splitMessage[0]
105+
val from = splitMessage[1]
106+
val cc = splitMessage[2]
107+
val bcc = splitMessage[3]
108+
val subject = splitMessage[4]
109+
val timestamp = splitMessage[5]
110+
val body = splitMessage.subList(6, splitMessage.size).joinToString(separator = "\n")
111+
112+
return EmailContent(
113+
to =mutableStateOf(alias),
114+
cc = mutableStateOf(cc),
115+
bcc = mutableStateOf(bcc),
116+
subject = mutableStateOf(subject),
117+
body = mutableStateOf(body),
118+
// mutableStateOf(image),
119+
)
120+
}
121+
100122
fun decomposeMessage(
101123
contentBytes: ByteArray,
102124
imageLength: Int,

app/src/main/java/com/example/sw0b_001/data/Cryptography.kt

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,32 @@
11
package com.example.sw0b_001.data
22

33
import android.content.Context
4+
import android.security.KeyStoreException
5+
import android.security.keystore.KeyGenParameterSpec
6+
import android.security.keystore.KeyProperties
47
import android.util.Base64
58
import androidx.core.content.edit
9+
import androidx.datastore.core.IOException
610
import com.afkanerd.smswithoutborders.libsignal_doubleratchet.KeystoreHelpers
711
import com.afkanerd.smswithoutborders.libsignal_doubleratchet.SecurityCurve25519
812
import com.afkanerd.smswithoutborders.libsignal_doubleratchet.SecurityRSA
13+
import com.example.sw0b_001.extensions.context.generateSecureRandom
14+
import com.example.sw0b_001.extensions.context.settingsGetDbPassword
15+
import com.example.sw0b_001.extensions.context.settingsSetDbPassword
16+
import java.security.InvalidAlgorithmParameterException
17+
import java.security.InvalidKeyException
18+
import java.security.KeyStore
19+
import java.security.NoSuchAlgorithmException
20+
import java.security.NoSuchProviderException
21+
import java.security.UnrecoverableEntryException
22+
import javax.crypto.BadPaddingException
23+
import javax.crypto.Cipher
24+
import javax.crypto.IllegalBlockSizeException
25+
import javax.crypto.KeyGenerator
26+
import javax.crypto.NoSuchPaddingException
27+
import javax.crypto.SecretKey
28+
import javax.crypto.spec.GCMParameterSpec
29+
import javax.security.cert.CertificateException
930

1031
object Cryptography {
1132
val HYBRID_KEYS_FILE = "com.afkanerd.relaysms.HYBRID_KEYS_FILE"
@@ -77,5 +98,106 @@ object Cryptography {
7798
val libSigCurve25519 = SecurityCurve25519(privateKey)
7899
return libSigCurve25519.calculateSharedSecret(publicKey)
79100
}
101+
@Throws(
102+
KeyStoreException::class,
103+
NoSuchAlgorithmException::class,
104+
NoSuchProviderException::class,
105+
InvalidAlgorithmParameterException::class
106+
)
107+
fun createAndStoreSecretKey(keystoreAlias: String) {
108+
val keyGenerator = KeyGenerator.getInstance(
109+
KeyProperties.KEY_ALGORITHM_AES,
110+
"AndroidKeyStore"
111+
)
112+
113+
val params = KeyGenParameterSpec.Builder(
114+
keystoreAlias,
115+
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
116+
)
117+
.setKeySize(256)
118+
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
119+
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
120+
.setRandomizedEncryptionRequired(true)
121+
.build()
122+
123+
keyGenerator.init(params)
124+
keyGenerator.generateKey()
125+
}
126+
127+
@Throws(
128+
KeyStoreException::class,
129+
UnrecoverableEntryException::class,
130+
NoSuchAlgorithmException::class,
131+
CertificateException::class,
132+
IOException::class,
133+
NoSuchPaddingException::class,
134+
InvalidKeyException::class,
135+
IllegalBlockSizeException::class,
136+
BadPaddingException::class
137+
)
138+
private fun encryptWithKeyStore(data: ByteArray, keystoreAlias: String): ByteArray {
139+
if(!KeystoreHelpers.isAvailableInKeystore(keystoreAlias))
140+
createAndStoreSecretKey(keystoreAlias)
141+
142+
// Initialize KeyStore
143+
val keyStore: KeyStore = KeyStore.getInstance("AndroidKeyStore")
144+
keyStore.load(null)
145+
// Retrieve the key with alias androidKeyStoreAlias created before
146+
val keyEntry: KeyStore.SecretKeyEntry =
147+
keyStore.getEntry(keystoreAlias, null) as KeyStore.SecretKeyEntry
148+
val key: SecretKey = keyEntry.secretKey
149+
// Use the secret key at your convenience
150+
val cipher: Cipher = Cipher.getInstance("AES/GCM/NoPadding")
151+
cipher.init(Cipher.ENCRYPT_MODE, key)
152+
return cipher.iv + cipher.doFinal(data)
153+
}
154+
155+
156+
@Throws(
157+
KeyStoreException::class,
158+
UnrecoverableEntryException::class,
159+
NoSuchAlgorithmException::class,
160+
CertificateException::class,
161+
IOException::class,
162+
NoSuchPaddingException::class,
163+
InvalidKeyException::class,
164+
IllegalBlockSizeException::class,
165+
BadPaddingException::class
166+
)
167+
private fun decryptWithKeyStore(data: ByteArray, keystoreAlias: String): ByteArray? {
168+
val ivSize = 12 // GCM standard
169+
val iv = data.copyOfRange(0, ivSize)
170+
val data = data.copyOfRange(ivSize, data.size)
171+
172+
// Initialize KeyStore
173+
val keyStore: KeyStore = KeyStore.getInstance("AndroidKeyStore")
174+
keyStore.load(null)
175+
// Retrieve the key with alias androidKeyStoreAlias created before
176+
val keyEntry: KeyStore.SecretKeyEntry =
177+
keyStore.getEntry(keystoreAlias, null) as KeyStore.SecretKeyEntry
178+
val key: SecretKey = keyEntry.secretKey
179+
// Use the secret key at your convenience
180+
val cipher: Cipher = Cipher.getInstance("AES/GCM/NoPadding")
181+
val spec = GCMParameterSpec(128, iv) // 128-bit auth tag
182+
cipher.init(Cipher.DECRYPT_MODE, key, spec)
183+
return cipher.doFinal(data)
184+
}
185+
186+
@JvmStatic
187+
fun getDatabasePassword(context: Context, keystoreAlias: String) : ByteArray {
188+
val password = context.settingsGetDbPassword
189+
if(password == null) {
190+
val password = context.generateSecureRandom()
191+
val encryptedPassword = encryptWithKeyStore(
192+
password,
193+
keystoreAlias
194+
)
195+
context.settingsSetDbPassword(encryptedPassword)
196+
return password
197+
} else {
198+
return decryptWithKeyStore(password, keystoreAlias) ?:
199+
throw Exception("Failed to decrypt database keystore")
200+
}
201+
}
80202

81203
}

app/src/main/java/com/example/sw0b_001/data/Datastore.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,12 @@
3232
import com.example.sw0b_001.data.models.StoredPlatformsEntity;
3333
import com.example.sw0b_001.data.models.RatchetStates;
3434

35+
import net.zetetic.database.sqlcipher.SupportOpenHelperFactory;
36+
3537
import org.jetbrains.annotations.NotNull;
3638

39+
import java.io.File;
40+
3741
@Database(entities = {
3842
RatchetStates.class,
3943
GatewayServer.class,
@@ -67,14 +71,28 @@ public abstract class Datastore extends RoomDatabase {
6771
@DeleteTable(tableName = "Notifications")
6872
static class DatastoreMigrations implements AutoMigrationSpec { }
6973

70-
public static String databaseName = "SMSWithoutBorders-Android-App-DB";
74+
public static String databaseName = "afkanerd.smswithoutborders.relaysms.db";
7175
private static Datastore datastore;
76+
private static final String dbKeystoreAlias = "afkanerd.smswithoutborders.sms_mms_keystore_alias";
77+
78+
public Datastore() {
79+
System.loadLibrary("sqlcipher");
80+
}
81+
7282

7383
public static Datastore getDatastore(Context context) {
7484
if(datastore == null || !datastore.isOpen()) {
75-
datastore = Room.databaseBuilder(context, Datastore.class, databaseName)
85+
byte[] password = Cryptography.getDatabasePassword(context, dbKeystoreAlias);
86+
87+
File databaseFile = context.getDatabasePath(databaseName);
88+
SupportOpenHelperFactory factory = new SupportOpenHelperFactory(password);
89+
datastore = Room.databaseBuilder(context, Datastore.class, databaseFile.getAbsolutePath())
7690
.enableMultiInstanceInvalidation()
91+
.openHelperFactory(factory)
7792
.build();
93+
// datastore = Room.databaseBuilder(context, Datastore.class, databaseName)
94+
// .enableMultiInstanceInvalidation()
95+
// .build();
7896
}
7997

8098
return datastore;

app/src/main/java/com/example/sw0b_001/extensions/context/security.kt

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,27 @@ import android.content.Context
44
import android.content.Intent
55
import android.os.Build
66
import android.provider.Settings
7+
import android.util.Base64
78
import android.widget.Toast
89
import androidx.appcompat.app.AppCompatActivity
910
import androidx.biometric.BiometricManager
1011
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
1112
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
1213
import androidx.biometric.BiometricPrompt
1314
import androidx.core.content.ContextCompat
15+
import androidx.core.content.edit
16+
import androidx.security.crypto.EncryptedSharedPreferences
17+
import androidx.security.crypto.MasterKey
1418
import com.example.sw0b_001.R
19+
import java.security.SecureRandom
1520
import java.util.concurrent.Executor
1621

22+
23+
private object Security {
24+
const val FILENAME: String = "com.afkanerd.deku.security"
25+
const val DB_PASSWORD = "DB_PASSWORD"
26+
}
27+
1728
fun Context.isBiometricLockAvailable(): Int {
1829
val biometricManager = BiometricManager.from(this)
1930
return biometricManager
@@ -98,3 +109,45 @@ fun Context.promptBiometrics(
98109

99110
biometricPrompt.authenticate(promptInfo)
100111
}
112+
113+
val Context.settingsGetDbPassword get(): ByteArray? {
114+
val masterKey: MasterKey = MasterKey.Builder(this)
115+
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
116+
.build()
117+
118+
return EncryptedSharedPreferences.create(
119+
this,
120+
Security.FILENAME,
121+
masterKey,
122+
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
123+
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
124+
).getString(Security.DB_PASSWORD, "").run {
125+
if(!this.isNullOrBlank()) Base64.decode(this, Base64.DEFAULT) else null
126+
}
127+
}
128+
129+
fun Context.settingsSetDbPassword(password: ByteArray) {
130+
val masterKey: MasterKey = MasterKey.Builder(this)
131+
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
132+
.build()
133+
134+
EncryptedSharedPreferences.create(
135+
this,
136+
Security.FILENAME,
137+
masterKey,
138+
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
139+
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
140+
).edit {
141+
putString(
142+
Security.DB_PASSWORD,
143+
Base64.encodeToString(password, Base64.DEFAULT))
144+
apply()
145+
}
146+
}
147+
148+
fun Context.generateSecureRandom() : ByteArray{
149+
val secureRandom = SecureRandom()
150+
val secretBytes = ByteArray(32) // Example: 256 bits
151+
secureRandom.nextBytes(secretBytes)
152+
return secretBytes
153+
}

app/src/main/java/com/example/sw0b_001/ui/onboarding/OnboardingInteractive.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ fun OnboardingScreen(onboardingScreen: InteractiveOnboarding) {
290290
}
291291
}
292292

293+
293294
@Preview(
294295
uiMode = Configuration.UI_MODE_NIGHT_YES,
295296
name = "DefaultPreviewDark",

0 commit comments

Comments
 (0)