1package eu.siacs.conversations.utils;
2
3import android.os.Build;
4import android.os.Process;
5import android.util.Log;
6
7import java.io.ByteArrayOutputStream;
8import java.io.DataInputStream;
9import java.io.DataOutputStream;
10import java.io.File;
11import java.io.FileInputStream;
12import java.io.FileOutputStream;
13import java.io.IOException;
14import java.io.OutputStream;
15import java.io.UnsupportedEncodingException;
16import java.security.NoSuchAlgorithmException;
17import java.security.Provider;
18import java.security.SecureRandom;
19import java.security.SecureRandomSpi;
20import java.security.Security;
21
22/**
23 * Fixes for the output of the default PRNG having low entropy.
24 *
25 * The fixes need to be applied via {@link #apply()} before any use of Java
26 * Cryptography Architecture primitives. A good place to invoke them is in the
27 * application's {@code onCreate}.
28 */
29public final class PRNGFixes {
30
31 private static final int VERSION_CODE_JELLY_BEAN = 16;
32 private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18;
33 private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL =
34 getBuildFingerprintAndDeviceSerial();
35
36 /** Hidden constructor to prevent instantiation. */
37 private PRNGFixes() {}
38
39 /**
40 * Applies all fixes.
41 *
42 * @throws SecurityException if a fix is needed but could not be applied.
43 */
44 public static void apply() {
45 applyOpenSSLFix();
46 installLinuxPRNGSecureRandom();
47 }
48
49 /**
50 * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the
51 * fix is not needed.
52 *
53 * @throws SecurityException if the fix is needed but could not be applied.
54 */
55 private static void applyOpenSSLFix() throws SecurityException {
56 if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN)
57 || (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) {
58 // No need to apply the fix
59 return;
60 }
61
62 try {
63 // Mix in the device- and invocation-specific seed.
64 Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
65 .getMethod("RAND_seed", byte[].class)
66 .invoke(null, generateSeed());
67
68 // Mix output of Linux PRNG into OpenSSL's PRNG
69 int bytesRead = (Integer) Class.forName(
70 "org.apache.harmony.xnet.provider.jsse.NativeCrypto")
71 .getMethod("RAND_load_file", String.class, long.class)
72 .invoke(null, "/dev/urandom", 1024);
73 if (bytesRead != 1024) {
74 throw new IOException(
75 "Unexpected number of bytes read from Linux PRNG: "
76 + bytesRead);
77 }
78 } catch (Exception e) {
79 throw new SecurityException("Failed to seed OpenSSL PRNG", e);
80 }
81 }
82
83 /**
84 * Installs a Linux PRNG-backed {@code SecureRandom} implementation as the
85 * default. Does nothing if the implementation is already the default or if
86 * there is not need to install the implementation.
87 *
88 * @throws SecurityException if the fix is needed but could not be applied.
89 */
90 private static void installLinuxPRNGSecureRandom()
91 throws SecurityException {
92 if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
93 // No need to apply the fix
94 return;
95 }
96
97 // Install a Linux PRNG-based SecureRandom implementation as the
98 // default, if not yet installed.
99 Provider[] secureRandomProviders =
100 Security.getProviders("SecureRandom.SHA1PRNG");
101 if ((secureRandomProviders == null)
102 || (secureRandomProviders.length < 1)
103 || (!LinuxPRNGSecureRandomProvider.class.equals(
104 secureRandomProviders[0].getClass()))) {
105 Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1);
106 }
107
108 // Assert that new SecureRandom() and
109 // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
110 // by the Linux PRNG-based SecureRandom implementation.
111 SecureRandom rng1 = new SecureRandom();
112 if (!LinuxPRNGSecureRandomProvider.class.equals(
113 rng1.getProvider().getClass())) {
114 throw new SecurityException(
115 "new SecureRandom() backed by wrong Provider: "
116 + rng1.getProvider().getClass());
117 }
118
119 SecureRandom rng2;
120 try {
121 rng2 = SecureRandom.getInstance("SHA1PRNG");
122 } catch (NoSuchAlgorithmException e) {
123 throw new SecurityException("SHA1PRNG not available", e);
124 }
125 if (!LinuxPRNGSecureRandomProvider.class.equals(
126 rng2.getProvider().getClass())) {
127 throw new SecurityException(
128 "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong"
129 + " Provider: " + rng2.getProvider().getClass());
130 }
131 }
132
133 /**
134 * {@code Provider} of {@code SecureRandom} engines which pass through
135 * all requests to the Linux PRNG.
136 */
137 private static class LinuxPRNGSecureRandomProvider extends Provider {
138
139 public LinuxPRNGSecureRandomProvider() {
140 super("LinuxPRNG",
141 1.0,
142 "A Linux-specific random number provider that uses"
143 + " /dev/urandom");
144 // Although /dev/urandom is not a SHA-1 PRNG, some apps
145 // explicitly request a SHA1PRNG SecureRandom and we thus need to
146 // prevent them from getting the default implementation whose output
147 // may have low entropy.
148 put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
149 put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
150 }
151 }
152
153 /**
154 * {@link SecureRandomSpi} which passes all requests to the Linux PRNG
155 * ({@code /dev/urandom}).
156 */
157 public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
158
159 /*
160 * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed
161 * are passed through to the Linux PRNG (/dev/urandom). Instances of
162 * this class seed themselves by mixing in the current time, PID, UID,
163 * build fingerprint, and hardware serial number (where available) into
164 * Linux PRNG.
165 *
166 * Concurrency: Read requests to the underlying Linux PRNG are
167 * serialized (on sLock) to ensure that multiple threads do not get
168 * duplicated PRNG output.
169 */
170
171 private static final File URANDOM_FILE = new File("/dev/urandom");
172
173 private static final Object sLock = new Object();
174
175 /**
176 * Input stream for reading from Linux PRNG or {@code null} if not yet
177 * opened.
178 *
179 * @GuardedBy("sLock")
180 */
181 private static DataInputStream sUrandomIn;
182
183 /**
184 * Output stream for writing to Linux PRNG or {@code null} if not yet
185 * opened.
186 *
187 * @GuardedBy("sLock")
188 */
189 private static OutputStream sUrandomOut;
190
191 /**
192 * Whether this engine instance has been seeded. This is needed because
193 * each instance needs to seed itself if the client does not explicitly
194 * seed it.
195 */
196 private boolean mSeeded;
197
198 @Override
199 protected void engineSetSeed(byte[] bytes) {
200 try {
201 OutputStream out;
202 synchronized (sLock) {
203 out = getUrandomOutputStream();
204 }
205 out.write(bytes);
206 out.flush();
207 } catch (IOException e) {
208 // On a small fraction of devices /dev/urandom is not writable.
209 // Log and ignore.
210 Log.w(PRNGFixes.class.getSimpleName(),
211 "Failed to mix seed into " + URANDOM_FILE);
212 } finally {
213 mSeeded = true;
214 }
215 }
216
217 @Override
218 protected void engineNextBytes(byte[] bytes) {
219 if (!mSeeded) {
220 // Mix in the device- and invocation-specific seed.
221 engineSetSeed(generateSeed());
222 }
223
224 try {
225 DataInputStream in;
226 synchronized (sLock) {
227 in = getUrandomInputStream();
228 }
229 synchronized (in) {
230 in.readFully(bytes);
231 }
232 } catch (IOException e) {
233 throw new SecurityException(
234 "Failed to read from " + URANDOM_FILE, e);
235 }
236 }
237
238 @Override
239 protected byte[] engineGenerateSeed(int size) {
240 byte[] seed = new byte[size];
241 engineNextBytes(seed);
242 return seed;
243 }
244
245 private DataInputStream getUrandomInputStream() {
246 synchronized (sLock) {
247 if (sUrandomIn == null) {
248 // NOTE: Consider inserting a BufferedInputStream between
249 // DataInputStream and FileInputStream if you need higher
250 // PRNG output performance and can live with future PRNG
251 // output being pulled into this process prematurely.
252 try {
253 sUrandomIn = new DataInputStream(
254 new FileInputStream(URANDOM_FILE));
255 } catch (IOException e) {
256 throw new SecurityException("Failed to open "
257 + URANDOM_FILE + " for reading", e);
258 }
259 }
260 return sUrandomIn;
261 }
262 }
263
264 private OutputStream getUrandomOutputStream() throws IOException {
265 synchronized (sLock) {
266 if (sUrandomOut == null) {
267 sUrandomOut = new FileOutputStream(URANDOM_FILE);
268 }
269 return sUrandomOut;
270 }
271 }
272 }
273
274 /**
275 * Generates a device- and invocation-specific seed to be mixed into the
276 * Linux PRNG.
277 */
278 private static byte[] generateSeed() {
279 try {
280 ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
281 DataOutputStream seedBufferOut =
282 new DataOutputStream(seedBuffer);
283 seedBufferOut.writeLong(System.currentTimeMillis());
284 seedBufferOut.writeLong(System.nanoTime());
285 seedBufferOut.writeInt(Process.myPid());
286 seedBufferOut.writeInt(Process.myUid());
287 seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
288 seedBufferOut.close();
289 return seedBuffer.toByteArray();
290 } catch (IOException e) {
291 throw new SecurityException("Failed to generate seed", e);
292 }
293 }
294
295 /**
296 * Gets the hardware serial number of this device.
297 *
298 * @return serial number or {@code null} if not available.
299 */
300 private static String getDeviceSerialNumber() {
301 // We're using the Reflection API because Build.SERIAL is only available
302 // since API Level 9 (Gingerbread, Android 2.3).
303 try {
304 return (String) Build.class.getField("SERIAL").get(null);
305 } catch (Exception ignored) {
306 return null;
307 }
308 }
309
310 private static byte[] getBuildFingerprintAndDeviceSerial() {
311 StringBuilder result = new StringBuilder();
312 String fingerprint = Build.FINGERPRINT;
313 if (fingerprint != null) {
314 result.append(fingerprint);
315 }
316 String serial = getDeviceSerialNumber();
317 if (serial != null) {
318 result.append(serial);
319 }
320 try {
321 return result.toString().getBytes("UTF-8");
322 } catch (UnsupportedEncodingException e) {
323 throw new RuntimeException("UTF-8 encoding not supported");
324 }
325 }
326}