1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.dynsystem; 18 19 import android.text.TextUtils; 20 import android.util.Log; 21 22 import com.android.internal.annotations.VisibleForTesting; 23 24 import org.json.JSONArray; 25 import org.json.JSONException; 26 import org.json.JSONObject; 27 28 import java.io.IOException; 29 import java.io.InputStream; 30 import java.net.URL; 31 import java.net.URLConnection; 32 import java.util.HashMap; 33 34 class KeyRevocationList { 35 36 private static final String TAG = "KeyRevocationList"; 37 38 private static final String JSON_ENTRIES = "entries"; 39 private static final String JSON_PUBLIC_KEY = "public_key"; 40 private static final String JSON_STATUS = "status"; 41 private static final String JSON_REASON = "reason"; 42 43 private static final String STATUS_REVOKED = "REVOKED"; 44 45 @VisibleForTesting 46 HashMap<String, RevocationStatus> mEntries; 47 48 static class RevocationStatus { 49 final String mStatus; 50 final String mReason; 51 RevocationStatus(String status, String reason)52 RevocationStatus(String status, String reason) { 53 mStatus = status; 54 mReason = reason; 55 } 56 } 57 KeyRevocationList()58 KeyRevocationList() { 59 mEntries = new HashMap<String, RevocationStatus>(); 60 } 61 62 /** 63 * Returns the revocation status of a public key. 64 * 65 * @return a RevocationStatus for |publicKey|, null if |publicKey| doesn't exist. 66 */ getRevocationStatusForKey(String publicKey)67 RevocationStatus getRevocationStatusForKey(String publicKey) { 68 return mEntries.get(publicKey); 69 } 70 71 /** Test if a public key is revoked or not. */ isRevoked(String publicKey)72 boolean isRevoked(String publicKey) { 73 RevocationStatus entry = getRevocationStatusForKey(publicKey); 74 return entry != null && TextUtils.equals(entry.mStatus, STATUS_REVOKED); 75 } 76 77 @VisibleForTesting addEntry(String publicKey, String status, String reason)78 void addEntry(String publicKey, String status, String reason) { 79 mEntries.put(publicKey, new RevocationStatus(status, reason)); 80 } 81 82 /** 83 * Creates a KeyRevocationList from a JSON String. 84 * 85 * @param jsonString the revocation list, for example: 86 * <pre>{@code 87 * { 88 * "entries": [ 89 * { 90 * "public_key": "00fa2c6637c399afa893fe83d85f3569998707d5", 91 * "status": "REVOKED", 92 * "reason": "Revocation Reason" 93 * } 94 * ] 95 * } 96 * }</pre> 97 * 98 * @throws JSONException if |jsonString| is malformed. 99 */ fromJsonString(String jsonString)100 static KeyRevocationList fromJsonString(String jsonString) throws JSONException { 101 JSONObject jsonObject = new JSONObject(jsonString); 102 KeyRevocationList list = new KeyRevocationList(); 103 Log.d(TAG, "Begin of revocation list"); 104 if (jsonObject.has(JSON_ENTRIES)) { 105 JSONArray entries = jsonObject.getJSONArray(JSON_ENTRIES); 106 for (int i = 0; i < entries.length(); ++i) { 107 JSONObject entry = entries.getJSONObject(i); 108 String publicKey = entry.getString(JSON_PUBLIC_KEY); 109 String status = entry.getString(JSON_STATUS); 110 String reason = entry.has(JSON_REASON) ? entry.getString(JSON_REASON) : ""; 111 list.addEntry(publicKey, status, reason); 112 Log.d(TAG, "Revocation entry: " + entry.toString()); 113 } 114 } 115 Log.d(TAG, "End of revocation list"); 116 return list; 117 } 118 119 /** 120 * Creates a KeyRevocationList from a URL. 121 * 122 * @throws IOException if |url| is inaccessible. 123 * @throws JSONException if fetched content is malformed. 124 */ fromUrl(URL url)125 static KeyRevocationList fromUrl(URL url) throws IOException, JSONException { 126 Log.d(TAG, "Fetch from URL: " + url.toString()); 127 // Force "conditional GET" 128 // Force validate the cached result with server each time, and use the cached result 129 // only if it is validated by server, else fetch new data from server. 130 // Ref: https://developer.android.com/reference/android/net/http/HttpResponseCache#force-a-network-response 131 URLConnection connection = url.openConnection(); 132 connection.setUseCaches(true); 133 connection.addRequestProperty("Cache-Control", "max-age=0"); 134 try (InputStream stream = connection.getInputStream()) { 135 return fromJsonString(readFully(stream)); 136 } 137 } 138 readFully(InputStream in)139 private static String readFully(InputStream in) throws IOException { 140 int n; 141 byte[] buffer = new byte[4096]; 142 StringBuilder builder = new StringBuilder(); 143 while ((n = in.read(buffer, 0, 4096)) > -1) { 144 builder.append(new String(buffer, 0, n)); 145 } 146 return builder.toString(); 147 } 148 } 149