1/*
2 * Copyright (C) 2009 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
17package com.android.server;
18
19import java.io.File;
20import java.io.FileNotFoundException;
21import java.io.FileOutputStream;
22import java.io.IOException;
23import java.io.OutputStream;
24import java.io.PrintWriter;
25
26import android.os.Binder;
27import android.os.Environment;
28import android.os.Handler;
29import android.os.Message;
30import android.os.SystemProperties;
31import android.util.Slog;
32
33/**
34 * A service designed to load and periodically save "randomness"
35 * for the Linux kernel.
36 *
37 * <p>When a Linux system starts up, the entropy pool associated with
38 * {@code /dev/random} may be in a fairly predictable state.  Applications which
39 * depend strongly on randomness may find {@code /dev/random} or
40 * {@code /dev/urandom} returning predictable data.  In order to counteract
41 * this effect, it's helpful to carry the entropy pool information across
42 * shutdowns and startups.
43 *
44 * <p>This class was modeled after the script in
45 * <a href="http://www.kernel.org/doc/man-pages/online/pages/man4/random.4.html">man
46 * 4 random</a>.
47 *
48 * <p>TODO: Investigate attempting to write entropy data at shutdown time
49 * instead of periodically.
50 */
51public class EntropyMixer extends Binder {
52    private static final String TAG = "EntropyMixer";
53    private static final int ENTROPY_WHAT = 1;
54    private static final int ENTROPY_WRITE_PERIOD = 3 * 60 * 60 * 1000;  // 3 hrs
55    private static final long START_TIME = System.currentTimeMillis();
56    private static final long START_NANOTIME = System.nanoTime();
57
58    private final String randomDevice;
59    private final String entropyFile;
60
61    /**
62     * Handler that periodically updates the entropy on disk.
63     */
64    private final Handler mHandler = new Handler() {
65        @Override
66        public void handleMessage(Message msg) {
67            if (msg.what != ENTROPY_WHAT) {
68                Slog.e(TAG, "Will not process invalid message");
69                return;
70            }
71            writeEntropy();
72            scheduleEntropyWriter();
73        }
74    };
75
76    public EntropyMixer() {
77        this(getSystemDir() + "/entropy.dat", "/dev/urandom");
78    }
79
80    /** Test only interface, not for public use */
81    public EntropyMixer(String entropyFile, String randomDevice) {
82        if (randomDevice == null) { throw new NullPointerException("randomDevice"); }
83        if (entropyFile == null) { throw new NullPointerException("entropyFile"); }
84
85        this.randomDevice = randomDevice;
86        this.entropyFile = entropyFile;
87        loadInitialEntropy();
88        addDeviceSpecificEntropy();
89        writeEntropy();
90        scheduleEntropyWriter();
91    }
92
93    private void scheduleEntropyWriter() {
94        mHandler.removeMessages(ENTROPY_WHAT);
95        mHandler.sendEmptyMessageDelayed(ENTROPY_WHAT, ENTROPY_WRITE_PERIOD);
96    }
97
98    private void loadInitialEntropy() {
99        try {
100            RandomBlock.fromFile(entropyFile).toFile(randomDevice, false);
101        } catch (FileNotFoundException e) {
102            Slog.w(TAG, "No existing entropy file -- first boot?");
103        } catch (IOException e) {
104            Slog.w(TAG, "Failure loading existing entropy file", e);
105        }
106    }
107
108    private void writeEntropy() {
109        try {
110            RandomBlock.fromFile(randomDevice).toFile(entropyFile, true);
111        } catch (IOException e) {
112            Slog.w(TAG, "Unable to write entropy", e);
113        }
114    }
115
116    /**
117     * Add additional information to the kernel entropy pool.  The
118     * information isn't necessarily "random", but that's ok.  Even
119     * sending non-random information to {@code /dev/urandom} is useful
120     * because, while it doesn't increase the "quality" of the entropy pool,
121     * it mixes more bits into the pool, which gives us a higher degree
122     * of uncertainty in the generated randomness.  Like nature, writes to
123     * the random device can only cause the quality of the entropy in the
124     * kernel to stay the same or increase.
125     *
126     * <p>For maximum effect, we try to target information which varies
127     * on a per-device basis, and is not easily observable to an
128     * attacker.
129     */
130    private void addDeviceSpecificEntropy() {
131        PrintWriter out = null;
132        try {
133            out = new PrintWriter(new FileOutputStream(randomDevice));
134            out.println("Copyright (C) 2009 The Android Open Source Project");
135            out.println("All Your Randomness Are Belong To Us");
136            out.println(START_TIME);
137            out.println(START_NANOTIME);
138            out.println(SystemProperties.get("ro.serialno"));
139            out.println(SystemProperties.get("ro.bootmode"));
140            out.println(SystemProperties.get("ro.baseband"));
141            out.println(SystemProperties.get("ro.carrier"));
142            out.println(SystemProperties.get("ro.bootloader"));
143            out.println(SystemProperties.get("ro.hardware"));
144            out.println(SystemProperties.get("ro.revision"));
145            out.println(new Object().hashCode());
146            out.println(System.currentTimeMillis());
147            out.println(System.nanoTime());
148        } catch (IOException e) {
149            Slog.w(TAG, "Unable to add device specific data to the entropy pool", e);
150        } finally {
151            if (out != null) {
152                out.close();
153            }
154        }
155    }
156
157    private static String getSystemDir() {
158        File dataDir = Environment.getDataDirectory();
159        File systemDir = new File(dataDir, "system");
160        systemDir.mkdirs();
161        return systemDir.toString();
162    }
163}
164