Skip to content

Commit d76d6d3

Browse files
authored
Merge pull request #18 from nativeapptemplate/fix_security_vulnerabilities
Fix security vulnerabilities from audit
2 parents 846dcd3 + edbfba8 commit d76d6d3

14 files changed

Lines changed: 185 additions & 23 deletions

File tree

app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ dependencies {
113113
implementation(libs.sandwich)
114114
implementation(libs.sandwich.retrofit)
115115
implementation(libs.sandwich.retrofit.serialization)
116+
implementation(libs.tink.android)
116117

117118
ksp(libs.hilt.compiler)
118119

app/src/debug/AndroidManifest.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:tools="http://schemas.android.com/tools">
4+
<application tools:ignore="MissingApplicationIcon">
5+
<profileable android:shell="true" tools:targetApi="q" />
6+
</application>
7+
</manifest>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<network-security-config>
3+
<base-config cleartextTrafficPermitted="true" />
4+
<domain-config cleartextTrafficPermitted="false">
5+
<domain includeSubdomains="true">api.nativeapptemplate.com</domain>
6+
</domain-config>
7+
</network-security-config>

app/src/main/AndroidManifest.xml

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@
1010

1111
<application
1212
android:name=".NativeAppTemplateApplication"
13-
android:allowBackup="true"
13+
android:allowBackup="false"
14+
android:dataExtractionRules="@xml/data_extraction_rules"
15+
android:fullBackupContent="@xml/backup_rules"
1416
android:enableOnBackInvokedCallback="true"
1517
android:icon="@mipmap/ic_launcher"
1618
android:label="@string/app_name"
1719
android:supportsRtl="true"
1820
android:theme="@style/Theme.Nat.Splash"
19-
android:usesCleartextTraffic="true"
21+
android:networkSecurityConfig="@xml/network_security_config"
2022
tools:ignore="GoogleAppIndexingWarning"
2123
tools:targetApi="tiramisu">
22-
<profileable android:shell="true" tools:targetApi="q" />
23-
2424
<!--
2525
`singleTask` for Background Tag Reading. Avoid running MainActivity onCreate again with Background Tag Reading.
2626
https://qiita.com/takagimeow/items/48b37c55ad8d73d5da88
@@ -33,14 +33,6 @@
3333
<action android:name="android.intent.action.MAIN" />
3434
<category android:name="android.intent.category.LAUNCHER" />
3535
</intent-filter>
36-
<intent-filter>
37-
<action android:name="android.intent.action.SEND" />
38-
<action android:name="android.intent.action.SENDTO" />
39-
<action android:name="android.intent.action.VIEW" />
40-
<category android:name="android.intent.category.BROWSABLE" />
41-
<data android:scheme="mailto" />
42-
<category android:name="android.intent.category.DEFAULT" />
43-
</intent-filter>
4436
<intent-filter>
4537
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
4638
<category android:name="android.intent.category.DEFAULT" />

app/src/main/kotlin/com/nativeapptemplate/nativeapptemplatefree/datastore/UserPreferencesSerializer.kt

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,28 +18,44 @@ package com.nativeapptemplate.nativeapptemplatefree.datastore
1818

1919
import androidx.datastore.core.CorruptionException
2020
import androidx.datastore.core.Serializer
21+
import com.google.crypto.tink.Aead
2122
import com.google.protobuf.InvalidProtocolBufferException
2223
import com.nativeapptemplate.nativeapptemplatefree.UserPreferences
2324
import java.io.InputStream
2425
import java.io.OutputStream
26+
import java.security.GeneralSecurityException
2527
import javax.inject.Inject
2628

2729
/**
2830
* An [androidx.datastore.core.Serializer] for the [UserPreferences] proto.
31+
*
32+
* Encrypts data at rest using Tink AEAD. On read, falls back to parsing
33+
* unencrypted proto for migration from the previous unencrypted format.
2934
*/
30-
class UserPreferencesSerializer @Inject constructor() : Serializer<UserPreferences> {
35+
class UserPreferencesSerializer @Inject constructor(
36+
private val aead: Aead,
37+
) : Serializer<UserPreferences> {
3138
override val defaultValue: UserPreferences = UserPreferences.getDefaultInstance()
3239

33-
override suspend fun readFrom(input: InputStream): UserPreferences =
34-
try {
35-
// readFrom is already called on the data store background thread
36-
UserPreferences.parseFrom(input)
37-
} catch (exception: InvalidProtocolBufferException) {
38-
throw CorruptionException("Cannot read proto.", exception)
40+
override suspend fun readFrom(input: InputStream): UserPreferences {
41+
val bytes = input.readBytes()
42+
if (bytes.isEmpty()) return defaultValue
43+
return try {
44+
val decrypted = aead.decrypt(bytes, null)
45+
UserPreferences.parseFrom(decrypted)
46+
} catch (_: GeneralSecurityException) {
47+
// Fallback: try parsing as unencrypted proto (migration from legacy format).
48+
// The data will be re-encrypted on the next write.
49+
try {
50+
UserPreferences.parseFrom(bytes)
51+
} catch (e: InvalidProtocolBufferException) {
52+
throw CorruptionException("Cannot read proto.", e)
53+
}
3954
}
55+
}
4056

4157
override suspend fun writeTo(t: UserPreferences, output: OutputStream) {
42-
// writeTo is already called on the data store background thread
43-
t.writeTo(output)
58+
val encrypted = aead.encrypt(t.toByteArray(), null)
59+
output.write(encrypted)
4460
}
4561
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.nativeapptemplate.nativeapptemplatefree.di.modules
2+
3+
import android.content.Context
4+
import com.google.crypto.tink.Aead
5+
import com.google.crypto.tink.KeyTemplates
6+
import com.google.crypto.tink.aead.AeadConfig
7+
import com.google.crypto.tink.integration.android.AndroidKeysetManager
8+
import dagger.Module
9+
import dagger.Provides
10+
import dagger.hilt.InstallIn
11+
import dagger.hilt.android.qualifiers.ApplicationContext
12+
import dagger.hilt.components.SingletonComponent
13+
import javax.inject.Singleton
14+
15+
@Module
16+
@InstallIn(SingletonComponent::class)
17+
object CryptoModule {
18+
19+
private const val KEYSET_NAME = "nat_datastore_keyset"
20+
private const val PREFERENCE_FILE = "nat_datastore_key_preference"
21+
private const val MASTER_KEY_URI = "android-keystore://nat_datastore_master_key"
22+
23+
@Provides
24+
@Singleton
25+
fun providesAead(@ApplicationContext context: Context): Aead {
26+
AeadConfig.register()
27+
return AndroidKeysetManager.Builder()
28+
.withSharedPref(context, KEYSET_NAME, PREFERENCE_FILE)
29+
.withKeyTemplate(KeyTemplates.get("AES256_GCM"))
30+
.withMasterKeyUri(MASTER_KEY_URI)
31+
.build()
32+
.keysetHandle
33+
.getPrimitive(Aead::class.java)
34+
}
35+
}

app/src/main/kotlin/com/nativeapptemplate/nativeapptemplatefree/di/modules/NetModule.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import dagger.hilt.InstallIn
1717
import dagger.hilt.components.SingletonComponent
1818
import kotlinx.serialization.json.Json
1919
import okhttp3.Call
20+
import okhttp3.CertificatePinner
2021
import okhttp3.MediaType.Companion.toMediaType
2122
import okhttp3.OkHttpClient
2223
import okhttp3.logging.HttpLoggingInterceptor
@@ -61,6 +62,14 @@ class NetModule {
6162
.writeTimeout(30, TimeUnit.SECONDS)
6263
.addNetworkInterceptor(authInterceptor)
6364
.addInterceptor(loggingInterceptor)
65+
.certificatePinner(
66+
CertificatePinner.Builder()
67+
// Leaf: api.nativeapptemplate.com
68+
.add("api.nativeapptemplate.com", "sha256/7Thx4p19FEZF2WeuXyjc8kr2t1FtT2zA5wWSWoIhh8A=")
69+
// Intermediate: Google Trust Services WE1
70+
.add("api.nativeapptemplate.com", "sha256/kIdp6NNEd8wsugYyyIYFsi1ylMCED3hZbSR8ZFsa/A4=")
71+
.build(),
72+
)
6473
.build()
6574

6675
private val json = Json {

app/src/main/kotlin/com/nativeapptemplate/nativeapptemplatefree/utils/Utility.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ object Utility {
6969
val ndefRecord = ndefRecords.first()
7070
val url = ndefRecord.toUri() ?: return itemTagInfo
7171

72+
if (url.scheme != "https" || url.host != BuildConfig.DOMAIN || url.path != "/${NatConstants.SCAN_PATH}") {
73+
return itemTagInfo
74+
}
75+
7276
val itemTagId = url.getQueryParameter("item_tag_id")
7377
val type = url.getQueryParameter("type")
7478

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<full-backup-content>
3+
<exclude domain="sharedpref" path="." />
4+
<exclude domain="database" path="." />
5+
<exclude domain="root" path="." />
6+
<exclude domain="file" path="." />
7+
<exclude domain="external" path="." />
8+
</full-backup-content>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<data-extraction-rules>
3+
<cloud-backup>
4+
<exclude domain="root" />
5+
<exclude domain="file" />
6+
<exclude domain="database" />
7+
<exclude domain="sharedpref" />
8+
<exclude domain="external" />
9+
</cloud-backup>
10+
<device-transfer>
11+
<exclude domain="root" />
12+
<exclude domain="file" />
13+
<exclude domain="database" />
14+
<exclude domain="sharedpref" />
15+
<exclude domain="external" />
16+
</device-transfer>
17+
</data-extraction-rules>

0 commit comments

Comments
 (0)