Skip to content

Commit 0ffaf4a

Browse files
committed
Add proguard optimization to the Android release build
This allows us to reliably remove all `*mock*` references from the Kotlin verifier code in production builds. By doing so, it is statically provable that test-only code can't be called despite Java/Kotlin not supporting true compile-time guards like Rust's `cfg!`. By ensuring branches which access `mock` functionality are removed at compile-time by `java`, `proguard` is able to delete the class parts natually because they are not referenced anywhere. Additionally, this has postiive size/performance impacts as it performs optimizationson the bytecode.
1 parent a0e5cc5 commit 0ffaf4a

File tree

5 files changed

+97
-1
lines changed

5 files changed

+97
-1
lines changed

.github/workflows/ci.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,14 @@ jobs:
159159
run: |
160160
cd ./android
161161
./gradlew ktlint
162+
163+
verify_android:
164+
name: Verify Android artifacts
165+
runs-on: ubuntu-latest
166+
steps:
167+
- uses: actions/checkout@v3
168+
with:
169+
persist-credentials: false
170+
171+
- name: Verify release artifact
172+
run: ./ci/verify_android_release.sh

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
/target
22
.DS_Store
33
/.idea
4+
5+
/android/verification/

android/rustls-platform-verifier/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ android {
3939

4040
buildTypes {
4141
release {
42-
minifyEnabled false
42+
minifyEnabled true
43+
proguardFiles "proguard-rules.pro"
4344
}
4445

4546
debug {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
-keepattributes SourceFile,LineNumberTable,MethodAttributes
9+
# Everything is called via the JNI, so code must not be removed or renamed
10+
# in a way Rust can't see.
11+
-dontobfuscate
12+
13+
# We need this function (and class) in all builds for Rust to call because its the JNI entrypoint
14+
# for the verifier functionality.
15+
-keep class org.rustls.platformverifier.CertificateVerifier {
16+
verifyCertificateChain(
17+
android.content.Context,
18+
java.lang.String,
19+
java.lang.String,
20+
java.lang.String[],
21+
byte[],
22+
long,
23+
byte[][]
24+
);
25+
}
26+
27+
# We need these classes so Rust can load their class definitions at runtime
28+
# and access their fields at runtime.
29+
-keep class org.rustls.platformverifier.StatusCode { *; }
30+
-keep class org.rustls.platformverifier.VerificationResult { *; }
31+
32+
# Note: We don't explicitly tell Proguard to remove test-only methods. They are instead
33+
# removed as dead code because `javac` removes all references to them when not building
34+
# in a test configuration.
35+
36+
# This can be uncommented during development if needed to quickly check if
37+
# a few test-only methods/fields are being removed by build time.
38+
# -whyareyoukeeping class org.rustls.platformverifier.CertificateVerifier {
39+
# private java.security.KeyStore mockKeystore;
40+
# addMockRoot(byte[]);
41+
#}

ci/verify_android_release.sh

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/usr/bin/env bash
2+
3+
# This script's purpose is to verify that no test-only code is present inside of the release-mode Android artifact.
4+
# It is validating that `javac` is performing the dead-code elimiation we expect and that `proguard` is deleting the
5+
# unreferenced test code. This can be ran both locally and in CI.
6+
#
7+
# It accomplishes this goal by building the artifact and then running a decompiler on it to look for names we expect or do not.
8+
9+
set -euo pipefail
10+
11+
mkdir -p ./android/verification
12+
13+
pushd ./android/
14+
15+
./gradlew clean
16+
./gradlew assembleRelease
17+
18+
pushd ./verification
19+
20+
if [ ! -f "./bin/jadx" ]; then
21+
echo "Decompiler not yet installed, downloading jadx"
22+
curl -L https://github.com/skylot/jadx/releases/download/v1.4.7/jadx-1.4.7.zip --output jadx.zip
23+
unzip jadx.zip
24+
echo "jadx downloaded"
25+
fi
26+
27+
./bin/jadx -d decompiled ../rustls-platform-verifier/build/outputs/aar/rustls-platform-verifier-release.aar
28+
29+
if grep -r -q "mock" ./decompiled; then
30+
echo "❌ Test-only code exists in release artifact! Please review changes made to locate the cause".
31+
exit 1
32+
else
33+
echo "✅ No test-only code found in release artifact"
34+
fi
35+
36+
if grep -r -q "verifyCertificateChain" ./decompiled; then
37+
echo "✅ JNI entrypoint present in release artifact"
38+
else
39+
echo "❌ JNI entrypoint not found in release artifact! Please review changes made to optimization rules which might cause this"
40+
exit 1
41+
fi

0 commit comments

Comments
 (0)