• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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