Revision ae993855

View differences:

src/info/guardianproject/notepadbot/NoteCipherApp.java
1

  
2
package info.guardianproject.notepadbot;
3

  
4
import android.app.Application;
5

  
6
public class NoteCipherApp extends Application {
7
    @Override
8
    public void onCreate() {
9
        // Apply the Google PRNG fixes to properly seed SecureRandom
10
        PRNGFixes.apply();
11
    }
12
}
src/info/guardianproject/notepadbot/PRNGFixes.java
1
package info.guardianproject.notepadbot;
2

  
3
import android.os.Build;
4
import android.os.Process;
5
import android.util.Log;
6

  
7
import java.io.ByteArrayOutputStream;
8
import java.io.DataInputStream;
9
import java.io.DataOutputStream;
10
import java.io.File;
11
import java.io.FileInputStream;
12
import java.io.FileOutputStream;
13
import java.io.IOException;
14
import java.io.OutputStream;
15
import java.io.UnsupportedEncodingException;
16
import java.security.NoSuchAlgorithmException;
17
import java.security.Provider;
18
import java.security.SecureRandom;
19
import java.security.SecureRandomSpi;
20
import 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
 */
29
public 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
    @SuppressWarnings("serial")
138
    private static class LinuxPRNGSecureRandomProvider extends Provider {
139

  
140
        public LinuxPRNGSecureRandomProvider() {
141
            super("LinuxPRNG",
142
                    1.0,
143
                    "A Linux-specific random number provider that uses"
144
                        + " /dev/urandom");
145
            // Although /dev/urandom is not a SHA-1 PRNG, some apps
146
            // explicitly request a SHA1PRNG SecureRandom and we thus need to
147
            // prevent them from getting the default implementation whose output
148
            // may have low entropy.
149
            put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName());
150
            put("SecureRandom.SHA1PRNG ImplementedIn", "Software");
151
        }
152
    }
153

  
154
    /**
155
     * {@link SecureRandomSpi} which passes all requests to the Linux PRNG
156
     * ({@code /dev/urandom}).
157
     */
158
    @SuppressWarnings("serial")
159
    public static class LinuxPRNGSecureRandom extends SecureRandomSpi {
160

  
161
        /*
162
         * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed
163
         * are passed through to the Linux PRNG (/dev/urandom). Instances of
164
         * this class seed themselves by mixing in the current time, PID, UID,
165
         * build fingerprint, and hardware serial number (where available) into
166
         * Linux PRNG.
167
         *
168
         * Concurrency: Read requests to the underlying Linux PRNG are
169
         * serialized (on sLock) to ensure that multiple threads do not get
170
         * duplicated PRNG output.
171
         */
172

  
173
        private static final File URANDOM_FILE = new File("/dev/urandom");
174

  
175
        private static final Object sLock = new Object();
176

  
177
        /**
178
         * Input stream for reading from Linux PRNG or {@code null} if not yet
179
         * opened.
180
         *
181
         * @GuardedBy("sLock")
182
         */
183
        private static DataInputStream sUrandomIn;
184

  
185
        /**
186
         * Output stream for writing to Linux PRNG or {@code null} if not yet
187
         * opened.
188
         *
189
         * @GuardedBy("sLock")
190
         */
191
        private static OutputStream sUrandomOut;
192

  
193
        /**
194
         * Whether this engine instance has been seeded. This is needed because
195
         * each instance needs to seed itself if the client does not explicitly
196
         * seed it.
197
         */
198
        private boolean mSeeded;
199

  
200
        @Override
201
        protected void engineSetSeed(byte[] bytes) {
202
            try {
203
                OutputStream out;
204
                synchronized (sLock) {
205
                    out = getUrandomOutputStream();
206
                }
207
                out.write(bytes);
208
                out.flush();
209
            } catch (IOException e) {
210
                // On a small fraction of devices /dev/urandom is not writable.
211
                // Log and ignore.
212
                Log.w(PRNGFixes.class.getSimpleName(),
213
                        "Failed to mix seed into " + URANDOM_FILE);
214
            } finally {
215
                mSeeded = true;
216
            }
217
        }
218

  
219
        @Override
220
        protected void engineNextBytes(byte[] bytes) {
221
            if (!mSeeded) {
222
                // Mix in the device- and invocation-specific seed.
223
                engineSetSeed(generateSeed());
224
            }
225

  
226
            try {
227
                DataInputStream in;
228
                synchronized (sLock) {
229
                    in = getUrandomInputStream();
230
                }
231
                synchronized (in) {
232
                    in.readFully(bytes);
233
                }
234
            } catch (IOException e) {
235
                throw new SecurityException(
236
                        "Failed to read from " + URANDOM_FILE, e);
237
            }
238
        }
239

  
240
        @Override
241
        protected byte[] engineGenerateSeed(int size) {
242
            byte[] seed = new byte[size];
243
            engineNextBytes(seed);
244
            return seed;
245
        }
246

  
247
        private DataInputStream getUrandomInputStream() {
248
            synchronized (sLock) {
249
                if (sUrandomIn == null) {
250
                    // NOTE: Consider inserting a BufferedInputStream between
251
                    // DataInputStream and FileInputStream if you need higher
252
                    // PRNG output performance and can live with future PRNG
253
                    // output being pulled into this process prematurely.
254
                    try {
255
                        sUrandomIn = new DataInputStream(
256
                                new FileInputStream(URANDOM_FILE));
257
                    } catch (IOException e) {
258
                        throw new SecurityException("Failed to open "
259
                                + URANDOM_FILE + " for reading", e);
260
                    }
261
                }
262
                return sUrandomIn;
263
            }
264
        }
265

  
266
        private OutputStream getUrandomOutputStream() throws IOException {
267
            synchronized (sLock) {
268
                if (sUrandomOut == null) {
269
                        sUrandomOut = new FileOutputStream(URANDOM_FILE);
270
                }
271
                return sUrandomOut;
272
            }
273
        }
274
    }
275

  
276
    /**
277
     * Generates a device- and invocation-specific seed to be mixed into the
278
     * Linux PRNG.
279
     */
280
    private static byte[] generateSeed() {
281
        try {
282
            ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
283
            DataOutputStream seedBufferOut =
284
                    new DataOutputStream(seedBuffer);
285
            seedBufferOut.writeLong(System.currentTimeMillis());
286
            seedBufferOut.writeLong(System.nanoTime());
287
            seedBufferOut.writeInt(Process.myPid());
288
            seedBufferOut.writeInt(Process.myUid());
289
            seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL);
290
            seedBufferOut.close();
291
            return seedBuffer.toByteArray();
292
        } catch (IOException e) {
293
            throw new SecurityException("Failed to generate seed", e);
294
        }
295
    }
296

  
297
    /**
298
     * Gets the hardware serial number of this device.
299
     *
300
     * @return serial number or {@code null} if not available.
301
     */
302
    private static String getDeviceSerialNumber() {
303
        // We're using the Reflection API because Build.SERIAL is only available
304
        // since API Level 9 (Gingerbread, Android 2.3).
305
        try {
306
            return (String) Build.class.getField("SERIAL").get(null);
307
        } catch (Exception ignored) {
308
            return null;
309
        }
310
    }
311

  
312
    private static byte[] getBuildFingerprintAndDeviceSerial() {
313
        StringBuilder result = new StringBuilder();
314
        String fingerprint = Build.FINGERPRINT;
315
        if (fingerprint != null) {
316
            result.append(fingerprint);
317
        }
318
        String serial = getDeviceSerialNumber();
319
        if (serial != null) {
320
            result.append(serial);
321
        }
322
        try {
323
            return result.toString().getBytes("UTF-8");
324
        } catch (UnsupportedEncodingException e) {
325
            throw new RuntimeException("UTF-8 encoding not supported");
326
        }
327
    }
328
}

Also available in: Unified diff