Skip to content

Commit

Permalink
add support for submitting sample data
Browse files Browse the repository at this point in the history
  • Loading branch information
thestinger committed Mar 17, 2018
1 parent 320ac89 commit afdd8ea
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 17 deletions.
5 changes: 5 additions & 0 deletions app/lint.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,9 @@
<issue id="UseCompoundDrawables">
<ignore path="src/main/res/layout/content_attestation.xml"/>
</issue>

<!-- AsyncTask intentionally holds onto the JobService to call jobFinished -->
<issue id="StaticFieldLeak">
<ignore path="src/main/java/co/copperhead/attestation/SubmitSampleJob.java"/>
</issue>
</lint>
29 changes: 18 additions & 11 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="co.copperhead.attestation">
package="co.copperhead.attestation">

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".AttestationActivity"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".AttestationActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
Expand All @@ -25,6 +27,11 @@

<service android:name=".GenerateAttestationService" />
<service android:name=".VerifyAttestationService" />
</application>

<service android:name=".SubmitSampleJob"
android:directBootAware="true"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false" />

</application>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,18 @@ private enum Stage {
"BKL-L04", "H3113", "Pixel 2", "Pixel 2 XL");

private static boolean isSupportedAuditee() {
return BuildConfig.DEBUG || supportedModels.contains(Build.MODEL);
return supportedModels.contains(Build.MODEL);
}

private static int getFirstApiLevel() {
return Integer.parseInt(SystemProperties.get("ro.product.first_api_level",
Integer.toString(Build.VERSION.SDK_INT)));
}

private static boolean potentialSupportedAuditee() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
getFirstApiLevel() >= Build.VERSION_CODES.O &&
SystemProperties.get("ro.boot.verifiedbootstate", "orange").equals("green");
}

@Override
Expand Down Expand Up @@ -370,8 +381,11 @@ public void onActivityResult(final int requestCode, final int resultCode, final
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.menu_attestation, menu);
if (!isSupportedAuditee()) {
menu.findItem(R.id.action_clear_auditee).setEnabled(false);
final boolean supported = isSupportedAuditee();
menu.findItem(R.id.action_clear_auditee).setEnabled(supported);
menu.findItem(R.id.action_submit_sample).setEnabled(potentialSupportedAuditee());
if (supported) {
menu.removeItem(R.id.action_submit_sample);
}
return true;
}
Expand All @@ -391,6 +405,10 @@ public boolean onOptionsItemSelected(final MenuItem item) {
startService(intent);
return true;
}
case R.id.action_submit_sample: {
SubmitSampleJob.schedule(this);
return true;
}
}
return super.onOptionsItemSelected(item);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ class AttestationProtocol {
private static final String KEY_VERIFIED_TIME_LAST = "verified_time_last";

private static final int CHALLENGE_LENGTH = 32;
private static final String EC_CURVE = "secp256r1";
static final String EC_CURVE = "secp256r1";
private static final String SIGNATURE_ALGORITHM = "SHA256WithECDSA";
private static final String KEY_DIGEST = DIGEST_SHA256;
static final String KEY_DIGEST = DIGEST_SHA256;
private static final HashFunction FINGERPRINT_HASH_FUNCTION = Hashing.sha256();
private static final int FINGERPRINT_LENGTH = FINGERPRINT_HASH_FUNCTION.bits() / 8;

Expand Down Expand Up @@ -914,7 +914,7 @@ static AttestationResult generateSerialized(final Context context, final byte[]
return new AttestationResult(!hasPersistentKey, serialized);
}

private static void generateKeyPair(final String algorithm, final KeyGenParameterSpec spec)
static void generateKeyPair(final String algorithm, final KeyGenParameterSpec spec)
throws NoSuchAlgorithmException, NoSuchProviderException,
InvalidAlgorithmParameterException {
final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm,
Expand Down
124 changes: 124 additions & 0 deletions app/src/main/java/co/copperhead/attestation/SubmitSampleJob.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package co.copperhead.attestation;

import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.os.AsyncTask;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Log;

import com.google.common.io.BaseEncoding;
import com.google.common.io.ByteStreams;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyStore;
import java.security.GeneralSecurityException;
import java.security.cert.Certificate;
import java.security.spec.ECGenParameterSpec;

import static android.security.keystore.KeyProperties.DIGEST_SHA256;
import static android.security.keystore.KeyProperties.KEY_ALGORITHM_EC;

public class SubmitSampleJob extends JobService {
private static final String TAG = "SubmitSampleJob";
private static final int JOB_ID = 0;
private static final String SUBMIT_URL = "https://attestation.copperhead.co/submit";
private static final int CONNECT_TIMEOUT = 60000;
private static final int READ_TIMEOUT = 60000;

private static final String KEYSTORE_ALIAS_SAMPLE = "sample_attestation_key";

private SubmitTask task;

static void schedule(final Context context) {
final ComponentName serviceName = new ComponentName(context, SubmitSampleJob.class);
final JobScheduler scheduler = context.getSystemService(JobScheduler.class);
final int result = scheduler.schedule(new JobInfo.Builder(JOB_ID, serviceName)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setPersisted(true)
.build());
if (result == JobScheduler.RESULT_FAILURE) {
Log.d(TAG, "job schedule failed");
}
}

private class SubmitTask extends AsyncTask<Void, Void, Boolean> {
final JobParameters params;

SubmitTask(final JobParameters params) {
this.params = params;
}

@Override
protected void onPostExecute(final Boolean success) {
jobFinished(params, success);
}

@Override
protected Boolean doInBackground(final Void... params) {
try {
final HttpURLConnection connection = (HttpURLConnection) new URL(SUBMIT_URL).openConnection();
connection.setConnectTimeout(CONNECT_TIMEOUT);
connection.setReadTimeout(READ_TIMEOUT);
connection.setDoOutput(true);

final KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);

final KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(KEYSTORE_ALIAS_SAMPLE,
KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
.setAlgorithmParameterSpec(new ECGenParameterSpec(AttestationProtocol.EC_CURVE))
.setDigests(AttestationProtocol.KEY_DIGEST)
.setAttestationChallenge("sample".getBytes());
AttestationProtocol.generateKeyPair(KEY_ALGORITHM_EC, builder.build());
final Certificate[] certs = keyStore.getCertificateChain(KEYSTORE_ALIAS_SAMPLE);
keyStore.deleteEntry(KEYSTORE_ALIAS_SAMPLE);

final Process process = new ProcessBuilder("getprop").start();
final InputStream propertyStream = process.getInputStream();

final OutputStream output = connection.getOutputStream();
for (final Certificate cert : certs) {
output.write(BaseEncoding.base64().encode(cert.getEncoded()).getBytes());
output.write("\n".getBytes());
}
ByteStreams.copy(propertyStream, output);
propertyStream.close();
output.close();

final int responseCode = connection.getResponseCode();
if (responseCode != 200) {
throw new IOException("response code: " + responseCode);
}

connection.disconnect();
} catch (final GeneralSecurityException | IOException e) {
Log.e(TAG, "submit failure", e);
return true;
}
return false;
}
}

@Override
public boolean onStartJob(final JobParameters params) {
Log.d(TAG, "start job");
task = new SubmitTask(params);
task.execute();
return true;
}

@Override
public boolean onStopJob(final JobParameters params) {
task.cancel(true);
return true;
}
}
3 changes: 3 additions & 0 deletions app/src/main/res/menu/menu_attestation.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@
<item android:id="@+id/action_clear_auditor"
android:title="@string/action_clear_auditor"
app:showAsAction="never" />
<item android:id="@+id/action_submit_sample"
android:title="@string/action_submit_sample"
app:showAsAction="never" />
</menu>
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<string name="qr_code_scan_hint_auditor">Now scan this QR Code from the other device.\n\nTap this QR code after scanning to proceed.</string>
<string name="action_clear_auditee">Clear Auditee pairings</string>
<string name="action_clear_auditor">Clear Auditor pairings</string>
<string name="action_submit_sample">Submit sample data</string>
<string name="scanner_label">Scan QR code shown on the other device.</string>
<string name="verifying_attestation">Verifying attestation…</string>
<string name="generating_attestation">Generating attestation…</string>
Expand Down

0 comments on commit afdd8ea

Please sign in to comment.