Skip to content

Commit

Permalink
Merge pull request #43 from scribd/katherine/APT-10344-2-encrypt
Browse files Browse the repository at this point in the history
[APT-10344] Use EncryptedSharedPreferences
  • Loading branch information
kabliz authored Sep 11, 2024
2 parents c89a439 + d7a6d86 commit 0439384
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 19 deletions.
4 changes: 3 additions & 1 deletion Armadillo/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ android {
compileSdk 34

defaultConfig {
minSdkVersion 21
minSdkVersion 23
targetSdkVersion 34
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'proguard-rules.pro'
Expand All @@ -54,6 +54,8 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'

implementation "androidx.security:security-crypto:1.0.0"

compileOnly files('../libs/exoplayer-core-release.aar')
implementation "com.google.android.exoplayer:exoplayer-common:${EXOPLAYER_VERSION}"
implementation ("com.google.android.exoplayer:exoplayer-hls:${EXOPLAYER_VERSION}") {
Expand Down
2 changes: 2 additions & 0 deletions Armadillo/src/main/java/com/scribd/armadillo/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ object Constants {
const val GLOBAL_SCOPE = "global_scope"

const val STANDARD_STORAGE = "standard_storage"
const val STANDARD_SECURE_STORAGE = "standard_secure_storage"
const val DRM_DOWNLOAD_STORAGE = "drm_download_storage"
const val DRM_SECURE_STORAGE = "drm_secure_storage"
}

internal object Exoplayer {
Expand Down
47 changes: 47 additions & 0 deletions Armadillo/src/main/java/com/scribd/armadillo/di/DownloadModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ package com.scribd.armadillo.di

import android.content.Context
import android.content.SharedPreferences
import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties.BLOCK_MODE_GCM
import android.security.keystore.KeyProperties.ENCRYPTION_PADDING_NONE
import android.security.keystore.KeyProperties.PURPOSE_DECRYPT
import android.security.keystore.KeyProperties.PURPOSE_ENCRYPT
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import com.google.android.exoplayer2.offline.DownloadManager
import com.google.android.exoplayer2.offline.DownloadService
import com.google.android.exoplayer2.offline.DownloaderFactory
Expand Down Expand Up @@ -111,6 +118,46 @@ internal class DownloadModule {
fun drmDownloadStorage(context: Context): SharedPreferences =
context.getSharedPreferences("armadillo.download.drm", Context.MODE_PRIVATE)

@Singleton
@Provides
@Named(Constants.DI.STANDARD_SECURE_STORAGE)
fun standardSecureStorage(context: Context): SharedPreferences {
val keys = MasterKeys.getOrCreate(
KeyGenParameterSpec.Builder("armadilloStandard", PURPOSE_ENCRYPT or PURPOSE_DECRYPT)
.setKeySize(256)
.setBlockModes(BLOCK_MODE_GCM)
.setEncryptionPaddings(ENCRYPTION_PADDING_NONE)
.build()
)
return EncryptedSharedPreferences.create(
"armadillo.standard.secure",
keys,
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
}

@Singleton
@Provides
@Named(Constants.DI.DRM_SECURE_STORAGE)
fun drmSecureStorage(context: Context): SharedPreferences {
val keys = MasterKeys.getOrCreate(
KeyGenParameterSpec.Builder("armadillo", PURPOSE_ENCRYPT or PURPOSE_DECRYPT)
.setKeySize(256)
.setBlockModes(BLOCK_MODE_GCM)
.setEncryptionPaddings(ENCRYPTION_PADDING_NONE)
.build()
)
return EncryptedSharedPreferences.create(
"armadillo.download.secure",
keys,
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
}

@Singleton
@Provides
fun downloadManagerFactory(downloadManagerFactory: ArmadilloDownloadManagerFactory): DownloadManagerFactory = downloadManagerFactory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ internal interface SecureStorage {

@Singleton
internal class ArmadilloSecureStorage @Inject constructor(
@Named(Constants.DI.STANDARD_STORAGE) private val standardStorage: SharedPreferences,
@Named(Constants.DI.DRM_DOWNLOAD_STORAGE) private val drmDownloadStorage: SharedPreferences,
@Named(Constants.DI.STANDARD_STORAGE) private val legacyStandardStorage: SharedPreferences,
@Named(Constants.DI.STANDARD_SECURE_STORAGE) private val secureStandardStorage: SharedPreferences,
@Named(Constants.DI.DRM_DOWNLOAD_STORAGE) private val legacyDrmStorage: SharedPreferences,
@Named(Constants.DI.DRM_SECURE_STORAGE) private val secureDrmStorage: SharedPreferences
) : SecureStorage {
companion object {
const val DOWNLOAD_KEY = "download_key"
Expand All @@ -39,15 +41,25 @@ internal class ArmadilloSecureStorage @Inject constructor(
}

override fun downloadSecretKey(context: Context): ByteArray {
return if (standardStorage.contains(DOWNLOAD_KEY)) {
val storedKey = standardStorage.getString(DOWNLOAD_KEY, DEFAULT)!!
return if (secureStandardStorage.contains(DOWNLOAD_KEY)) {
val storedKey = secureDrmStorage.getString(DOWNLOAD_KEY, DEFAULT)!!
if (storedKey == DEFAULT) {
Log.e(TAG, "Storage Is Out of Alignment")
}
storedKey.toSecretByteArray
} else if(legacyStandardStorage.contains(DOWNLOAD_KEY)) {
//migrate to secured version
val storedKey = legacyStandardStorage.getString(DOWNLOAD_KEY, DEFAULT)!!
if (storedKey == DEFAULT) {
Log.e(TAG, "Storage Is Out of Alignment")
}
secureStandardStorage.edit().putString(DOWNLOAD_KEY, storedKey).apply()
legacyStandardStorage.edit().remove(DOWNLOAD_KEY).apply()
storedKey.toSecretByteArray
} else {
//no key exists anywhere yet
createRandomString().also {
standardStorage.edit().putString(DOWNLOAD_KEY, it).apply()
secureStandardStorage.edit().putString(DOWNLOAD_KEY, it).apply()
}.toSecretByteArray
}
}
Expand All @@ -59,27 +71,48 @@ internal class ArmadilloSecureStorage @Inject constructor(
}

override fun saveDrmDownload(context: Context, audioUrl: String, drmDownload: DrmDownload) {
val key = getDrmDownloadKey(audioUrl, drmDownload.drmType)
val alias = getDrmDownloadAlias(audioUrl, drmDownload.drmType)
val value = Base64.encodeToString(Json.encodeToString(drmDownload).toByteArray(StandardCharsets.UTF_8), Base64.NO_WRAP)
drmDownloadStorage.edit().putString(key, value).apply()
secureDrmStorage.edit().putString(alias, value).apply()
}

override fun getDrmDownload(context: Context, audioUrl: String, drmType: DrmType): DrmDownload? =
drmDownloadStorage.getString(getDrmDownloadKey(audioUrl, drmType), null)?.decodeToDrmDownload()
override fun getDrmDownload(context: Context, audioUrl: String, drmType: DrmType): DrmDownload? {
val alias = getDrmDownloadAlias(audioUrl, drmType)
var download = secureDrmStorage.getString(alias, null)?.decodeToDrmDownload()
if (download == null && legacyDrmStorage.contains(alias)) {
//migrate old storage to secure storage
val downloadValue = legacyDrmStorage.getString(alias, null)
download = downloadValue?.decodeToDrmDownload()
secureDrmStorage.edit().putString(alias, downloadValue).apply()
legacyDrmStorage.edit().remove(alias).apply()
}
return download
}

override fun getAllDrmDownloads(context: Context): Map<String, DrmDownload> =
drmDownloadStorage.all.keys.mapNotNull { key ->
drmDownloadStorage.getString(key, null)?.let { drmResult ->
key to drmResult.decodeToDrmDownload()
override fun getAllDrmDownloads(context: Context): Map<String, DrmDownload> {
val drmDownloads = secureDrmStorage.all.keys.mapNotNull { alias ->
secureDrmStorage.getString(alias, null)?.let { drmResult ->
alias to drmResult.decodeToDrmDownload()
}
}.toMap()
val legacyDownloads = legacyDrmStorage.all.keys.mapNotNull { alias ->
legacyDrmStorage.getString(alias, null)?.let { drmResult ->
alias to drmResult.decodeToDrmDownload()
}
}.toMap()

return drmDownloads.plus(legacyDownloads)
}

override fun removeDrmDownload(context: Context, audioUrl: String, drmType: DrmType) {
drmDownloadStorage.edit().remove(getDrmDownloadKey(audioUrl, drmType)).apply()
val alias = getDrmDownloadAlias(audioUrl, drmType)
legacyDrmStorage.edit().remove(alias).apply()
secureDrmStorage.edit().remove(alias).apply()
}

override fun removeDrmDownload(context: Context, key: String) {
drmDownloadStorage.edit().remove(key).apply()
legacyDrmStorage.edit().remove(key).apply()
secureDrmStorage.edit().remove(key).apply()
}

private val String.toSecretByteArray: ByteArray
Expand All @@ -91,7 +124,7 @@ internal class ArmadilloSecureStorage @Inject constructor(
return keyBytes
}

private fun getDrmDownloadKey(audioUrl: String, drmType: DrmType) =
private fun getDrmDownloadAlias(audioUrl: String, drmType: DrmType) =
Base64.encodeToString(audioUrl.toSecretByteArray + drmType.name.toSecretByteArray, Base64.NO_WRAP)

private fun String.decodeToDrmDownload(): DrmDownload =
Expand Down
3 changes: 3 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Project Armadillo Release Notes

## 1.6.0
- Encrypts widevine drm keys. The Android minSDK has been changed to version 23 in order to support this feature.

## 1.5.4
- Ensured that ArmadilloPlayer.armadilloStateObservable has a state as soon as the player is initialized
- Fixed UnknownHostException being mapped to a HTTP status code issue rather than a Connectivity issue.
Expand Down
2 changes: 1 addition & 1 deletion TestApp/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ android {

defaultConfig {
applicationId "com.scribd.armadillotestapp"
minSdkVersion 21
minSdkVersion 23
targetSdkVersion 34
versionCode 1
versionName "1.0"
Expand Down
4 changes: 4 additions & 0 deletions TestApp/src/main/res/xml/backup.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<exclude domain="external" path="exoplayer/"/>
<exclude domain="sharedpref" path="armadillo.storage.xml"/>
<exclude domain="sharedpref" path="armadillo.download.secure.xml"/>
<exclude domain="sharedpref" path="armadillo.download.secure.xml"/>
<exclude domain="sharedpref" path="armadillo.download.drm.xml"/>
</full-backup-content>
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ org.gradle.jvmargs=-Xmx1536m
# org.gradle.parallel=true
PACKAGE_NAME=com.scribd.armadillo
GRADLE_PLUGIN_VERSION=7.2.0
LIBRARY_VERSION=1.5.4
LIBRARY_VERSION=1.6.0
EXOPLAYER_VERSION=2.19.1
RXJAVA_VERSION=2.2.4
RXANDROID_VERSION=2.0.1
Expand Down

0 comments on commit 0439384

Please sign in to comment.