Skip to content

Commit 84aa125

Browse files
Copilotvvb2060
andcommitted
Add network support for revocation list and UI display for publish time
Co-authored-by: vvb2060 <26996262+vvb2060@users.noreply.github.com>
1 parent ada0d7f commit 84aa125

5 files changed

Lines changed: 87 additions & 3 deletions

File tree

app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
33
xmlns:tools="http://schemas.android.com/tools">
44

5+
<uses-permission android:name="android.permission.INTERNET" />
6+
57
<permission
68
android:name="${applicationId}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
79
android:protectionLevel="signature"

app/src/main/java/io/github/vvb2060/keyattestation/attestation/RevocationList.java

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.github.vvb2060.keyattestation.attestation;
22

33
import android.os.Build;
4+
import android.util.Log;
45

56
import org.json.JSONException;
67
import org.json.JSONObject;
@@ -9,14 +10,23 @@
910
import java.io.IOException;
1011
import java.io.InputStream;
1112
import java.math.BigInteger;
13+
import java.net.HttpURLConnection;
14+
import java.net.URL;
1215
import java.nio.charset.StandardCharsets;
16+
import java.util.Date;
1317
import java.util.Locale;
1418

1519
import io.github.vvb2060.keyattestation.AppApplication;
1620
import io.github.vvb2060.keyattestation.R;
1721

1822
public record RevocationList(String status, String reason) {
19-
private static final JSONObject data = getStatus();
23+
private static final String TAG = "RevocationList";
24+
private static JSONObject data;
25+
private static Date publishTime;
26+
27+
static {
28+
data = getStatus();
29+
}
2030

2131
private static String toString(InputStream input) throws IOException {
2232
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
@@ -34,12 +44,50 @@ private static String toString(InputStream input) throws IOException {
3444
private static JSONObject parseStatus(InputStream inputStream) throws IOException {
3545
try {
3646
var statusListJson = new JSONObject(toString(inputStream));
47+
// Try to extract the publish time if it exists
48+
try {
49+
if (statusListJson.has("timestamp")) {
50+
long timestamp = statusListJson.getLong("timestamp");
51+
publishTime = new Date(timestamp);
52+
}
53+
} catch (JSONException e) {
54+
Log.w(TAG, "Failed to parse timestamp from revocation list", e);
55+
}
3756
return statusListJson.getJSONObject("entries");
3857
} catch (JSONException e) {
3958
throw new IOException(e);
4059
}
4160
}
4261

62+
private static JSONObject fetchFromNetwork(String statusUrl) {
63+
HttpURLConnection connection = null;
64+
try {
65+
URL url = new URL(statusUrl);
66+
connection = (HttpURLConnection) url.openConnection();
67+
connection.setRequestMethod("GET");
68+
connection.setConnectTimeout(10000);
69+
connection.setReadTimeout(10000);
70+
connection.setRequestProperty("User-Agent", "KeyAttestation");
71+
72+
int responseCode = connection.getResponseCode();
73+
if (responseCode == HttpURLConnection.HTTP_OK) {
74+
try (var input = connection.getInputStream()) {
75+
return parseStatus(input);
76+
}
77+
} else {
78+
Log.w(TAG, "Failed to fetch revocation list from network, HTTP " + responseCode);
79+
return null;
80+
}
81+
} catch (Exception e) {
82+
Log.w(TAG, "Failed to fetch revocation list from network", e);
83+
return null;
84+
} finally {
85+
if (connection != null) {
86+
connection.disconnect();
87+
}
88+
}
89+
}
90+
4391
private static JSONObject getStatus() {
4492
var statusUrl = "https://android.googleapis.com/attestation/status";
4593
var resName = "android:string/vendor_required_attestation_revocation_list_url";
@@ -49,17 +97,34 @@ private static JSONObject getStatus() {
4997
if (id != 0) {
5098
var url = res.getString(id);
5199
if (!statusUrl.equals(url) && url.toLowerCase(Locale.ROOT).startsWith("https")) {
52-
// no network permission, waiting for user report
53-
throw new RuntimeException("unknown status url: " + url);
100+
statusUrl = url;
54101
}
55102
}
103+
104+
// Try to fetch from network first
105+
JSONObject networkData = fetchFromNetwork(statusUrl);
106+
if (networkData != null) {
107+
Log.i(TAG, "Successfully fetched revocation list from network");
108+
return networkData;
109+
}
110+
111+
// Fallback to local resource
112+
Log.i(TAG, "Using local revocation list");
56113
try (var input = res.openRawResource(R.raw.status)) {
57114
return parseStatus(input);
58115
} catch (IOException e) {
59116
throw new RuntimeException("Failed to parse certificate revocation status", e);
60117
}
61118
}
62119

120+
public static Date getPublishTime() {
121+
return publishTime;
122+
}
123+
124+
public static void refresh() {
125+
data = getStatus();
126+
}
127+
63128
public static RevocationList get(BigInteger serialNumber) {
64129
String serialNumberString = serialNumber.toString(16).toLowerCase();
65130
JSONObject revocationStatus;

app/src/main/java/io/github/vvb2060/keyattestation/home/HomeAdapter.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,18 @@ class HomeAdapter(listener: Listener) : IdBasedRecyclerViewAdapter() {
9494
addItem(CommonItemViewHolder.CERT_INFO_CREATOR, certInfo, id++)
9595
}
9696

97+
// Add revocation list information
98+
val publishTime = io.github.vvb2060.keyattestation.attestation.RevocationList.getPublishTime()
99+
val dateStr = if (publishTime != null) {
100+
io.github.vvb2060.keyattestation.attestation.AuthorizationList.formatDate(publishTime)
101+
} else {
102+
null
103+
}
104+
addItem(CommonItemViewHolder.COMMON_CREATOR, CommonData(
105+
R.string.revocation_list_publish_time,
106+
R.string.revocation_list_description,
107+
dateStr), ID_REVOCATION_INFO)
108+
97109
when (baseData) {
98110
is AttestationData -> updateData(baseData)
99111
is RemoteProvisioningData -> updateData(baseData)
@@ -271,6 +283,7 @@ class HomeAdapter(listener: Listener) : IdBasedRecyclerViewAdapter() {
271283
private const val ID_CERT_STATUS = 1L
272284
private const val ID_BOOT_STATUS = 2L
273285
private const val ID_CERT_INFO_START = 1000L
286+
private const val ID_REVOCATION_INFO = 1900L
274287
private const val ID_RKP_HOSTNAME = 2000L
275288
private const val ID_DESCRIPTION_START = 3000L
276289
private const val ID_AUTHORIZATION_LIST_START = 4000L

app/src/main/res/values-zh-rCN/strings.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151
<string name="cert_error_expired">已过期:</string>
5252
<string name="provisioning_info_certs_issued">过去 30 天证书颁发数量:</string>
5353
<string name="provisioning_info_manufacturer">制造商:</string>
54+
<string name="revocation_list_publish_time">吊销列表发布时间</string>
55+
<string name="revocation_list_description">吊销列表用于检查证书是否被吊销。这里显示当前使用的吊销列表发布时间。</string>
5456

5557
<string name="error_message_subtitle">详细信息:</string>
5658
<string name="error_unknown">未知错误</string>

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151
<string name="cert_error_expired">expired: </string>
5252
<string name="provisioning_info_certs_issued">number of certs issued in last 30 days: </string>
5353
<string name="provisioning_info_manufacturer">manufacturer: </string>
54+
<string name="revocation_list_publish_time">Revocation list publish time</string>
55+
<string name="revocation_list_description">The revocation list is used to check if certificates have been revoked. This shows the publication time of the currently used revocation list.</string>
5456

5557
<string name="error_message_subtitle">Detailed messages:</string>
5658
<string name="error_unknown">Unknown error</string>

0 commit comments

Comments
 (0)