1 /** 2 * Copyright (C) 2021 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.remoteprovisioner; 18 19 import android.content.Context; 20 import android.util.Base64; 21 import android.util.Log; 22 23 import java.io.BufferedInputStream; 24 import java.io.ByteArrayOutputStream; 25 import java.io.IOException; 26 import java.io.OutputStream; 27 import java.net.HttpURLConnection; 28 import java.net.SocketTimeoutException; 29 import java.net.URL; 30 import java.util.List; 31 32 /** 33 * Provides convenience methods for interfacing with the remote provisioning server. 34 */ 35 public class ServerInterface { 36 37 private static final int TIMEOUT_MS = 5000; 38 39 private static final String TAG = "ServerInterface"; 40 private static final String GEEK_URL = ":fetchEekChain"; 41 private static final String CERTIFICATE_SIGNING_URL = ":signCertificates?challenge="; 42 43 /** 44 * Ferries the CBOR blobs returned by KeyMint to the provisioning server. The data sent to the 45 * provisioning server contains the MAC'ed CSRs and encrypted bundle containing the MAC key and 46 * the hardware unique public key. 47 * 48 * @param context The application context which is required to use SettingsManager. 49 * @param csr The CBOR encoded data containing the relevant pieces needed for the server to 50 * sign the CSRs. The data encoded within comes from Keystore / KeyMint. 51 * @param challenge The challenge that was sent from the server. It is included here even though 52 * it is also included in `cborBlob` in order to allow the server to more 53 * easily reject bad requests. 54 * @return A List of byte arrays, where each array contains an entire DER-encoded certificate 55 * chain for one attestation key pair. 56 */ requestSignedCertificates(Context context, byte[] csr, byte[] challenge)57 public static List<byte[]> requestSignedCertificates(Context context, byte[] csr, 58 byte[] challenge) { 59 try { 60 URL url = new URL(SettingsManager.getUrl(context) + CERTIFICATE_SIGNING_URL 61 + Base64.encodeToString(challenge, Base64.URL_SAFE)); 62 HttpURLConnection con = (HttpURLConnection) url.openConnection(); 63 con.setRequestMethod("POST"); 64 con.setDoOutput(true); 65 con.setConnectTimeout(TIMEOUT_MS); 66 67 // May not be able to use try-with-resources here if the connection gets closed due to 68 // the output stream being automatically closed. 69 try (OutputStream os = con.getOutputStream()) { 70 os.write(csr, 0, csr.length); 71 } 72 73 if (con.getResponseCode() != HttpURLConnection.HTTP_OK) { 74 int failures = SettingsManager.incrementFailureCounter(context); 75 Log.e(TAG, "Server connection for signing failed, response code: " 76 + con.getResponseCode() + "\nRepeated failure count: " + failures); 77 return null; 78 } 79 SettingsManager.clearFailureCounter(context); 80 BufferedInputStream inputStream = new BufferedInputStream(con.getInputStream()); 81 ByteArrayOutputStream cborBytes = new ByteArrayOutputStream(); 82 byte[] buffer = new byte[1024]; 83 int read = 0; 84 while ((read = inputStream.read(buffer, 0, buffer.length)) != -1) { 85 cborBytes.write(buffer, 0, read); 86 } 87 return CborUtils.parseSignedCertificates(cborBytes.toByteArray()); 88 } catch (SocketTimeoutException e) { 89 SettingsManager.incrementFailureCounter(context); 90 Log.e(TAG, "Server timed out", e); 91 return null; 92 } catch (IOException e) { 93 SettingsManager.incrementFailureCounter(context); 94 Log.e(TAG, "Failed to request signed certificates from the server", e); 95 return null; 96 } 97 } 98 99 /** 100 * Calls out to the specified backend servers to retrieve an Endpoint Encryption Key and 101 * corresponding certificate chain to provide to KeyMint. This public key will be used to 102 * perform an ECDH computation, using the shared secret to encrypt privacy sensitive components 103 * in the bundle that the server needs from the device in order to provision certificates. 104 * 105 * A challenge is also returned from the server so that it can check freshness of the follow-up 106 * request to get keys signed. 107 * 108 * @param context The application context which is required to use SettingsManager. 109 * @return A GeekResponse object which optionally contains configuration data. 110 */ fetchGeek(Context context)111 public static GeekResponse fetchGeek(Context context) { 112 try { 113 URL url = new URL(SettingsManager.getUrl(context) + GEEK_URL); 114 HttpURLConnection con = (HttpURLConnection) url.openConnection(); 115 con.setRequestMethod("POST"); 116 con.setConnectTimeout(TIMEOUT_MS); 117 con.setDoOutput(true); 118 119 byte[] config = CborUtils.buildProvisioningInfo(context); 120 try (OutputStream os = con.getOutputStream()) { 121 os.write(config, 0, config.length); 122 } 123 124 if (con.getResponseCode() != HttpURLConnection.HTTP_OK) { 125 int failures = SettingsManager.incrementFailureCounter(context); 126 Log.e(TAG, "Server connection for GEEK failed, response code: " 127 + con.getResponseCode() + "\nRepeated failure count: " + failures); 128 return null; 129 } 130 SettingsManager.clearFailureCounter(context); 131 132 BufferedInputStream inputStream = new BufferedInputStream(con.getInputStream()); 133 ByteArrayOutputStream cborBytes = new ByteArrayOutputStream(); 134 byte[] buffer = new byte[1024]; 135 int read = 0; 136 while ((read = inputStream.read(buffer, 0, buffer.length)) != -1) { 137 cborBytes.write(buffer, 0, read); 138 } 139 inputStream.close(); 140 return CborUtils.parseGeekResponse(cborBytes.toByteArray()); 141 } catch (SocketTimeoutException e) { 142 SettingsManager.incrementFailureCounter(context); 143 Log.e(TAG, "Server timed out", e); 144 } catch (IOException e) { 145 // This exception will trigger on a completely malformed URL. 146 SettingsManager.incrementFailureCounter(context); 147 Log.e(TAG, "Failed to fetch GEEK from the servers.", e); 148 } 149 return null; 150 } 151 } 152