Skip to content
Merged
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package app.revanced.patches.all.misc.customcertificates

import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.booleanOption
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringsOption
import app.revanced.util.Utils.trimIndentMultiline
import org.w3c.dom.Element
import java.io.File


private const val NSC_FILE_NAME_BARE = "network_security_config"
private const val RES_XML_DIR = "res/xml"
private const val RES_RAW_DIR = "res/raw"
private const val NSC_FILE_NAME_WITH_SUFFIX = "$NSC_FILE_NAME_BARE.xml"


val customNetworkSecurityPatch = resourcePatch(
name = "Custom network security",
description = "Allows trusting custom certificate authorities for a specific domain.",
use = false
) {

val domains by stringsOption(
key = "targetDomains",
title = "Target Domains",
description = "List of domains to which the custom trust configuration will be applied (one domain per entry).",
default = listOf("example.com"),
required = true
)

val includeSubdomains by booleanOption(
key = "includeSubdomains",
title = "Include subdomains",
description = "Applies the configuration to all subdomains of the target domains.",
default = false,
required = true
)

val customCAFilePaths by stringsOption(
key = "customCAFilePaths",
title = "Custom CA file paths",
description = """
List of paths to files in PEM or DER format (one file path per entry).

Makes an app trust the provided custom certificate authorities (CAs),
for the specified domains, and if the option "Include Subdomains" is enabled then also the subdomains.


CA files will be bundled in res/raw/ of resulting APK
""".trimIndentMultiline(),
default = null,
required = false
)

val allowUserCerts by booleanOption(
key = "allowUserCerts",
title = "Trust User-Added CAs",
description = "Makes an app trust certificates from the Android user store for the specified domains, and if the option \"Include Subdomains\" is enabled then also the subdomains.",

default = false,
required = true
)

val allowSystemCerts by booleanOption(
key = "allowSystemCerts",
title = "Trust System CAs",
description = "Makes an app trust certificates from the Android system store for the specified domains, and and if the option \"Include Subdomains\" is enabled then also the subdomains.",

default = true,
required = true
)

val allowCleartextTraffic by booleanOption(
key = "allowCleartextTraffic",
title = "Allow Cleartext Traffic (HTTP)",
description = "Allows unencrypted HTTP traffic for the specified domains, and if \"Include Subdomains\" is enabled then also the subdomains.",

default = false,
required = true
)

val overridePins by booleanOption(
key = "overridePins",
title = "Override Certificate Pinning",
description = "Overrides certificate pinning for the specified domains and their subdomains if the option \"Include Subdomains\" is enabled to allow inspecting app traffic via a proxy.",

default = false,
required = true
)

fun generateNetworkSecurityConfig(): String {
val domains = domains ?: emptyList()
val includeSubdomains = includeSubdomains ?: false
val customCAFilePaths = customCAFilePaths ?: emptyList()
val allowUser = allowUserCerts ?: false
val allowSystem = allowSystemCerts ?: true
val allowCleartextTraffic = allowCleartextTraffic ?: false
val overridePins = overridePins ?: false


val domainsXMLString = StringBuilder()
domains.forEachIndexed { index, domain ->
val domainLine = """ <domain includeSubdomains="$includeSubdomains">$domain</domain>"""
if (index < domains.lastIndex) {
domainsXMLString.appendLine(domainLine)
} else {
domainsXMLString.append(domainLine)
}
}

val trustAnchorsXMLString = StringBuilder()
if (allowSystem) {
trustAnchorsXMLString.appendLine()
trustAnchorsXMLString.append(""" <certificates src="system" overridePins="$overridePins" />""")
}
if (allowUser) {
trustAnchorsXMLString.appendLine()
trustAnchorsXMLString.append(""" <certificates src="user" overridePins="$overridePins" />""")
}

for (caFilePath in customCAFilePaths) {
val caFileNameWithoutSuffix = caFilePath.substringAfterLast('/').substringBeforeLast('.')
trustAnchorsXMLString.appendLine()
trustAnchorsXMLString.append(""" <certificates src="@raw/$caFileNameWithoutSuffix" overridePins="$overridePins"/>""")
}

if (trustAnchorsXMLString.isBlank()) {
throw PatchException("At least one trust anchor (System, User, or Custom CA) must be enabled.")
}

return """
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="$allowCleartextTraffic">
$domainsXMLString
<trust-anchors>$trustAnchorsXMLString
</trust-anchors>
</domain-config>
</network-security-config>
""".trimIndent()
}


execute {
document("AndroidManifest.xml").use { document ->
val applicationNode =
document
.getElementsByTagName("application")
.item(0) as Element

applicationNode.setAttribute("android:networkSecurityConfig", "@xml/$NSC_FILE_NAME_BARE")
}


File(get(RES_XML_DIR), NSC_FILE_NAME_WITH_SUFFIX).apply {
writeText(generateNetworkSecurityConfig())
}



for (customCAFilePath in customCAFilePaths ?: emptyList()) {
File(customCAFilePath).apply {
if (!exists()) {
throw PatchException(
"The custom CA file path cannot be found: " +
absolutePath
)
}

if (!isFile) {
throw PatchException(
"The custom CA file path must be a file: "
+ absolutePath
)
}
}
val caFileNameWithoutSuffix = customCAFilePath.substringAfterLast('/').substringBefore('.')
File(
get(RES_RAW_DIR),
caFileNameWithoutSuffix
).writeText(File(customCAFilePath).readText())

}


}
}