11package io .github .vvb2060 .keyattestation .attestation ;
22
33import android .os .Build ;
4+ import android .util .Log ;
45
56import org .json .JSONException ;
67import org .json .JSONObject ;
910import java .io .IOException ;
1011import java .io .InputStream ;
1112import java .math .BigInteger ;
13+ import java .net .HttpURLConnection ;
14+ import java .net .URL ;
1215import java .nio .charset .StandardCharsets ;
16+ import java .util .Date ;
1317import java .util .Locale ;
1418
1519import io .github .vvb2060 .keyattestation .AppApplication ;
1620import io .github .vvb2060 .keyattestation .R ;
1721
1822public 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 ;
0 commit comments