Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / android / java / src / org / chromium / chrome / browser / WebappAuthenticator.java
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.chrome.browser;
6
7 import android.content.Context;
8 import android.os.AsyncTask;
9 import android.util.Log;
10
11 import org.chromium.base.SecureRandomInitializer;
12
13 import java.io.File;
14 import java.io.FileInputStream;
15 import java.io.FileOutputStream;
16 import java.io.IOException;
17 import java.security.GeneralSecurityException;
18 import java.security.SecureRandom;
19 import java.util.concurrent.Callable;
20 import java.util.concurrent.ExecutionException;
21 import java.util.concurrent.FutureTask;
22
23 import javax.crypto.KeyGenerator;
24 import javax.crypto.Mac;
25 import javax.crypto.SecretKey;
26 import javax.crypto.spec.SecretKeySpec;
27
28 /**
29  * Authenticate the source of Intents to launch web apps (see e.g. {@link #FullScreenActivity}).
30  *
31  * Chrome does not keep a store of valid URLs for installed web apps (because it cannot know when
32  * any have been uninstalled). Therefore, upon installation, it tells the Launcher a message
33  * authentication code (MAC) along with the URL for the web app, and then Chrome can verify the MAC
34  * when starting e.g. {@link #FullScreenActivity}. Chrome can thus distinguish between legitimate,
35  * installed web apps and arbitrary other URLs.
36  */
37 public class WebappAuthenticator {
38     private static final String TAG = "WebappAuthenticator";
39     private static final String MAC_ALGORITHM_NAME = "HmacSHA256";
40     private static final String MAC_KEY_BASENAME = "webapp-authenticator";
41     private static final int MAC_KEY_BYTE_COUNT = 32;
42     private static final Object sLock = new Object();
43
44     private static FutureTask<SecretKey> sMacKeyGenerator;
45     private static SecretKey sKey = null;
46
47     /**
48      * @see #getMacForUrl
49      *
50      * @param url The URL to validate.
51      * @param mac The bytes of a previously-calculated MAC.
52      *
53      * @return true if the MAC is a valid MAC for the URL, false otherwise.
54      */
55     public static boolean isUrlValid(Context context, String url, byte[] mac) {
56         byte[] goodMac = getMacForUrl(context, url);
57         if (goodMac == null) {
58             return false;
59         }
60         return constantTimeAreArraysEqual(goodMac, mac);
61     }
62
63     /**
64      * @see #isUrlValid
65      *
66      * @param url A URL for which to calculate a MAC.
67      *
68      * @return The bytes of a MAC for the URL, or null if a secure MAC was not available.
69      */
70     public static byte[] getMacForUrl(Context context, String url) {
71         Mac mac = getMac(context);
72         if (mac == null) {
73             return null;
74         }
75         return mac.doFinal(url.getBytes());
76     }
77
78     // TODO(palmer): Put this method, and as much of this class as possible, in a utility class.
79     private static boolean constantTimeAreArraysEqual(byte[] a, byte[] b) {
80         if (a.length != b.length) {
81             return false;
82         }
83
84         int result = 0;
85         for (int i = 0; i < a.length; i++) {
86             result |= a[i] ^ b[i];
87         }
88         return result == 0;
89     }
90
91     private static SecretKey readKeyFromFile(
92             Context context, String basename, String algorithmName) {
93         FileInputStream input = null;
94         File file = context.getFileStreamPath(basename);
95         try {
96             if (file.length() != MAC_KEY_BYTE_COUNT) {
97                 Log.w(TAG, "Could not read key from '" + file + "': invalid file contents");
98                 return null;
99             }
100
101             byte[] keyBytes = new byte[MAC_KEY_BYTE_COUNT];
102             input = new FileInputStream(file);
103             if (MAC_KEY_BYTE_COUNT != input.read(keyBytes)) {
104                 return null;
105             }
106
107             try {
108                 return new SecretKeySpec(keyBytes, algorithmName);
109             } catch (IllegalArgumentException e) {
110                 return null;
111             }
112         } catch (Exception e) {
113             Log.w(TAG, "Could not read key from '" + file + "': " + e);
114             return null;
115         } finally {
116             try {
117                 if (input != null) {
118                     input.close();
119                 }
120             } catch (IOException e) {
121                 Log.e(TAG, "Could not close key input stream '" + file + "': " + e);
122             }
123         }
124     }
125
126     private static boolean writeKeyToFile(Context context, String basename, SecretKey key) {
127         File file = context.getFileStreamPath(basename);
128         byte[] keyBytes = key.getEncoded();
129         FileOutputStream output = null;
130         if (MAC_KEY_BYTE_COUNT != keyBytes.length) {
131             Log.e(TAG, "writeKeyToFile got key encoded bytes length " + keyBytes.length +
132                        "; expected " + MAC_KEY_BYTE_COUNT);
133             return false;
134         }
135
136         try {
137             output = new FileOutputStream(file);
138             output.write(keyBytes);
139             return true;
140         } catch (Exception e) {
141             Log.e(TAG, "Could not write key to '" + file + "': " + e);
142             return false;
143         } finally {
144             try {
145                 if (output != null) {
146                     output.close();
147                 }
148             } catch (IOException e) {
149                 Log.e(TAG, "Could not close key output stream '" + file + "': " + e);
150             }
151         }
152     }
153
154     private static SecretKey getKey(Context context) {
155         synchronized (sLock) {
156             if (sKey == null) {
157                 SecretKey key = readKeyFromFile(context, MAC_KEY_BASENAME, MAC_ALGORITHM_NAME);
158                 if (key != null) {
159                     sKey = key;
160                     return sKey;
161                 }
162
163                 triggerMacKeyGeneration();
164                 try {
165                     sKey = sMacKeyGenerator.get();
166                     sMacKeyGenerator = null;
167                     if (!writeKeyToFile(context, MAC_KEY_BASENAME, sKey)) {
168                         sKey = null;
169                         return null;
170                     }
171                     return sKey;
172                 } catch (InterruptedException e) {
173                     throw new RuntimeException(e);
174                 } catch (ExecutionException e) {
175                     throw new RuntimeException(e);
176                 }
177             }
178             return sKey;
179         }
180     }
181
182     /**
183      * Generates the authentication encryption key in a background thread (if necessary).
184      */
185     private static void triggerMacKeyGeneration() {
186         synchronized (sLock) {
187             if (sKey != null || sMacKeyGenerator != null) {
188                 return;
189             }
190
191             sMacKeyGenerator = new FutureTask<SecretKey>(new Callable<SecretKey>() {
192                 @Override
193                 public SecretKey call() throws Exception {
194                     KeyGenerator generator = KeyGenerator.getInstance(MAC_ALGORITHM_NAME);
195                     SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
196                     SecureRandomInitializer.initialize(random);
197                     generator.init(MAC_KEY_BYTE_COUNT * 8, random);
198                     return generator.generateKey();
199                 }
200             });
201             AsyncTask.THREAD_POOL_EXECUTOR.execute(sMacKeyGenerator);
202         }
203     }
204
205     /**
206      * @return A Mac, or null if it is not possible to instantiate one.
207      */
208     private static Mac getMac(Context context) {
209         try {
210             SecretKey key = getKey(context);
211             if (key == null) {
212                 // getKey should have invoked triggerMacKeyGeneration, which should have set the
213                 // random seed and generated a key from it. If not, there is a problem with the
214                 // random number generator, and we must not claim that authentication can work.
215                 return null;
216             }
217             Mac mac = Mac.getInstance(MAC_ALGORITHM_NAME);
218             mac.init(key);
219             return mac;
220         } catch (GeneralSecurityException e) {
221             Log.w(TAG, "Error in creating MAC instance", e);
222             return null;
223         }
224     }
225 }