mirror of
https://gitee.com/wanghongenpin/Magisk-ProxyPinCA.git
synced 2026-05-06 11:35:46 +08:00
Merge pull request #15 from firdausmntp/main
v1.0 - Initial release: ProxyPin Certificate Installer
This commit is contained in:
170
.github/workflows/build.yml
vendored
Normal file
170
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
name: Build and Release Module
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, master ]
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
branches: [ main, master ]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
MODULE_ID: proxypin-cert-installer
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get version info
|
||||
id: version
|
||||
run: |
|
||||
VERSION=$(grep 'version=' module.prop | cut -d'=' -f2)
|
||||
VERSION_CODE=$(grep 'versionCode=' module.prop | cut -d'=' -f2)
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "version_code=$VERSION_CODE" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Validate module structure
|
||||
run: |
|
||||
echo "🔍 Validating module structure..."
|
||||
|
||||
# Check required files
|
||||
required_files=("module.prop" "customize.sh" "post-fs-data.sh" "service.sh" "action.sh" "uninstall.sh" "META-INF/com/google/android/update-binary" "META-INF/com/google/android/updater-script")
|
||||
for file in "${required_files[@]}"; do
|
||||
if [ ! -f "$file" ]; then
|
||||
echo "❌ Missing required file: $file"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Found: $file"
|
||||
done
|
||||
|
||||
# Check certificate directory exists
|
||||
if [ ! -d "system/etc/security/cacerts" ]; then
|
||||
echo "❌ Missing certificate directory"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Certificate directory exists"
|
||||
|
||||
# Check certificate exists
|
||||
if [ ! -f "system/etc/security/cacerts/243f0bfb.0" ]; then
|
||||
echo "⚠️ Warning: ProxyPin certificate not found"
|
||||
fi
|
||||
|
||||
echo "✅ Module structure validated!"
|
||||
|
||||
- name: Make scripts executable
|
||||
run: |
|
||||
chmod +x post-fs-data.sh
|
||||
chmod +x service.sh
|
||||
chmod +x customize.sh
|
||||
chmod +x action.sh
|
||||
chmod +x uninstall.sh
|
||||
chmod +x META-INF/com/google/android/update-binary
|
||||
|
||||
- name: Build module ZIP
|
||||
run: |
|
||||
MODULE_NAME="ProxyPin-Cert-Installer-${{ steps.version.outputs.version }}"
|
||||
|
||||
echo "📦 Building $MODULE_NAME.zip..."
|
||||
|
||||
# Create zip with only required files
|
||||
zip -r9 "${MODULE_NAME}.zip" \
|
||||
module.prop \
|
||||
customize.sh \
|
||||
post-fs-data.sh \
|
||||
service.sh \
|
||||
action.sh \
|
||||
uninstall.sh \
|
||||
update.json \
|
||||
META-INF/ \
|
||||
system/ \
|
||||
webroot/
|
||||
|
||||
echo "✅ Module built: ${MODULE_NAME}.zip"
|
||||
echo "module_zip=${MODULE_NAME}.zip" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: proxypin-cert-installer-${{ steps.version.outputs.version }}
|
||||
path: ${{ env.module_zip }}
|
||||
retention-days: 30
|
||||
|
||||
release:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get version info
|
||||
id: version
|
||||
run: |
|
||||
VERSION=$(grep 'version=' module.prop | cut -d'=' -f2)
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Make scripts executable
|
||||
run: |
|
||||
chmod +x post-fs-data.sh
|
||||
chmod +x service.sh
|
||||
chmod +x customize.sh
|
||||
chmod +x action.sh
|
||||
chmod +x uninstall.sh
|
||||
chmod +x META-INF/com/google/android/update-binary
|
||||
|
||||
- name: Build release ZIP
|
||||
run: |
|
||||
MODULE_NAME="ProxyPin-Cert-Installer-${{ steps.version.outputs.version }}"
|
||||
|
||||
zip -r9 "${MODULE_NAME}.zip" \
|
||||
module.prop \
|
||||
customize.sh \
|
||||
post-fs-data.sh \
|
||||
service.sh \
|
||||
action.sh \
|
||||
uninstall.sh \
|
||||
update.json \
|
||||
META-INF/ \
|
||||
system/ \
|
||||
webroot/
|
||||
|
||||
echo "module_zip=${MODULE_NAME}.zip" >> $GITHUB_ENV
|
||||
|
||||
- name: Generate changelog
|
||||
id: changelog
|
||||
run: |
|
||||
echo "## ProxyPin Certificate Installer ${{ steps.version.outputs.version }}" > RELEASE_NOTES.md
|
||||
echo "" >> RELEASE_NOTES.md
|
||||
echo "**Author:** [firdausmntp](https://github.com/firdausmntp)" >> RELEASE_NOTES.md
|
||||
echo "" >> RELEASE_NOTES.md
|
||||
echo "### Features" >> RELEASE_NOTES.md
|
||||
echo "- ✅ Universal root support (Magisk/KernelSU/SukiSU/APatch)" >> RELEASE_NOTES.md
|
||||
echo "- ✅ Android 5.0 - 16 (API 21-36) support" >> RELEASE_NOTES.md
|
||||
echo "- ✅ **APEX CA bypass for Android 14+**" >> RELEASE_NOTES.md
|
||||
echo "- ✅ WebUI for status monitoring" >> RELEASE_NOTES.md
|
||||
echo "- ✅ ProxyPin certificate pre-included" >> RELEASE_NOTES.md
|
||||
echo "- ✅ SELinux enforcing compatible" >> RELEASE_NOTES.md
|
||||
echo "" >> RELEASE_NOTES.md
|
||||
echo "### Installation" >> RELEASE_NOTES.md
|
||||
echo "1. Download the ZIP file below" >> RELEASE_NOTES.md
|
||||
echo "2. Install via Magisk/KernelSU/SukiSU/APatch Manager" >> RELEASE_NOTES.md
|
||||
echo "3. Reboot device" >> RELEASE_NOTES.md
|
||||
echo "" >> RELEASE_NOTES.md
|
||||
echo "### Important for Android 14+" >> RELEASE_NOTES.md
|
||||
echo "This module includes APEX bypass to inject certificates into \`com.android.conscrypt\` APEX." >> RELEASE_NOTES.md
|
||||
echo "If ProxyPin shows 'Certificate Not Installed', check logs at \`/data/local/tmp/ProxyPinCert.log\`" >> RELEASE_NOTES.md
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: ${{ env.module_zip }}
|
||||
body_path: RELEASE_NOTES.md
|
||||
52
.gitignore
vendored
52
.gitignore
vendored
@@ -1,17 +1,39 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
# Build artifacts
|
||||
*.zip
|
||||
*.tar.gz
|
||||
build/
|
||||
dist/
|
||||
_build_temp/
|
||||
|
||||
# Build scripts (personal use only)
|
||||
build.py
|
||||
build.sh
|
||||
build.bat
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
Podfile.lock
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
ProxyPinCert.log
|
||||
ProxyPinCA.log
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
desktop.ini
|
||||
ehthumbs.db
|
||||
|
||||
# Temp
|
||||
*.tmp
|
||||
*.temp
|
||||
*.bak
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
|
||||
22
CHANGELOG.md
Normal file
22
CHANGELOG.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [v1.0] - 2026-04-16
|
||||
|
||||
### Added
|
||||
- Initial release
|
||||
- ProxyPin CA certificate (`243f0bfb.0`) pre-included in module
|
||||
- Install certificate to System CA Store automatically
|
||||
- Multi-root support: Magisk, KernelSU, SukiSU, APatch
|
||||
- Android 5.0 - 15+ (API 21-36) compatibility
|
||||
- APEX CA bypass for Android 14+ (conscrypt module injection)
|
||||
- Namespace injection into zygote processes
|
||||
- Dynamic re-injection via service script after boot
|
||||
- Per-process mount for all app processes
|
||||
- WebUI for status monitoring (APEX status, logs, reboot)
|
||||
- Action button handler for root managers
|
||||
- SELinux enforcing compatible
|
||||
- Comprehensive logging to `/data/local/tmp/ProxyPinCert.log`
|
||||
- Uninstall cleanup script (unmount APEX, remove temp files)
|
||||
- GitHub Actions CI/CD for automated builds and releases
|
||||
208
README.md
208
README.md
@@ -1,8 +1,204 @@
|
||||
# ProxyPin Certificate
|
||||
这是一个 Magisk 模块 用于安装ProxyPin系统证书, 安装完后需要重启手机.
|
||||
# 📱 ProxyPin Certificate Installer
|
||||
|
||||
抓包下载地址
|
||||
https://github.com/wanghongenpin/network_proxy_flutter
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/Version-v1.0-blue?style=for-the-badge" alt="Version"/>
|
||||
<img src="https://img.shields.io/badge/Android-5.0--15-green?style=for-the-badge&logo=android" alt="Android"/>
|
||||
<img src="https://img.shields.io/badge/License-GPL--3.0-yellow?style=for-the-badge" alt="License"/>
|
||||
</p>
|
||||
|
||||
安装证书不成功可尝试其他模块
|
||||
如: https://github.com/ys1231/MoveCertificate
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/Magisk-20.4%2B-00AF9C?style=flat-square&logo=magisk" alt="Magisk"/>
|
||||
<img src="https://img.shields.io/badge/KernelSU-Supported-orange?style=flat-square" alt="KernelSU"/>
|
||||
<img src="https://img.shields.io/badge/SukiSU-✓%20Tested-9333ea?style=flat-square" alt="SukiSU"/>
|
||||
<img src="https://img.shields.io/badge/APatch-Supported-blue?style=flat-square" alt="APatch"/>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<b>📱 Install ProxyPin CA Certificate to System CA Store</b><br>
|
||||
<sub>Tested on Android 15 with SukiSU v40201</sub>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## 📋 Description
|
||||
|
||||
This Magisk/KernelSU/APatch module installs the **ProxyPin CA Certificate** (`243f0bfb.0`) into the Android System CA Store, enabling HTTPS traffic interception with the [ProxyPin](https://github.com/wanghongenpin/network_proxy_flutter) app.
|
||||
|
||||
> ✅ **Certificate is pre-included** in this module. Just install, reboot, and you're done!
|
||||
|
||||
---
|
||||
|
||||
## ✨ Key Features
|
||||
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| 🔧 **Multi-Root Support** | Works with Magisk, KernelSU, SukiSU, APatch |
|
||||
| 📱 **Wide Android Support** | Android 5.0 - 15 (API 21-35) |
|
||||
| 🔓 **APEX Bypass** | Proper injection for Android 14+ conscrypt APEX |
|
||||
| 🖥️ **WebUI Interface** | Monitor status and manage module visually |
|
||||
| 🛡️ **SELinux Compatible** | Works with SELinux enforcing |
|
||||
| 💾 **Systemless** | Does not modify /system partition |
|
||||
| 📦 **Pre-included Cert** | Certificate `243f0bfb.0` included, no manual steps |
|
||||
|
||||
---
|
||||
|
||||
## 📱 Tested Compatibility
|
||||
|
||||
### ✅ Verified Working
|
||||
|
||||
| Device | Android | Root | Status |
|
||||
|--------|---------|------|--------|
|
||||
| Redmi Note 8 Pro | 15 (API 35) | SukiSU v40201 | ✅ **Tested** |
|
||||
|
||||
### Root Solutions Support
|
||||
|
||||
| Solution | Status | Notes |
|
||||
|----------|--------|-------|
|
||||
| **SukiSU** | ✅ Tested | Fully working with WebUI |
|
||||
| **KernelSU** | ✅ Supported | With WebUI support |
|
||||
| **Magisk** | ✅ Supported | v20.4+ required |
|
||||
| **APatch** | ✅ Supported | v10300+ required |
|
||||
|
||||
### Android Versions
|
||||
|
||||
| Version | API | Status |
|
||||
|---------|-----|--------|
|
||||
| Android 5.0 - 13 | 21-33 | ✅ Standard Magic Mount |
|
||||
| Android 14 | 34 | ✅ APEX Bypass |
|
||||
| Android 15 | 35 | ✅ APEX Bypass (Tested) |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Step 1: Install Module
|
||||
|
||||
1. Download `ProxyPin-Cert-Installer-v1.0.zip`
|
||||
2. Install via **Magisk/KernelSU/SukiSU/APatch** Manager
|
||||
3. **Reboot** device
|
||||
|
||||
### Step 2: Verify
|
||||
|
||||
1. Go to **Settings** → **Security** → **Trusted credentials** → **System**
|
||||
2. Look for **ProxyPin CA** certificate
|
||||
3. Open ProxyPin and start capturing
|
||||
|
||||
> 💡 The certificate (`243f0bfb.0`) is already pre-included in the module. No export or manual copy needed.
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ WebUI Features
|
||||
|
||||
Access WebUI through your root manager's module settings:
|
||||
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| 📊 **Status Monitor** | View module and APEX injection status |
|
||||
| 💉 **Re-inject** | Manually trigger certificate injection |
|
||||
| 📋 **View Logs** | Check module operation logs |
|
||||
| 🔄 **Reboot** | Quick reboot to apply changes |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Android 14+ APEX Bypass
|
||||
|
||||
Starting Android 14, CA certificates moved to APEX module (`com.android.conscrypt`).
|
||||
|
||||
This module implements:
|
||||
- **Namespace Injection** - Mounts into zygote namespaces
|
||||
- **Dynamic Re-injection** - Service script reinjects after boot
|
||||
- **Per-process Mount** - All app processes see the certificate
|
||||
|
||||
### Verify Injection
|
||||
```bash
|
||||
# Check if certificate is in APEX
|
||||
ls /apex/com.android.conscrypt/cacerts/*.0 | head -5
|
||||
|
||||
# View module logs
|
||||
cat /data/local/tmp/ProxyPinCert.log
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Troubleshooting
|
||||
|
||||
### "Certificate Not Installed" in ProxyPin
|
||||
|
||||
```bash
|
||||
# 1. Check logs
|
||||
cat /data/local/tmp/ProxyPinCert.log
|
||||
|
||||
# 2. Manual re-inject
|
||||
su -c "sh /data/adb/modules/proxypin-cert-installer/post-fs-data.sh"
|
||||
|
||||
# 3. Force stop and reopen ProxyPin
|
||||
```
|
||||
|
||||
### "Unknown Publisher" Error
|
||||
|
||||
- **SukiSU/KernelSU**: Settings → Enable "Allow untrusted modules"
|
||||
- **APatch**: Settings → Security → Enable "Allow unknown sources"
|
||||
|
||||
### WebUI Not Loading
|
||||
|
||||
1. Ensure `webroot/index.html` exists in module
|
||||
2. Check if root manager supports WebUI
|
||||
3. Try reinstalling module
|
||||
|
||||
---
|
||||
|
||||
## 📁 Module Structure
|
||||
|
||||
```
|
||||
proxypin-cert-installer/
|
||||
├── META-INF/com/google/android/
|
||||
│ ├── update-binary
|
||||
│ └── updater-script
|
||||
├── system/etc/security/cacerts/
|
||||
│ └── 243f0bfb.0 # ProxyPin CA cert (pre-included)
|
||||
├── webroot/
|
||||
│ └── index.html # WebUI
|
||||
├── module.prop
|
||||
├── customize.sh # Installation script
|
||||
├── post-fs-data.sh # APEX injection
|
||||
├── service.sh # Post-boot injection
|
||||
├── action.sh # Action button handler
|
||||
└── uninstall.sh # Cleanup script
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 Changelog
|
||||
|
||||
### v1.0 (Current)
|
||||
- ✅ Initial release
|
||||
- ✅ Certificate pre-included (`243f0bfb.0`)
|
||||
- ✅ Multi-root support (Magisk/KernelSU/SukiSU/APatch)
|
||||
- ✅ Android 5.0-15+ support
|
||||
- ✅ APEX CA bypass for Android 14+
|
||||
- ✅ WebUI for status monitoring
|
||||
- ✅ SELinux enforcing compatible
|
||||
- ✅ GitHub Actions CI/CD
|
||||
|
||||
## 📄 License
|
||||
|
||||
GPL-3.0 License - See [LICENSE](LICENSE) for details.
|
||||
|
||||
---
|
||||
|
||||
## 🙏 Credits
|
||||
|
||||
- **[firdausmntp](https://github.com/firdausmntp)** - Author & Maintainer
|
||||
- [wanghongenpin](https://github.com/wanghongenpin/network_proxy_flutter) - ProxyPin App
|
||||
- [topjohnwu](https://github.com/topjohnwu) - Magisk Framework
|
||||
- [tiann](https://github.com/tiann) - KernelSU Framework
|
||||
- [pomelohan](https://github.com/pomelohan/SukiSU-Ultra) - SukiSU Framework
|
||||
- [bmax121](https://github.com/bmax121) - APatch Framework
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Links
|
||||
|
||||
[](https://github.com/firdausmntp/ProxyPin-cert-installer)
|
||||
[](https://github.com/firdausmntp/ProxyPin-cert-installer/issues)
|
||||
[](https://github.com/wanghongenpin/network_proxy_flutter)
|
||||
|
||||
138
action.sh
Normal file
138
action.sh
Normal file
@@ -0,0 +1,138 @@
|
||||
#!/system/bin/sh
|
||||
# This script runs when user presses Action button in root manager
|
||||
MODDIR=${0%/*}
|
||||
LOG_FILE="/data/local/tmp/ProxyPinCert.log"
|
||||
CERT_DIR="$MODDIR/system/etc/security/cacerts"
|
||||
|
||||
echo "╔════════════════════════════════════════╗"
|
||||
echo "║ ProxyPin Certificate Installer v1.0 ║"
|
||||
echo "║ by firdausmntp ║"
|
||||
echo "╚════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
# Get API level
|
||||
API=$(getprop ro.build.version.sdk)
|
||||
ANDROID_VERSION=$(getprop ro.build.version.release)
|
||||
echo "- Android: $ANDROID_VERSION (API $API)"
|
||||
|
||||
# Check certificate using find (more reliable)
|
||||
CERT_COUNT=$(find "$CERT_DIR" -maxdepth 1 -type f -name "*.0" 2>/dev/null | wc -l)
|
||||
CERT_COUNT=$(echo "$CERT_COUNT" | tr -d ' ')
|
||||
|
||||
echo "- Certificates in module: $CERT_COUNT"
|
||||
|
||||
if [ "$CERT_COUNT" -eq 0 ] || [ -z "$CERT_COUNT" ]; then
|
||||
echo ""
|
||||
echo "⚠️ CERTIFICATE NOT FOUND!"
|
||||
echo ""
|
||||
echo "Certificate 243f0bfb.0 is missing."
|
||||
echo "Try reinstalling the module."
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Show certificate info
|
||||
echo ""
|
||||
echo "Certificates:"
|
||||
find "$CERT_DIR" -maxdepth 1 -type f -name "*.0" 2>/dev/null | while read cert; do
|
||||
name=$(basename "$cert")
|
||||
size=$(ls -la "$cert" | awk '{print $5}')
|
||||
# Try to get certificate subject
|
||||
subject=$(openssl x509 -in "$cert" -noout -subject 2>/dev/null | sed 's/subject=//g' | head -1)
|
||||
echo " - $name ($size bytes)"
|
||||
if [ -n "$subject" ]; then
|
||||
echo " Subject: $(echo "$subject" | cut -c1-60)..."
|
||||
fi
|
||||
done
|
||||
|
||||
# Check system cacerts
|
||||
echo ""
|
||||
echo "System CA Store Status:"
|
||||
SYSTEM_CERT=$(find "$CERT_DIR" -maxdepth 1 -type f -name "*.0" 2>/dev/null | head -1 | xargs basename 2>/dev/null)
|
||||
|
||||
if [ -n "$SYSTEM_CERT" ]; then
|
||||
if [ -f "/system/etc/security/cacerts/$SYSTEM_CERT" ]; then
|
||||
echo " ✓ $SYSTEM_CERT present in /system/etc/security/cacerts"
|
||||
else
|
||||
echo " ✗ $SYSTEM_CERT NOT in /system/etc/security/cacerts (Magic Mount may not be active yet)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check APEX status on Android 14+
|
||||
if [ "$API" -ge 34 ]; then
|
||||
echo ""
|
||||
echo "Android 14+ APEX Status:"
|
||||
APEX_DIR="/apex/com.android.conscrypt/cacerts"
|
||||
|
||||
if [ -d "$APEX_DIR" ]; then
|
||||
find "$CERT_DIR" -maxdepth 1 -type f -name "*.0" 2>/dev/null | while read cert; do
|
||||
name=$(basename "$cert")
|
||||
if [ -f "$APEX_DIR/$name" ]; then
|
||||
echo " ✓ $name present in APEX"
|
||||
else
|
||||
echo " ✗ $name NOT in APEX (needs re-injection or reboot)"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
APEX_COUNT=$(find "$APEX_DIR" -maxdepth 1 -name "*.0" -type f 2>/dev/null | wc -l)
|
||||
echo " Total certs in APEX: $APEX_COUNT"
|
||||
else
|
||||
echo " APEX CA directory not found"
|
||||
fi
|
||||
else
|
||||
echo ""
|
||||
echo "Note: Standard Magic Mount is used for Android < 14"
|
||||
fi
|
||||
|
||||
# Check Trusted Credentials hint
|
||||
echo ""
|
||||
echo "📱 How to verify:"
|
||||
echo " Settings → Security → Encryption & credentials"
|
||||
echo " → Trusted credentials → System"
|
||||
echo " Look for: ProxyPin CA"
|
||||
|
||||
# Option to re-inject
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "Options:"
|
||||
echo " 1. Re-inject certificates (for Android 14+ APEX)"
|
||||
echo " 2. View logs"
|
||||
echo " 3. Force reboot"
|
||||
echo " 4. Exit"
|
||||
echo ""
|
||||
read -p "Select option [1-4]: " choice
|
||||
|
||||
case "$choice" in
|
||||
1)
|
||||
echo ""
|
||||
echo "Running certificate re-injection..."
|
||||
sh "$MODDIR/service.sh"
|
||||
echo ""
|
||||
echo "Done! Please check:"
|
||||
echo " - Settings → Security → Trusted credentials → System"
|
||||
echo " - ProxyPin app should detect the certificate"
|
||||
;;
|
||||
2)
|
||||
echo ""
|
||||
echo "=== Recent Logs ==="
|
||||
tail -50 "$LOG_FILE" 2>/dev/null || echo "No logs found"
|
||||
;;
|
||||
3)
|
||||
echo ""
|
||||
echo "Rebooting device..."
|
||||
reboot
|
||||
;;
|
||||
4)
|
||||
echo "Goodbye!"
|
||||
;;
|
||||
*)
|
||||
echo "Invalid option"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$KSU" = "true" ] || [ "$APATCH" = "true" ]; then
|
||||
echo ""
|
||||
echo "Dialog will close in 10 seconds..."
|
||||
sleep 10
|
||||
fi
|
||||
214
customize.sh
214
customize.sh
@@ -1,17 +1,215 @@
|
||||
#!/system/bin/sh
|
||||
|
||||
# Android 5.0 - 16 (API 21-36) compatible
|
||||
SKIPUNZIP=0
|
||||
|
||||
ASH_STANDALONE=0
|
||||
#################
|
||||
# Helper Functions
|
||||
#################
|
||||
|
||||
ui_print "开始安装模块"
|
||||
print_banner() {
|
||||
ui_print "╔════════════════════════════════════════╗"
|
||||
ui_print "║ ProxyPin Certificate Installer v1.0 ║"
|
||||
ui_print "║ by firdausmntp ║"
|
||||
ui_print "╚════════════════════════════════════════╝"
|
||||
ui_print ""
|
||||
}
|
||||
|
||||
ui_print "提取模块证书"
|
||||
detect_root_solution() {
|
||||
if [ "$KSU" = "true" ]; then
|
||||
# Check for SukiSU specifically
|
||||
if [ -f /data/adb/ksu/bin/ksud ]; then
|
||||
if strings /data/adb/ksu/bin/ksud 2>/dev/null | grep -qi "sukisu"; then
|
||||
ROOT_IMPL="SukiSU"
|
||||
else
|
||||
ROOT_IMPL="KernelSU"
|
||||
fi
|
||||
else
|
||||
ROOT_IMPL="KernelSU"
|
||||
fi
|
||||
ROOT_VER="$KSU_VER"
|
||||
ROOT_VER_CODE="$KSU_VER_CODE"
|
||||
elif [ "$APATCH" = "true" ]; then
|
||||
ROOT_IMPL="APatch"
|
||||
ROOT_VER="$APATCH_VER"
|
||||
ROOT_VER_CODE="$APATCH_VER_CODE"
|
||||
else
|
||||
ROOT_IMPL="Magisk"
|
||||
ROOT_VER="$MAGISK_VER"
|
||||
ROOT_VER_CODE="$MAGISK_VER_CODE"
|
||||
fi
|
||||
}
|
||||
|
||||
unzip -o "$ZIPFILE" 'system/*' -d $MODPATH >&2
|
||||
#################
|
||||
# Compatibility Check
|
||||
#################
|
||||
|
||||
ui_print "安装成功,重启手机后去系统证书查看ProxyPinCA是否生效."
|
||||
check_compatibility() {
|
||||
# Check API level
|
||||
API=$(getprop ro.build.version.sdk)
|
||||
[ -z "$API" ] && API=21
|
||||
|
||||
ui_print " "
|
||||
if [ "$API" -lt 21 ]; then
|
||||
abort "! ERROR: Minimum Android 5.0 (API 21) required!"
|
||||
fi
|
||||
|
||||
set_perm_recursive $MODPATH 0 0 0755 0644
|
||||
if [ "$API" -gt 36 ]; then
|
||||
ui_print "! WARNING: Untested Android version (API $API)"
|
||||
ui_print " Proceeding anyway..."
|
||||
fi
|
||||
|
||||
# Root solution version checks
|
||||
case "$ROOT_IMPL" in
|
||||
"Magisk")
|
||||
[ "$ROOT_VER_CODE" -lt 20400 ] && abort "! ERROR: Magisk v20.4+ required!"
|
||||
;;
|
||||
"KernelSU"|"SukiSU")
|
||||
if [ "$ROOT_VER_CODE" -lt 10000 ]; then
|
||||
ui_print "! WARNING: Old $ROOT_IMPL version"
|
||||
fi
|
||||
;;
|
||||
"APatch")
|
||||
if [ "$ROOT_VER_CODE" -lt 10300 ]; then
|
||||
ui_print "! WARNING: Old APatch version"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
#################
|
||||
#################
|
||||
|
||||
setup_permissions() {
|
||||
ui_print "- Setting permissions..."
|
||||
|
||||
# System certificate directory
|
||||
if [ -d "$MODPATH/system/etc/security/cacerts" ]; then
|
||||
set_perm_recursive "$MODPATH/system/etc/security/cacerts" 0 0 0755 0644
|
||||
fi
|
||||
|
||||
# Scripts
|
||||
for script in post-fs-data.sh service.sh uninstall.sh action.sh; do
|
||||
[ -f "$MODPATH/$script" ] && set_perm "$MODPATH/$script" 0 0 0755
|
||||
done
|
||||
|
||||
# WebUI
|
||||
if [ -d "$MODPATH/webroot" ]; then
|
||||
set_perm_recursive "$MODPATH/webroot" 0 0 0755 0644
|
||||
fi
|
||||
}
|
||||
|
||||
cleanup_certificates() {
|
||||
ui_print "- Cleaning up non-certificate files..."
|
||||
|
||||
local CERT_DIR="$MODPATH/system/etc/security/cacerts"
|
||||
|
||||
# Remove README.md if exists
|
||||
if [ -f "$CERT_DIR/README.md" ]; then
|
||||
rm -f "$CERT_DIR/README.md"
|
||||
ui_print " Removed README.md"
|
||||
fi
|
||||
|
||||
# Remove .gitkeep if exists
|
||||
if [ -f "$CERT_DIR/.gitkeep" ]; then
|
||||
rm -f "$CERT_DIR/.gitkeep"
|
||||
ui_print " Removed .gitkeep"
|
||||
fi
|
||||
|
||||
# Remove any other non-.0 files that might cause issues
|
||||
for file in "$CERT_DIR"/*; do
|
||||
if [ -f "$file" ]; then
|
||||
case "$(basename "$file")" in
|
||||
*.0)
|
||||
# Valid certificate, keep it
|
||||
;;
|
||||
*)
|
||||
# Non-certificate file, remove it
|
||||
rm -f "$file"
|
||||
ui_print " Removed $(basename "$file")"
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
check_certificate() {
|
||||
local cert_dir="$MODPATH/system/etc/security/cacerts"
|
||||
local cert_count=$(find "$cert_dir" -maxdepth 1 -type f -name "*.0" 2>/dev/null | wc -l)
|
||||
cert_count=$(echo "$cert_count" | tr -d ' ') # Remove whitespace
|
||||
|
||||
if [ "$cert_count" -eq 0 ] || [ -z "$cert_count" ]; then
|
||||
ui_print ""
|
||||
ui_print "╔════════════════════════════════════════╗"
|
||||
ui_print "║ ⚠️ CERTIFICATE NOT FOUND! ║"
|
||||
ui_print "╚════════════════════════════════════════╝"
|
||||
ui_print ""
|
||||
ui_print "! Certificate 243f0bfb.0 was not extracted."
|
||||
ui_print "! Try reinstalling the module."
|
||||
ui_print ""
|
||||
else
|
||||
ui_print "✓ Found $cert_count certificate(s)"
|
||||
# List certificates
|
||||
find "$cert_dir" -maxdepth 1 -type f -name "*.0" 2>/dev/null | while read cert; do
|
||||
ui_print " - $(basename "$cert")"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
setup_android14_plus() {
|
||||
API=$(getprop ro.build.version.sdk)
|
||||
|
||||
if [ "$API" -ge 34 ]; then
|
||||
ui_print "- Android 14+ detected (API $API)"
|
||||
ui_print "- APEX CA bypass will be configured"
|
||||
|
||||
# Ensure scripts are properly configured for APEX
|
||||
chmod 0755 "$MODPATH/post-fs-data.sh" 2>/dev/null
|
||||
chmod 0755 "$MODPATH/service.sh" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
print_summary() {
|
||||
ui_print ""
|
||||
ui_print "╔════════════════════════════════════════╗"
|
||||
ui_print "║ ✓ Installation Complete! ║"
|
||||
ui_print "╚════════════════════════════════════════╝"
|
||||
ui_print ""
|
||||
ui_print " Root Solution: $ROOT_IMPL ($ROOT_VER)"
|
||||
ui_print " Android API: $API"
|
||||
ui_print ""
|
||||
ui_print " ⚡ Actions:"
|
||||
ui_print " • Reboot your device"
|
||||
ui_print " • Check: Settings → Security → Trusted credentials"
|
||||
ui_print ""
|
||||
|
||||
if [ "$API" -ge 34 ]; then
|
||||
ui_print " 📱 Android 14+ Notes:"
|
||||
ui_print " • APEX bypass runs automatically"
|
||||
ui_print " • Check ProxyPin after reboot"
|
||||
ui_print ""
|
||||
fi
|
||||
|
||||
ui_print " 📋 Logs: /data/local/tmp/ProxyPinCert.log"
|
||||
ui_print ""
|
||||
|
||||
# Installation
|
||||
ui_print " GitHub: https://github.com/firdausmntp/ProxyPin-cert-installer"
|
||||
ui_print ""
|
||||
}
|
||||
|
||||
#################
|
||||
# Main
|
||||
#################
|
||||
|
||||
print_banner
|
||||
detect_root_solution
|
||||
|
||||
ui_print "- Detected: $ROOT_IMPL"
|
||||
ui_print "- Version: $ROOT_VER (code: $ROOT_VER_CODE)"
|
||||
ui_print ""
|
||||
|
||||
check_compatibility
|
||||
setup_permissions
|
||||
cleanup_certificates
|
||||
check_certificate
|
||||
setup_android14_plus
|
||||
print_summary
|
||||
17
module.prop
17
module.prop
@@ -1,6 +1,11 @@
|
||||
id=ProxyPinCA
|
||||
name=ProxyPinCA
|
||||
version=1.2.0
|
||||
versionCode=3
|
||||
author=ProxyPin
|
||||
description=ProxyPin certificate.
|
||||
id=proxypin-cert-installer
|
||||
name=ProxyPin Certificate Installer
|
||||
version=v1.0
|
||||
versionCode=1
|
||||
author=firdausmntp
|
||||
description=Install ProxyPin CA certificate to System CA Store. Supports Magisk/KernelSU/SukiSU/APatch. Android 5.0-15 compatible with APEX bypass for Android 14+.
|
||||
updateJson=https://raw.githubusercontent.com/firdausmntp/ProxyPin-cert-installer/main/update.json
|
||||
support=https://github.com/firdausmntp/ProxyPin-cert-installer
|
||||
donate=https://github.com/sponsors/firdausmntp
|
||||
minApi=21
|
||||
maxApi=36
|
||||
|
||||
187
post-fs-data.sh
187
post-fs-data.sh
@@ -1,70 +1,137 @@
|
||||
#!/system/bin/sh
|
||||
|
||||
|
||||
exec > /data/local/tmp/ProxyPinCA.log
|
||||
exec 2>&1
|
||||
|
||||
#set -x
|
||||
# ProxyPin Certificate Installer - Post-fs-data Script
|
||||
# Author: firdausmntp
|
||||
# GitHub: https://github.com/firdausmntp/ProxyPin-cert-installer
|
||||
#
|
||||
# Android 14+ APEX Bypass - Aggressive Implementation
|
||||
|
||||
MODDIR=${0%/*}
|
||||
LOG_FILE="/data/local/tmp/ProxyPinCert.log"
|
||||
CERT_DIR="${MODDIR}/system/etc/security/cacerts"
|
||||
TEMP_DIR="/data/local/tmp/proxypin-apex-ca"
|
||||
|
||||
set_context() {
|
||||
[ "$(getenforce)" = "Enforcing" ] || return 0
|
||||
# Initialize logging
|
||||
mkdir -p /data/local/tmp 2>/dev/null
|
||||
echo "" > "$LOG_FILE"
|
||||
|
||||
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
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
#LOG_PATH="/data/local/tmp/ProxyPinCA.log"
|
||||
echo "[$(date +%F) $(date +%T)] - ProxyPinCA post-fs-data.sh start."
|
||||
chown -R 0:0 ${MODDIR}/system/etc/security/cacerts
|
||||
if [ -d /apex/com.android.conscrypt/cacerts ]; then
|
||||
# 检测到 android 14 以上,存在该证书目录
|
||||
CERT_HASH=243f0bfb
|
||||
log "╔══════════════════════════════════════════════════════╗"
|
||||
log "║ ProxyPin Certificate Installer v1.0 ║"
|
||||
log "╚══════════════════════════════════════════════════════╝"
|
||||
log ""
|
||||
log "Post-fs-data started"
|
||||
log "Module: $MODDIR"
|
||||
|
||||
CERT_FILE=${MODDIR}/system/etc/security/cacerts/${CERT_HASH}.0
|
||||
echo "[$(date +%F) $(date +%T)] - CERT_FILE: ${CERT_FILE}"
|
||||
if ! [ -e "${CERT_FILE}" ]; then
|
||||
echo "[$(date +%F) $(date +%T)] - ProxyPinCA certificate not found."
|
||||
exit 0
|
||||
fi
|
||||
API=$(getprop ro.build.version.sdk)
|
||||
ANDROID_VERSION=$(getprop ro.build.version.release)
|
||||
log "Android: $ANDROID_VERSION (API $API)"
|
||||
|
||||
TEMP_DIR=/data/local/tmp/cacerts-copy
|
||||
rm -rf "$TEMP_DIR"
|
||||
mkdir -p -m 700 "$TEMP_DIR"
|
||||
mount -t tmpfs tmpfs "$TEMP_DIR"
|
||||
|
||||
# 复制证书到临时目录
|
||||
cp -f /apex/com.android.conscrypt/cacerts/* "$TEMP_DIR"
|
||||
cp -f $CERT_FILE "$TEMP_DIR"
|
||||
|
||||
chown -R 0:0 "$TEMP_DIR"
|
||||
set_context /apex/com.android.conscrypt/cacerts "$TEMP_DIR"
|
||||
|
||||
# 检查新证书是否成功添加
|
||||
CERTS_NUM="$(ls -1 "$TEMP_DIR" | wc -l)"
|
||||
if [ "$CERTS_NUM" -gt 10 ]; then
|
||||
mount -o bind "$TEMP_DIR" /apex/com.android.conscrypt/cacerts
|
||||
for pid in 1 $(pgrep zygote) $(pgrep zygote64); do
|
||||
nsenter --mount=/proc/${pid}/ns/mnt -- \
|
||||
mount --bind "$TEMP_DIR" /apex/com.android.conscrypt/cacerts
|
||||
done
|
||||
echo "[$(date +%F) $(date +%T)] - Mount success!"
|
||||
else
|
||||
echo "[$(date +%F) $(date +%T)] - Mount failed!"
|
||||
fi
|
||||
|
||||
# 卸载临时目录
|
||||
umount "$TEMP_DIR"
|
||||
rmdir "$TEMP_DIR"
|
||||
# Detect root
|
||||
if [ "$KSU" = "true" ]; then
|
||||
log "Root: KernelSU/SukiSU (v$KSU_VER_CODE)"
|
||||
elif [ "$APATCH" = "true" ]; then
|
||||
log "Root: APatch (v$APATCH_VER_CODE)"
|
||||
else
|
||||
echo "[$(date +%F) $(date +%T)] - Android version lower than 14 detected"
|
||||
set_context /system/etc/security/cacerts ${MODDIR}/system/etc/security/cacerts
|
||||
echo "[$(date +%F) $(date +%T)] - Mount success!"
|
||||
fi
|
||||
log "Root: Magisk/Other"
|
||||
fi
|
||||
|
||||
# Find our certificate
|
||||
CERT_FILE=$(find "$CERT_DIR" -maxdepth 1 -type f -name "*.0" 2>/dev/null | head -1)
|
||||
CERT_NAME=$(basename "$CERT_FILE" 2>/dev/null)
|
||||
|
||||
if [ -z "$CERT_FILE" ] || [ ! -f "$CERT_FILE" ]; then
|
||||
log "ERROR: No certificate file found in $CERT_DIR"
|
||||
log "Certificate 243f0bfb.0 is missing. Try reinstalling the module."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log "Certificate: $CERT_NAME"
|
||||
|
||||
# For Android < 14, Magic Mount handles it
|
||||
if [ "$API" -lt 34 ]; then
|
||||
log "Android < 14: Using Magic Mount"
|
||||
log "Done!"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log ""
|
||||
log "=== Android 14+ APEX Injection ==="
|
||||
|
||||
APEX_CACERTS="/apex/com.android.conscrypt/cacerts"
|
||||
|
||||
if [ ! -d "$APEX_CACERTS" ]; then
|
||||
log "ERROR: APEX cacerts not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Prepare temp directory with tmpfs
|
||||
log "Preparing tmpfs..."
|
||||
umount "$TEMP_DIR" 2>/dev/null
|
||||
rm -rf "$TEMP_DIR" 2>/dev/null
|
||||
mkdir -p "$TEMP_DIR"
|
||||
|
||||
if ! mount -t tmpfs tmpfs "$TEMP_DIR"; then
|
||||
log "ERROR: Failed to mount tmpfs"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Copy existing certs
|
||||
log "Copying system certificates..."
|
||||
cp -a "$APEX_CACERTS"/* "$TEMP_DIR/" 2>/dev/null
|
||||
ORIG_COUNT=$(ls -1 "$TEMP_DIR"/*.0 2>/dev/null | wc -l)
|
||||
log "System certs: $ORIG_COUNT"
|
||||
|
||||
# Add our certificate
|
||||
log "Adding: $CERT_NAME"
|
||||
cp -f "$CERT_FILE" "$TEMP_DIR/$CERT_NAME"
|
||||
|
||||
# Set permissions
|
||||
chown -R 0:0 "$TEMP_DIR"
|
||||
chmod 755 "$TEMP_DIR"
|
||||
chmod 644 "$TEMP_DIR"/*
|
||||
|
||||
# Set SELinux context
|
||||
APEX_CONTEXT=$(ls -Zd "$APEX_CACERTS" 2>/dev/null | awk '{print $1}')
|
||||
if [ -n "$APEX_CONTEXT" ] && [ "$APEX_CONTEXT" != "?" ]; then
|
||||
chcon -R "$APEX_CONTEXT" "$TEMP_DIR" 2>/dev/null
|
||||
log "SELinux context: $APEX_CONTEXT"
|
||||
fi
|
||||
|
||||
TOTAL_COUNT=$(ls -1 "$TEMP_DIR"/*.0 2>/dev/null | wc -l)
|
||||
log "Total certs: $TOTAL_COUNT"
|
||||
|
||||
# Mount to APEX - try multiple approaches
|
||||
log ""
|
||||
log "Mounting to APEX..."
|
||||
|
||||
# 1. Global mount
|
||||
mount --bind "$TEMP_DIR" "$APEX_CACERTS" && log "✓ Global mount"
|
||||
|
||||
# 2. Init namespace (PID 1)
|
||||
nsenter --mount=/proc/1/ns/mnt -- mount --bind "$TEMP_DIR" "$APEX_CACERTS" 2>/dev/null && log "✓ Init (PID 1)"
|
||||
|
||||
# 3. Zygote namespaces - critical for apps
|
||||
for zygote in zygote zygote64; do
|
||||
PID=$(pidof "$zygote" 2>/dev/null)
|
||||
if [ -n "$PID" ]; then
|
||||
nsenter --mount=/proc/$PID/ns/mnt -- mount --bind "$TEMP_DIR" "$APEX_CACERTS" 2>/dev/null && log "✓ $zygote (PID $PID)"
|
||||
fi
|
||||
done
|
||||
|
||||
# 4. Keep post-fs-data lightweight; service.sh handles targeted post-boot app namespaces.
|
||||
log "Deferred per-app namespace injection to service.sh"
|
||||
|
||||
# Verify
|
||||
log ""
|
||||
if [ -f "$APEX_CACERTS/$CERT_NAME" ]; then
|
||||
log "✓ SUCCESS: $CERT_NAME is in APEX!"
|
||||
else
|
||||
log "✗ Certificate not visible in APEX (namespace isolation)"
|
||||
fi
|
||||
|
||||
log ""
|
||||
log "Post-fs-data completed"
|
||||
log "══════════════════════════════════════════════════════"
|
||||
116
service.sh
Normal file
116
service.sh
Normal file
@@ -0,0 +1,116 @@
|
||||
#!/system/bin/sh
|
||||
# ProxyPin Certificate Installer - Service Script
|
||||
# Runs after boot to ensure APEX injection persists for all apps
|
||||
|
||||
MODDIR=${0%/*}
|
||||
LOG_FILE="/data/local/tmp/ProxyPinCert.log"
|
||||
CERT_DIR="${MODDIR}/system/etc/security/cacerts"
|
||||
TEMP_DIR="/data/local/tmp/proxypin-apex-ca"
|
||||
APEX_CACERTS="/apex/com.android.conscrypt/cacerts"
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [service] $1" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
log ""
|
||||
log "══════════════════════════════════════════════════════"
|
||||
log "Service script started"
|
||||
|
||||
API=$(getprop ro.build.version.sdk)
|
||||
log "Android API: $API"
|
||||
|
||||
# Wait for boot
|
||||
count=0
|
||||
while [ "$(getprop sys.boot_completed)" != "1" ] && [ $count -lt 60 ]; do
|
||||
sleep 1
|
||||
count=$((count + 1))
|
||||
done
|
||||
log "Boot completed (${count}s)"
|
||||
|
||||
# Skip for Android < 14
|
||||
if [ "$API" -lt 34 ]; then
|
||||
log "Android < 14, skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Find our cert
|
||||
CERT_FILE=$(find "$CERT_DIR" -maxdepth 1 -type f -name "*.0" 2>/dev/null | head -1)
|
||||
CERT_NAME=$(basename "$CERT_FILE" 2>/dev/null)
|
||||
|
||||
if [ -z "$CERT_NAME" ]; then
|
||||
log "ERROR: No certificate found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Certificate: $CERT_NAME"
|
||||
|
||||
# Check if already mounted correctly
|
||||
if [ -f "$APEX_CACERTS/$CERT_NAME" ]; then
|
||||
log "✓ Certificate already in APEX"
|
||||
else
|
||||
log "Certificate not in APEX, re-injecting..."
|
||||
|
||||
# Ensure tmpfs is mounted
|
||||
if ! mountpoint -q "$TEMP_DIR" 2>/dev/null; then
|
||||
mkdir -p "$TEMP_DIR"
|
||||
mount -t tmpfs tmpfs "$TEMP_DIR"
|
||||
cp -a "$APEX_CACERTS"/* "$TEMP_DIR/" 2>/dev/null
|
||||
cp -f "$CERT_FILE" "$TEMP_DIR/"
|
||||
chown -R 0:0 "$TEMP_DIR"
|
||||
chmod 755 "$TEMP_DIR"
|
||||
chmod 644 "$TEMP_DIR"/*
|
||||
|
||||
APEX_CONTEXT=$(ls -Zd "$APEX_CACERTS" 2>/dev/null | awk '{print $1}')
|
||||
[ -n "$APEX_CONTEXT" ] && chcon -R "$APEX_CONTEXT" "$TEMP_DIR" 2>/dev/null
|
||||
fi
|
||||
|
||||
# Mount globally
|
||||
mount --bind "$TEMP_DIR" "$APEX_CACERTS" 2>/dev/null
|
||||
nsenter --mount=/proc/1/ns/mnt -- mount --bind "$TEMP_DIR" "$APEX_CACERTS" 2>/dev/null
|
||||
fi
|
||||
|
||||
# CRITICAL: Mount in all zygote namespaces
|
||||
# This ensures ALL apps can see the certificate
|
||||
log "Injecting to zygote namespaces..."
|
||||
|
||||
for zygote in zygote zygote64; do
|
||||
PID=$(pidof "$zygote" 2>/dev/null)
|
||||
if [ -n "$PID" ]; then
|
||||
nsenter --mount=/proc/$PID/ns/mnt -- mount --bind "$TEMP_DIR" "$APEX_CACERTS" 2>/dev/null
|
||||
log " $zygote (PID $PID)"
|
||||
fi
|
||||
done
|
||||
|
||||
# Also inject to Settings app (for Trusted Credentials visibility)
|
||||
SETTINGS_PID=$(pidof com.android.settings 2>/dev/null)
|
||||
if [ -n "$SETTINGS_PID" ]; then
|
||||
nsenter --mount=/proc/$SETTINGS_PID/ns/mnt -- mount --bind "$TEMP_DIR" "$APEX_CACERTS" 2>/dev/null
|
||||
log " Settings app (PID $SETTINGS_PID)"
|
||||
fi
|
||||
|
||||
# Inject to ProxyPin if running
|
||||
PROXYPIN_PID=$(pidof com.proxy.pin 2>/dev/null)
|
||||
if [ -z "$PROXYPIN_PID" ]; then
|
||||
# Try alternative package names
|
||||
PROXYPIN_PID=$(pidof com.network.proxy 2>/dev/null)
|
||||
fi
|
||||
if [ -n "$PROXYPIN_PID" ]; then
|
||||
nsenter --mount=/proc/$PROXYPIN_PID/ns/mnt -- mount --bind "$TEMP_DIR" "$APEX_CACERTS" 2>/dev/null
|
||||
log " ProxyPin app (PID $PROXYPIN_PID)"
|
||||
fi
|
||||
|
||||
# Final verification
|
||||
sleep 2
|
||||
if [ -f "$APEX_CACERTS/$CERT_NAME" ]; then
|
||||
log "✓ SUCCESS: Certificate in APEX"
|
||||
else
|
||||
log "✗ Certificate may not be visible to all apps"
|
||||
fi
|
||||
|
||||
# Count
|
||||
APEX_COUNT=$(ls -1 "$APEX_CACERTS"/*.0 2>/dev/null | wc -l)
|
||||
log "APEX certificates: $APEX_COUNT"
|
||||
|
||||
log ""
|
||||
log "Service completed"
|
||||
log "══════════════════════════════════════════════════════"
|
||||
56
uninstall.sh
Normal file
56
uninstall.sh
Normal file
@@ -0,0 +1,56 @@
|
||||
#!/system/bin/sh
|
||||
# ProxyPin Certificate Installer - Uninstall Script
|
||||
# Author: firdausmntp
|
||||
# GitHub: https://github.com/firdausmntp/ProxyPin-cert-installer
|
||||
|
||||
MODDIR=${0%/*}
|
||||
LOG_FILE="/data/local/tmp/ProxyPinCert.log"
|
||||
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [uninstall] $1" >> "$LOG_FILE"
|
||||
}
|
||||
|
||||
log "Uninstall script started"
|
||||
|
||||
# Unmount APEX certificates if mounted
|
||||
unmount_apex() {
|
||||
local apex_dir="/apex/com.android.conscrypt/cacerts"
|
||||
local temp_dir="/data/local/tmp/proxypin-apex-ca"
|
||||
|
||||
# Unmount APEX directory
|
||||
if mountpoint -q "$apex_dir" 2>/dev/null; then
|
||||
umount "$apex_dir" 2>/dev/null
|
||||
log "Unmounted APEX CA directory"
|
||||
fi
|
||||
|
||||
# Try to unmount in all namespaces
|
||||
for pid in 1 $(pidof zygote 2>/dev/null) $(pidof zygote64 2>/dev/null); do
|
||||
if [ -d "/proc/$pid/ns/mnt" ]; then
|
||||
nsenter --mount=/proc/$pid/ns/mnt -- \
|
||||
umount "$apex_dir" 2>/dev/null
|
||||
fi
|
||||
done
|
||||
|
||||
# Unmount and remove temp directory
|
||||
if mountpoint -q "$temp_dir" 2>/dev/null; then
|
||||
umount "$temp_dir" 2>/dev/null
|
||||
log "Unmounted temp directory"
|
||||
fi
|
||||
rm -rf "$temp_dir" 2>/dev/null
|
||||
}
|
||||
|
||||
# Cleanup temporary files
|
||||
cleanup_temp() {
|
||||
rm -rf /data/local/tmp/apex-ca-copy 2>/dev/null
|
||||
rm -rf /data/local/tmp/apex-ca-reinject 2>/dev/null
|
||||
rm -rf /data/local/tmp/proxypin-apex-ca 2>/dev/null
|
||||
rm -f "$MODDIR/.apex_bypass_needed" 2>/dev/null
|
||||
log "Cleaned up temporary files"
|
||||
}
|
||||
|
||||
# Main
|
||||
unmount_apex
|
||||
cleanup_temp
|
||||
|
||||
log "Uninstall completed"
|
||||
log "NOTE: Reboot recommended to fully remove certificate from system"
|
||||
6
update.json
Normal file
6
update.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": "v1.0",
|
||||
"versionCode": 1,
|
||||
"zipUrl": "https://github.com/firdausmntp/ProxyPin-cert-installer/releases/download/v1.0/ProxyPin-Cert-Installer-v1.0.zip",
|
||||
"changelog": "v1.0 (2026-04-16)\n- Initial release\n- ProxyPin CA certificate pre-included (243f0bfb.0)\n- Multi-root support (Magisk/KernelSU/SukiSU/APatch)\n- Android 5.0-15+ support\n- APEX CA bypass for Android 14+\n- WebUI for status monitoring\n- SELinux enforcing compatible"
|
||||
}
|
||||
741
webroot/index.html
Normal file
741
webroot/index.html
Normal file
@@ -0,0 +1,741 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>ProxyPin Certificate Manager</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
|
||||
|
||||
:root {
|
||||
--bg: #0f1118;
|
||||
--surface: #181a24;
|
||||
--surface-2: #1e2130;
|
||||
--surface-3: #252838;
|
||||
--border: #2a2d3a;
|
||||
--text: #e2e4ea;
|
||||
--text-2: #9196a8;
|
||||
--text-3: #5e6374;
|
||||
--primary: #5b7bf7;
|
||||
--primary-dim: rgba(91, 123, 247, 0.12);
|
||||
--green: #3ddc84;
|
||||
--green-dim: rgba(61, 220, 132, 0.12);
|
||||
--red: #f25555;
|
||||
--red-dim: rgba(242, 85, 85, 0.12);
|
||||
--yellow: #f5c542;
|
||||
--yellow-dim: rgba(245, 197, 66, 0.12);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.5;
|
||||
min-height: 100vh;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.app {
|
||||
max-width: 460px;
|
||||
margin: 0 auto;
|
||||
padding: 0 16px 32px;
|
||||
}
|
||||
|
||||
/* Toolbar */
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px 0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
.toolbar-title {
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.toolbar-version {
|
||||
font-size: 12px;
|
||||
color: var(--text-3);
|
||||
background: var(--surface);
|
||||
padding: 4px 10px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
/* Section */
|
||||
.section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--text-3);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.6px;
|
||||
padding: 0 4px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* Card base */
|
||||
.card {
|
||||
background: var(--surface);
|
||||
border-radius: 14px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Status rows */
|
||||
.status-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 16px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.status-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.status-row .label {
|
||||
font-size: 14px;
|
||||
color: var(--text-2);
|
||||
}
|
||||
|
||||
.status-row .value {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 4px 10px;
|
||||
border-radius: 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.chip.ok { background: var(--green-dim); color: var(--green); }
|
||||
.chip.warn { background: var(--yellow-dim); color: var(--yellow); }
|
||||
.chip.err { background: var(--red-dim); color: var(--red); }
|
||||
.chip.info { background: var(--primary-dim); color: var(--primary); }
|
||||
.chip .dot {
|
||||
width: 6px; height: 6px;
|
||||
border-radius: 50%;
|
||||
background: currentColor;
|
||||
}
|
||||
|
||||
/* Upload area */
|
||||
.upload-card {
|
||||
background: var(--surface);
|
||||
border-radius: 14px;
|
||||
padding: 20px 16px;
|
||||
}
|
||||
|
||||
.upload-area {
|
||||
border: 1.5px dashed var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 28px 16px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s, background 0.2s;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.upload-area:active,
|
||||
.upload-area.over {
|
||||
border-color: var(--primary);
|
||||
background: var(--primary-dim);
|
||||
}
|
||||
|
||||
.upload-area input {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.upload-area .ic {
|
||||
font-size: 28px;
|
||||
opacity: 0.8;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.upload-area .t1 {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text-2);
|
||||
}
|
||||
|
||||
.upload-area .t2 {
|
||||
font-size: 12px;
|
||||
color: var(--text-3);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* Selected file */
|
||||
.file-row {
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-top: 14px;
|
||||
padding: 12px;
|
||||
background: var(--surface-2);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.file-row.show { display: flex; }
|
||||
|
||||
.file-row .name {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.file-row .size {
|
||||
font-size: 11px;
|
||||
color: var(--text-3);
|
||||
}
|
||||
|
||||
.file-row .rm {
|
||||
width: 28px; height: 28px;
|
||||
border: none;
|
||||
background: var(--red-dim);
|
||||
color: var(--red);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 13px;
|
||||
border-radius: 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s, transform 0.1s;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.btn:active { transform: scale(0.98); }
|
||||
.btn:disabled { opacity: 0.4; pointer-events: none; }
|
||||
|
||||
.btn-primary {
|
||||
background: var(--primary);
|
||||
color: #fff;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
color: var(--text-2);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.btn-outline:active {
|
||||
background: var(--surface-2);
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: var(--red);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Action list */
|
||||
.action-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 14px 16px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.action-item:last-child { border-bottom: none; }
|
||||
.action-item:active { background: var(--surface-2); }
|
||||
|
||||
.action-item .a-icon {
|
||||
width: 36px; height: 36px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 17px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.action-item .a-icon.blue { background: var(--primary-dim); }
|
||||
.action-item .a-icon.amber { background: var(--yellow-dim); }
|
||||
.action-item .a-icon.red { background: var(--red-dim); }
|
||||
.action-item .a-icon.green { background: var(--green-dim); }
|
||||
|
||||
.action-item .a-text .a-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.action-item .a-text .a-desc {
|
||||
font-size: 12px;
|
||||
color: var(--text-3);
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.action-item .arrow {
|
||||
margin-left: auto;
|
||||
color: var(--text-3);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Logs */
|
||||
.log-box {
|
||||
display: none;
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
margin: 12px 16px 16px;
|
||||
padding: 12px;
|
||||
max-height: 260px;
|
||||
overflow-y: auto;
|
||||
font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace;
|
||||
font-size: 11px;
|
||||
line-height: 1.7;
|
||||
color: var(--text-3);
|
||||
}
|
||||
|
||||
.log-box.show { display: block; }
|
||||
|
||||
.log-box::-webkit-scrollbar { width: 3px; }
|
||||
.log-box::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
|
||||
|
||||
.log-box .l { padding: 1px 0; word-break: break-all; }
|
||||
.log-box .l.g { color: var(--green); }
|
||||
.log-box .l.r { color: var(--red); }
|
||||
.log-box .l.y { color: var(--yellow); }
|
||||
.log-box .l.b { color: var(--primary); }
|
||||
|
||||
/* Progress */
|
||||
.progress {
|
||||
height: 3px;
|
||||
background: var(--surface-3);
|
||||
border-radius: 3px;
|
||||
margin-top: 12px;
|
||||
overflow: hidden;
|
||||
display: none;
|
||||
}
|
||||
.progress.show { display: block; }
|
||||
.progress .bar {
|
||||
height: 100%;
|
||||
background: var(--primary);
|
||||
width: 0%;
|
||||
border-radius: 3px;
|
||||
transition: width 0.4s ease;
|
||||
}
|
||||
|
||||
/* Toast */
|
||||
.toast-wrap {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 100;
|
||||
width: calc(100% - 32px);
|
||||
max-width: 428px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.toast {
|
||||
padding: 13px 16px;
|
||||
border-radius: 12px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
pointer-events: auto;
|
||||
animation: tIn 0.3s ease;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.toast.ok { background: #1a2e22; border: 1px solid #264d35; color: var(--green); }
|
||||
.toast.err { background: #2e1a1a; border: 1px solid #4d2626; color: var(--red); }
|
||||
.toast.inf { background: #1a1e2e; border: 1px solid #26354d; color: var(--primary); }
|
||||
.toast.out { animation: tOut 0.25s ease forwards; }
|
||||
|
||||
@keyframes tIn { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }
|
||||
@keyframes tOut { to { opacity: 0; transform: translateY(8px); } }
|
||||
|
||||
/* Modal */
|
||||
.modal-bg {
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0,0,0,0.6);
|
||||
z-index: 50;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
}
|
||||
.modal-bg.show { display: flex; }
|
||||
|
||||
.modal-box {
|
||||
background: var(--surface);
|
||||
border-radius: 18px;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
padding: 24px 20px;
|
||||
animation: mIn 0.25s ease;
|
||||
}
|
||||
|
||||
@keyframes mIn { from { opacity: 0; transform: translateY(40px); } to { opacity: 1; transform: translateY(0); } }
|
||||
|
||||
.modal-box h3 { font-size: 16px; font-weight: 600; margin-bottom: 6px; }
|
||||
.modal-box p { font-size: 13px; color: var(--text-2); margin-bottom: 18px; }
|
||||
.modal-box .btns { display: flex; gap: 10px; }
|
||||
.modal-box .btns .btn { flex: 1; }
|
||||
|
||||
/* Spinner */
|
||||
.spin {
|
||||
width: 15px; height: 15px;
|
||||
border: 2px solid rgba(255,255,255,0.25);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: sp 0.6s linear infinite;
|
||||
display: inline-block;
|
||||
}
|
||||
@keyframes sp { to { transform: rotate(360deg); } }
|
||||
|
||||
/* Footer */
|
||||
.foot {
|
||||
text-align: center;
|
||||
padding: 16px 0 8px;
|
||||
font-size: 11px;
|
||||
color: var(--text-3);
|
||||
}
|
||||
.foot a { color: var(--text-2); text-decoration: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="toast-wrap" id="toasts"></div>
|
||||
|
||||
<div class="modal-bg" id="rebootModal">
|
||||
<div class="modal-box">
|
||||
<h3>Reboot device?</h3>
|
||||
<p>Device will restart to apply certificate changes. Make sure all work is saved.</p>
|
||||
<div class="btns">
|
||||
<button class="btn btn-outline" onclick="closeModal()">Cancel</button>
|
||||
<button class="btn btn-danger" onclick="doReboot()">Reboot</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="app">
|
||||
<div class="toolbar">
|
||||
<span class="toolbar-title">ProxyPin Cert Manager</span>
|
||||
<span class="toolbar-version">v1.0</span>
|
||||
</div>
|
||||
|
||||
<!-- Status -->
|
||||
<div class="section">
|
||||
<div class="section-label">Status</div>
|
||||
<div class="card">
|
||||
<div class="status-row">
|
||||
<span class="label">Module</span>
|
||||
<span id="sModule"><span class="chip info"><span class="dot"></span> checking</span></span>
|
||||
</div>
|
||||
<div class="status-row">
|
||||
<span class="label">Certificate</span>
|
||||
<span id="sCert"><span class="chip info"><span class="dot"></span> checking</span></span>
|
||||
</div>
|
||||
<div class="status-row">
|
||||
<span class="label">APEX Injection</span>
|
||||
<span id="sApex"><span class="chip info"><span class="dot"></span> checking</span></span>
|
||||
</div>
|
||||
<div class="status-row">
|
||||
<span class="label">Android</span>
|
||||
<span id="sAndroid" class="value" style="color:var(--text-2)">—</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upload -->
|
||||
<div class="section">
|
||||
<div class="section-label">Certificate</div>
|
||||
<div class="upload-card">
|
||||
<div class="upload-area" id="dropZone">
|
||||
<div class="ic">📄</div>
|
||||
<div class="t1">Select certificate file</div>
|
||||
<div class="t2">Use hash filename format (.0), example: 243f0bfb.0</div>
|
||||
<input type="file" id="fileIn" accept=".0">
|
||||
</div>
|
||||
<div class="file-row" id="fileRow">
|
||||
<span class="name" id="fName">—</span>
|
||||
<span class="size" id="fSize">—</span>
|
||||
<button class="rm" onclick="clearFile()">✕</button>
|
||||
</div>
|
||||
<div class="progress" id="prog"><div class="bar" id="progBar"></div></div>
|
||||
<button class="btn btn-primary" id="installBtn" disabled onclick="install()">Install Certificate</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="section">
|
||||
<div class="section-label">Actions</div>
|
||||
<div class="card">
|
||||
<div class="action-item" onclick="reinject()">
|
||||
<div class="a-icon blue">💉</div>
|
||||
<div class="a-text">
|
||||
<div class="a-title">Re-inject Certificate</div>
|
||||
<div class="a-desc">Force APEX namespace injection</div>
|
||||
</div>
|
||||
<span class="arrow">›</span>
|
||||
</div>
|
||||
<div class="action-item" onclick="toggleLog()">
|
||||
<div class="a-icon amber">📋</div>
|
||||
<div class="a-text">
|
||||
<div class="a-title">View Logs</div>
|
||||
<div class="a-desc">Module operation logs</div>
|
||||
</div>
|
||||
<span class="arrow">›</span>
|
||||
</div>
|
||||
<div class="action-item" onclick="checkAll()">
|
||||
<div class="a-icon green">🔄</div>
|
||||
<div class="a-text">
|
||||
<div class="a-title">Refresh Status</div>
|
||||
<div class="a-desc">Recheck module and certificate</div>
|
||||
</div>
|
||||
<span class="arrow">›</span>
|
||||
</div>
|
||||
<div class="action-item" onclick="showReboot()">
|
||||
<div class="a-icon red">⏻</div>
|
||||
<div class="a-text">
|
||||
<div class="a-title">Reboot Device</div>
|
||||
<div class="a-desc">Apply pending changes</div>
|
||||
</div>
|
||||
<span class="arrow">›</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-box" id="logBox"></div>
|
||||
</div>
|
||||
|
||||
<div class="foot">
|
||||
<a href="https://github.com/firdausmntp/ProxyPin-cert-installer">GitHub</a>
|
||||
·
|
||||
<a href="https://github.com/wanghongenpin/network_proxy_flutter">ProxyPin</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const MOD = '/data/adb/modules/proxypin-cert-installer';
|
||||
const CERTS = MOD + '/system/etc/security/cacerts';
|
||||
const LOG = '/data/local/tmp/ProxyPinCert.log';
|
||||
|
||||
function exec(cmd) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const cb = '_c' + Date.now() + Math.random().toString(36).slice(2,6);
|
||||
window[cb] = (e, out, err) => { delete window[cb]; e === 0 ? resolve(out||'') : reject(new Error(err||'err')); };
|
||||
try { ksu.exec(cmd, '{}', cb); } catch(x) { delete window[cb]; reject(x); }
|
||||
});
|
||||
}
|
||||
|
||||
async function run(cmd) { try { return await exec(cmd); } catch(e) { return ''; } }
|
||||
|
||||
// Toast
|
||||
function toast(msg, type='inf') {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'toast ' + type;
|
||||
el.textContent = msg;
|
||||
document.getElementById('toasts').appendChild(el);
|
||||
setTimeout(() => { el.classList.add('out'); setTimeout(() => el.remove(), 250); }, 3000);
|
||||
}
|
||||
|
||||
// Status
|
||||
async function checkAll() {
|
||||
const sM = document.getElementById('sModule');
|
||||
const sC = document.getElementById('sCert');
|
||||
const sA = document.getElementById('sApex');
|
||||
const sV = document.getElementById('sAndroid');
|
||||
|
||||
// Module
|
||||
try {
|
||||
const exists = (await run(`[ -d "${MOD}" ] && echo y`)).trim();
|
||||
const disabled = (await run(`[ -f "${MOD}/disable" ] && echo y`)).trim();
|
||||
if (exists === 'y' && disabled !== 'y') sM.innerHTML = '<span class="chip ok"><span class="dot"></span> Active</span>';
|
||||
else if (disabled === 'y') sM.innerHTML = '<span class="chip warn"><span class="dot"></span> Disabled</span>';
|
||||
else sM.innerHTML = '<span class="chip err"><span class="dot"></span> Not found</span>';
|
||||
} catch(e) { sM.innerHTML = '<span class="chip warn"><span class="dot"></span> Unknown</span>'; }
|
||||
|
||||
// Cert
|
||||
try {
|
||||
const n = parseInt((await run(`find "${CERTS}" -maxdepth 1 -type f -name "*.0" 2>/dev/null | wc -l`)).trim()) || 0;
|
||||
if (n > 0) sC.innerHTML = '<span class="chip ok"><span class="dot"></span> Installed (' + n + ')</span>';
|
||||
else sC.innerHTML = '<span class="chip err"><span class="dot"></span> Missing</span>';
|
||||
} catch(e) { sC.innerHTML = '<span class="chip warn"><span class="dot"></span> Error</span>'; }
|
||||
|
||||
// APEX
|
||||
try {
|
||||
const api = parseInt((await run('getprop ro.build.version.sdk')).trim()) || 0;
|
||||
if (api >= 34) {
|
||||
const cn = (await run(`find "${CERTS}" -maxdepth 1 -type f -name "*.0" 2>/dev/null | head -1 | xargs basename 2>/dev/null`)).trim();
|
||||
if (cn) {
|
||||
const ok = (await run(`[ -f "/apex/com.android.conscrypt/cacerts/${cn}" ] && echo y`)).trim();
|
||||
sA.innerHTML = ok === 'y'
|
||||
? '<span class="chip ok"><span class="dot"></span> Injected</span>'
|
||||
: '<span class="chip warn"><span class="dot"></span> Pending</span>';
|
||||
} else {
|
||||
sA.innerHTML = '<span class="chip err"><span class="dot"></span> No cert</span>';
|
||||
}
|
||||
} else {
|
||||
sA.innerHTML = '<span class="chip info"><span class="dot"></span> N/A</span>';
|
||||
}
|
||||
} catch(e) { sA.innerHTML = '<span class="chip info"><span class="dot"></span> N/A</span>'; }
|
||||
|
||||
// Android
|
||||
try {
|
||||
const ver = (await run('getprop ro.build.version.release')).trim();
|
||||
const api = (await run('getprop ro.build.version.sdk')).trim();
|
||||
sV.textContent = ver + ' (API ' + api + ')';
|
||||
} catch(e) { sV.textContent = '—'; }
|
||||
}
|
||||
|
||||
// File
|
||||
const fileIn = document.getElementById('fileIn');
|
||||
const dropZone = document.getElementById('dropZone');
|
||||
|
||||
fileIn.addEventListener('change', onFile);
|
||||
dropZone.addEventListener('dragover', e => { e.preventDefault(); dropZone.classList.add('over'); });
|
||||
dropZone.addEventListener('dragleave', () => dropZone.classList.remove('over'));
|
||||
dropZone.addEventListener('drop', e => { e.preventDefault(); dropZone.classList.remove('over'); fileIn.files = e.dataTransfer.files; onFile(); });
|
||||
|
||||
function onFile() {
|
||||
const f = fileIn.files[0];
|
||||
if (!f) return;
|
||||
const certName = f.name.trim().toLowerCase();
|
||||
const ok = /^[0-9a-f]{8}\.0$/.test(certName);
|
||||
if (!ok) {
|
||||
toast('Filename must be hash format: 8 hex chars + .0', 'err');
|
||||
fileIn.value = '';
|
||||
return;
|
||||
}
|
||||
document.getElementById('fName').textContent = f.name;
|
||||
document.getElementById('fSize').textContent = fmt(f.size);
|
||||
document.getElementById('fileRow').classList.add('show');
|
||||
document.getElementById('installBtn').disabled = false;
|
||||
}
|
||||
|
||||
function clearFile() {
|
||||
fileIn.value = '';
|
||||
document.getElementById('fileRow').classList.remove('show');
|
||||
document.getElementById('installBtn').disabled = true;
|
||||
}
|
||||
|
||||
function fmt(b) {
|
||||
if (b < 1024) return b + ' B';
|
||||
return (b/1024).toFixed(1) + ' KB';
|
||||
}
|
||||
|
||||
// Install
|
||||
async function install() {
|
||||
const f = fileIn.files[0];
|
||||
if (!f) return;
|
||||
const certName = f.name.trim().toLowerCase();
|
||||
if (!/^[0-9a-f]{8}\.0$/.test(certName)) {
|
||||
toast('Invalid certificate filename', 'err');
|
||||
return;
|
||||
}
|
||||
|
||||
const btn = document.getElementById('installBtn');
|
||||
const prog = document.getElementById('prog');
|
||||
const bar = document.getElementById('progBar');
|
||||
const tmpPath = '/data/local/tmp/proxypin-upload.cert';
|
||||
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="spin"></span> Installing...';
|
||||
prog.classList.add('show');
|
||||
bar.style.width = '30%';
|
||||
|
||||
try {
|
||||
const b64 = await new Promise((ok, no) => {
|
||||
const r = new FileReader();
|
||||
r.onload = () => ok(r.result.split(',')[1]);
|
||||
r.onerror = no;
|
||||
r.readAsDataURL(f);
|
||||
});
|
||||
bar.style.width = '60%';
|
||||
await run(`mkdir -p "${CERTS}"; rm -f "${tmpPath}"`);
|
||||
await run(`echo '${b64}' | base64 -d > "${tmpPath}"`);
|
||||
await run(`install -m 0644 -o 0 -g 0 "${tmpPath}" "${CERTS}/${certName}"`);
|
||||
await run(`rm -f "${tmpPath}"`);
|
||||
bar.style.width = '90%';
|
||||
const v = (await run(`[ -f "${CERTS}/${certName}" ] && echo y`)).trim();
|
||||
if (v === 'y') {
|
||||
bar.style.width = '100%';
|
||||
toast('Certificate installed. Reboot to apply.', 'ok');
|
||||
setTimeout(() => { clearFile(); prog.classList.remove('show'); bar.style.width='0'; checkAll(); }, 1200);
|
||||
} else throw new Error('Write failed');
|
||||
} catch(e) {
|
||||
toast('Install failed: ' + e.message, 'err');
|
||||
prog.classList.remove('show');
|
||||
bar.style.width = '0';
|
||||
}
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Install Certificate';
|
||||
}
|
||||
|
||||
// Actions
|
||||
async function reinject() {
|
||||
toast('Running re-injection...', 'inf');
|
||||
await run(`sh ${MOD}/service.sh`);
|
||||
toast('Re-injection done', 'ok');
|
||||
setTimeout(checkAll, 800);
|
||||
}
|
||||
|
||||
let logOpen = false;
|
||||
async function toggleLog() {
|
||||
const box = document.getElementById('logBox');
|
||||
if (logOpen) { box.classList.remove('show'); logOpen = false; return; }
|
||||
const raw = await run(`tail -60 "${LOG}" 2>/dev/null || echo "No logs available"`);
|
||||
const lines = raw.split('\n').filter(l => l.trim());
|
||||
box.innerHTML = lines.map(l => {
|
||||
let c = '';
|
||||
if (/✓|SUCCESS/.test(l)) c = 'g';
|
||||
else if (/ERROR|✗|FAIL/.test(l)) c = 'r';
|
||||
else if (/WARNING|⚠/.test(l)) c = 'y';
|
||||
else if (/═|╔|╚|===/.test(l)) c = 'b';
|
||||
return '<div class="l ' + c + '">' + esc(l) + '</div>';
|
||||
}).join('');
|
||||
box.classList.add('show');
|
||||
box.scrollTop = box.scrollHeight;
|
||||
logOpen = true;
|
||||
}
|
||||
|
||||
function esc(s) { const d = document.createElement('span'); d.textContent = s; return d.innerHTML; }
|
||||
|
||||
function showReboot() { document.getElementById('rebootModal').classList.add('show'); }
|
||||
function closeModal() { document.getElementById('rebootModal').classList.remove('show'); }
|
||||
async function doReboot() { closeModal(); toast('Rebooting...', 'inf'); await run('reboot'); }
|
||||
|
||||
document.addEventListener('DOMContentLoaded', checkAll);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user