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=