diff --git a/patches/src/main/kotlin/app/revanced/patches/all/misc/customcertificates/CustomCertificatesPatch.kt b/patches/src/main/kotlin/app/revanced/patches/all/misc/customcertificates/CustomCertificatesPatch.kt
new file mode 100644
index 000000000..009758977
--- /dev/null
+++ b/patches/src/main/kotlin/app/revanced/patches/all/misc/customcertificates/CustomCertificatesPatch.kt
@@ -0,0 +1,182 @@
+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 app.revanced.util.getNode
+import org.w3c.dom.Element
+import java.io.File
+
+
+
+
+val customNetworkSecurityPatch = resourcePatch(
+ name = "Custom network security",
+ description = "Allows trusting custom certificate authorities for a specific domain.",
+ use = false
+) {
+
+ val targetDomains 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 targetDomains = targetDomains ?: emptyList()
+ val includeSubdomains = includeSubdomains ?: false
+ val customCAFilePaths = customCAFilePaths ?: emptyList()
+ val allowUserCerts = allowUserCerts ?: false
+ val allowSystemCerts = allowSystemCerts ?: true
+ val allowCleartextTraffic = allowCleartextTraffic ?: false
+ val overridePins = overridePins ?: false
+
+ val domainsXML = buildString {
+ targetDomains.forEach {
+ appendLine(""" $it""")
+ }
+ }.trimEnd()
+
+ val trustAnchorsXML = buildString {
+ if (allowSystemCerts) {
+ appendLine(""" """)
+ }
+ if (allowUserCerts) {
+ appendLine(""" """)
+ }
+ customCAFilePaths.forEach { path ->
+ val fileName = path.substringAfterLast('/').substringBeforeLast('.')
+ appendLine(""" """)
+ }
+ }
+
+ if (trustAnchorsXML.isBlank()) {
+ throw PatchException("At least one trust anchor (System, User, or Custom CA) must be enabled.")
+ }
+
+ return """
+
+
+
+$domainsXML
+
+${trustAnchorsXML.trimEnd()}
+
+
+
+ """.trimIndent()
+ }
+
+
+ execute {
+ val nscFileNameBare = "network_security_config"
+ val resXmlDir = "res/xml"
+ val resRawDir = "res/raw"
+ val nscFileNameWithSuffix = "$nscFileNameBare.xml"
+
+
+ document("AndroidManifest.xml").use { document ->
+ val applicationNode = document.getNode("application") as Element
+ applicationNode.setAttribute("android:networkSecurityConfig", "@xml/$nscFileNameBare")
+ }
+
+
+ File(get(resXmlDir), nscFileNameWithSuffix).apply {
+ writeText(generateNetworkSecurityConfig())
+ }
+
+
+
+ for (customCAFilePath in customCAFilePaths ?: emptyList()) {
+ val file = File(customCAFilePath)
+ if (!file.exists()) {
+ throw PatchException(
+ "The custom CA file path cannot be found: " +
+ file.absolutePath
+ )
+ }
+
+ if (!file.isFile) {
+ throw PatchException(
+ "The custom CA file path must be a file: "
+ + file.absolutePath
+ )
+ }
+ val caFileNameWithoutSuffix = customCAFilePath.substringAfterLast('/').substringBefore('.')
+ val caFile = File(customCAFilePath)
+ File(
+ get(resRawDir),
+ caFileNameWithoutSuffix
+ ).writeText(
+ caFile.readText()
+ )
+
+ }
+
+
+ }
+}