Skip to content

Commit dde1267

Browse files
committed
Added Intellicheck barcode verification
1 parent bf975aa commit dde1267

14 files changed

+463
-38
lines changed

app/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ android {
1010
versionCode rootProject.generateVersionCode()
1111
versionName rootProject.generateVersionName()
1212
buildConfigField "String", "BLINK_LICENCE_KEY", "\"" + blink5LicenceKey + "\""
13+
buildConfigField "String", "INTELLICHECK_URL", "\"https://dev.ver-id.com/id-check/parse-verify\""
1314
}
1415
buildTypes {
1516
release {
@@ -35,4 +36,5 @@ dependencies {
3536
implementation 'androidx.appcompat:appcompat:1.1.0'
3637
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
3738
implementation 'org.apache.commons:commons-math3:3.0'
39+
implementation 'androidx.security:security-crypto:1.0.0-alpha02'
3840
}

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
android:roundIcon="@mipmap/ic_launcher_round"
1515
android:supportsRtl="true"
1616
android:theme="@style/Theme.AppCompat.Light.DarkActionBar">
17+
<activity android:name=".SettingsActivity"></activity>
1718
<activity
1819
android:name=".DocumentDetailsActivity"
1920
android:label="@string/details" />

app/src/main/java/com/appliedrec/credentials/app/BlinkLicenceKeyUpdater.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,18 @@ Single<String> getSavedLicenceKey() {
4141
}).subscribeOn(Schedulers.io());
4242
}
4343

44+
Completable deleteSavedLicenceKey() {
45+
return Completable.create(emitter -> {
46+
try {
47+
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
48+
preferences.edit().remove(BLINK_LICENCE_PREF_KEY).apply();
49+
emitter.onComplete();
50+
} catch (Exception e) {
51+
emitter.onError(e);
52+
}
53+
}).subscribeOn(Schedulers.io());
54+
}
55+
4456
Single<String> getLicenceKeyFromRemote() {
4557
return Single.<String>create(emitter -> {
4658
try {

app/src/main/java/com/appliedrec/credentials/app/DocumentData.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public class DocumentData implements Parcelable {
1616
private String dateOfIssue;
1717
private String documentNumber;
1818
private String sex;
19+
private String rawBarcode;
1920

2021
public DocumentData(UsdlCombinedRecognizer.Result result) {
2122
firstName = result.getFirstName();
@@ -26,6 +27,7 @@ public DocumentData(UsdlCombinedRecognizer.Result result) {
2627
dateOfIssue = result.getDateOfIssue().getOriginalDateString();
2728
documentNumber = result.getDocumentNumber();
2829
sex = result.getSex();
30+
rawBarcode = result.getRawStringData();
2931
}
3032

3133
public DocumentData(BlinkIdCombinedRecognizer.Result result) {
@@ -48,6 +50,7 @@ protected DocumentData(Parcel in) {
4850
dateOfIssue = in.readString();
4951
documentNumber = in.readString();
5052
sex = in.readString();
53+
rawBarcode = in.readString();
5154
}
5255

5356
public static final Creator<DocumentData> CREATOR = new Creator<DocumentData>() {
@@ -94,6 +97,10 @@ public String getSex() {
9497
return sex;
9598
}
9699

100+
public String getRawBarcode() {
101+
return rawBarcode;
102+
}
103+
97104
@Override
98105
public int describeContents() {
99106
return 0;
@@ -109,5 +116,6 @@ public void writeToParcel(Parcel parcel, int i) {
109116
parcel.writeString(dateOfIssue);
110117
parcel.writeString(documentNumber);
111118
parcel.writeString(sex);
119+
parcel.writeString(rawBarcode);
112120
}
113121
}

app/src/main/java/com/appliedrec/credentials/app/DocumentDetailsActivity.java

Lines changed: 68 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,17 @@
99
import android.widget.TextView;
1010

1111
import androidx.annotation.NonNull;
12-
import androidx.appcompat.app.AppCompatActivity;
12+
import androidx.appcompat.app.AlertDialog;
1313
import androidx.core.util.Pair;
1414
import androidx.recyclerview.widget.LinearLayoutManager;
1515
import androidx.recyclerview.widget.RecyclerView;
1616

1717
import java.util.ArrayList;
18+
import java.util.List;
1819

19-
public class DocumentDetailsActivity extends AppCompatActivity {
20+
import io.reactivex.android.schedulers.AndroidSchedulers;
21+
22+
public class DocumentDetailsActivity extends RxVerIDActivity {
2023

2124
static class DocumentPropertyViewHolder extends RecyclerView.ViewHolder {
2225

@@ -38,9 +41,9 @@ void bind(String name, String value) {
3841
static class DocumentDetailsAdapter extends RecyclerView.Adapter<DocumentPropertyViewHolder> {
3942

4043
private LayoutInflater layoutInflater;
41-
private ArrayList<Pair<String,String>> documentProperties;
44+
private List<Pair<String,String>> documentProperties;
4245

43-
DocumentDetailsAdapter(Context context, ArrayList<Pair<String,String>> documentProperties) {
46+
DocumentDetailsAdapter(Context context, List<Pair<String,String>> documentProperties) {
4447
layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
4548
this.documentProperties = documentProperties;
4649
}
@@ -68,41 +71,73 @@ public int getItemCount() {
6871
protected void onCreate(Bundle savedInstanceState) {
6972
super.onCreate(savedInstanceState);
7073
setContentView(R.layout.activity_document_details);
71-
RecyclerView recyclerView = findViewById(R.id.recyclerView);
7274
Intent intent = getIntent();
7375
if (intent != null) {
7476
DocumentData documentData = intent.getParcelableExtra(IDCardActivity.EXTRA_DOCUMENT_DATA);
7577
if (documentData != null) {
76-
ArrayList<Pair<String,String>> properties = new ArrayList<>();
77-
if (documentData.getFirstName() != null && !documentData.getFirstName().isEmpty()) {
78-
properties.add(new Pair<>("First name", documentData.getFirstName()));
79-
}
80-
if (documentData.getLastName() != null && !documentData.getLastName().isEmpty()) {
81-
properties.add(new Pair<>("Last name", documentData.getLastName()));
82-
}
83-
if (documentData.getDateOfBirth() != null && !documentData.getDateOfBirth().isEmpty()) {
84-
properties.add(new Pair<>("Date of birth", documentData.getDateOfBirth()));
85-
}
86-
if (documentData.getSex() != null && !documentData.getSex().isEmpty()) {
87-
properties.add(new Pair<>("Sex", documentData.getSex()));
88-
}
89-
if (documentData.getAddress() != null && !documentData.getAddress().isEmpty()) {
90-
properties.add(new Pair<>("Address", documentData.getAddress()));
78+
if (documentData.getRawBarcode() != null) {
79+
try {
80+
SecureStorage secureStorage = new SecureStorage(this);
81+
String intellicheckSecret = secureStorage.getValueForKey(SecureStorage.CommonKeys.INTELLICHECK_API_KEY);
82+
if (intellicheckSecret != null) {
83+
IntellicheckBarcodeVerifier barcodeVerifier = new IntellicheckBarcodeVerifier(this, intellicheckSecret);
84+
addDisposable(barcodeVerifier.parseBarcode(documentData.getRawBarcode()).toList().observeOn(AndroidSchedulers.mainThread()).subscribe(
85+
this::showProperties,
86+
error -> {
87+
showProperties(propertiesFromDocumentData(documentData));
88+
new AlertDialog.Builder(this)
89+
.setTitle(R.string.error)
90+
.setMessage(R.string.failed_to_verify_barcode)
91+
.setNeutralButton(android.R.string.ok, null)
92+
.create()
93+
.show();
94+
}
95+
));
96+
return;
97+
}
98+
} catch (Exception e) {
99+
e.printStackTrace();
100+
}
91101
}
92-
if (documentData.getDateOfIssue() != null && !documentData.getDateOfIssue().isEmpty()) {
93-
properties.add(new Pair<>("Date of issue", documentData.getDateOfIssue()));
94-
}
95-
if (documentData.getDateOfExpiry() != null && !documentData.getDateOfExpiry().isEmpty()) {
96-
properties.add(new Pair<>("Date of expiry", documentData.getDateOfExpiry()));
97-
}
98-
if (documentData.getDocumentNumber() != null && !documentData.getDocumentNumber().isEmpty()) {
99-
properties.add(new Pair<>("Document number", documentData.getDocumentNumber()));
100-
}
101-
recyclerView.setLayoutManager(new LinearLayoutManager(this));
102-
recyclerView.setHasFixedSize(true);
103-
DocumentDetailsAdapter adapter = new DocumentDetailsAdapter(this, properties);
104-
recyclerView.setAdapter(adapter);
102+
showProperties(propertiesFromDocumentData(documentData));
105103
}
106104
}
107105
}
106+
107+
private List<Pair<String,String>> propertiesFromDocumentData(DocumentData documentData) {
108+
ArrayList<Pair<String,String>> properties = new ArrayList<>();
109+
if (documentData.getFirstName() != null && !documentData.getFirstName().isEmpty()) {
110+
properties.add(new Pair<>("First name", documentData.getFirstName()));
111+
}
112+
if (documentData.getLastName() != null && !documentData.getLastName().isEmpty()) {
113+
properties.add(new Pair<>("Last name", documentData.getLastName()));
114+
}
115+
if (documentData.getDateOfBirth() != null && !documentData.getDateOfBirth().isEmpty()) {
116+
properties.add(new Pair<>("Date of birth", documentData.getDateOfBirth()));
117+
}
118+
if (documentData.getSex() != null && !documentData.getSex().isEmpty()) {
119+
properties.add(new Pair<>("Sex", documentData.getSex()));
120+
}
121+
if (documentData.getAddress() != null && !documentData.getAddress().isEmpty()) {
122+
properties.add(new Pair<>("Address", documentData.getAddress()));
123+
}
124+
if (documentData.getDateOfIssue() != null && !documentData.getDateOfIssue().isEmpty()) {
125+
properties.add(new Pair<>("Date of issue", documentData.getDateOfIssue()));
126+
}
127+
if (documentData.getDateOfExpiry() != null && !documentData.getDateOfExpiry().isEmpty()) {
128+
properties.add(new Pair<>("Date of expiry", documentData.getDateOfExpiry()));
129+
}
130+
if (documentData.getDocumentNumber() != null && !documentData.getDocumentNumber().isEmpty()) {
131+
properties.add(new Pair<>("Document number", documentData.getDocumentNumber()));
132+
}
133+
return properties;
134+
}
135+
136+
private void showProperties(List<Pair<String,String>> properties) {
137+
RecyclerView recyclerView = findViewById(R.id.recyclerView);
138+
recyclerView.setLayoutManager(new LinearLayoutManager(this));
139+
recyclerView.setHasFixedSize(true);
140+
DocumentDetailsAdapter adapter = new DocumentDetailsAdapter(this, properties);
141+
recyclerView.setAdapter(adapter);
142+
}
108143
}
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
package com.appliedrec.credentials.app;
2+
3+
import android.content.Context;
4+
import android.content.SharedPreferences;
5+
import android.net.Uri;
6+
import android.os.Build;
7+
import android.util.Base64;
8+
9+
import androidx.core.util.Pair;
10+
import androidx.preference.PreferenceManager;
11+
12+
import com.google.gson.Gson;
13+
import com.google.gson.stream.JsonReader;
14+
15+
import java.io.ByteArrayInputStream;
16+
import java.io.ByteArrayOutputStream;
17+
import java.io.InputStream;
18+
import java.io.InputStreamReader;
19+
import java.io.OutputStream;
20+
import java.net.MalformedURLException;
21+
import java.net.URL;
22+
import java.nio.charset.StandardCharsets;
23+
import java.util.List;
24+
import java.util.UUID;
25+
26+
import javax.net.ssl.HttpsURLConnection;
27+
28+
import io.reactivex.Completable;
29+
import io.reactivex.Observable;
30+
import io.reactivex.schedulers.Schedulers;
31+
32+
public class IntellicheckBarcodeVerifier {
33+
34+
private String url;
35+
private SharedPreferences sharedPreferences;
36+
private String password;
37+
private String appId;
38+
39+
public IntellicheckBarcodeVerifier(Context context, String password) {
40+
this.url = BuildConfig.INTELLICHECK_URL;
41+
this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
42+
this.password = password;
43+
this.appId = context.getApplicationContext().getPackageName();
44+
}
45+
46+
private String getDeviceId() {
47+
String key = "device_id";
48+
String deviceId = sharedPreferences.getString(key, UUID.randomUUID().toString());
49+
if (!sharedPreferences.contains(key)) {
50+
sharedPreferences.edit().putString(key, deviceId).apply();
51+
}
52+
return deviceId;
53+
}
54+
55+
public Completable testPassword() {
56+
return Completable.create(emitter -> {
57+
try {
58+
Uri uri = Uri.parse(url);
59+
List<String> pathSegments = uri.getPathSegments();
60+
Uri.Builder builder = new Uri.Builder()
61+
.scheme(uri.getScheme())
62+
.authority(uri.getAuthority());
63+
for (int i=0; i<pathSegments.size()-1; i++) {
64+
builder.appendPath(pathSegments.get(i));
65+
}
66+
builder.appendPath("check-password");
67+
uri = builder.build();
68+
HttpsURLConnection connection = (HttpsURLConnection) new URL(uri.toString()).openConnection();
69+
connection.setRequestMethod("POST");
70+
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
71+
connection.setDoOutput(true);
72+
connection.setDoInput(true);
73+
byte[] body = new Uri.Builder()
74+
.appendQueryParameter("device_id", getDeviceId())
75+
.appendQueryParameter("app_id", appId)
76+
.appendQueryParameter("password", password).build().getQuery().getBytes(StandardCharsets.UTF_8);
77+
OutputStream outputStream = connection.getOutputStream();
78+
ByteArrayInputStream inputStream = new ByteArrayInputStream(body);
79+
int read;
80+
byte[] buffer = new byte[512];
81+
while ((read = inputStream.read(buffer, 0, buffer.length)) > 0) {
82+
outputStream.write(buffer, 0, read);
83+
}
84+
outputStream.close();
85+
if (connection.getResponseCode() == 200) {
86+
emitter.onComplete();
87+
} else {
88+
StringBuilder stringBuilder = new StringBuilder();
89+
while ((read = connection.getErrorStream().read(buffer, 0, buffer.length)) > 0) {
90+
stringBuilder.append(new String(buffer, 0, read, StandardCharsets.UTF_8));
91+
}
92+
String response = stringBuilder.toString();
93+
if (!response.isEmpty()) {
94+
throw new Exception(response);
95+
} else {
96+
throw new Exception("Unknown error");
97+
}
98+
}
99+
} catch (Exception e) {
100+
emitter.onError(e);
101+
}
102+
}).subscribeOn(Schedulers.io());
103+
}
104+
105+
public Observable<Pair<String,String>> parseBarcode(String barcode) {
106+
Observable<Pair<String,String>> observable = Observable.create(emitter -> {
107+
try {
108+
URL url = new URL(this.url);
109+
byte[] barcodeData = barcode.getBytes(StandardCharsets.UTF_8);
110+
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
111+
connection.setRequestMethod("POST");
112+
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
113+
connection.setDoInput(true);
114+
connection.setDoOutput(true);
115+
String body = new Uri.Builder()
116+
.appendQueryParameter("device_os", "Android")
117+
.appendQueryParameter("device_id", getDeviceId())
118+
.appendQueryParameter("device_name", Build.MODEL)
119+
.appendQueryParameter("password", password)
120+
.appendQueryParameter("app_id", appId)
121+
.appendQueryParameter("data", Base64.encodeToString(barcodeData, Base64.NO_WRAP))
122+
.build()
123+
.getQuery();
124+
ByteArrayInputStream inputStream = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
125+
OutputStream outputStream = connection.getOutputStream();
126+
int read;
127+
byte[] buffer = new byte[512];
128+
while ((read = inputStream.read(buffer, 0, buffer.length)) > 0) {
129+
outputStream.write(buffer, 0, read);
130+
}
131+
outputStream.close();
132+
if (connection.getResponseCode() >= 400) {
133+
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
134+
while ((read = connection.getErrorStream().read(buffer, 0, buffer.length)) > 0) {
135+
byteArrayOutputStream.write(buffer, 0, read);
136+
}
137+
String response = new String(byteArrayOutputStream.toByteArray(), StandardCharsets.UTF_8);
138+
throw new Exception(response);
139+
}
140+
// TODO: Finish this
141+
JsonReader jsonReader = new JsonReader(new InputStreamReader(connection.getInputStream()));
142+
jsonReader.setLenient(true);
143+
jsonReader.beginObject();
144+
while (jsonReader.hasNext()) {
145+
if ("document_t".equals(jsonReader.nextName())) {
146+
jsonReader.beginObject();
147+
while (jsonReader.hasNext()) {
148+
String name = jsonReader.nextName();
149+
if ("$".equals(name)) {
150+
jsonReader.skipValue();
151+
} else if ("testCard".equals(name)) {
152+
emitter.onNext(new Pair<>(name, jsonReader.nextBoolean() ? "Yes" : "No"));
153+
} else {
154+
try {
155+
String value = jsonReader.nextString();
156+
if (value != null && !value.trim().isEmpty()) {
157+
emitter.onNext(new Pair<>(name, value));
158+
}
159+
} catch (Exception e) {
160+
e.printStackTrace();
161+
}
162+
}
163+
}
164+
jsonReader.endObject();
165+
}
166+
}
167+
jsonReader.endObject();
168+
emitter.onComplete();
169+
} catch (Exception e) {
170+
emitter.onError(e);
171+
}
172+
});
173+
return observable.subscribeOn(Schedulers.io());
174+
}
175+
}

0 commit comments

Comments
 (0)