diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..7ffd891
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,113 @@
+name: CI
+
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - master
+
+permissions:
+ contents: read
+
+concurrency:
+ group: ci-${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ build:
+ name: Format, lint, and build
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v6
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v5
+ with:
+ distribution: temurin
+ java-version: 17
+
+ - name: Set up Gradle
+ uses: gradle/actions/setup-gradle@v6
+
+ - name: Check formatting
+ run: ./gradlew spotlessCheck
+
+ - name: Run Android lint
+ run: ./gradlew lint
+
+ - name: Build TrustKit library
+ run: ./gradlew :trustkit:assembleRelease
+
+ - name: Build Java demo app
+ run: ./gradlew :app:assembleDebug
+
+ - name: Build Kotlin demo app
+ run: ./gradlew :demoappkotlin:assembleDebug
+
+ - name: Upload build reports
+ if: failure()
+ uses: actions/upload-artifact@v7
+ with:
+ name: build-reports
+ path: |
+ **/build/reports/**
+ **/build/outputs/logs/**
+ if-no-files-found: ignore
+ retention-days: 7
+
+ instrumentation-tests:
+ name: Instrumentation tests (API ${{ matrix.api-level }})
+ needs: build
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ strategy:
+ fail-fast: false
+ matrix:
+ api-level:
+ - 27
+ - 36
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v6
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v5
+ with:
+ distribution: temurin
+ java-version: 17
+
+ - name: Set up Gradle
+ uses: gradle/actions/setup-gradle@v6
+
+ - name: Enable KVM
+ run: |
+ echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \
+ | sudo tee /etc/udev/rules.d/99-kvm4all.rules
+ sudo udevadm control --reload-rules
+ sudo udevadm trigger --name-match=kvm
+
+ - name: Run instrumentation tests
+ uses: reactivecircus/android-emulator-runner@v2.37.0
+ with:
+ api-level: ${{ matrix.api-level }}
+ arch: x86_64
+ target: default
+ script: ./gradlew :trustkit:connectedDebugAndroidTest
+
+ - name: Upload instrumentation test reports
+ if: failure()
+ uses: actions/upload-artifact@v7
+ with:
+ name: instrumentation-reports-api-${{ matrix.api-level }}
+ path: |
+ trustkit/build/reports/androidTests/**
+ trustkit/build/outputs/androidTest-results/**
+ trustkit/build/outputs/managed_device_android_test_additional_output/**
+ if-no-files-found: ignore
+ retention-days: 7
diff --git a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/pinning/SSLSocketFactoryTest.java b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/pinning/SSLSocketFactoryTest.java
index b71b67e..0592c94 100644
--- a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/pinning/SSLSocketFactoryTest.java
+++ b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/pinning/SSLSocketFactoryTest.java
@@ -26,6 +26,7 @@
import java.util.HashSet;
import java.util.List;
import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import org.junit.Before;
import org.junit.BeforeClass;
@@ -113,6 +114,22 @@ public void setUp() {
TestableTrustKit.reset();
}
+ private static Socket createSocketAndStartHandshake(
+ SSLSocketFactory socketFactory, String serverHostname) throws IOException {
+ SSLSocket socket = (SSLSocket) socketFactory.createSocket(serverHostname, 443);
+ try {
+ socket.startHandshake();
+ return socket;
+ } catch (IOException handshakeException) {
+ try {
+ socket.close();
+ } catch (IOException closeException) {
+ handshakeException.addSuppressed(closeException);
+ }
+ throw handshakeException;
+ }
+ }
+
// region Tests for when the domain is pinned
@Test
public void testPinnedDomainExpiredChain() throws IOException {
@@ -125,7 +142,7 @@ public void testPinnedDomainExpiredChain() throws IOException {
SSLSocketFactory test = TestableTrustKit.getInstance().getSSLSocketFactory(serverHostname);
boolean didReceiveHandshakeError = false;
try {
- test.createSocket(serverHostname, 443).getInputStream();
+ createSocketAndStartHandshake(test, serverHostname).close();
} catch (SSLHandshakeException e) {
if ((e.getCause() instanceof CertificateException
&& !(e.getCause().getMessage().startsWith("Pin verification failed")))) {
@@ -164,7 +181,7 @@ public void testPinnedDomainWrongHostnameChain() throws IOException {
SSLSocketFactory test = TestableTrustKit.getInstance().getSSLSocketFactory(serverHostname);
boolean didReceiveHandshakeError = false;
try {
- test.createSocket(serverHostname, 443).getInputStream();
+ createSocketAndStartHandshake(test, serverHostname).close();
} catch (SSLHandshakeException e) {
if ((e.getCause() instanceof CertificateException
&& !(e.getCause().getMessage().startsWith("Pin verification failed")))) {
@@ -200,8 +217,7 @@ public void testPinnedDomainSuccessAnchor() throws IOException {
// Create a TrustKit SocketFactory and ensure the connection succeeds
SSLSocketFactory test = TestableTrustKit.getInstance().getSSLSocketFactory(serverHostname);
- Socket socket = test.createSocket(serverHostname, 443);
- socket.getInputStream();
+ Socket socket = createSocketAndStartHandshake(test, serverHostname);
assertTrue(socket.isConnected());
socket.close();
@@ -228,8 +244,7 @@ public void testPinnedDomainSuccessLeaf() throws IOException {
// Create a TrustKit SocketFactory and ensure the connection succeeds
SSLSocketFactory test = TestableTrustKit.getInstance().getSSLSocketFactory(serverHostname);
- Socket socket = test.createSocket(serverHostname, 443);
- socket.getInputStream();
+ Socket socket = createSocketAndStartHandshake(test, serverHostname);
assertTrue(socket.isConnected());
socket.close();
@@ -263,7 +278,7 @@ public void testPinnedDomainInvalidPin() throws IOException {
SSLSocketFactory test = TestableTrustKit.getInstance().getSSLSocketFactory(serverHostname);
boolean didReceivePinningError = false;
try {
- test.createSocket(serverHostname, 443).getInputStream();
+ createSocketAndStartHandshake(test, serverHostname).close();
} catch (SSLHandshakeException e) {
if ((e.getCause() instanceof CertificateException
&& (e.getCause().getMessage().startsWith("Pin verification failed")))) {
@@ -294,8 +309,7 @@ public void testPinnedDomainInvalidPinAndPinningNotEnforced() throws IOException
// Create a TrustKit SocketFactory and ensure the connection succeeds
SSLSocketFactory test = TestableTrustKit.getInstance().getSSLSocketFactory(serverHostname);
- Socket socket = test.createSocket(serverHostname, 443);
- socket.getInputStream();
+ Socket socket = createSocketAndStartHandshake(test, serverHostname);
assertTrue(socket.isConnected());
socket.close();
@@ -327,8 +341,7 @@ public void testPinnedDomainInvalidPinAndPolicyExpired() throws IOException {
// Create a TrustKit SocketFactory and ensure the connection succeeds
SSLSocketFactory test = TestableTrustKit.getInstance().getSSLSocketFactory(serverHostname);
- Socket socket = test.createSocket(serverHostname, 443);
- socket.getInputStream();
+ Socket socket = createSocketAndStartHandshake(test, serverHostname);
assertTrue(socket.isConnected());
socket.close();
@@ -354,7 +367,7 @@ public void testPinnedDomainUntrustedChainAndPinningNotEnforced() throws IOExcep
SSLSocketFactory test = TestableTrustKit.getInstance().getSSLSocketFactory(serverHostname);
boolean didReceiveHandshakeError = false;
try {
- test.createSocket(serverHostname, 443).getInputStream();
+ createSocketAndStartHandshake(test, serverHostname).close();
} catch (SSLHandshakeException e) {
if ((e.getCause() instanceof CertificateException
&& !(e.getCause().getMessage().startsWith("Pin verification failed")))) {
@@ -434,8 +447,7 @@ public void testDebugOverridesInvalidPinButOverridePins()
// This means that debug-overrides properly enables the supplied debug CA cert and
// disables pinning when overridePins is true
SSLSocketFactory test = TestableTrustKit.getInstance().getSSLSocketFactory(serverHostname);
- Socket socket = test.createSocket(serverHostname, 443);
- socket.getInputStream();
+ Socket socket = createSocketAndStartHandshake(test, serverHostname);
assertTrue(socket.isConnected());
socket.close();
@@ -505,7 +517,7 @@ public void testDebugOverridesButAppNotDebuggable() throws IOException, Certific
SSLSocketFactory test = TestableTrustKit.getInstance().getSSLSocketFactory(serverHostname);
boolean didReceiveHandshakeError = false;
try {
- test.createSocket(serverHostname, 443).getInputStream();
+ createSocketAndStartHandshake(test, serverHostname).close();
} catch (SSLHandshakeException e) {
didReceiveHandshakeError = true;
}
@@ -575,7 +587,7 @@ public void testDebugOverridesInvalidPin() throws IOException, CertificateExcept
SSLSocketFactory test = TestableTrustKit.getInstance().getSSLSocketFactory(serverHostname);
boolean didReceivePinningError = false;
try {
- test.createSocket(serverHostname, 443).getInputStream();
+ createSocketAndStartHandshake(test, serverHostname).close();
} catch (SSLHandshakeException e) {
if ((e.getCause() instanceof CertificateException
&& (e.getCause().getMessage().startsWith("Pin verification failed")))) {
@@ -631,7 +643,7 @@ public void testNonPinnedDomainUntrustedRootChain() throws IOException {
SSLSocketFactory test = TestableTrustKit.getInstance().getSSLSocketFactory(serverHostname);
boolean didReceiveHandshakeError = false;
try {
- test.createSocket(serverHostname, 443).getInputStream();
+ createSocketAndStartHandshake(test, serverHostname).close();
} catch (SSLHandshakeException e) {
if ((e.getCause() instanceof CertificateException
&& !(e.getCause().getMessage().startsWith("Pin verification failed")))) {
@@ -663,8 +675,7 @@ public void testNonPinnedDomainSuccess() throws IOException {
// Create a TrustKit SocketFactory and ensure the connection succeeds
SSLSocketFactory test = TestableTrustKit.getInstance().getSSLSocketFactory(serverHostname);
- Socket socket = test.createSocket(serverHostname, 443);
- socket.getInputStream();
+ Socket socket = createSocketAndStartHandshake(test, serverHostname);
assertTrue(socket.isConnected());
socket.close();
@@ -728,8 +739,7 @@ public void testDebugOverrides() throws IOException, CertificateException {
// Create a TrustKit SocketFactory and ensure the connection succeeds
// This means that debug-overrides properly enables the supplied debug CA cert
SSLSocketFactory test = TestableTrustKit.getInstance().getSSLSocketFactory(serverHostname);
- Socket socket = test.createSocket(serverHostname, 443);
- socket.getInputStream();
+ Socket socket = createSocketAndStartHandshake(test, serverHostname);
assertTrue(socket.isConnected());
socket.close();
@@ -789,8 +799,7 @@ public void testDebugOverridesSystemCa() throws IOException, CertificateExceptio
// Create a TrustKit SocketFactory and ensure the connection succeeds
// This means that debug-overrides does not disable the System CAs
SSLSocketFactory test = TestableTrustKit.getInstance().getSSLSocketFactory(serverHostname);
- Socket socket = test.createSocket(serverHostname, 443);
- socket.getInputStream();
+ Socket socket = createSocketAndStartHandshake(test, serverHostname);
assertTrue(socket.isConnected());
socket.close();
diff --git a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/reporting/BackgroundReporterTaskTest.java b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/reporting/BackgroundReporterTaskTest.java
index 8c01c99..c8d516c 100644
--- a/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/reporting/BackgroundReporterTaskTest.java
+++ b/trustkit/src/androidTest/java/com/datatheorem/android/trustkit/reporting/BackgroundReporterTaskTest.java
@@ -92,7 +92,7 @@ public void testExecuteSucceedHttp() throws MalformedURLException {
// Run the task synchronously and ensure it succeeded
Integer lastResponseCode = testTask.doInBackground(taskParameters.toArray());
- assertEquals(Integer.valueOf(302), lastResponseCode);
+ assertEquals(Integer.valueOf(200), lastResponseCode);
}
@Test
diff --git a/trustkit/src/androidTest/res/xml/network_security_config.xml b/trustkit/src/androidTest/res/xml/network_security_config.xml
index 85e8bf5..3b4fa6a 100644
--- a/trustkit/src/androidTest/res/xml/network_security_config.xml
+++ b/trustkit/src/androidTest/res/xml/network_security_config.xml
@@ -7,7 +7,7 @@
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
- F6jTih9VkkYZS8yuYqeU/4DUGehJ+niBGkkQ1yg8H3U=
+ u91x/rv9ivMrXO0du6N2ABI6BiiVfD0T99DTEzoqSv4=