1 /*
2  * Copyright (C) 2016 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 android.security.net.config.cts;
18 
19 import android.security.net.config.cts.CtsNetSecConfigDownloadManagerTestCases.R;
20 
21 import android.app.DownloadManager;
22 import android.content.BroadcastReceiver;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.database.Cursor;
27 import android.net.Uri;
28 import android.os.SystemClock;
29 import android.text.format.DateUtils;
30 import android.util.Log;
31 
32 import java.io.ByteArrayOutputStream;
33 import java.io.InputStream;
34 import java.net.Socket;
35 import java.net.ServerSocket;
36 import java.security.KeyFactory;
37 import java.security.KeyStore;
38 import java.security.PrivateKey;
39 import java.security.Provider;
40 import java.security.Security;
41 import java.security.Signature;
42 import java.security.cert.Certificate;
43 import java.security.cert.CertificateFactory;
44 import java.security.cert.X509Certificate;
45 import java.security.spec.PKCS8EncodedKeySpec;
46 import java.util.Collection;
47 import java.util.HashSet;
48 import java.util.concurrent.Callable;
49 import java.util.concurrent.ExecutionException;
50 import java.util.concurrent.FutureTask;
51 import java.util.concurrent.TimeUnit;
52 import java.util.concurrent.TimeoutException;
53 
54 import javax.net.ssl.KeyManagerFactory;
55 import javax.net.ssl.SSLContext;
56 import javax.net.ssl.SSLServerSocket;
57 import javax.net.ssl.TrustManager;
58 import javax.net.ssl.TrustManagerFactory;
59 import javax.net.ssl.X509TrustManager;
60 
61 public class DownloadManagerTest extends BaseTestCase {
62 
63     private static final String HTTP_RESPONSE =
64             "HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\nContent-length: 5\r\n\r\nhello";
65     private static final long TIMEOUT = 3 * DateUtils.SECOND_IN_MILLIS;
66 
testConfigTrustedCaAccepted()67     public void testConfigTrustedCaAccepted() throws Exception {
68         SSLServerSocket serverSocket = bindTLSServer(R.raw.valid_chain, R.raw.test_key);
69         runDownloadManagerTest(serverSocket, true);
70     }
71 
testUntrustedCaRejected()72     public void testUntrustedCaRejected() throws Exception {
73         try {
74             SSLServerSocket serverSocket = bindTLSServer(R.raw.invalid_chain, R.raw.test_key);
75             runDownloadManagerTest(serverSocket, true);
76             fail("Invalid CA should be rejected");
77         } catch (Exception expected) {
78         }
79     }
80 
testPerDomainCleartextAccepted()81     public void testPerDomainCleartextAccepted() throws Exception {
82         ServerSocket serverSocket = new ServerSocket();
83         serverSocket.bind(null);
84         runDownloadManagerTest(serverSocket, false);
85     }
86 
runDownloadManagerTest(ServerSocket serverSocket, boolean https)87     private void runDownloadManagerTest(ServerSocket serverSocket, boolean https) throws Exception {
88         DownloadManager dm =
89                 (DownloadManager) getContext().getSystemService(Context.DOWNLOAD_SERVICE);
90         DownloadCompleteReceiver receiver = new DownloadCompleteReceiver();
91         FutureTask<Void> serverFuture = new FutureTask<Void>(new Callable() {
92             @Override
93             public Void call() throws Exception {
94                 runServer(serverSocket);
95                 return null;
96             }
97         });
98         try {
99             IntentFilter filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
100             getContext().registerReceiver(receiver, filter);
101             new Thread(serverFuture).start();
102             String host = (https ? "https" : "http") + "://localhost";
103             Uri destination = Uri.parse(host + ":" + serverSocket.getLocalPort());
104             long id = dm.enqueue(new DownloadManager.Request(destination));
105             try {
106                 serverFuture.get(TIMEOUT, TimeUnit.MILLISECONDS);
107                 // Check that the download was successful.
108                 receiver.waitForDownloadComplete(TIMEOUT, id);
109                 assertSuccessfulDownload(id);
110             } catch (InterruptedException e) {
111                 // Wrap InterruptedException since otherwise it gets eaten by AndroidTest
112                 throw new RuntimeException(e);
113             } finally {
114                 dm.remove(id);
115             }
116         } finally {
117             getContext().unregisterReceiver(receiver);
118             serverFuture.cancel(true);
119             try {
120                 serverSocket.close();
121             } catch (Exception ignored) {}
122         }
123     }
124 
runServer(ServerSocket server)125     private void runServer(ServerSocket server) throws Exception {
126         Socket s = server.accept();
127         s.getOutputStream().write(HTTP_RESPONSE.getBytes());
128         s.getOutputStream().flush();
129         s.close();
130     }
131 
bindTLSServer(int chainResId, int keyResId)132     private SSLServerSocket bindTLSServer(int chainResId, int keyResId) throws Exception {
133         // Load certificate chain.
134         CertificateFactory fact = CertificateFactory.getInstance("X.509");
135         Collection<? extends Certificate> certs;
136         try (InputStream is = getContext().getResources().openRawResource(chainResId)) {
137             certs = fact.generateCertificates(is);
138         }
139         X509Certificate[] chain = new X509Certificate[certs.size()];
140         int i = 0;
141         for (Certificate cert : certs) {
142             chain[i++] = (X509Certificate) cert;
143         }
144 
145         // Load private key for the leaf.
146         PrivateKey key;
147         try (InputStream is = getContext().getResources().openRawResource(keyResId)) {
148             ByteArrayOutputStream keyout = new ByteArrayOutputStream();
149             byte[] buffer = new byte[4096];
150             int chunk_size;
151             while ((chunk_size = is.read(buffer)) != -1) {
152                 keyout.write(buffer, 0, chunk_size);
153             }
154             is.close();
155             byte[] keyBytes = keyout.toByteArray();
156             key = KeyFactory.getInstance("RSA")
157                     .generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
158         }
159 
160         // Create KeyStore based on the private key/chain.
161         KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
162         ks.load(null);
163         ks.setKeyEntry("name", key, null, chain);
164 
165         // Create SSLContext.
166         TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX");
167         tmf.init(ks);
168         KeyManagerFactory kmf =
169                 KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
170         kmf.init(ks, null);
171         SSLContext context = SSLContext.getInstance("TLS");
172         context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
173 
174         SSLServerSocket s = (SSLServerSocket) context.getServerSocketFactory().createServerSocket();
175         s.bind(null);
176         return s;
177     }
178 
assertSuccessfulDownload(long id)179     private void assertSuccessfulDownload(long id) throws Exception {
180         Cursor cursor = null;
181         DownloadManager dm =
182                 (DownloadManager) getContext().getSystemService(Context.DOWNLOAD_SERVICE);
183         try {
184             cursor = dm.query(new DownloadManager.Query().setFilterById(id));
185             assertTrue(cursor.moveToNext());
186             assertEquals(DownloadManager.STATUS_SUCCESSFUL, cursor.getInt(
187                     cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)));
188         } finally {
189             if (cursor != null) {
190                 cursor.close();
191             }
192         }
193     }
194 
195     private static final class DownloadCompleteReceiver extends BroadcastReceiver {
196         private HashSet<Long> mCompletedDownloads = new HashSet<>();
197 
DownloadCompleteReceiver()198         public DownloadCompleteReceiver() {
199         }
200 
201         @Override
onReceive(Context context, Intent intent)202         public void onReceive(Context context, Intent intent) {
203             synchronized(mCompletedDownloads) {
204                 mCompletedDownloads.add(intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1));
205                 mCompletedDownloads.notifyAll();
206             }
207         }
208 
waitForDownloadComplete(long timeout, long id)209         public void waitForDownloadComplete(long timeout, long id)
210                 throws TimeoutException, InterruptedException  {
211             long deadline = SystemClock.elapsedRealtime() + timeout;
212             do {
213                 synchronized (mCompletedDownloads) {
214                     long millisTillTimeout = deadline - SystemClock.elapsedRealtime();
215                     if (millisTillTimeout > 0) {
216                         mCompletedDownloads.wait(millisTillTimeout);
217                     }
218                     if (mCompletedDownloads.contains(id)) {
219                         return;
220                     }
221                 }
222             } while (SystemClock.elapsedRealtime() < deadline);
223 
224             throw new TimeoutException("Timed out waiting for download complete");
225         }
226     }
227 
228 
229 }
230