Skip to content

Commit 5ed3d4b

Browse files
authored
Add encryption support through SQLite3MultipleCiphers (#288)
1 parent 8682730 commit 5ed3d4b

File tree

37 files changed

+3560
-37
lines changed

37 files changed

+3560
-37
lines changed

.github/workflows/prebuild_assets.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
id: cache_prebuild
2727
with:
2828
path: internal/prebuild-binaries/build/output
29-
key: sqlite-build-${{ hashFiles('internal/prebuild-binaries', 'plugins/build-plugin') }}
29+
key: sqlite-build-${{ hashFiles('internal/prebuild-binaries/build.gradle.kts', 'plugins/build-plugin/src') }}
3030

3131
- name: Validate Gradle Wrapper
3232
if: steps.cache_prebuild.outputs.cache-hit != 'true'
@@ -50,10 +50,18 @@ jobs:
5050
- name: Set up XCode
5151
if: steps.cache_prebuild.outputs.cache-hit != 'true'
5252
uses: maxim-lobanov/setup-xcode@v1
53+
- name: Download build tools
54+
if: steps.cache_prebuild.outputs.cache-hit != 'true'
55+
run: |
56+
brew install lld
57+
cd internal/prebuild-binaries
58+
./download_glibc.sh
59+
./download_llvm_mingw.sh
60+
5361
- name: Compile SQLite with Gradle
5462
if: steps.cache_prebuild.outputs.cache-hit != 'true'
5563
run: |
56-
./gradlew --scan internal:prebuild-binaries:compileNative
64+
./gradlew --scan internal:prebuild-binaries:compileAll
5765
shell: bash
5866
- uses: actions/upload-artifact@v5
5967
id: upload

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ and API documentation [here](https://powersync-ja.github.io/powersync-kotlin/).
4141
connector provides the connection between your application backend and the PowerSync managed database. It is used to:
4242
1. Retrieve a token to connect to the PowerSync service.
4343
2. Apply local changes on your backend application server (and from there, to your backend database).
44+
- [sqlite3multipleciphers](./sqlite3multipleciphers/)
45+
46+
- A SQLite driver implementation based on SQLite3MultipleCiphers.
4447

4548
## Demo Apps / Example Projects
4649

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ dependencies {
7373
dokka(project(":integrations:room"))
7474
dokka(project(":integrations:sqldelight"))
7575
dokka(project(":integrations:supabase"))
76+
dokka(projects.sqlite3multipleciphers)
7677
}
7778

7879
dokka {

common/build.gradle.kts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ kotlin {
137137
optIn("kotlinx.cinterop.ExperimentalForeignApi")
138138
optIn("kotlin.time.ExperimentalTime")
139139
optIn("kotlin.experimental.ExperimentalObjCRefinement")
140+
optIn("com.powersync.PowerSyncInternal")
140141
}
141142
}
142143

@@ -239,8 +240,6 @@ android {
239240
.toInt()
240241
consumerProguardFiles("proguard-rules.pro")
241242
}
242-
243-
ndkVersion = "27.1.12297006"
244243
}
245244

246245
tasks.named<ProcessResources>(kotlin.jvm().compilations["main"].processResourcesTaskName) {
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.powersync
2+
3+
@RequiresOptIn(message = "This API should not be used outside of PowerSync SDK packages")
4+
@Retention(AnnotationRetention.BINARY)
5+
@Target(
6+
AnnotationTarget.CLASS,
7+
AnnotationTarget.FUNCTION,
8+
AnnotationTarget.CONSTRUCTOR,
9+
AnnotationTarget.PROPERTY,
10+
AnnotationTarget.VALUE_PARAMETER,
11+
)
12+
public annotation class PowerSyncInternal

common/src/jvmMain/kotlin/com/powersync/ConnectionFactory.jvm.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ import com.powersync.db.runWrapped
55
@Throws(PowerSyncException::class)
66
public actual fun resolvePowerSyncLoadableExtensionPath(): String? = runWrapped { powersyncExtension }
77

8-
private val powersyncExtension: String by lazy { extractLib("powersync") }
8+
private val powersyncExtension: String by lazy { extractLib(BuildConfig::class, "powersync") }

common/src/jvmMain/kotlin/com/powersync/ExtractLib.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ package com.powersync
22

33
import java.io.File
44
import java.util.UUID
5+
import kotlin.reflect.KClass
56

6-
private class R
7-
8-
internal fun extractLib(fileName: String): String {
7+
@PowerSyncInternal
8+
public fun extractLib(
9+
reference: KClass<*>,
10+
fileName: String,
11+
): String {
912
val os = System.getProperty("os.name").lowercase()
1013
val (prefix, extension) =
1114
when {
@@ -34,7 +37,7 @@ internal fun extractLib(fileName: String): String {
3437

3538
val resourcePath = "/$prefix${fileName}_$arch.$extension"
3639

37-
(R::class.java.getResourceAsStream(resourcePath) ?: error("Resource $resourcePath not found")).use { input ->
40+
(reference.java.getResourceAsStream(resourcePath) ?: error("Resource $resourcePath not found")).use { input ->
3841
file.outputStream().use { output -> input.copyTo(output) }
3942
}
4043

core-tests-android/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ plugins {
88

99
dependencies {
1010
implementation(projects.core)
11+
implementation(projects.sqlite3multipleciphers)
1112
implementation(libs.androidx.core)
1213
implementation(libs.androidx.appcompat)
1314
implementation(libs.androidx.material)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.powersync
2+
3+
import androidx.test.ext.junit.runners.AndroidJUnit4
4+
import androidx.test.platform.app.InstrumentationRegistry
5+
import androidx.sqlite.SQLiteException
6+
import androidx.sqlite.execSQL
7+
import app.cash.turbine.turbineScope
8+
import com.powersync.db.schema.Schema
9+
import com.powersync.encryption.AndroidEncryptedDatabaseFactory
10+
import com.powersync.encryption.Key
11+
import com.powersync.testutils.UserRow
12+
import kotlinx.coroutines.*
13+
import kotlinx.coroutines.runBlocking
14+
import kotlinx.coroutines.test.runTest
15+
import org.junit.After
16+
import org.junit.Assert.*
17+
import org.junit.Before
18+
import org.junit.Test
19+
import org.junit.runner.RunWith
20+
21+
@RunWith(AndroidJUnit4::class)
22+
class EncryptedDatabaseTest {
23+
24+
@Test
25+
fun testEncryptedDatabase() =
26+
runTest {
27+
val context = InstrumentationRegistry.getInstrumentation().targetContext
28+
29+
val database = PowerSyncDatabase(
30+
factory = AndroidEncryptedDatabaseFactory(
31+
context,
32+
Key.Passphrase("mykey")
33+
),
34+
schema = Schema(UserRow.table),
35+
dbFilename = "encrypted_test",
36+
)
37+
38+
assertEquals("chacha20", database.get("PRAGMA cipher") { it.getString(0)!! })
39+
40+
database.execute(
41+
"INSERT INTO users (id, name, email) VALUES (uuid(), ?, ?)",
42+
listOf("Test", "test@example.org"),
43+
)
44+
database.close()
45+
46+
val unencryptedFactory = DatabaseDriverFactory(context)
47+
val unencrypted = unencryptedFactory.openConnection("encrypted_test", null, false)
48+
49+
try {
50+
unencrypted.execSQL("SELECT * FROM sqlite_schema")
51+
throw IllegalStateException("Was able to read schema from encrypted database without supplying a key")
52+
} catch (_: SQLiteException) {
53+
// Expected
54+
}
55+
unencrypted.close()
56+
}
57+
}

core/build.gradle.kts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,6 @@ android {
106106
.toInt()
107107
consumerProguardFiles("proguard-rules.pro")
108108
}
109-
110-
ndkVersion = "27.1.12297006"
111109
}
112110

113111
// We want to build with recent JDKs, but need to make sure we support Java 8. https://jakewharton.com/build-on-latest-java-test-through-lowest-java/

0 commit comments

Comments
 (0)