From 5c5b69a13aee5cebc1547e62b730360debf0be6f Mon Sep 17 00:00:00 2001 From: Nikita Gorskikh Date: Thu, 19 May 2022 21:15:05 +0300 Subject: [PATCH 01/24] AdGuard Certificate 2.0 --- .gitmodules | 3 - README.md | 73 ++--- build.gradle | 17 -- dist.sh | 22 -- gradle.properties | 19 -- gradle/wrapper/gradle-wrapper.jar | Bin 59203 -> 0 bytes gradle/wrapper/gradle-wrapper.properties | 6 - gradlew | 185 ------------ gradlew.bat | 89 ------ module/module.prop | 6 +- module/post-fs-data.sh | 7 +- ndk_path.py | 23 -- settings.gradle | 8 - zygisk_module/.gitignore | 3 - zygisk_module/build.gradle | 14 - zygisk_module/jni/Android.mk | 19 -- zygisk_module/jni/Application.mk | 4 - zygisk_module/jni/browsers.inc | 48 --- zygisk_module/jni/common.cpp | 33 --- zygisk_module/jni/common.h | 32 -- zygisk_module/jni/companion.cpp | 97 ------ zygisk_module/jni/libcxx | 1 - zygisk_module/jni/module.cpp | 41 --- zygisk_module/jni/zygisk.hpp | 328 --------------------- zygisk_module/src/main/AndroidManifest.xml | 2 - 25 files changed, 34 insertions(+), 1046 deletions(-) delete mode 100644 build.gradle delete mode 100644 gradle.properties delete mode 100644 gradle/wrapper/gradle-wrapper.jar delete mode 100644 gradle/wrapper/gradle-wrapper.properties delete mode 100755 gradlew delete mode 100644 gradlew.bat delete mode 100755 ndk_path.py delete mode 100644 settings.gradle delete mode 100644 zygisk_module/.gitignore delete mode 100644 zygisk_module/build.gradle delete mode 100644 zygisk_module/jni/Android.mk delete mode 100644 zygisk_module/jni/Application.mk delete mode 100644 zygisk_module/jni/browsers.inc delete mode 100644 zygisk_module/jni/common.cpp delete mode 100644 zygisk_module/jni/common.h delete mode 100644 zygisk_module/jni/companion.cpp delete mode 160000 zygisk_module/jni/libcxx delete mode 100644 zygisk_module/jni/module.cpp delete mode 100644 zygisk_module/jni/zygisk.hpp delete mode 100644 zygisk_module/src/main/AndroidManifest.xml diff --git a/.gitmodules b/.gitmodules index b2f6d09..e69de29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "zygisk_module/jni/libcxx"] - path = zygisk_module/jni/libcxx - url = https://github.com/topjohnwu/libcxx.git diff --git a/README.md b/README.md index dbe6f37..5f9e8b9 100644 --- a/README.md +++ b/README.md @@ -5,15 +5,31 @@ Based on [Move Certificates](https://github.com/Magisk-Modules-Repo/movecert). This Magisk module supplements [AdGuard for Android][agandroid] and allows installing AdGuard's CA certificate to the System store on rooted devices. -## Why could you need it? +## Explanation + +Chrome (and subsequently many other Chromium-based browsers) +has recently started requiring Certificate Transparency logs +for CA certs found in the **system certificate store**. + +If your device is rooted, and you want AdGuard's CA certificate to be installed +in the **system store** , then AdGuard will generate two CA certificates and ask you +to install both of them in the **user store**. This module moves one of them to the +**system store**. The certificate that is left in the **user store** is cross-signed +with the one that goes into the **system store**. This allows apps that don't trust +user certificates to still accept AdGuard's certificate, while apps that do trust +user certificates (like Chrome or other browsers) will construct a shorter validation +path to the certificate stored in the **user store**. And since it is stored in the +**user store**, they won't require CT logs. + +## Why would I want AdGuard's certificate in the system store? AdGuard for Android provides a feature called [HTTPS filtering][httpsfiltering]. It allows filtering of encrypted HTTPS traffic on your Android device. This feature requires adding the AdGuard's CA certificate to the list of trusted certificates. By default, on a non-rooted device only a limited subset of apps (mostly, browsers) -trust the CA certificates installed to the **User store**. The only option to allow -filtering of all other apps' traffic is to install the certificate to the **System store**. +trust the CA certificates installed to the **user store**. The only option to allow +filtering of all other apps' traffic is to install the certificate to the **system store**. Unfortunately, this is only possible on rooted devices. [agandroid]: https://adguard.com/adguard-android/overview.html @@ -21,26 +37,19 @@ Unfortunately, this is only possible on rooted devices. ## Usage -1. Enable HTTPS filtering in AdGuard for Android and save AdGuard's certificate to the User store. -2. Go to *Magisk -> Settings* and enable **Zygisk**. -3. Download the `.zip` file from the [latest release][latestrelease]. -4. Go to *Magisk -> Modules -> Install from storage* and select the downloaded `.zip` file. -5. Reboot. +1. Enable HTTPS filtering in AdGuard for Android and save AdGuard's certificate(s) to the User store +2. Download the `.zip` file from the [latest release][latestrelease]. +3. Go to *Magisk -> Modules -> Install from storage* and select the downloaded `.zip` file. +4. Reboot. -If a new version comes out, repeat steps 3-5 to update the module. +If a new version comes out, repeat steps 2-4 to update the module. -The module does its work during the system boot. If your AdGuard certificate changes, +The module does its work during the system boot. If your AdGuard certificate(s) change, you'll have to reboot the device for the new certificate to be copied to the system store.
Illustrated instruction -![Open Magisk settings](https://user-images.githubusercontent.com/5947035/161061257-680c784b-b476-432d-8dfd-2528fe239346.png) - -![Enable Zygisk](https://user-images.githubusercontent.com/5947035/161061268-3367d668-cbbd-441d-9e6d-a4cbc3978b3e.png) - -![Go back to Magisk main screen](https://user-images.githubusercontent.com/5947035/161061273-329e3f8a-c957-4005-a8f7-2056b1866b08.png) - ![Open Magisk modules](https://user-images.githubusercontent.com/5947035/161061277-1ada3a87-d0cb-44c0-9edd-77b00669759c.png) ![Install from storage](https://user-images.githubusercontent.com/5947035/161061283-8e3d6ed2-ca36-4825-bca4-fbb9f9185f68.png) @@ -51,7 +60,7 @@ you'll have to reboot the device for the new certificate to be copied to the sys
-Please note that in order for **Bromite** browser to work properly, you need to set flag "Allow user certificates" in `chrome://flags` to "Enabled" state. +Please note that in order for **Bromite** browser to work properly, you need to set the "Allow user certificates" flag in `chrome://flags` to "Enabled".
Bromite setup @@ -62,39 +71,11 @@ Please note that in order for **Bromite** browser to work properly, you need to [latestrelease]: https://github.com/AdguardTeam/adguardcert/releases/latest/ -## Chrome and Chromium-based browsers - -Chrome (and subsequently many other Chromium-based browsers) -has recently started requiring CT logs for CA certs found in the **System store**. -This module copies AdGuard's CA certificate from the **User store** to the **System store**. -It also contains a Zygisk module that reverts any modifications done by Magisk for -[certain browsers](./zygisk_module/jni/browsers.inc). -This way the browsers only find AdGuard's certificate in the User store -and don't complain about the missing CT log, while other apps continue to use the -same certificate from the System store. - ## Building - -Update git modules: - -```shell -git submodule init && git submodule update -``` - -You'll need an Android SDK with NDK installed (tested with NDK 22 and 23). Run: - ```shell -ANDROID_HOME= ./dist.sh +./dist.sh ``` How to release a new version: 1. Push a new tag with a name like `v*`. 2. A new release will be automatically created. - -## Advanced - -If you prefer to manage your Zygisk denylist yourself, simply remove the Zygisk part of the module: - -```shell -zip adguardcert-v1.0.zip -d "zygisk/*" -``` diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 8ca4829..0000000 --- a/build.gradle +++ /dev/null @@ -1,17 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. -buildscript { - repositories { - google() - mavenCentral() - } - dependencies { - classpath "com.android.tools.build:gradle:7.0.3" - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files - } -} - -task clean(type: Delete) { - delete rootProject.buildDir -} diff --git a/dist.sh b/dist.sh index ffdd090..4bdf8a3 100755 --- a/dist.sh +++ b/dist.sh @@ -1,27 +1,5 @@ #!/bin/bash -if [ -z "${ANDROID_HOME}" ]; then - echo "Specify the Android SDK directory through the ANDROID_HOME environment variable" - exit 1 -fi - -NDK_PATH=$(./ndk_path.py) - -if [ ! -d "${NDK_PATH}" ]; then - echo "NDK version ${NDK_VERSION} is required and was not found at ${NDK_PATH}" - exit 1 -fi - -NDK_BUILD="${NDK_PATH}/ndk-build" - -(cd ./zygisk_module && ${NDK_BUILD} -j8) || exit 1 - -mkdir ./module/zygisk - -for i in $(ls ./zygisk_module/libs); do - cp -f ./zygisk_module/libs/$i/*.so ./module/zygisk/$i.so -done - UPDATE_BINARY_URL="https://raw.githubusercontent.com/topjohnwu/Magisk/master/scripts/module_installer.sh" mkdir -p ./module/META-INF/com/google/android diff --git a/gradle.properties b/gradle.properties deleted file mode 100644 index 01b80d7..0000000 --- a/gradle.properties +++ /dev/null @@ -1,19 +0,0 @@ -# Project-wide Gradle settings. -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true -# AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app"s APK -# https://developer.android.com/topic/libraries/support-library/androidx-rn -android.useAndroidX=true -# Automatically convert third-party libraries to use AndroidX -android.enableJetifier=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index e708b1c023ec8b20f512888fe07c5bd3ff77bb8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59203 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

K7UpUK$>l@ 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 E2O(~oYXATM diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index f4bd034..0000000 --- a/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Sun Sep 26 02:13:18 PDT 2021 -distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip -distributionPath=wrapper/dists -zipStorePath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew deleted file mode 100755 index 4f906e0..0000000 --- a/gradlew +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env sh - -# -# Copyright 2015 the original author or authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=`expr $i + 1` - done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat deleted file mode 100644 index ac1b06f..0000000 --- a/gradlew.bat +++ /dev/null @@ -1,89 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/module/module.prop b/module/module.prop index 07faa93..985bfe4 100644 --- a/module/module.prop +++ b/module/module.prop @@ -1,6 +1,6 @@ id=adguardcert name=AdGuard Certificate -version=v1.2 -versionCode=3 +version=v2.0 +versionCode=30 author=AdGuard -description=Copies AdGuard's CA certificate from the user certificate store to the system store and forces Zygisk unmount procedures for certain browsers. +description=Moves AdGuard's root CA certificate from the user certificate store to the system certificate store. diff --git a/module/post-fs-data.sh b/module/post-fs-data.sh index a823a50..dcb8d56 100644 --- a/module/post-fs-data.sh +++ b/module/post-fs-data.sh @@ -2,21 +2,22 @@ MODDIR=${0%/*} # Android hashes the subject to get the filename, field order is significant. +# (`openssl x509 -in ... -noout -hash`) # AdGuard's certificate is "/C=EN/O=AdGuard/CN=AdGuard Personal CA". # The filename is then . where is an integer to disambiguate # different certs with the same hash (e.g. when the same cert is installed repeatedly). -# +# # Due to https://github.com/AdguardTeam/AdguardForAndroid/issues/2108 # 1. Take the last cert with our hash from the user store. # Assuming the last installed AdGuard's cert is the correct one. -# 2. Copy it to the system store under the name ".0". +# 2. Move it to the system store under the name ".0". # Apparently, some apps may ignore other certs. # 3. Remove all certs with our hash from the `cacerts-removed` directory. # They get there if a certificate is "disabled" in the security settings. # Apps will reject certs that are in the `cacerts-removed`. AG_CERT_HASH=0f4ed297 AG_CERT_FILE=$(ls /data/misc/user/*/cacerts-added/${AG_CERT_HASH}.* | sort | tail -n1) -cp -f ${AG_CERT_FILE} ${MODDIR}/system/etc/security/cacerts/${AG_CERT_HASH}.0 +mv -f ${AG_CERT_FILE} ${MODDIR}/system/etc/security/cacerts/${AG_CERT_HASH}.0 rm -f /data/misc/user/*/cacerts-removed/${AG_CERT_HASH}.* chown -R 0:0 ${MODDIR}/system/etc/security/cacerts diff --git a/ndk_path.py b/ndk_path.py deleted file mode 100755 index 30f4a1c..0000000 --- a/ndk_path.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 - -import os - -def get_ndk_path(): - sdk_path = os.environ.get('ANDROID_HOME') - if os.path.isdir(sdk_path): - path = os.path.join(sdk_path, 'ndk') - if os.path.isdir(path): - # Android Studio can install multiple ndk versions in 'ndk'. - # Find the newest one. - ndk_version = None - for name in os.listdir(path): - if not ndk_version or ndk_version < name: - ndk_version = name - if ndk_version: - return os.path.join(path, ndk_version) - ndk_path = os.path.join(sdk_path, 'ndk-bundle') - if os.path.isdir(ndk_path): - return ndk_path - return None - -print(get_ndk_path()) diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index d0da8c5..0000000 --- a/settings.gradle +++ /dev/null @@ -1,8 +0,0 @@ -dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) - repositories { - google() - mavenCentral() - } -} -include ':zygisk_module' diff --git a/zygisk_module/.gitignore b/zygisk_module/.gitignore deleted file mode 100644 index a264cd9..0000000 --- a/zygisk_module/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/build -/libs -/obj diff --git a/zygisk_module/build.gradle b/zygisk_module/build.gradle deleted file mode 100644 index c2019b7..0000000 --- a/zygisk_module/build.gradle +++ /dev/null @@ -1,14 +0,0 @@ -plugins { - id 'com.android.library' -} - -android { - compileSdkVersion 31 - ndkVersion "23.1.7779620" - - externalNativeBuild { - ndkBuild { - path("jni/Android.mk") - } - } -} diff --git a/zygisk_module/jni/Android.mk b/zygisk_module/jni/Android.mk deleted file mode 100644 index cc263e6..0000000 --- a/zygisk_module/jni/Android.mk +++ /dev/null @@ -1,19 +0,0 @@ -LOCAL_PATH := $(call my-dir) - -include $(CLEAR_VARS) -LOCAL_MODULE := copycert -LOCAL_SRC_FILES := module.cpp companion.cpp common.cpp -LOCAL_STATIC_LIBRARIES := libcxx -LOCAL_LDLIBS := -llog -include $(BUILD_SHARED_LIBRARY) - -include jni/libcxx/Android.mk - -# If you do not want to use libc++, link to system stdc++ -# so that you can at least call the new operator in your code - -# include $(CLEAR_VARS) -# LOCAL_MODULE := example -# LOCAL_SRC_FILES := example.cpp -# LOCAL_LDLIBS := -llog -lstdc++ -# include $(BUILD_SHARED_LIBRARY) diff --git a/zygisk_module/jni/Application.mk b/zygisk_module/jni/Application.mk deleted file mode 100644 index 96948f8..0000000 --- a/zygisk_module/jni/Application.mk +++ /dev/null @@ -1,4 +0,0 @@ -APP_ABI := armeabi-v7a arm64-v8a x86 x86_64 -APP_CPPFLAGS := -std=c++17 -fno-exceptions -fno-rtti -fvisibility=hidden -fvisibility-inlines-hidden -APP_STL := none -APP_PLATFORM := android-21 diff --git a/zygisk_module/jni/browsers.inc b/zygisk_module/jni/browsers.inc deleted file mode 100644 index cf4d2bb..0000000 --- a/zygisk_module/jni/browsers.inc +++ /dev/null @@ -1,48 +0,0 @@ -"com.android.chrome", -"com.chrome.beta", -"com.chrome.canary", -"com.chrome.dev", -"org.chromium.chrome", -"com.brave.browser", -"com.hsv.freeadblockerbrowser", -"com.microsoft.emmx", -"com.microsoft.emmx.canary", -"com.yandex.browser", -"com.yandex.browser.alpha", -"com.yandex.browser.beta", -"com.sec.android.app.sbrowser", -"com.sec.android.app.sbrowser.beta", -"com.yandex.browser.lite", -"jp.co.fenrir.android.sleipnir", -"jp.co.fenrir.android.sleipnir_black", -"jp.co.fenrir.android.sleipnir_test", -"jp.hazuki.yuzubrowser", -"org.mozilla.fennec", -"org.mozilla.fennec_aurora", -"org.mozilla.fennec_fdroid", -"cn.mozilla.firefox", -"mobi.browser.flashfox", -"mobi.browser.flfoxpro", -"org.adblockplus.browser", -"org.iron.srware", -"org.mozilla.firefox", -"org.mozilla.firefox_beta", -"org.mozilla.fenix", -"com.android.browser", -"com.droid.browser", -"com.vionika.firephoenix", -"com.kiwibrowser.browser", -"org.bromite.bromite", -"com.vivaldi.browser", -"com.vivaldi.browser.snapshot", -"com.brave.browser", -"com.brave.browser_dev", -"com.brave.browser_beta", -"com.brave.browser_default", -"com.brave.browser_nightly", -"com.huawei.browser", -"io.github.forkmaintainers.iceraven", -"com.stoutner.privacybrowser.free", -"com.stoutner.privacybrowser.standard", -"com.naver.whale", -"us.spotco.mulch", diff --git a/zygisk_module/jni/common.cpp b/zygisk_module/jni/common.cpp deleted file mode 100644 index 4e0ba97..0000000 --- a/zygisk_module/jni/common.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include "common.h" -#include - -int ag::read_int(int fd) { - int i; - if (read(fd, &i, sizeof(i)) != sizeof(i)) { - return INT_MIN; - } - return i; -} - -std::string ag::read_string(int fd) { - size_t size = 0; - if (read(fd, &size, sizeof(size)) != sizeof(size)) { - return ""; - } - std::string s; - s.resize(size); - if (read(fd, s.data(), s.size()) != s.size()) { - return ""; - } - return s; -} - -void ag::write_int(int fd, int v) { - write(fd, &v, sizeof(v)); -} - -void ag::write_string(int fd, std::string_view s) { - size_t size = s.size(); - write(fd, &size, sizeof(size)); - write(fd, s.data(), size); -} diff --git a/zygisk_module/jni/common.h b/zygisk_module/jni/common.h deleted file mode 100644 index 9d2968d..0000000 --- a/zygisk_module/jni/common.h +++ /dev/null @@ -1,32 +0,0 @@ -#include -#include -#include -#include - -#include - -#include - -#define LOG_TAG "AdGuardCertificate" - -#ifndef NDEBUG -#define dbglog(fmt_, ...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "%s(): " fmt_, __func__, ##__VA_ARGS__) -#else -#define dbglog(fmt_, ...) ((void) fmt_) -#endif - -#define warnlog(fmt_, ...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, "%s(): " fmt_, __func__, ##__VA_ARGS__) - -namespace ag { - -template -using Ftor = std::integral_constant; -using Dir = std::unique_ptr>; - -int read_int(int fd); -std::string read_string(int fd); - -void write_int(int fd, int v); -void write_string(int fd, std::string_view s); - -} diff --git a/zygisk_module/jni/companion.cpp b/zygisk_module/jni/companion.cpp deleted file mode 100644 index 68cc9cc..0000000 --- a/zygisk_module/jni/companion.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include - -#include "common.h" -#include "zygisk.hpp" - -#define APP_ID(uid) (uid % 100000) - -using Map = std::unordered_map; -static std::shared_ptr g_app_id_to_pkg; - -static void rescan_packages() { - struct stat st{}; - static std::atomic_int64_t packages_xml_ino{0}; - - stat("/data/system/packages.xml", &st); - - if (packages_xml_ino == st.st_ino) { - dbglog("Packages unchanged, no need to rescan"); - return; - } - - static std::mutex rescan_mutex; - std::scoped_lock l(rescan_mutex); - - if (packages_xml_ino == st.st_ino) { - dbglog("Packages unchanged, no need to rescan"); - return; - } - - dbglog("Rescanning packages"); - - packages_xml_ino = st.st_ino; - - ag::Dir data_dir; - data_dir.reset(opendir("/data/user_de")); - - if (!data_dir) { - dbglog("Failed to open /data/user_de"); - data_dir.reset(opendir("/data/user")); - if (!data_dir) { - warnlog("Failed to open /data/user, can't scan apps"); - return; - } - } - - Map app_id_to_pkg; - dirent *entry; - while ((entry = readdir(data_dir.get()))) { - // For each user - int dir_fd = openat(dirfd(data_dir.get()), entry->d_name, O_RDONLY); - if (ag::Dir dir{fdopendir(dir_fd)}) { - while ((entry = readdir(dir.get()))) { - // For each package - struct stat st{}; - fstatat(dir_fd, entry->d_name, &st, 0); - int uid = st.st_uid; - int app_id = APP_ID(uid); - if (app_id_to_pkg.count(app_id)) { - // This app ID has been handled - continue; - } - dbglog("uid: %d, app_id: %d, package: %s", uid, app_id, entry->d_name); - app_id_to_pkg.emplace(app_id, entry->d_name); - } - } else { - close(dir_fd); - } - } - - std::atomic_store(&g_app_id_to_pkg, std::make_shared(std::move(app_id_to_pkg))); -} - -static void companion_handler(int fd) { - rescan_packages(); - - auto app_id_to_pkg = std::atomic_load(&g_app_id_to_pkg); - - int uid = ag::read_int(fd); - - auto it = app_id_to_pkg->find(APP_ID(uid)); - if (it == app_id_to_pkg->end()) { - dbglog("uid: %d, app_id: %d, package not found", uid, APP_ID(uid)); - ag::write_string(fd, ""); - } else { - dbglog("uid: %d, app_id: %d, package: %s", uid, APP_ID(uid), it->second.c_str()); - ag::write_string(fd, it->second); - } -} - -REGISTER_ZYGISK_COMPANION(companion_handler) diff --git a/zygisk_module/jni/libcxx b/zygisk_module/jni/libcxx deleted file mode 160000 index 0aa67c3..0000000 --- a/zygisk_module/jni/libcxx +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0aa67c3ffea069bdb58fe3b5e7aad06934afb292 diff --git a/zygisk_module/jni/module.cpp b/zygisk_module/jni/module.cpp deleted file mode 100644 index 1467d67..0000000 --- a/zygisk_module/jni/module.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include -#include - -#include - -#include "common.h" -#include "zygisk.hpp" - -static const std::unordered_set PACKAGES_TO_UNMOUNT = { -#include "browsers.inc" -}; - -class MyModule : public zygisk::ModuleBase { -public: - void onLoad(zygisk::Api *api, JNIEnv *env) override { - this->api = api; - this->env = env; - } - - void preAppSpecialize(zygisk::AppSpecializeArgs *args) override { - int fd = api->connectCompanion(); - if (fd < 0) { - warnlog("Failed to connect companion"); - return; - } - ag::write_int(fd, args->uid); - std::string package = ag::read_string(fd); - if (PACKAGES_TO_UNMOUNT.count(package)) { - dbglog("Forcing denylist unmount routines for package: %s", package.c_str()); - api->setOption(zygisk::Option::FORCE_DENYLIST_UNMOUNT); - } - api->setOption(zygisk::Option::DLCLOSE_MODULE_LIBRARY); - close(fd); - } - -private: - zygisk::Api *api; - JNIEnv *env; -}; - -REGISTER_ZYGISK_MODULE(MyModule) diff --git a/zygisk_module/jni/zygisk.hpp b/zygisk_module/jni/zygisk.hpp deleted file mode 100644 index 82d6601..0000000 --- a/zygisk_module/jni/zygisk.hpp +++ /dev/null @@ -1,328 +0,0 @@ -// This is the public API for Zygisk modules. -// DO NOT MODIFY ANY CODE IN THIS HEADER. - -#pragma once - -#include - -#define ZYGISK_API_VERSION 3 - -/* - -Define a class and inherit zygisk::ModuleBase to implement the functionality of your module. -Use the macro REGISTER_ZYGISK_MODULE(className) to register that class to Zygisk. - -Please note that modules will only be loaded after zygote has forked the child process. -THIS MEANS ALL OF YOUR CODE RUNS IN THE APP/SYSTEM SERVER PROCESS, NOT THE ZYGOTE DAEMON! - -Example code: - -static jint (*orig_logger_entry_max)(JNIEnv *env); -static jint my_logger_entry_max(JNIEnv *env) { return orig_logger_entry_max(env); } - -static void example_handler(int socket) { ... } - -class ExampleModule : public zygisk::ModuleBase { -public: - void onLoad(zygisk::Api *api, JNIEnv *env) override { - this->api = api; - this->env = env; - } - void preAppSpecialize(zygisk::AppSpecializeArgs *args) override { - JNINativeMethod methods[] = { - { "logger_entry_max_payload_native", "()I", (void*) my_logger_entry_max }, - }; - api->hookJniNativeMethods(env, "android/util/Log", methods, 1); - *(void **) &orig_logger_entry_max = methods[0].fnPtr; - } -private: - zygisk::Api *api; - JNIEnv *env; -}; - -REGISTER_ZYGISK_MODULE(ExampleModule) - -REGISTER_ZYGISK_COMPANION(example_handler) - -*/ - -namespace zygisk { - -struct Api; -struct AppSpecializeArgs; -struct ServerSpecializeArgs; - -class ModuleBase { -public: - - // This function is called when the module is loaded into the target process. - // A Zygisk API handle will be sent as an argument; call utility functions or interface - // with Zygisk through this handle. - virtual void onLoad([[maybe_unused]] Api *api, [[maybe_unused]] JNIEnv *env) {} - - // This function is called before the app process is specialized. - // At this point, the process just got forked from zygote, but no app specific specialization - // is applied. This means that the process does not have any sandbox restrictions and - // still runs with the same privilege of zygote. - // - // All the arguments that will be sent and used for app specialization is passed as a single - // AppSpecializeArgs object. You can read and overwrite these arguments to change how the app - // process will be specialized. - // - // If you need to run some operations as superuser, you can call Api::connectCompanion() to - // get a socket to do IPC calls with a root companion process. - // See Api::connectCompanion() for more info. - virtual void preAppSpecialize([[maybe_unused]] AppSpecializeArgs *args) {} - - // This function is called after the app process is specialized. - // At this point, the process has all sandbox restrictions enabled for this application. - // This means that this function runs as the same privilege of the app's own code. - virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {} - - // This function is called before the system server process is specialized. - // See preAppSpecialize(args) for more info. - virtual void preServerSpecialize([[maybe_unused]] ServerSpecializeArgs *args) {} - - // This function is called after the system server process is specialized. - // At this point, the process runs with the privilege of system_server. - virtual void postServerSpecialize([[maybe_unused]] const ServerSpecializeArgs *args) {} -}; - -struct AppSpecializeArgs { - // Required arguments. These arguments are guaranteed to exist on all Android versions. - jint &uid; - jint &gid; - jintArray &gids; - jint &runtime_flags; - jobjectArray &rlimits; - jint &mount_external; - jstring &se_info; - jstring &nice_name; - jstring &instruction_set; - jstring &app_data_dir; - - // Optional arguments. Please check whether the pointer is null before de-referencing - jintArray *const fds_to_ignore; - jboolean *const is_child_zygote; - jboolean *const is_top_app; - jobjectArray *const pkg_data_info_list; - jobjectArray *const whitelisted_data_info_list; - jboolean *const mount_data_dirs; - jboolean *const mount_storage_dirs; - - AppSpecializeArgs() = delete; -}; - -struct ServerSpecializeArgs { - jint &uid; - jint &gid; - jintArray &gids; - jint &runtime_flags; - jlong &permitted_capabilities; - jlong &effective_capabilities; - - ServerSpecializeArgs() = delete; -}; - -namespace internal { -struct api_table; -template void entry_impl(api_table *, JNIEnv *); -} - -// These values are used in Api::setOption(Option) -enum Option : int { - // Force Magisk's denylist unmount routines to run on this process. - // - // Setting this option only makes sense in preAppSpecialize. - // The actual unmounting happens during app process specialization. - // - // Set this option to force all Magisk and modules' files to be unmounted from the - // mount namespace of the process, regardless of the denylist enforcement status. - FORCE_DENYLIST_UNMOUNT = 0, - - // When this option is set, your module's library will be dlclose-ed after post[XXX]Specialize. - // Be aware that after dlclose-ing your module, all of your code will be unmapped from memory. - // YOU MUST NOT ENABLE THIS OPTION AFTER HOOKING ANY FUNCTIONS IN THE PROCESS. - DLCLOSE_MODULE_LIBRARY = 1, -}; - -// Bit masks of the return value of Api::getFlags() -enum StateFlag : uint32_t { - // The user has granted root access to the current process - PROCESS_GRANTED_ROOT = (1u << 0), - - // The current process was added on the denylist - PROCESS_ON_DENYLIST = (1u << 1), -}; - -// All API functions will stop working after post[XXX]Specialize as Zygisk will be unloaded -// from the specialized process afterwards. -struct Api { - - // Connect to a root companion process and get a Unix domain socket for IPC. - // - // This API only works in the pre[XXX]Specialize functions due to SELinux restrictions. - // - // The pre[XXX]Specialize functions run with the same privilege of zygote. - // If you would like to do some operations with superuser permissions, register a handler - // function that would be called in the root process with REGISTER_ZYGISK_COMPANION(func). - // Another good use case for a companion process is that if you want to share some resources - // across multiple processes, hold the resources in the companion process and pass it over. - // - // The root companion process is ABI aware; that is, when calling this function from a 32-bit - // process, you will be connected to a 32-bit companion process, and vice versa for 64-bit. - // - // Returns a file descriptor to a socket that is connected to the socket passed to your - // module's companion request handler. Returns -1 if the connection attempt failed. - int connectCompanion(); - - // Get the file descriptor of the root folder of the current module. - // - // This API only works in the pre[XXX]Specialize functions. - // Accessing the directory returned is only possible in the pre[XXX]Specialize functions - // or in the root companion process (assuming that you sent the fd over the socket). - // Both restrictions are due to SELinux and UID. - // - // Returns -1 if errors occurred. - int getModuleDir(); - - // Set various options for your module. - // Please note that this function accepts one single option at a time. - // Check zygisk::Option for the full list of options available. - void setOption(Option opt); - - // Get information about the current process. - // Returns bitwise-or'd zygisk::StateFlag values. - uint32_t getFlags(); - - // Hook JNI native methods for a class - // - // Lookup all registered JNI native methods and replace it with your own functions. - // The original function pointer will be saved in each JNINativeMethod's fnPtr. - // If no matching class, method name, or signature is found, that specific JNINativeMethod.fnPtr - // will be set to nullptr. - void hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods); - - // For ELFs loaded in memory matching `regex`, replace function `symbol` with `newFunc`. - // If `oldFunc` is not nullptr, the original function pointer will be saved to `oldFunc`. - void pltHookRegister(const char *regex, const char *symbol, void *newFunc, void **oldFunc); - - // For ELFs loaded in memory matching `regex`, exclude hooks registered for `symbol`. - // If `symbol` is nullptr, then all symbols will be excluded. - void pltHookExclude(const char *regex, const char *symbol); - - // Commit all the hooks that was previously registered. - // Returns false if an error occurred. - bool pltHookCommit(); - -private: - internal::api_table *impl; - template friend void internal::entry_impl(internal::api_table *, JNIEnv *); -}; - -// Register a class as a Zygisk module - -#define REGISTER_ZYGISK_MODULE(clazz) \ -void zygisk_module_entry(zygisk::internal::api_table *table, JNIEnv *env) { \ - zygisk::internal::entry_impl(table, env); \ -} - -// Register a root companion request handler function for your module -// -// The function runs in a superuser daemon process and handles a root companion request from -// your module running in a target process. The function has to accept an integer value, -// which is a socket that is connected to the target process. -// See Api::connectCompanion() for more info. -// -// NOTE: the function can run concurrently on multiple threads. -// Be aware of race conditions if you have a globally shared resource. - -#define REGISTER_ZYGISK_COMPANION(func) \ -void zygisk_companion_entry(int client) { func(client); } - -/************************************************************************************ - * All the code after this point is internal code used to interface with Zygisk - * and guarantee ABI stability. You do not have to understand what it is doing. - ************************************************************************************/ - -namespace internal { - -struct module_abi { - long api_version; - ModuleBase *_this; - - void (*preAppSpecialize)(ModuleBase *, AppSpecializeArgs *); - void (*postAppSpecialize)(ModuleBase *, const AppSpecializeArgs *); - void (*preServerSpecialize)(ModuleBase *, ServerSpecializeArgs *); - void (*postServerSpecialize)(ModuleBase *, const ServerSpecializeArgs *); - - module_abi(ModuleBase *module) : api_version(ZYGISK_API_VERSION), _this(module) { - preAppSpecialize = [](auto self, auto args) { self->preAppSpecialize(args); }; - postAppSpecialize = [](auto self, auto args) { self->postAppSpecialize(args); }; - preServerSpecialize = [](auto self, auto args) { self->preServerSpecialize(args); }; - postServerSpecialize = [](auto self, auto args) { self->postServerSpecialize(args); }; - } -}; - -struct api_table { - // These first 2 entries are permanent, shall never change - void *_this; - bool (*registerModule)(api_table *, module_abi *); - - // Utility functions - void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int); - void (*pltHookRegister)(const char *, const char *, void *, void **); - void (*pltHookExclude)(const char *, const char *); - bool (*pltHookCommit)(); - - // Zygisk functions - int (*connectCompanion)(void * /* _this */); - void (*setOption)(void * /* _this */, Option); - int (*getModuleDir)(void * /* _this */); - uint32_t (*getFlags)(void * /* _this */); -}; - -template -void entry_impl(api_table *table, JNIEnv *env) { - ModuleBase *module = new T(); - if (!table->registerModule(table, new module_abi(module))) - return; - auto api = new Api(); - api->impl = table; - module->onLoad(api, env); -} - -} // namespace internal - -inline int Api::connectCompanion() { - return impl->connectCompanion ? impl->connectCompanion(impl->_this) : -1; -} -inline int Api::getModuleDir() { - return impl->getModuleDir ? impl->getModuleDir(impl->_this) : -1; -} -inline void Api::setOption(Option opt) { - if (impl->setOption) impl->setOption(impl->_this, opt); -} -inline uint32_t Api::getFlags() { - return impl->getFlags ? impl->getFlags(impl->_this) : 0; -} -inline void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) { - if (impl->hookJniNativeMethods) impl->hookJniNativeMethods(env, className, methods, numMethods); -} -inline void Api::pltHookRegister(const char *regex, const char *symbol, void *newFunc, void **oldFunc) { - if (impl->pltHookRegister) impl->pltHookRegister(regex, symbol, newFunc, oldFunc); -} -inline void Api::pltHookExclude(const char *regex, const char *symbol) { - if (impl->pltHookExclude) impl->pltHookExclude(regex, symbol); -} -inline bool Api::pltHookCommit() { - return impl->pltHookCommit != nullptr && impl->pltHookCommit(); -} - -} // namespace zygisk - -[[gnu::visibility("default")]] [[gnu::used]] -extern "C" void zygisk_module_entry(zygisk::internal::api_table *, JNIEnv *); - -[[gnu::visibility("default")]] [[gnu::used]] -extern "C" void zygisk_companion_entry(int); diff --git a/zygisk_module/src/main/AndroidManifest.xml b/zygisk_module/src/main/AndroidManifest.xml deleted file mode 100644 index 3d76432..0000000 --- a/zygisk_module/src/main/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - From c2e0fcd629c01db195e546d0fe7850097c55d840 Mon Sep 17 00:00:00 2001 From: Sergey Fionov Date: Mon, 10 Jul 2023 15:23:59 +0300 Subject: [PATCH 02/24] Update module.prop --- module/module.prop | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/module.prop b/module/module.prop index 985bfe4..08673e4 100644 --- a/module/module.prop +++ b/module/module.prop @@ -1,6 +1,6 @@ id=adguardcert name=AdGuard Certificate -version=v2.0 +version=v2.0-beta versionCode=30 author=AdGuard description=Moves AdGuard's root CA certificate from the user certificate store to the system certificate store. From 20c150e9e7ebfc2a265faebfdb1548e95bbf51d3 Mon Sep 17 00:00:00 2001 From: Max Grupper Date: Tue, 11 Jul 2023 16:53:38 +0300 Subject: [PATCH 03/24] copy cert to system only if both adguard certs exist in user storage --- module/post-fs-data.sh | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/module/post-fs-data.sh b/module/post-fs-data.sh index dcb8d56..2b5771c 100644 --- a/module/post-fs-data.sh +++ b/module/post-fs-data.sh @@ -16,8 +16,15 @@ MODDIR=${0%/*} # They get there if a certificate is "disabled" in the security settings. # Apps will reject certs that are in the `cacerts-removed`. AG_CERT_HASH=0f4ed297 +AG_INTERMEDIATE_CERT_HASH=47ec1af8 AG_CERT_FILE=$(ls /data/misc/user/*/cacerts-added/${AG_CERT_HASH}.* | sort | tail -n1) -mv -f ${AG_CERT_FILE} ${MODDIR}/system/etc/security/cacerts/${AG_CERT_HASH}.0 +AG_INTERMEDIATE_CERT_FILE=$(ls /data/misc/user/*/cacerts-added/${AG_INTERMEDIATE_CERT_HASH}.* | sort | tail -n1) + +if [ ! -e "${AG_INTERMEDIATE_CERT_FILE}" ]; then + exit 1 +fi + +cp -f ${AG_CERT_FILE} ${MODDIR}/system/etc/security/cacerts/${AG_CERT_HASH}.0 rm -f /data/misc/user/*/cacerts-removed/${AG_CERT_HASH}.* chown -R 0:0 ${MODDIR}/system/etc/security/cacerts From 0de69c14234c1d9e158d89adbe4466b219359e63 Mon Sep 17 00:00:00 2001 From: Max Grupper Date: Tue, 11 Jul 2023 18:08:57 +0300 Subject: [PATCH 04/24] fix --- module/post-fs-data.sh | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/module/post-fs-data.sh b/module/post-fs-data.sh index 2b5771c..5105c12 100644 --- a/module/post-fs-data.sh +++ b/module/post-fs-data.sh @@ -3,16 +3,19 @@ MODDIR=${0%/*} # Android hashes the subject to get the filename, field order is significant. # (`openssl x509 -in ... -noout -hash`) -# AdGuard's certificate is "/C=EN/O=AdGuard/CN=AdGuard Personal CA". +# AdGuard's primary certificate is "/C=EN/O=AdGuard/CN=AdGuard Personal CA". +# AdGuard's intermediate certificate is "/C=EN/O=AdGuard/CN=AdGuard Personal Intermediate". # The filename is then . where is an integer to disambiguate # different certs with the same hash (e.g. when the same cert is installed repeatedly). # # Due to https://github.com/AdguardTeam/AdguardForAndroid/issues/2108 -# 1. Take the last cert with our hash from the user store. -# Assuming the last installed AdGuard's cert is the correct one. -# 2. Move it to the system store under the name ".0". -# Apparently, some apps may ignore other certs. -# 3. Remove all certs with our hash from the `cacerts-removed` directory. +# 1. Retrieve the most recent primary and intermediate certificates with our hash from the user store. +# It is assumed that the last installed AdGuard's certs are the correct ones. +# 2. Check the existence of AdGuard's intermediate certificate. If the certificate is not present, +# the steps 3 and 4 will be skipped. +# 3. Copy the primary AdGuard certificate to the system store under the name ".0". +# Note that some apps may ignore other certs. +# 4. Remove all certs with our hash from the `cacerts-removed` directory. # They get there if a certificate is "disabled" in the security settings. # Apps will reject certs that are in the `cacerts-removed`. AG_CERT_HASH=0f4ed297 @@ -20,13 +23,11 @@ AG_INTERMEDIATE_CERT_HASH=47ec1af8 AG_CERT_FILE=$(ls /data/misc/user/*/cacerts-added/${AG_CERT_HASH}.* | sort | tail -n1) AG_INTERMEDIATE_CERT_FILE=$(ls /data/misc/user/*/cacerts-added/${AG_INTERMEDIATE_CERT_HASH}.* | sort | tail -n1) -if [ ! -e "${AG_INTERMEDIATE_CERT_FILE}" ]; then - exit 1 +if [ -e "${AG_INTERMEDIATE_CERT_FILE}" ]; then + cp -f ${AG_CERT_FILE} ${MODDIR}/system/etc/security/cacerts/${AG_CERT_HASH}.0 + rm -f /data/misc/user/*/cacerts-removed/${AG_CERT_HASH}.* fi -cp -f ${AG_CERT_FILE} ${MODDIR}/system/etc/security/cacerts/${AG_CERT_HASH}.0 -rm -f /data/misc/user/*/cacerts-removed/${AG_CERT_HASH}.* - chown -R 0:0 ${MODDIR}/system/etc/security/cacerts [ "$(getenforce)" = "Enforcing" ] || exit 0 From 635e0d409e6177832a7c2f7b47c04431783bd24f Mon Sep 17 00:00:00 2001 From: Max Grupper Date: Tue, 11 Jul 2023 18:32:53 +0300 Subject: [PATCH 05/24] check both files --- module/post-fs-data.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/post-fs-data.sh b/module/post-fs-data.sh index 5105c12..39ec524 100644 --- a/module/post-fs-data.sh +++ b/module/post-fs-data.sh @@ -23,7 +23,7 @@ AG_INTERMEDIATE_CERT_HASH=47ec1af8 AG_CERT_FILE=$(ls /data/misc/user/*/cacerts-added/${AG_CERT_HASH}.* | sort | tail -n1) AG_INTERMEDIATE_CERT_FILE=$(ls /data/misc/user/*/cacerts-added/${AG_INTERMEDIATE_CERT_HASH}.* | sort | tail -n1) -if [ -e "${AG_INTERMEDIATE_CERT_FILE}" ]; then +if [ -e "${AG_CERT_FILE}" ] && [ -e "${AG_INTERMEDIATE_CERT_FILE}" ]; then cp -f ${AG_CERT_FILE} ${MODDIR}/system/etc/security/cacerts/${AG_CERT_HASH}.0 rm -f /data/misc/user/*/cacerts-removed/${AG_CERT_HASH}.* fi From f7d33495beacc1b77ba9d5a0fd2395bae50efa75 Mon Sep 17 00:00:00 2001 From: Sergey Fionov Date: Tue, 11 Jul 2023 18:59:20 +0300 Subject: [PATCH 06/24] Increment version --- module/module.prop | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module/module.prop b/module/module.prop index 08673e4..c857fff 100644 --- a/module/module.prop +++ b/module/module.prop @@ -1,6 +1,6 @@ id=adguardcert name=AdGuard Certificate -version=v2.0-beta -versionCode=30 +version=v2.0-beta2 +versionCode=31 author=AdGuard description=Moves AdGuard's root CA certificate from the user certificate store to the system certificate store. From 2c94de1063b2bbb93b0a481bb3d2ff3641bdb278 Mon Sep 17 00:00:00 2001 From: Max Grupper Date: Mon, 17 Jul 2023 14:37:53 +0300 Subject: [PATCH 07/24] fix cert sort --- module/module.prop | 4 ++-- module/post-fs-data.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/module/module.prop b/module/module.prop index c857fff..33eb5a3 100644 --- a/module/module.prop +++ b/module/module.prop @@ -1,6 +1,6 @@ id=adguardcert name=AdGuard Certificate -version=v2.0-beta2 -versionCode=31 +version=v2.0-beta3 +versionCode=32 author=AdGuard description=Moves AdGuard's root CA certificate from the user certificate store to the system certificate store. diff --git a/module/post-fs-data.sh b/module/post-fs-data.sh index 39ec524..4971267 100644 --- a/module/post-fs-data.sh +++ b/module/post-fs-data.sh @@ -20,8 +20,8 @@ MODDIR=${0%/*} # Apps will reject certs that are in the `cacerts-removed`. AG_CERT_HASH=0f4ed297 AG_INTERMEDIATE_CERT_HASH=47ec1af8 -AG_CERT_FILE=$(ls /data/misc/user/*/cacerts-added/${AG_CERT_HASH}.* | sort | tail -n1) -AG_INTERMEDIATE_CERT_FILE=$(ls /data/misc/user/*/cacerts-added/${AG_INTERMEDIATE_CERT_HASH}.* | sort | tail -n1) +AG_CERT_FILE=$(ls /data/misc/user/*/cacerts-added/${AG_CERT_HASH}.* | (IFS=.; while read -r left right; do echo $right $left.$right; done) | sort -nr | (read left right; echo $right)) +AG_INTERMEDIATE_CERT_FILE=$(ls /data/misc/user/*/cacerts-added/${AG_INTERMEDIATE_CERT_HASH}.* | (IFS=.; while read -r left right; do echo $right $left.$right; done) | sort -nr | (read left right; echo $right)) if [ -e "${AG_CERT_FILE}" ] && [ -e "${AG_INTERMEDIATE_CERT_FILE}" ]; then cp -f ${AG_CERT_FILE} ${MODDIR}/system/etc/security/cacerts/${AG_CERT_HASH}.0 From 517eef3ff0c4beb412bba425c511b54ac27892ed Mon Sep 17 00:00:00 2001 From: Sergey Fionov Date: Mon, 17 Jul 2023 19:38:22 +0300 Subject: [PATCH 08/24] Update post-fs-data.sh --- module/post-fs-data.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module/post-fs-data.sh b/module/post-fs-data.sh index 4971267..2e964c9 100644 --- a/module/post-fs-data.sh +++ b/module/post-fs-data.sh @@ -20,8 +20,8 @@ MODDIR=${0%/*} # Apps will reject certs that are in the `cacerts-removed`. AG_CERT_HASH=0f4ed297 AG_INTERMEDIATE_CERT_HASH=47ec1af8 -AG_CERT_FILE=$(ls /data/misc/user/*/cacerts-added/${AG_CERT_HASH}.* | (IFS=.; while read -r left right; do echo $right $left.$right; done) | sort -nr | (read left right; echo $right)) -AG_INTERMEDIATE_CERT_FILE=$(ls /data/misc/user/*/cacerts-added/${AG_INTERMEDIATE_CERT_HASH}.* | (IFS=.; while read -r left right; do echo $right $left.$right; done) | sort -nr | (read left right; echo $right)) +AG_CERT_FILE=$(ls /data/misc/user/*/cacerts-added/${AG_CERT_HASH}.* | (IFS=.; while read -r left right; do echo $right $left.$right; done) | sort -nr | (read -r left right; echo $right)) +AG_INTERMEDIATE_CERT_FILE=$(ls /data/misc/user/*/cacerts-added/${AG_INTERMEDIATE_CERT_HASH}.* | (IFS=.; while read -r left right; do echo $right $left.$right; done) | sort -nr | (read -r left right; echo $right)) if [ -e "${AG_CERT_FILE}" ] && [ -e "${AG_INTERMEDIATE_CERT_FILE}" ]; then cp -f ${AG_CERT_FILE} ${MODDIR}/system/etc/security/cacerts/${AG_CERT_HASH}.0 From ba8aba4a458a6c117e582ee00d11f0be14533e27 Mon Sep 17 00:00:00 2001 From: Sergey Fionov Date: Mon, 17 Jul 2023 19:42:59 +0300 Subject: [PATCH 09/24] Remove "setup ndk" step from build We do not have native code anymore --- .github/workflows/workflow.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 7ffadd2..a2bbef4 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -15,13 +15,6 @@ jobs: steps: - uses: actions/checkout@v3 - - name: setup ndk - uses: nttld/setup-ndk@v1 - id: setup-ndk - with: - ndk-version: r23 - add-to-path: true - - name: build dist run: | git submodule init && git submodule update From 6e3b1d61809357186a127389ef167a24d74b7967 Mon Sep 17 00:00:00 2001 From: Max Grupper Date: Wed, 19 Jul 2023 15:29:58 +0300 Subject: [PATCH 10/24] rollback --- module/module.prop | 4 ++-- module/post-fs-data.sh | 17 ++++++----------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/module/module.prop b/module/module.prop index 33eb5a3..85ccaf3 100644 --- a/module/module.prop +++ b/module/module.prop @@ -1,6 +1,6 @@ id=adguardcert name=AdGuard Certificate -version=v2.0-beta3 -versionCode=32 +version=v2.0-beta4 +versionCode=33 author=AdGuard description=Moves AdGuard's root CA certificate from the user certificate store to the system certificate store. diff --git a/module/post-fs-data.sh b/module/post-fs-data.sh index 2e964c9..5229c6c 100644 --- a/module/post-fs-data.sh +++ b/module/post-fs-data.sh @@ -3,27 +3,22 @@ MODDIR=${0%/*} # Android hashes the subject to get the filename, field order is significant. # (`openssl x509 -in ... -noout -hash`) -# AdGuard's primary certificate is "/C=EN/O=AdGuard/CN=AdGuard Personal CA". -# AdGuard's intermediate certificate is "/C=EN/O=AdGuard/CN=AdGuard Personal Intermediate". +# AdGuard's certificate is "/C=EN/O=AdGuard/CN=AdGuard Personal CA". # The filename is then . where is an integer to disambiguate # different certs with the same hash (e.g. when the same cert is installed repeatedly). # # Due to https://github.com/AdguardTeam/AdguardForAndroid/issues/2108 -# 1. Retrieve the most recent primary and intermediate certificates with our hash from the user store. -# It is assumed that the last installed AdGuard's certs are the correct ones. -# 2. Check the existence of AdGuard's intermediate certificate. If the certificate is not present, -# the steps 3 and 4 will be skipped. -# 3. Copy the primary AdGuard certificate to the system store under the name ".0". +# 1. Retrieve the most recent certificate with our hash from the user store. +# It is assumed that the last installed AdGuard's cert is the correct one. +# 2. Copy the AdGuard certificate to the system store under the name ".0". # Note that some apps may ignore other certs. -# 4. Remove all certs with our hash from the `cacerts-removed` directory. +# 3. Remove all certs with our hash from the `cacerts-removed` directory. # They get there if a certificate is "disabled" in the security settings. # Apps will reject certs that are in the `cacerts-removed`. AG_CERT_HASH=0f4ed297 -AG_INTERMEDIATE_CERT_HASH=47ec1af8 AG_CERT_FILE=$(ls /data/misc/user/*/cacerts-added/${AG_CERT_HASH}.* | (IFS=.; while read -r left right; do echo $right $left.$right; done) | sort -nr | (read -r left right; echo $right)) -AG_INTERMEDIATE_CERT_FILE=$(ls /data/misc/user/*/cacerts-added/${AG_INTERMEDIATE_CERT_HASH}.* | (IFS=.; while read -r left right; do echo $right $left.$right; done) | sort -nr | (read -r left right; echo $right)) -if [ -e "${AG_CERT_FILE}" ] && [ -e "${AG_INTERMEDIATE_CERT_FILE}" ]; then +if [ -e "${AG_CERT_FILE}" ]; then cp -f ${AG_CERT_FILE} ${MODDIR}/system/etc/security/cacerts/${AG_CERT_HASH}.0 rm -f /data/misc/user/*/cacerts-removed/${AG_CERT_HASH}.* fi From ee8118869648a4aeffe53006c426b3fadee7e401 Mon Sep 17 00:00:00 2001 From: Andrey Meshkov Date: Sat, 16 Sep 2023 18:39:28 +0300 Subject: [PATCH 11/24] Added info about AG v4.2 --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 5f9e8b9..0630a8b 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,10 @@ Based on [Move Certificates](https://github.com/Magisk-Modules-Repo/movecert). This Magisk module supplements [AdGuard for Android][agandroid] and allows installing AdGuard's CA certificate to the System store on rooted devices. +**Attention** +If you're using AdGuard for Android v4.2 Nightly 10 or newer, use the beta version of +this magisk module: https://github.com/AdguardTeam/adguardcert/releases/tag/v2.0-beta4. + ## Explanation Chrome (and subsequently many other Chromium-based browsers) From ee7607066b60db31d3ee78a770e017e633f5c4c3 Mon Sep 17 00:00:00 2001 From: Sergey Fionov Date: Sat, 23 Sep 2023 17:49:40 +0300 Subject: [PATCH 12/24] Android 14 support --- module/module.prop | 4 +-- module/post-fs-data.sh | 55 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/module/module.prop b/module/module.prop index 85ccaf3..0db5eac 100644 --- a/module/module.prop +++ b/module/module.prop @@ -1,6 +1,6 @@ id=adguardcert name=AdGuard Certificate -version=v2.0-beta4 -versionCode=33 +version=v2.0-beta5 +versionCode=34 author=AdGuard description=Moves AdGuard's root CA certificate from the user certificate store to the system certificate store. diff --git a/module/post-fs-data.sh b/module/post-fs-data.sh index 5229c6c..e6864aa 100644 --- a/module/post-fs-data.sh +++ b/module/post-fs-data.sh @@ -1,6 +1,25 @@ #!/system/bin/sh + +exec > /data/local/tmp/adguardcert.log +exec 2>&1 + +set -x + MODDIR=${0%/*} +set_context() { + [ "$(getenforce)" = "Enforcing" ] || return 0 + + default_selinux_context=u:object_r:system_file:s0 + selinux_context=$(ls -Zd $1 | awk '{print $1}') + + if [ -n "$selinux_context" ] && [ "$selinux_context" != "?" ]; then + chcon -R $selinux_context $2 + else + chcon -R $default_selinux_context $2 + fi +} + # Android hashes the subject to get the filename, field order is significant. # (`openssl x509 -in ... -noout -hash`) # AdGuard's certificate is "/C=EN/O=AdGuard/CN=AdGuard Personal CA". @@ -18,20 +37,36 @@ MODDIR=${0%/*} AG_CERT_HASH=0f4ed297 AG_CERT_FILE=$(ls /data/misc/user/*/cacerts-added/${AG_CERT_HASH}.* | (IFS=.; while read -r left right; do echo $right $left.$right; done) | sort -nr | (read -r left right; echo $right)) -if [ -e "${AG_CERT_FILE}" ]; then - cp -f ${AG_CERT_FILE} ${MODDIR}/system/etc/security/cacerts/${AG_CERT_HASH}.0 - rm -f /data/misc/user/*/cacerts-removed/${AG_CERT_HASH}.* +if ! [ -e "${AG_CERT_FILE}" ]; then + exit 0 fi +rm -f /data/misc/user/*/cacerts-removed/${AG_CERT_HASH}.* + +cp -f ${AG_CERT_FILE} ${MODDIR}/system/etc/security/cacerts/${AG_CERT_HASH}.0 chown -R 0:0 ${MODDIR}/system/etc/security/cacerts +set_context /system/etc/security/cacerts $MODDIR/system/etc/security/cacerts -[ "$(getenforce)" = "Enforcing" ] || exit 0 +# Android 14 support +# Since Magisk ignore /apex for module file injections, use non-Magisk way +if [ -e /apex/com.android.conscrypt/cacerts ]; then + # Clone directory into tmpfs + mkdir -p /data/local/tmp-ca-copy + mount -t tmpfs tmpfs /data/local/tmp/tmp-ca-copy + cp -f /apex/com.android.conscrypt/cacerts/* /data/local/tmp/tmp-ca-copy -default_selinux_context=u:object_r:system_file:s0 -selinux_context=$(ls -Zd /system/etc/security/cacerts | awk '{print $1}') + # Do the same as in Magisk module + cp -f ${AG_CERT_FILE} /data/local/tmp/tmp-ca-copy + chown -R 0:0 /data/local/tmp/tmp-ca-copy + set_context /apex/com.android.conscrypt/cacerts /data/local/tmp/tmp-ca-copy -if [ -n "$selinux_context" ] && [ "$selinux_context" != "?" ]; then - chcon -R $selinux_context $MODDIR/system/etc/security/cacerts -else - chcon -R $default_selinux_context $MODDIR/system/etc/security/cacerts + # Mount directory inside APEX if it is valid, and remove temporary one. + CERTS_NUM="$(ls -1 /apex/com.android.conscrypt/cacerts | wc -l)" + if [ "$CERTS_NUM" -gt 10 ]; then + mount --bind /data/local/tmp/tmp-ca-copy /apex/com.android.conscrypt/cacerts + else + echo "Cancelling replacing CA storage due to safety" + fi + umount /data/local/tmp/tmp-ca-copy + rmdir /data/local/tmp/tmp-ca-copy fi From 5fc025c7af6dd560a84516c8f4cb749f42a7eecb Mon Sep 17 00:00:00 2001 From: Sergey Fionov Date: Sat, 23 Sep 2023 18:37:06 +0300 Subject: [PATCH 13/24] Fix safety check --- module/post-fs-data.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module/post-fs-data.sh b/module/post-fs-data.sh index e6864aa..b23af19 100644 --- a/module/post-fs-data.sh +++ b/module/post-fs-data.sh @@ -61,7 +61,7 @@ if [ -e /apex/com.android.conscrypt/cacerts ]; then set_context /apex/com.android.conscrypt/cacerts /data/local/tmp/tmp-ca-copy # Mount directory inside APEX if it is valid, and remove temporary one. - CERTS_NUM="$(ls -1 /apex/com.android.conscrypt/cacerts | wc -l)" + CERTS_NUM="$(ls -1 /data/local/tmp/tmp-ca-copy | wc -l)" if [ "$CERTS_NUM" -gt 10 ]; then mount --bind /data/local/tmp/tmp-ca-copy /apex/com.android.conscrypt/cacerts else From bdfd0dd9cbc387ee302a8070a0d062d9a4048524 Mon Sep 17 00:00:00 2001 From: Sergey Fionov Date: Sun, 24 Sep 2023 21:48:07 +0300 Subject: [PATCH 14/24] Fix path in Cloning directory step. Thanks to @aussiebro from github --- module/post-fs-data.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/module/post-fs-data.sh b/module/post-fs-data.sh index b23af19..09fb35e 100644 --- a/module/post-fs-data.sh +++ b/module/post-fs-data.sh @@ -51,9 +51,10 @@ set_context /system/etc/security/cacerts $MODDIR/system/etc/security/cacerts # Since Magisk ignore /apex for module file injections, use non-Magisk way if [ -e /apex/com.android.conscrypt/cacerts ]; then # Clone directory into tmpfs - mkdir -p /data/local/tmp-ca-copy + rm -f /data/local/tmp/tmp-ca-copy + mkdir -p /data/local/tmp/tmp-ca-copy mount -t tmpfs tmpfs /data/local/tmp/tmp-ca-copy - cp -f /apex/com.android.conscrypt/cacerts/* /data/local/tmp/tmp-ca-copy + cp -f /apex/com.android.conscrypt/cacerts/* /data/local/tmp/tmp-ca-copy/ # Do the same as in Magisk module cp -f ${AG_CERT_FILE} /data/local/tmp/tmp-ca-copy From 88da5254f7d40405ce6451953110f8a7476e7d29 Mon Sep 17 00:00:00 2001 From: Sergey Fionov Date: Mon, 25 Sep 2023 08:19:58 +0300 Subject: [PATCH 15/24] Small fixes --- module/post-fs-data.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module/post-fs-data.sh b/module/post-fs-data.sh index 09fb35e..3d267d8 100644 --- a/module/post-fs-data.sh +++ b/module/post-fs-data.sh @@ -45,11 +45,11 @@ rm -f /data/misc/user/*/cacerts-removed/${AG_CERT_HASH}.* cp -f ${AG_CERT_FILE} ${MODDIR}/system/etc/security/cacerts/${AG_CERT_HASH}.0 chown -R 0:0 ${MODDIR}/system/etc/security/cacerts -set_context /system/etc/security/cacerts $MODDIR/system/etc/security/cacerts +set_context /system/etc/security/cacerts ${MODDIR}/system/etc/security/cacerts # Android 14 support # Since Magisk ignore /apex for module file injections, use non-Magisk way -if [ -e /apex/com.android.conscrypt/cacerts ]; then +if [ -d /apex/com.android.conscrypt/cacerts ]; then # Clone directory into tmpfs rm -f /data/local/tmp/tmp-ca-copy mkdir -p /data/local/tmp/tmp-ca-copy From 763f325883c1d38a3705dcdc94e11fcc745eacfb Mon Sep 17 00:00:00 2001 From: Sergey Fionov Date: Mon, 25 Sep 2023 08:20:11 +0300 Subject: [PATCH 16/24] tmp-ca-copy -> adg-ca-copy --- module/post-fs-data.sh | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/module/post-fs-data.sh b/module/post-fs-data.sh index 3d267d8..3492e0f 100644 --- a/module/post-fs-data.sh +++ b/module/post-fs-data.sh @@ -51,23 +51,23 @@ set_context /system/etc/security/cacerts ${MODDIR}/system/etc/security/cacerts # Since Magisk ignore /apex for module file injections, use non-Magisk way if [ -d /apex/com.android.conscrypt/cacerts ]; then # Clone directory into tmpfs - rm -f /data/local/tmp/tmp-ca-copy - mkdir -p /data/local/tmp/tmp-ca-copy - mount -t tmpfs tmpfs /data/local/tmp/tmp-ca-copy - cp -f /apex/com.android.conscrypt/cacerts/* /data/local/tmp/tmp-ca-copy/ + rm -f /data/local/tmp/adg-ca-copy + mkdir -p /data/local/tmp/adg-ca-copy + mount -t tmpfs tmpfs /data/local/tmp/adg-ca-copy + cp -f /apex/com.android.conscrypt/cacerts/* /data/local/tmp/adg-ca-copy/ # Do the same as in Magisk module - cp -f ${AG_CERT_FILE} /data/local/tmp/tmp-ca-copy - chown -R 0:0 /data/local/tmp/tmp-ca-copy - set_context /apex/com.android.conscrypt/cacerts /data/local/tmp/tmp-ca-copy + cp -f ${AG_CERT_FILE} /data/local/tmp/adg-ca-copy + chown -R 0:0 /data/local/tmp/adg-ca-copy + set_context /apex/com.android.conscrypt/cacerts /data/local/tmp/adg-ca-copy # Mount directory inside APEX if it is valid, and remove temporary one. - CERTS_NUM="$(ls -1 /data/local/tmp/tmp-ca-copy | wc -l)" + CERTS_NUM="$(ls -1 /data/local/tmp/adg-ca-copy | wc -l)" if [ "$CERTS_NUM" -gt 10 ]; then - mount --bind /data/local/tmp/tmp-ca-copy /apex/com.android.conscrypt/cacerts + mount --bind /data/local/tmp/adg-ca-copy /apex/com.android.conscrypt/cacerts else echo "Cancelling replacing CA storage due to safety" fi - umount /data/local/tmp/tmp-ca-copy - rmdir /data/local/tmp/tmp-ca-copy + umount /data/local/tmp/adg-ca-copy + rmdir /data/local/tmp/adg-ca-copy fi From e54986f505f6cb4e745f2888ee44aa6b0ee35019 Mon Sep 17 00:00:00 2001 From: Sergey Fionov Date: Mon, 25 Sep 2023 10:16:46 +0300 Subject: [PATCH 17/24] Build artifacts in PRs --- .github/workflows/workflow.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index a2bbef4..3303243 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -28,3 +28,10 @@ jobs: artifacts: "adguardcert-*.zip" token: ${{ secrets.GITHUB_TOKEN }} generateReleaseNotes: true + + - name: upload artifact + uses: actions/upload-artifact@v3 + if: github.event_name == 'pull_request' + with: + name: module build + path: "adguardcert-*.zip" From 0d473c80ce243b52faf949b2b88d4207cd97fad2 Mon Sep 17 00:00:00 2001 From: Sergey Fionov Date: Mon, 25 Sep 2023 10:19:19 +0300 Subject: [PATCH 18/24] Update workflow.yml --- .github/workflows/workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 3303243..a4dffda 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -33,5 +33,5 @@ jobs: uses: actions/upload-artifact@v3 if: github.event_name == 'pull_request' with: - name: module build + name: module zip path: "adguardcert-*.zip" From 6c3f58dcb063962f2128d697b4c49eda1f5701fe Mon Sep 17 00:00:00 2001 From: Sergey Fionov Date: Mon, 25 Sep 2023 10:44:15 +0300 Subject: [PATCH 19/24] Update workflow.yml --- .github/workflows/workflow.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index a4dffda..9c35302 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -29,9 +29,17 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} generateReleaseNotes: true + - name: write PR url + if: github.event_name == 'pull_request' + run: | + echo "{{ github.event.pull_request.html_url }}" > pull_request_url.txt + - name: upload artifact uses: actions/upload-artifact@v3 if: github.event_name == 'pull_request' with: - name: module zip - path: "adguardcert-*.zip" + name: adguardcert module build + path: | + "adguardcert-*.zip" + README.md + pull_request_url.txt From 283b47fcf972d893552119747d1322c12de75487 Mon Sep 17 00:00:00 2001 From: Sergey Fionov Date: Mon, 25 Sep 2023 10:46:39 +0300 Subject: [PATCH 20/24] Fix --- .github/workflows/workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 9c35302..eb7fab9 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -32,7 +32,7 @@ jobs: - name: write PR url if: github.event_name == 'pull_request' run: | - echo "{{ github.event.pull_request.html_url }}" > pull_request_url.txt + echo "${{ github.event.pull_request.html_url }}" > pull_request_url.txt - name: upload artifact uses: actions/upload-artifact@v3 @@ -40,6 +40,6 @@ jobs: with: name: adguardcert module build path: | - "adguardcert-*.zip" + adguardcert-*.zip README.md pull_request_url.txt From 5115883a1c2258b890b18cb21cff17262147f363 Mon Sep 17 00:00:00 2001 From: Sergey Fionov Date: Mon, 2 Oct 2023 15:11:05 +0300 Subject: [PATCH 21/24] Update README.md Changed beta version of module --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0630a8b..4038516 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ AdGuard's CA certificate to the System store on rooted devices. **Attention** If you're using AdGuard for Android v4.2 Nightly 10 or newer, use the beta version of -this magisk module: https://github.com/AdguardTeam/adguardcert/releases/tag/v2.0-beta4. +this magisk module: https://github.com/AdguardTeam/adguardcert/releases/tag/v2.0-beta5. ## Explanation From 6fd39fc26ce652e8aa1db99becf140aadb3805ad Mon Sep 17 00:00:00 2001 From: Sergey Fionov Date: Mon, 23 Oct 2023 15:55:11 +0300 Subject: [PATCH 22/24] adguardcert v2.0 release --- module/module.prop | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module/module.prop b/module/module.prop index 0db5eac..82e5da6 100644 --- a/module/module.prop +++ b/module/module.prop @@ -1,6 +1,6 @@ id=adguardcert name=AdGuard Certificate -version=v2.0-beta5 -versionCode=34 +version=v2.0 +versionCode=35 author=AdGuard description=Moves AdGuard's root CA certificate from the user certificate store to the system certificate store. From ccaa7965a44f8c3a0f7a58fd441f0aae034bea59 Mon Sep 17 00:00:00 2001 From: Sergey Fionov Date: Mon, 23 Oct 2023 16:06:19 +0300 Subject: [PATCH 23/24] Update README --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4038516..00e5fe8 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,10 @@ This Magisk module supplements [AdGuard for Android][agandroid] and allows insta AdGuard's CA certificate to the System store on rooted devices. **Attention** -If you're using AdGuard for Android v4.2 Nightly 10 or newer, use the beta version of -this magisk module: https://github.com/AdguardTeam/adguardcert/releases/tag/v2.0-beta5. +Current version of this module is designed for Adguard for Android 4.2 and newer. + +If you're using AdGuard for Android v4.1 or older, please use the earlier version of +this magisk module: https://github.com/AdguardTeam/adguardcert/releases/tag/v1.2. ## Explanation From 57b46ba5c5e97178503d58512fbe8413d4f91232 Mon Sep 17 00:00:00 2001 From: Sergey Fionov Date: Mon, 23 Oct 2023 16:35:33 +0300 Subject: [PATCH 24/24] Add link to latest version too --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 00e5fe8..22feda9 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@ This Magisk module supplements [AdGuard for Android][agandroid] and allows insta AdGuard's CA certificate to the System store on rooted devices. **Attention** -Current version of this module is designed for Adguard for Android 4.2 and newer. +[Current version](https://github.com/AdguardTeam/adguardcert/releases/latest) +of this module is designed for Adguard for Android 4.2 and newer. If you're using AdGuard for Android v4.1 or older, please use the earlier version of this magisk module: https://github.com/AdguardTeam/adguardcert/releases/tag/v1.2.