mirror of
https://gitee.com/wanghongenpin/Magisk-ProxyPinCA.git
synced 2026-05-11 15:24:58 +08:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4ff2b93e5 | ||
|
|
aeabd988b1 | ||
|
|
91ae85d6e0 | ||
|
|
eff32b6154 | ||
|
|
8aa5113ab5 | ||
|
|
0171e496d3 | ||
|
|
0ef8eb8e45 | ||
|
|
05f5f2727f | ||
|
|
7e89b0c61c | ||
|
|
80c5ff5151 | ||
|
|
bb7cfd50f7 | ||
|
|
1512fee934 | ||
|
|
7300f853a6 |
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
|
# Build artifacts
|
||||||
*.class
|
*.zip
|
||||||
|
*.tar.gz
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
_build_temp/
|
||||||
|
|
||||||
|
# Build scripts (personal use only)
|
||||||
|
build.py
|
||||||
|
build.sh
|
||||||
|
build.bat
|
||||||
|
|
||||||
|
# Logs
|
||||||
*.log
|
*.log
|
||||||
*.pyc
|
ProxyPinCert.log
|
||||||
*.swp
|
ProxyPinCA.log
|
||||||
.DS_Store
|
|
||||||
.atom/
|
# IDE
|
||||||
.buildlog/
|
|
||||||
.history
|
|
||||||
.svn/
|
|
||||||
migrate_working_dir/
|
|
||||||
Podfile.lock
|
|
||||||
# IntelliJ related
|
|
||||||
*.iml
|
|
||||||
*.ipr
|
|
||||||
*.iws
|
|
||||||
.idea/
|
.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
|
||||||
207
README.md
207
README.md
@@ -1,5 +1,204 @@
|
|||||||
# ProxyPin Certificate
|
# 📱 ProxyPin Certificate Installer
|
||||||
这是一个Magisk 模块 用于安装ProxyPin系统证书, 安装完后需要重启手机.
|
|
||||||
|
|
||||||
抓包下载地址
|
<p align="center">
|
||||||
https://github.com/wanghongenpin/network_proxy_flutter
|
<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>
|
||||||
|
|
||||||
|
<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
|
||||||
215
customize.sh
215
customize.sh
@@ -1,4 +1,215 @@
|
|||||||
#!/system/bin/sh
|
#!/system/bin/sh
|
||||||
|
# Android 5.0 - 16 (API 21-36) compatible
|
||||||
|
SKIPUNZIP=0
|
||||||
|
|
||||||
ui_print $MODPATH
|
#################
|
||||||
ui_print "安装成功,重启手机后去系统证书查看ProxyPinCA是否生效."
|
# Helper Functions
|
||||||
|
#################
|
||||||
|
|
||||||
|
print_banner() {
|
||||||
|
ui_print "╔════════════════════════════════════════╗"
|
||||||
|
ui_print "║ ProxyPin Certificate Installer v1.0 ║"
|
||||||
|
ui_print "║ by firdausmntp ║"
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
#################
|
||||||
|
# Compatibility Check
|
||||||
|
#################
|
||||||
|
|
||||||
|
check_compatibility() {
|
||||||
|
# Check API level
|
||||||
|
API=$(getprop ro.build.version.sdk)
|
||||||
|
[ -z "$API" ] && API=21
|
||||||
|
|
||||||
|
if [ "$API" -lt 21 ]; then
|
||||||
|
abort "! ERROR: Minimum Android 5.0 (API 21) required!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
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
|
||||||
15
module.prop
15
module.prop
@@ -1,6 +1,11 @@
|
|||||||
id=ProxyPinCa
|
id=proxypin-cert-installer
|
||||||
name=ProxyPinCa
|
name=ProxyPin Certificate Installer
|
||||||
version=1.0.0
|
version=v1.0
|
||||||
versionCode=1
|
versionCode=1
|
||||||
author=ProxyPin
|
author=firdausmntp
|
||||||
description=ProxyPin certificate.
|
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
|
||||||
|
|||||||
137
post-fs-data.sh
Normal file
137
post-fs-data.sh
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
#!/system/bin/sh
|
||||||
|
# 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"
|
||||||
|
|
||||||
|
# Initialize logging
|
||||||
|
mkdir -p /data/local/tmp 2>/dev/null
|
||||||
|
echo "" > "$LOG_FILE"
|
||||||
|
|
||||||
|
log() {
|
||||||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
log "╔══════════════════════════════════════════════════════╗"
|
||||||
|
log "║ ProxyPin Certificate Installer v1.0 ║"
|
||||||
|
log "╚══════════════════════════════════════════════════════╝"
|
||||||
|
log ""
|
||||||
|
log "Post-fs-data started"
|
||||||
|
log "Module: $MODDIR"
|
||||||
|
|
||||||
|
API=$(getprop ro.build.version.sdk)
|
||||||
|
ANDROID_VERSION=$(getprop ro.build.version.release)
|
||||||
|
log "Android: $ANDROID_VERSION (API $API)"
|
||||||
|
|
||||||
|
# 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
|
||||||
|
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