-
Notifications
You must be signed in to change notification settings - Fork 119
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Related to #1818 ## Test Plan > How do we know the code works? Unit tests pass. ## Checklist - [x] Documented - [x] Unit tested
- Loading branch information
Showing
8 changed files
with
362 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
# Shard obfuscation | ||
|
||
Allows obfuscating test cases names for security reasons. | ||
|
||
## Example | ||
|
||
Obfuscation will change the test cases names as following: | ||
|
||
``` | ||
app1.test1.Test1#case1 -> a.a.A#a | ||
app1.test1.Test1#case2 -> a.a.A#b | ||
app2.test1.Test2#case1 -> b.a.A#a | ||
app2.test1.Test2#case2 -> b.a.A#b | ||
``` | ||
|
||
So, for the example input structure: | ||
|
||
```kotlin | ||
val input: List<List<Shard.App>> = | ||
listOf( | ||
listOf( | ||
Shard.App( | ||
name = "app1", | ||
tests = listOf( | ||
Shard.Test( | ||
name = "app1-test1", | ||
cases = listOf( | ||
Shard.Test.Case( | ||
name = "app1.test1.Test1#case1", | ||
duration = 10_000, | ||
), | ||
) | ||
), | ||
) | ||
), | ||
), | ||
listOf( | ||
Shard.App( | ||
name = "app1", | ||
tests = listOf( | ||
Shard.Test( | ||
name = "app1-test1", | ||
cases = listOf( | ||
Shard.Test.Case( | ||
name = "app1.test1.Test1#case2", | ||
duration = 2_000, | ||
), | ||
) | ||
), | ||
) | ||
), | ||
Shard.App( | ||
name = "app2", | ||
tests = listOf( | ||
Shard.Test( | ||
name = "app2-test1", | ||
cases = listOf( | ||
Shard.Test.Case( | ||
name = "app2.test1.Test2#case1", | ||
duration = 1_000, | ||
), | ||
) | ||
), | ||
Shard.Test( | ||
name = "app2-test1", | ||
cases = listOf( | ||
Shard.Test.Case( | ||
name = "app2.test1.Test2#case2", | ||
), | ||
) | ||
), | ||
) | ||
), | ||
) | ||
) | ||
``` | ||
|
||
The call of: | ||
|
||
```kotlin | ||
input.obfuscate() | ||
``` | ||
|
||
Should return result equal to the following: | ||
|
||
```kotlin | ||
val output: List<List<Shard.App>> = | ||
listOf( | ||
listOf( | ||
Shard.App( | ||
name = "app1", | ||
tests = listOf( | ||
Shard.Test( | ||
name = "app1-test1", | ||
cases = listOf( | ||
Shard.Test.Case( | ||
name = "a.a.A#a", | ||
duration = 10_000, | ||
), | ||
) | ||
), | ||
) | ||
), | ||
), | ||
listOf( | ||
Shard.App( | ||
name = "app1", | ||
tests = listOf( | ||
Shard.Test( | ||
name = "app1-test1", | ||
cases = listOf( | ||
Shard.Test.Case( | ||
name = "a.a.A#b", | ||
duration = 2_000, | ||
), | ||
) | ||
), | ||
) | ||
), | ||
Shard.App( | ||
name = "app2", | ||
tests = listOf( | ||
Shard.Test( | ||
name = "app2-test1", | ||
cases = listOf( | ||
Shard.Test.Case( | ||
name = "b.a.A#a", | ||
duration = 1_000, | ||
), | ||
) | ||
), | ||
Shard.Test( | ||
name = "app2-test1", | ||
cases = listOf( | ||
Shard.Test.Case( | ||
name = "b.a.A#b", | ||
), | ||
) | ||
), | ||
) | ||
), | ||
) | ||
) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile | ||
|
||
plugins { | ||
kotlin(Plugins.Kotlin.PLUGIN_JVM) | ||
} | ||
|
||
repositories { | ||
jcenter() | ||
mavenCentral() | ||
maven(url = "https://kotlin.bintray.com/kotlinx") | ||
} | ||
|
||
tasks.withType<KotlinCompile> { kotlinOptions.jvmTarget = "1.8" } | ||
|
||
dependencies { | ||
api(project(":corellium:shard")) | ||
|
||
testImplementation(Dependencies.JUNIT) | ||
} |
63 changes: 63 additions & 0 deletions
63
corellium/shard/obfuscate/src/main/kotlin/flank/corellium/shard/Internal.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package flank.corellium.shard | ||
|
||
private const val LOWER_CASE_CHARS = "abcdefghijklmnopqrstuvwxyz" | ||
private const val UPPER_CASE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" | ||
private const val ANDROID_TEST_METHOD_SEPARATOR = '#' | ||
private const val ANDROID_PACKAGE_SEPARATOR = '.' | ||
private const val IOS_TEST_METHOD_SEPARATOR = '/' | ||
|
||
internal typealias ObfuscationMappings = MutableMap<String, MutableMap<String, String>> | ||
|
||
internal fun ObfuscationMappings.obfuscateAndroidTestName(input: String): String { | ||
val obfuscatedPackageNameWithClass = | ||
obfuscateAndroidPackageAndClass(input.split(ANDROID_TEST_METHOD_SEPARATOR).first()) | ||
|
||
return obfuscatedPackageNameWithClass + obfuscateAndroidMethodIfPresent(input, obfuscatedPackageNameWithClass) | ||
} | ||
|
||
private fun ObfuscationMappings.obfuscateAndroidPackageAndClass(packageNameWithClass: String) = | ||
packageNameWithClass | ||
.split(ANDROID_PACKAGE_SEPARATOR) | ||
.fold("") { previous, next -> | ||
val classChunk = getOrPut(previous) { linkedMapOf() } | ||
val obfuscatedPart = classChunk.getOrPut(next) { nextSymbol(next, classChunk) } | ||
if (previous.isBlank()) obfuscatedPart else "$previous$ANDROID_PACKAGE_SEPARATOR$obfuscatedPart" | ||
} | ||
|
||
private fun ObfuscationMappings.obfuscateAndroidMethodIfPresent( | ||
input: String, | ||
obfuscatedPackageNameWithClass: String | ||
) = if (input.contains(ANDROID_TEST_METHOD_SEPARATOR)) | ||
ANDROID_TEST_METHOD_SEPARATOR + obfuscateMethodName( | ||
methodName = input.split(ANDROID_TEST_METHOD_SEPARATOR).last(), | ||
context = getOrPut(obfuscatedPackageNameWithClass) { mutableMapOf() } | ||
) | ||
else "" | ||
|
||
internal fun ObfuscationMappings.obfuscateIosTestName(input: String): String { | ||
val className = input.split(IOS_TEST_METHOD_SEPARATOR).first() | ||
val obfuscatedClassName = getOrPut("") { mutableMapOf() }.run { | ||
getOrPut(className) { nextSymbol(className, this) } | ||
} | ||
return obfuscatedClassName + | ||
IOS_TEST_METHOD_SEPARATOR + | ||
obfuscateMethodName( | ||
methodName = input.split(IOS_TEST_METHOD_SEPARATOR).last(), | ||
context = getOrPut(obfuscatedClassName) { linkedMapOf() } | ||
) | ||
} | ||
|
||
private fun nextSymbol(key: String, context: Map<String, String>): String { | ||
val isLowerCaseKey = key.first().isLowerCase() | ||
val possibleSymbols = if (isLowerCaseKey) LOWER_CASE_CHARS else UPPER_CASE_CHARS | ||
|
||
val currentContextItemCount = context.values.count { | ||
if (isLowerCaseKey) it.first().isLowerCase() else it.first().isUpperCase() | ||
} | ||
val repeatSymbol = currentContextItemCount / possibleSymbols.length + 1 | ||
|
||
return possibleSymbols[currentContextItemCount % possibleSymbols.length].toString().repeat(repeatSymbol) | ||
} | ||
|
||
private fun obfuscateMethodName(methodName: String, context: MutableMap<String, String>) = | ||
context.getOrPut(methodName) { nextSymbol(methodName, context) } |
29 changes: 29 additions & 0 deletions
29
corellium/shard/obfuscate/src/main/kotlin/flank/corellium/shard/Obfuscate.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package flank.corellium.shard | ||
|
||
/** | ||
* Obfuscate each test cases names. | ||
* Be aware that this function is not touching the structure, | ||
* just only hashing each [Shard.Test.Case.name] using alphabetical letters. | ||
* Use for security purpose. | ||
* | ||
* @receiver calculated shards | ||
* @return obfuscated shards | ||
*/ | ||
fun obfuscate(shards: Shards): Shards = | ||
// Those nested mappings looks fearfully, but there are just a bunch of iterations where only the last one is important. | ||
shards.map { shard: List<Shard.App> -> | ||
shard.map { app: Shard.App -> | ||
app.copy( | ||
tests = app.tests.map { test: Shard.Test -> | ||
test.copy( | ||
cases = test.cases.map { case: Shard.Test.Case -> | ||
// The only crucial operation which is making the result different than the input. | ||
case.copy(name = obfuscationMappings.obfuscateAndroidTestName(case.name)) | ||
} | ||
) | ||
} | ||
) | ||
} | ||
} | ||
|
||
internal val obfuscationMappings: ObfuscationMappings = mutableMapOf() |
100 changes: 100 additions & 0 deletions
100
corellium/shard/obfuscate/src/test/kotlin/flank/corellium/shard/ObfuscateTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package flank.corellium.shard | ||
|
||
import org.junit.Assert.assertEquals | ||
import org.junit.Assert.assertNotEquals | ||
import org.junit.Test | ||
|
||
class ObfuscateTest { | ||
|
||
private val shards: List<List<Shard.App>> = | ||
listOf( | ||
listOf( | ||
Shard.App( | ||
name = "app1", | ||
tests = listOf( | ||
Shard.Test( | ||
name = "app1-test1", | ||
cases = listOf( | ||
Shard.Test.Case( | ||
name = "app1.test1.Test1#case1", | ||
duration = 10_000, | ||
), | ||
) | ||
), | ||
) | ||
), | ||
), | ||
listOf( | ||
Shard.App( | ||
name = "app1", | ||
tests = listOf( | ||
Shard.Test( | ||
name = "app1-test1", | ||
cases = listOf( | ||
Shard.Test.Case( | ||
name = "app1.test1.Test1#case2", | ||
duration = 2_000, | ||
), | ||
) | ||
), | ||
) | ||
), | ||
Shard.App( | ||
name = "app2", | ||
tests = listOf( | ||
Shard.Test( | ||
name = "app2-test1", | ||
cases = listOf( | ||
Shard.Test.Case( | ||
name = "app2.test1.Test2#case1", | ||
duration = 1_000, | ||
), | ||
) | ||
), | ||
Shard.Test( | ||
name = "app2-test1", | ||
cases = listOf( | ||
Shard.Test.Case( | ||
name = "app2.test1.Test2#case2", | ||
), | ||
) | ||
), | ||
) | ||
), | ||
) | ||
) | ||
|
||
@Test | ||
fun test() { | ||
val obfuscatedShards = obfuscate(shards) | ||
|
||
assertEquals(shards.size, obfuscatedShards.size) | ||
|
||
shards.forEachIndexed { shardIndex, apps -> | ||
val obfuscatedApps = obfuscatedShards[shardIndex] | ||
|
||
assertEquals(apps.size, obfuscatedApps.size) | ||
|
||
apps.forEachIndexed { appIndex, app -> | ||
val obfuscatedApp = obfuscatedApps[appIndex] | ||
|
||
assertEquals(app.tests.size, obfuscatedApp.tests.size) | ||
assertEquals(app.name, obfuscatedApp.name) | ||
|
||
app.tests.forEachIndexed { testIndex, test -> | ||
val obfuscatedTest = obfuscatedApp.tests[testIndex] | ||
|
||
assertEquals(test.cases.size, obfuscatedTest.cases.size) | ||
assertEquals(test.name, obfuscatedTest.name) | ||
|
||
test.cases.forEachIndexed { caseIndex, case -> | ||
val obfuscatedCase = obfuscatedTest.cases[caseIndex] | ||
|
||
println("${case.name} -> ${obfuscatedCase.name}") | ||
assertNotEquals(case.name, obfuscatedCase.name) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.