Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encrypt FHIR engine database via SQLCipher #787

Merged
merged 26 commits into from
Nov 29, 2021
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
e3f45e4
Import SQLCipher dependencies
stevenckngaa Sep 15, 2021
9e43cb9
Generate a 16 bytes key for database encryption
stevenckngaa Sep 15, 2021
b0d3388
Merge branch 'master' into ckng/db_encryption
stevenckngaa Sep 15, 2021
75a5006
Apply spotless
stevenckngaa Sep 15, 2021
5b03a8a
Allow database encryption on Android 6.0 or above
stevenckngaa Sep 20, 2021
4d1a969
Merge branch 'master' into ckng/db_encryption
stevenckngaa Sep 30, 2021
b44b9c8
Use a HMAC 256 signed empty string as the database key
stevenckngaa Sep 30, 2021
a9c1b94
Enable database encryption in the reference app by default
stevenckngaa Sep 30, 2021
ce43106
Load database key in RoomDatabase background thread
stevenckngaa Oct 8, 2021
98fa50d
Merge branch 'master' into ckng/db_encryption
stevenckngaa Oct 8, 2021
2746cc3
Add retry when Android keystore is busy
stevenckngaa Oct 18, 2021
a941caa
Reenable some tests
stevenckngaa Oct 18, 2021
d5ab15f
Merge branch 'master' into ckng/db_encryption
stevenckngaa Oct 27, 2021
bf9105c
Remove outdated TODO
stevenckngaa Oct 27, 2021
7335ec8
Use a different database name for unencrypted and encrypted database
stevenckngaa Oct 29, 2021
956f019
Update documentation
stevenckngaa Oct 29, 2021
68e0e52
Merge branch 'master' into ckng/db_encryption
stevenckngaa Nov 5, 2021
d7f7dc1
Move keystore timeout retry to SQLCipherSupportHelper
stevenckngaa Nov 5, 2021
e4d009b
Update kdoc
stevenckngaa Nov 5, 2021
6137ba6
Update documentation
stevenckngaa Nov 12, 2021
386808a
Merge branch 'master' into ckng/db_encryption
stevenckngaa Nov 12, 2021
d54b91c
Apply spotless
stevenckngaa Nov 12, 2021
73441e5
Allow SQLCipher license because it's effectively BSD-3
stevenckngaa Nov 15, 2021
00b2b9e
Fix SQLCipher license allowed dependency
stevenckngaa Nov 15, 2021
b00b47c
Merge branch 'master' into ckng/db_encryption
stevenckngaa Nov 26, 2021
650ac9a
Address Jing's comments
stevenckngaa Nov 26, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions buildSrc/src/main/kotlin/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ object Dependencies {
const val jsonToolsPatch = "com.github.java-json-tools:json-patch:${Versions.jsonToolsPatch}"
const val material = "com.google.android.material:material:${Versions.material}"
const val kotlinPoet = "com.squareup:kotlinpoet:${Versions.kotlinPoet}"
const val sqlcipher = "net.zetetic:android-database-sqlcipher:${Versions.sqlcipher}"
stevenckngaa marked this conversation as resolved.
Show resolved Hide resolved

// Dependencies for testing go here
object AndroidxTest {
Expand Down Expand Up @@ -143,6 +144,7 @@ object Dependencies {
const val jsonToolsPatch = "1.13"
const val material = "1.3.0"
const val retrofit = "2.7.2"
const val sqlcipher = "4.4.3"
stevenckngaa marked this conversation as resolved.
Show resolved Hide resolved
const val truth = "1.0.1"
const val flexBox = "3.0.0"
const val kotlinPoet = "1.9.0"
Expand Down
1 change: 1 addition & 0 deletions engine/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ dependencies {
implementation(Dependencies.Kotlin.stdlib)
implementation(Dependencies.Room.runtime)
implementation(Dependencies.Room.ktx)
implementation(Dependencies.sqlcipher)
stevenckngaa marked this conversation as resolved.
Show resolved Hide resolved
implementation(Dependencies.guava)
implementation(Dependencies.jsonToolsPatch)
implementation(Dependencies.Lifecycle.liveDataKtx)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http:https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.fhir.db.impl

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.google.common.truth.Truth.assertThat
import java.security.KeyStore
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

/** Integration test for [DatabaseEncryptionKeyProviderTest]. */
@SmallTest
@RunWith(AndroidJUnit4::class)
class DatabaseEncryptionKeyProviderTest {
stevenckngaa marked this conversation as resolved.
Show resolved Hide resolved
@Before fun setup() = deleteTestKeys()

@After fun tearDown() = deleteTestKeys()

@Test
fun getOrCreatePassphrase_aliasNotExists_shouldGenerateKey() {
assertThat(DatabaseEncryptionKeyProvider.getOrCreatePassphrase(ALIAS_NAME)).isNotNull()
}

@Test
fun getOrCreatePassphrase_aliasExists_shouldReturnSameKey() {
val key = DatabaseEncryptionKeyProvider.getOrCreatePassphrase(ALIAS_NAME)

assertThat(DatabaseEncryptionKeyProvider.getOrCreatePassphrase(ALIAS_NAME)).isEqualTo(key)
}

@Test
fun getOrCreatePassphrase_otherAliasExists_shouldReplaceKey() {
val key = DatabaseEncryptionKeyProvider.getOrCreatePassphrase(ALIAS_NAME)

assertThat(DatabaseEncryptionKeyProvider.getOrCreatePassphrase(OTHER_ALIAS_NAME))
.isNotEqualTo(key)
}

private fun deleteTestKeys() {
val keyStore = KeyStore.getInstance(DatabaseEncryptionKeyProvider.ANDROID_KEYSTORE_NAME)
keyStore.load(/* param = */ null)
keyStore.deleteEntry(ALIAS_NAME)
keyStore.deleteEntry(OTHER_ALIAS_NAME)
DatabaseEncryptionKeyProvider.clearKeyCache()
}

private companion object {
const val ALIAS_NAME = "test_key"
const val OTHER_ALIAS_NAME = "other_test_key"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@

package com.google.android.fhir.db.impl

import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import ca.uhn.fhir.rest.param.ParamPrefixEnum
import com.google.android.fhir.FhirServices
import com.google.android.fhir.db.ResourceNotFoundException
Expand Down Expand Up @@ -59,6 +60,8 @@ import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameters

/**
* Integration tests for [DatabaseImpl]. There are written as integration tests as officially
Expand All @@ -68,8 +71,11 @@ import org.junit.runner.RunWith
* * Robolectric's SQLite implementation does not match Android, e.g.:
* https://github.com/robolectric/robolectric/blob/master/shadows/framework/src/main/java/org/robolectric/shadows/ShadowSQLiteConnection.java#L97
*/
@RunWith(AndroidJUnit4::class)
@MediumTest
@RunWith(Parameterized::class)
class DatabaseImplTest {
@JvmField @Parameterized.Parameter(0) var encrypted: Boolean = false
stevenckngaa marked this conversation as resolved.
Show resolved Hide resolved
stevenckngaa marked this conversation as resolved.
Show resolved Hide resolved

private val dataSource =
object : DataSource {

Expand Down Expand Up @@ -97,8 +103,13 @@ class DatabaseImplTest {
return OperationOutcome()
}
}
private val context: Context = ApplicationProvider.getApplicationContext()
private val services =
FhirServices.builder(ApplicationProvider.getApplicationContext()).inMemory().build()
if (encrypted) {
FhirServices.builder(context).inMemory().enableEncryption().build()
} else {
FhirServices.builder(context).inMemory().build()
}
stevenckngaa marked this conversation as resolved.
Show resolved Hide resolved
stevenckngaa marked this conversation as resolved.
Show resolved Hide resolved
private val testingUtils = TestingUtils(services.parser)
private val database = services.database

Expand Down Expand Up @@ -2399,5 +2410,7 @@ class DatabaseImplTest {
TEST_PATIENT_2.setId(TEST_PATIENT_2_ID)
TEST_PATIENT_2.setGender(Enumerations.AdministrativeGender.MALE)
}

@JvmStatic @Parameters(name = "encrypted={0}") fun data(): Array<Boolean> = arrayOf(true, false)
}
}
Loading