Skip to content

Commit 819b867

Browse files
committed
Add install signing batch test harness
1 parent 5392271 commit 819b867

3 files changed

Lines changed: 181 additions & 66 deletions

File tree

shapes-app/app/src/main/java/io/approov/shapes/MainActivity.java

Lines changed: 167 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,17 @@
3535
import java.io.InputStream;
3636
import java.io.InputStreamReader;
3737
import java.net.URL;
38+
import java.nio.charset.StandardCharsets;
39+
import java.util.Locale;
3840

3941
import javax.net.ssl.HttpsURLConnection;
4042

4143
// *** UNCOMMENT THE LINE BELOW FOR APPROOV ***
42-
//import io.approov.service.httpsurlconn.ApproovService;
44+
import io.approov.service.httpsurlconn.ApproovService;
4345

4446
public class MainActivity extends Activity {
4547
private static final String TAG = MainActivity.class.getSimpleName();
48+
private static final int MESSAGE_SIGNING_BATCH_SIZE = 100;
4649
private Activity activity;
4750
private View statusView = null;
4851
private ImageView statusImageView = null;
@@ -115,6 +118,168 @@ private int readShapesResponse(HttpsURLConnection connection) {
115118
return R.drawable.confused;
116119
}
117120

121+
private String readResponseBody(HttpsURLConnection connection) throws IOException {
122+
StringBuilder sb = new StringBuilder();
123+
InputStream is = null;
124+
try {
125+
try {
126+
is = new BufferedInputStream(connection.getInputStream());
127+
} catch (IOException e) {
128+
InputStream errorStream = connection.getErrorStream();
129+
if (errorStream == null)
130+
throw e;
131+
is = new BufferedInputStream(errorStream);
132+
}
133+
BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
134+
String inputLine;
135+
while ((inputLine = br.readLine()) != null)
136+
sb.append(inputLine);
137+
return sb.toString();
138+
}
139+
finally {
140+
if (is != null) {
141+
try {
142+
is.close();
143+
}
144+
catch (IOException e) {
145+
Log.d(TAG, "Error closing Shapes response InputStream");
146+
}
147+
}
148+
}
149+
}
150+
151+
private static final class BatchResult {
152+
int validAccepted;
153+
int validRejected;
154+
int tamperedAccepted;
155+
int tamperedRejected;
156+
int networkErrors;
157+
int unexpectedResponses;
158+
}
159+
160+
private String mutateSignatureHeader(String signatureHeader) {
161+
if (signatureHeader == null)
162+
return null;
163+
164+
int firstColon = signatureHeader.indexOf(':');
165+
int lastColon = signatureHeader.lastIndexOf(':');
166+
if ((firstColon < 0) || (lastColon <= firstColon + 1))
167+
return signatureHeader + "A";
168+
169+
char[] chars = signatureHeader.toCharArray();
170+
for (int i = firstColon + 1; i < lastColon; i++) {
171+
char c = chars[i];
172+
if (Character.isLetterOrDigit(c)) {
173+
chars[i] = (c == 'A') ? 'B' : 'A';
174+
return new String(chars);
175+
}
176+
}
177+
chars[firstColon + 1] = (chars[firstColon + 1] == 'A') ? 'B' : 'A';
178+
return new String(chars);
179+
}
180+
181+
private boolean isAcceptedResponse(int responseCode, String responseBody) {
182+
return (responseCode >= 200) && (responseCode < 300)
183+
&& (responseBody != null)
184+
&& responseBody.contains("\"messageSigningResult\":\"VALID\"");
185+
}
186+
187+
private boolean isRejectedResponse(int responseCode, String responseBody) {
188+
return (responseCode >= 400)
189+
|| ((responseBody != null) && responseBody.contains("\"messageSigningResult\":\"INVALID\""))
190+
|| ((responseBody != null) && responseBody.contains("\"status\":\"FAIL\""));
191+
}
192+
193+
private void runMessageSigningBatchTest() {
194+
Log.d(TAG, "Starting installation message signing batch test with " + MESSAGE_SIGNING_BATCH_SIZE + " requests");
195+
activity.runOnUiThread(new Runnable() {
196+
@Override
197+
public void run() {
198+
statusView.setVisibility(View.INVISIBLE);
199+
}
200+
});
201+
202+
AsyncTask.execute(new Runnable() {
203+
@Override
204+
public void run() {
205+
BatchResult batchResult = new BatchResult();
206+
207+
for (int i = 0; i < MESSAGE_SIGNING_BATCH_SIZE; i++) {
208+
boolean tamperSignature = (i % 2) == 1;
209+
HttpsURLConnection connection = null;
210+
String responseBody = null;
211+
try {
212+
URL url = new URL(getResources().getString(R.string.shapes_url));
213+
connection = (HttpsURLConnection) url.openConnection();
214+
connection.setRequestMethod("GET");
215+
connection.setInstanceFollowRedirects(false);
216+
connection.addRequestProperty("Accept", "application/json");
217+
connection.addRequestProperty("Api-Key", getResources().getString(R.string.shapes_api_key));
218+
ApproovService.addApproov(connection);
219+
220+
if (tamperSignature) {
221+
String signature = connection.getRequestProperty("Signature");
222+
connection.setRequestProperty("Signature", mutateSignatureHeader(signature));
223+
}
224+
225+
connection.connect();
226+
int responseCode = connection.getResponseCode();
227+
responseBody = readResponseBody(connection);
228+
229+
if (!tamperSignature) {
230+
if (isAcceptedResponse(responseCode, responseBody))
231+
batchResult.validAccepted++;
232+
else if (isRejectedResponse(responseCode, responseBody))
233+
batchResult.validRejected++;
234+
else
235+
batchResult.unexpectedResponses++;
236+
} else {
237+
if (isRejectedResponse(responseCode, responseBody))
238+
batchResult.tamperedRejected++;
239+
else if (isAcceptedResponse(responseCode, responseBody))
240+
batchResult.tamperedAccepted++;
241+
else
242+
batchResult.unexpectedResponses++;
243+
}
244+
245+
Log.d(TAG, "Batch request " + (i + 1) + "/" + MESSAGE_SIGNING_BATCH_SIZE
246+
+ " tampered=" + tamperSignature
247+
+ " code=" + responseCode
248+
+ " body=" + responseBody);
249+
} catch (Exception e) {
250+
batchResult.networkErrors++;
251+
Log.e(TAG, "Batch request " + (i + 1) + "/" + MESSAGE_SIGNING_BATCH_SIZE
252+
+ " tampered=" + tamperSignature + " failed", e);
253+
} finally {
254+
if (connection != null)
255+
connection.disconnect();
256+
}
257+
}
258+
259+
final String summary = String.format(
260+
Locale.US,
261+
"Batch test complete (%d requests)\nvalid accepted: %d\nvalid rejected: %d\ntampered accepted: %d\ntampered rejected: %d\nnetwork errors: %d\nunexpected: %d",
262+
MESSAGE_SIGNING_BATCH_SIZE,
263+
batchResult.validAccepted,
264+
batchResult.validRejected,
265+
batchResult.tamperedAccepted,
266+
batchResult.tamperedRejected,
267+
batchResult.networkErrors,
268+
batchResult.unexpectedResponses
269+
);
270+
271+
activity.runOnUiThread(new Runnable() {
272+
@Override
273+
public void run() {
274+
statusImageView.setVisibility(View.GONE);
275+
statusTextView.setText(summary);
276+
statusView.setVisibility(View.VISIBLE);
277+
}
278+
});
279+
}
280+
});
281+
}
282+
118283
@Override
119284
protected void onCreate(Bundle savedInstanceState) {
120285
super.onCreate(savedInstanceState);
@@ -185,62 +350,7 @@ public void run() {
185350
shapesCheckButton.setOnClickListener(new View.OnClickListener() {
186351
@Override
187352
public void onClick(View view) {
188-
// hide status
189-
activity.runOnUiThread(new Runnable() {
190-
@Override
191-
public void run() {
192-
statusView.setVisibility(View.INVISIBLE);
193-
}
194-
});
195-
196-
// run our HTTP request in a background thread to avoid blocking the UI thread
197-
AsyncTask.execute(new Runnable() {
198-
@Override
199-
public void run() {
200-
// fetch from the endpoint
201-
int imgId = R.drawable.confused;
202-
String msg;
203-
HttpsURLConnection connection = null;
204-
try {
205-
URL url = new URL(getResources().getString(R.string.shapes_url));
206-
connection = (HttpsURLConnection) url.openConnection();
207-
connection.setRequestMethod("GET");
208-
// Keep the originally signed request target unchanged (redirects can invalidate signatures).
209-
connection.setInstanceFollowRedirects(false);
210-
connection.addRequestProperty("Api-Key", getResources().getString(R.string.shapes_api_key));
211-
212-
// *** UNCOMMENT THE LINE BELOW FOR APPROOV USING SECRETS PROTECTION ***
213-
//ApproovService.addSubstitutionHeader("Api-Key", null);
214-
215-
// *** UNCOMMENT THE LINE BELOW FOR APPROOV ***
216-
//ApproovService.addApproov(connection);
217-
218-
connection.connect();
219-
220-
msg = "Http status code " + connection.getResponseCode();
221-
if (connection.getResponseCode() == 200)
222-
imgId = readShapesResponse(connection);
223-
} catch (IOException e) {
224-
Log.d(TAG, "Shapes call failed: " + e.toString());
225-
msg = "Shapes call failed: " + e.toString();
226-
}
227-
if (connection != null)
228-
connection.disconnect();
229-
230-
// display the result
231-
final int finalImgId = imgId;
232-
final String finalMsg = msg;
233-
activity.runOnUiThread(new Runnable() {
234-
@Override
235-
public void run() {
236-
statusImageView.setImageResource(finalImgId);
237-
statusTextView.setText(finalMsg);
238-
statusView.setVisibility(View.VISIBLE);
239-
}
240-
});
241-
242-
}
243-
});
353+
runMessageSigningBatchTest();
244354
}
245355
});
246356
}

shapes-app/app/src/main/java/io/approov/shapes/ShapesApp.java

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,30 @@
2020
import android.app.Application;
2121

2222
// *** UNCOMMENT THE LINE BELOW FOR APPROOV ***
23-
//import io.approov.service.httpsurlconn.ApproovService;
23+
import io.approov.service.httpsurlconn.ApproovDefaultMessageSigning;
24+
import io.approov.service.httpsurlconn.ApproovException;
25+
import io.approov.service.httpsurlconn.ApproovService;
2426

2527
// *** UNCOMMENT THE LINE BELOW FOR APPROOV WITH INSTALLATION MESSAGE SIGNING ***
26-
//import io.approov.service.httpsurlconn.ApproovDefaultMessageSigning;
27-
2828

2929
public class ShapesApp extends Application {
3030
@Override
3131
public void onCreate() {
3232
super.onCreate();
3333

3434
// *** UNCOMMENT THE LINE BELOW FOR APPROOV ***
35-
//ApproovService.initialize(getApplicationContext(), "#199896#cjjyY5WNuuhkdzTDYV49eGZfXOYbnQrL16nzxoYqWbc=");
35+
ApproovService.initialize(getApplicationContext(), "#199896#cjjyY5WNuuhkdzTDYV49eGZfXOYbnQrL16nzxoYqWbc=");
36+
try {
37+
ApproovService.setDevKey("1JvoZZZ-tGZ6epKk");
38+
} catch (ApproovException e) {
39+
throw new RuntimeException(e);
40+
}
3641

3742
// *** UNCOMMENT THE LINES BELOW FOR APPROOV WITH INSTALLATION MESSAGE SIGNING ***
38-
// ApproovService.setServiceMutator(
39-
// new ApproovDefaultMessageSigning()
40-
// .setDefaultFactory(ApproovDefaultMessageSigning.generateDefaultSignatureParametersFactory())
41-
// );
43+
ApproovService.setServiceMutator(
44+
new ApproovDefaultMessageSigning()
45+
.setDefaultFactory(ApproovDefaultMessageSigning.generateDefaultSignatureParametersFactory())
46+
);
4247

4348
}
4449
}

shapes-app/app/src/main/res/values/strings.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
<string name="check_network_label">Say Hello</string>
55
<string name="check_shapes_label">Get Shape</string>
66
<string name="hello_url">https://shapes.approov.io/v1/hello/</string>
7-
<string name="shapes_url">https://shapes.approov.io/v1/shapes/</string>
7+
<string name="shapes_url">https://james-approov-gateway.example1313.workers.dev/test</string>
88
<string name="shapes_api_key">yXClypapWNHIifHUWmBIyPFAm</string>
99
</resources>

0 commit comments

Comments
 (0)