Skip to content

Commit

Permalink
[APT-9577] Add DRM for downloads for offline usage : update SecureSto…
Browse files Browse the repository at this point in the history
…rage to it can store the DRM key id

Update SecureStorage to add new methods to save and fetch DRM key id.
This ID will later be used to fetch the DRM key needed to decrypt a DRM-protected content.
The ID is saved per content (per audio URL) and per DRM technology (we currently only support Widevine).

Also make SecureStorage injectable with Dagger.
  • Loading branch information
rubeus90 committed Feb 5, 2024
1 parent 5f4401b commit 214dc60
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,18 @@ import com.scribd.armadillo.download.ArmadilloDatabaseProviderImpl
import com.scribd.armadillo.download.ArmadilloDownloadManagerFactory
import com.scribd.armadillo.download.CacheManager
import com.scribd.armadillo.download.CacheManagerImpl
import com.scribd.armadillo.download.MaxAgeCacheEvictor
import com.scribd.armadillo.download.DefaultExoplayerDownloadService
import com.scribd.armadillo.download.DownloadEngine
import com.scribd.armadillo.download.DownloadManagerFactory
import com.scribd.armadillo.download.DownloadTracker
import com.scribd.armadillo.download.ExoplayerDownloadEngine
import com.scribd.armadillo.download.ExoplayerDownloadTracker
import com.scribd.armadillo.download.HeaderAwareDownloaderFactory
import com.scribd.armadillo.download.MaxAgeCacheEvictor
import com.scribd.armadillo.encryption.ArmadilloSecureStorage
import com.scribd.armadillo.encryption.ExoplayerEncryption
import com.scribd.armadillo.encryption.ExoplayerEncryptionImpl
import com.scribd.armadillo.encryption.SecureStorage
import com.scribd.armadillo.exoplayerExternalDirectory
import dagger.Module
import dagger.Provides
Expand Down Expand Up @@ -92,6 +94,10 @@ internal class DownloadModule {
@Provides
fun exoplayerEncryption(exoplayerEncryption: ExoplayerEncryptionImpl): ExoplayerEncryption = exoplayerEncryption

@Singleton
@Provides
fun secureStorage(secureStorage: ArmadilloSecureStorage): SecureStorage = secureStorage

@Singleton
@Provides
fun downloadManagerFactory(downloadManagerFactory: ArmadilloDownloadManagerFactory): DownloadManagerFactory = downloadManagerFactory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ interface ExoplayerEncryption {
* This class provides the plumbing for encrypting downloaded content & then reading this encrypted content.
*/
@Singleton
internal class ExoplayerEncryptionImpl @Inject constructor(applicationContext: Context) : ExoplayerEncryption {
internal class ExoplayerEncryptionImpl @Inject constructor(applicationContext: Context,
secureStorage: SecureStorage) : ExoplayerEncryption {

private val secureStorage: SecureStorage = ArmadilloSecureStorage()
private val secret = secureStorage.downloadSecretKey(applicationContext)

override fun dataSinkFactory(downloadCache: Cache) = DataSink.Factory {
Expand All @@ -30,6 +30,6 @@ internal class ExoplayerEncryptionImpl @Inject constructor(applicationContext: C
}

override fun dataSourceFactory(upstream: DataSource.Factory) =
DataSource.Factory { AesCipherDataSource(secret, upstream.createDataSource()) }
DataSource.Factory { AesCipherDataSource(secret, upstream.createDataSource()) }

}
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
package com.scribd.armadillo.encryption

import android.content.Context
import android.util.Base64
import android.util.Log
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import javax.inject.Inject
import javax.inject.Singleton

internal interface SecureStorage {
fun downloadSecretKey(context: Context): ByteArray
fun saveDrmKeyId(context: Context, audioUrl: String, drmType: String, drmKeyId: ByteArray)
fun getDrmKeyId(context: Context, audioUrl: String, drmType: String): ByteArray?
}

internal class ArmadilloSecureStorage : SecureStorage {
@Singleton
internal class ArmadilloSecureStorage @Inject constructor() : SecureStorage {
private companion object {
const val DOWNLOAD_KEY = "download_key"
const val STRING_LENGTH = 20
Expand All @@ -19,24 +26,42 @@ internal class ArmadilloSecureStorage : SecureStorage {
}

override fun downloadSecretKey(context: Context): ByteArray {
val sharedPreferences = context.getSharedPreferences(LOCATION, Context.MODE_PRIVATE)
return if (sharedPreferences.contains(DOWNLOAD_KEY)) {
val storedKey = sharedPreferences.getString(DOWNLOAD_KEY, DEFAULT)!!
if(storedKey == DEFAULT){
Log.e(TAG, "Storage Is Out of Alignment")
}
storedKey.toSecretByteArray
} else {
createRandomString().also {
sharedPreferences.edit().putString(DOWNLOAD_KEY, it).apply()
}.toSecretByteArray
val sharedPreferences = context.getSharedPreferences(LOCATION, Context.MODE_PRIVATE)
return if (sharedPreferences.contains(DOWNLOAD_KEY)) {
val storedKey = sharedPreferences.getString(DOWNLOAD_KEY, DEFAULT)!!
if (storedKey == DEFAULT) {
Log.e(TAG, "Storage Is Out of Alignment")
}
storedKey.toSecretByteArray
} else {
createRandomString().also {
sharedPreferences.edit().putString(DOWNLOAD_KEY, it).apply()
}.toSecretByteArray
}
}

private fun createRandomString(): String {
return (1..STRING_LENGTH)
.map { ALLOWED_CHARS.random() }
.joinToString("")
return (1..STRING_LENGTH)
.map { ALLOWED_CHARS.random() }
.joinToString("")
}

override fun saveDrmKeyId(context: Context, audioUrl: String, drmType: String, drmKeyId: ByteArray) {
context.getSharedPreferences(LOCATION, Context.MODE_PRIVATE).also { sharedPrefs ->
val key = Base64.encodeToString(audioUrl.toByteArray(StandardCharsets.UTF_8) + drmType.toByteArray(StandardCharsets.UTF_8),
Base64.NO_WRAP)
val value = Base64.encodeToString(drmKeyId, Base64.NO_WRAP)
sharedPrefs.edit().putString(key, value).apply()
}
}

override fun getDrmKeyId(context: Context, audioUrl: String, drmType: String): ByteArray? =
context.getSharedPreferences(LOCATION, Context.MODE_PRIVATE).let { sharedPrefs ->
val key = Base64.encodeToString(audioUrl.toByteArray(StandardCharsets.UTF_8) + drmType.toByteArray(StandardCharsets.UTF_8),
Base64.NO_WRAP)
sharedPrefs.getString(key, null)?.let { encodedKeyId ->
Base64.decode(encodedKeyId, Base64.NO_WRAP)
}
}

private val String.toSecretByteArray: ByteArray
Expand Down

0 comments on commit 214dc60

Please sign in to comment.