Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incrementing to new build tools / SDK and adding local broadcasts for Pinning Failure Reports #50

Merged
merged 7 commits into from Mar 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Expand Up @@ -105,6 +105,11 @@ protected void onCreate(Bundle savedInstanceState) {

URL url = new URL("https://www.datatheorem.com");
String serverHostname = url.getHost();

//Optionally add a local broadcast receiver to receive PinningFailureReports
PinningValidationReportTestBroadcastReceiver receiver = new PinningValidationReportTestBroadcastReceiver();
LocalBroadcastManager.getInstance(context)
.registerReceiver(receiver, new IntentFilter(BackgroundReporter.REPORT_VALIDATION_EVENT));

// HttpsUrlConnection
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
Expand All @@ -127,10 +132,21 @@ protected void onCreate(Bundle savedInstanceState) {
TrustKit.getInstance().getTrustManager(serverHostname))
.build();
}

class PinningFailureReportBroadcastReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
PinningFailureReport report = (PinningFailureReport) intent.getSerializableExtra(BackgroundReporter.EXTRA_REPORT);
}
}
```

Once TrustKit has been initialized and the client or connection's `SSLSocketFactory` has been set, it will verify the server's certificate chain against the configured pinning policy whenever an HTTPS connection is initiated. If a report URI has been configured, the App will also send reports to the specified URI whenever a pin validation failure occurred.

You can also create and register local broadcast receivers to receive the same certificate pinning error reports that would be sent to the report_uris.



Limitations
----------
Expand Down
@@ -1,24 +1,31 @@
package com.datatheorem.android.trustkit.demoapp;

import android.content.IntentFilter;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;

import com.datatheorem.android.trustkit.TrustKit;
import com.datatheorem.android.trustkit.reporting.BackgroundReporter;

import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;

import javax.net.ssl.HttpsURLConnection;


public class DemoMainActivity extends AppCompatActivity {

private static final String DEBUG_TAG = "TrustKit-Demo";
protected static final String DEBUG_TAG = "TrustKit-Demo";
private static final PinningFailureReportBroadcastReceiver pinningFailureReportBroadcastReceiver = new PinningFailureReportBroadcastReceiver();

@Override
protected void onCreate(Bundle savedInstanceState) {
Expand All @@ -38,6 +45,17 @@ protected void onCreate(Bundle savedInstanceState) {
new DownloadWebpageTask().execute("https://www.google.com");

textView.setText("Connection results are in the logs");

IntentFilter intentFilter = new IntentFilter(BackgroundReporter.REPORT_VALIDATION_EVENT);
LocalBroadcastManager.getInstance(getApplicationContext())
.registerReceiver(pinningFailureReportBroadcastReceiver,intentFilter);
}

@Override
protected void onDestroy() {
LocalBroadcastManager.getInstance(getApplicationContext())
.unregisterReceiver(pinningFailureReportBroadcastReceiver);
super.onDestroy();
}

private class DownloadWebpageTask extends AsyncTask<String, Void, String> {
Expand Down
@@ -0,0 +1,26 @@
package com.datatheorem.android.trustkit.demoapp;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import com.datatheorem.android.trustkit.reporting.BackgroundReporter;

import java.io.Serializable;

/**
* Class that provides an example broadcast receiver
*
* <p>
* Applications using TrustKit can listen for local broadcasts and receive the same report that
* would be sent to the report_url.
* </p>
**/
class PinningFailureReportBroadcastReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
Serializable result = intent.getSerializableExtra(BackgroundReporter.EXTRA_REPORT);
Log.i(DemoMainActivity.DEBUG_TAG, result.toString());
}
}
12 changes: 6 additions & 6 deletions build.gradle
Expand Up @@ -26,14 +26,14 @@ allprojects {


ext{
trustkitVersionCode = 6
trustkitVersionName = "1.1.0"
trustkitVersionCode = 7
trustkitVersionName = "1.1.1"

demoAppTrustKitVersionCode = 1
demoAppTrustKitVersionName = "1.0"
demoAppTrustKitVersionCode = 2
demoAppTrustKitVersionName = "1.1"

demoAppKotlinTrustKitVersionCode = 1
demoAppKotlinTrustKitVersionName = "1.0"
demoAppKotlinTrustKitVersionCode = 2
demoAppKotlinTrustKitVersionName = "1.1"
javaSourceCompatibilty = '1.6'
toolVersions = [
android : [
Expand Down
1 change: 1 addition & 0 deletions demoappkotlin/src/main/AndroidManifest.xml
Expand Up @@ -20,6 +20,7 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>

</application>


Expand Down
@@ -1,7 +1,9 @@
package com.datatheorem.android.trustkit.demoappkotlin

import android.content.IntentFilter
import android.os.AsyncTask
import android.os.Bundle
import android.support.v4.content.LocalBroadcastManager
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.util.Log
Expand All @@ -10,13 +12,15 @@ import android.view.MenuItem
import android.view.View
import android.widget.TextView
import com.datatheorem.android.trustkit.TrustKit
import com.datatheorem.android.trustkit.reporting.BackgroundReporter
import java.io.IOException
import java.net.MalformedURLException
import java.net.URL
import javax.net.ssl.HttpsURLConnection


class DemoMainActivity : AppCompatActivity() {
private lateinit var pinningFailureReportBroadcastReceiver: PinningFailureReportBroadcastReceiver

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand All @@ -35,6 +39,18 @@ class DemoMainActivity : AppCompatActivity() {
DownloadWebpageTask().execute("https://www.google.com")

textView.text = "Connection results are in the logs"

// Adding a local broadcast receiver to listen for validation report events
pinningFailureReportBroadcastReceiver = PinningFailureReportBroadcastReceiver()
val intentFilter = IntentFilter(BackgroundReporter.REPORT_VALIDATION_EVENT)
LocalBroadcastManager.getInstance(this.applicationContext)
.registerReceiver(pinningFailureReportBroadcastReceiver,intentFilter)
}

override fun onDestroy() {
LocalBroadcastManager.getInstance(this.applicationContext)
.unregisterReceiver(pinningFailureReportBroadcastReceiver)
super.onDestroy()
}

private inner class DownloadWebpageTask : AsyncTask<String, Void, String>() {
Expand Down Expand Up @@ -83,7 +99,7 @@ class DemoMainActivity : AppCompatActivity() {

companion object {

private const val DEBUG_TAG = "TrustKit-Demo"
internal const val DEBUG_TAG = "TrustKit-Demo"
}
}

@@ -0,0 +1,24 @@
package com.datatheorem.android.trustkit.demoappkotlin

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import com.datatheorem.android.trustkit.reporting.BackgroundReporter

/**
* Class that provides an example broadcast receiver
*
* <p>
* Applications using TrustKit can listen for local broadcasts and receive the same report that
* would be sent to the report_url.
* </p>
**/
class PinningFailureReportBroadcastReceiver : BroadcastReceiver() {

override fun onReceive(context: Context, intent: Intent) {
val result = intent.getSerializableExtra(BackgroundReporter.EXTRA_REPORT)
Log.i(DemoMainActivity.DEBUG_TAG, result.toString())
}

}
1 change: 1 addition & 0 deletions trustkit/build.gradle
Expand Up @@ -33,6 +33,7 @@ dependencies {
androidTestImplementation "com.android.support.test:runner:$rootProject.libVersions.android.testRunner"
androidTestImplementation "com.android.support.test:rules:$rootProject.libVersions.android.testRunner"
androidTestImplementation "org.mockito:mockito-core:$rootProject.libVersions.mockito.android"
androidTestImplementation "org.awaitility:awaitility:3.1.6"
androidTestImplementation "com.crittercism.dexmaker:dexmaker:$rootProject.libVersions.dexmaker"
androidTestImplementation "com.crittercism.dexmaker:dexmaker-dx:$rootProject.libVersions.dexmaker"
androidTestImplementation "com.crittercism.dexmaker:dexmaker-mockito:$rootProject.libVersions.dexmaker"
Expand Down
4 changes: 4 additions & 0 deletions trustkit/proguard-rules.pro
Expand Up @@ -15,3 +15,7 @@
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

-keep class com.datatheorem.android.trustkit.reporting.BackgroundReporter {
public static *;
}
@@ -1,25 +1,20 @@
package com.datatheorem.android.trustkit.reporting;

import static com.datatheorem.android.trustkit.CertificateUtils.testCertChain;
import static com.datatheorem.android.trustkit.CertificateUtils.testCertChainPem;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.support.v4.content.LocalBroadcastManager;

import com.datatheorem.android.trustkit.TestableTrustKit;
import com.datatheorem.android.trustkit.config.DomainPinningPolicy;
import com.datatheorem.android.trustkit.pinning.PinningValidationResult;
import com.datatheorem.android.trustkit.utils.VendorIdentifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;

import org.awaitility.Awaitility;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
Expand All @@ -29,6 +24,23 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;

import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import static com.datatheorem.android.trustkit.CertificateUtils.testCertChain;
import static com.datatheorem.android.trustkit.CertificateUtils.testCertChainPem;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;


@RunWith(AndroidJUnit4.class)
public class BackgroundReporterTest {
Expand All @@ -44,7 +56,7 @@ public void testPinValidationFailed() throws MalformedURLException, JSONExceptio
// TrustKit does not do anything for API level < 17 hence there is no reporting
return;
}

Context context = InstrumentationRegistry.getContext();
// Initialize TrustKit
String serverHostname = "mail.google.com";
final DomainPinningPolicy domainPolicy = new DomainPinningPolicy.Builder()
Expand All @@ -60,9 +72,14 @@ public void testPinValidationFailed() throws MalformedURLException, JSONExceptio
.setReportUris(new HashSet<String>() {{ add("https://overmind.datatheorem.com"); }})
.build();

TestableBackgroundReporter reporter = new TestableBackgroundReporter("com.unit.tests",
final PinningValidationReportTestBroadcastReceiver receiver = new PinningValidationReportTestBroadcastReceiver();
LocalBroadcastManager.getInstance(context)
.registerReceiver(receiver, new IntentFilter(BackgroundReporter.REPORT_VALIDATION_EVENT));

TestableBackgroundReporter reporter = new TestableBackgroundReporter( context,
"com.unit.tests",
"1.2",
VendorIdentifier.getOrCreate(InstrumentationRegistry.getContext()));
VendorIdentifier.getOrCreate(context));
TestableBackgroundReporter reporterSpy = Mockito.spy(reporter);

// Call the method twice to also test the report rate limiter
Expand All @@ -81,8 +98,17 @@ public void testPinValidationFailed() throws MalformedURLException, JSONExceptio
eq(new HashSet<URL>() {{ add(new URL("https://overmind.datatheorem.com")); }} )
);

validateSentReport(reportSent.getValue());

Awaitility.await().atMost(2, TimeUnit.SECONDS).untilTrue(receiver.broadcastReceived);
assertTrue(receiver.broadcastReceived.get());
assertTrue(receiver.containedReport instanceof PinningFailureReport);
validateSentReport((PinningFailureReport) receiver.containedReport);
}

private void validateSentReport(PinningFailureReport reportSent) throws JSONException {
// Validate the content of the generated report
JSONObject reportSentJson = reportSent.getValue().toJson();
JSONObject reportSentJson = reportSent.toJson();
assertEquals("com.unit.tests", reportSentJson.getString("app-bundle-id"));
assertEquals("1.2", reportSentJson.getString("app-version"));
assertEquals("ANDROID", reportSentJson.getString("app-platform"));
Expand Down Expand Up @@ -123,7 +149,17 @@ public void testPinValidationFailed() throws MalformedURLException, JSONExceptio
.contains("pin-sha256=\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\""));
assertTrue(pinsTestable
.contains("pin-sha256=\"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=\""));
}

private class PinningValidationReportTestBroadcastReceiver extends BroadcastReceiver{
public AtomicBoolean broadcastReceived = new AtomicBoolean(false);
public Serializable containedReport;

@Override
public void onReceive(Context context, Intent intent) {
broadcastReceived.set(true);
containedReport = intent.getSerializableExtra(BackgroundReporter.EXTRA_REPORT);
}
}
}

Expand Down
@@ -1,6 +1,7 @@
package com.datatheorem.android.trustkit.reporting;


import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import java.net.URL;
Expand All @@ -9,8 +10,8 @@

@RequiresApi(api = 16)
public class TestableBackgroundReporter extends BackgroundReporter {
public TestableBackgroundReporter(String appPackageName, String appVersion, String appVendorId){
super(appPackageName, appVersion, appVendorId);
public TestableBackgroundReporter(Context context, String appPackageName, String appVersion, String appVendorId){
super(context, appPackageName, appVersion, appVendorId);
}

@Override
Expand Down