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 17 package com.android.server; 18 19 import java.io.File; 20 import java.io.FileNotFoundException; 21 import java.io.FileOutputStream; 22 import java.io.IOException; 23 import java.io.PrintWriter; 24 25 import android.content.BroadcastReceiver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.os.Binder; 30 import android.os.Environment; 31 import android.os.Handler; 32 import android.os.Message; 33 import android.os.SystemProperties; 34 import android.util.Slog; 35 36 /** 37 * A service designed to load and periodically save "randomness" 38 * for the Linux kernel RNG and to mix in data from Hardware RNG (if present) 39 * into the Linux RNG. 40 * 41 * <p>When a Linux system starts up, the entropy pool associated with 42 * {@code /dev/random} may be in a fairly predictable state. Applications which 43 * depend strongly on randomness may find {@code /dev/random} or 44 * {@code /dev/urandom} returning predictable data. In order to counteract 45 * this effect, it's helpful to carry the entropy pool information across 46 * shutdowns and startups. 47 * 48 * <p>On systems with Hardware RNG (/dev/hw_random), a block of output from HW 49 * RNG is mixed into the Linux RNG on EntropyMixer's startup and whenever 50 * EntropyMixer periodically runs to save a block of output from Linux RNG on 51 * disk. This mixing is done in a way that does not increase the Linux RNG's 52 * entropy estimate is not increased. This is to avoid having to trust/verify 53 * the quality and authenticity of the "randomness" of the HW RNG. 54 * 55 * <p>This class was modeled after the script in 56 * <a href="http://www.kernel.org/doc/man-pages/online/pages/man4/random.4.html">man 57 * 4 random</a>. 58 */ 59 public class EntropyMixer extends Binder { 60 private static final String TAG = "EntropyMixer"; 61 private static final int ENTROPY_WHAT = 1; 62 private static final int ENTROPY_WRITE_PERIOD = 3 * 60 * 60 * 1000; // 3 hrs 63 private static final long START_TIME = System.currentTimeMillis(); 64 private static final long START_NANOTIME = System.nanoTime(); 65 66 private final String randomDevice; 67 private final String hwRandomDevice; 68 private final String entropyFile; 69 70 /** 71 * Handler that periodically updates the entropy on disk. 72 */ 73 private final Handler mHandler = new Handler(IoThread.getHandler().getLooper()) { 74 // IMPLEMENTATION NOTE: This handler runs on the I/O thread to avoid I/O on the main thread. 75 // The reason we're using our own Handler instead of IoThread.getHandler() is to create our 76 // own ID space for the "what" parameter of messages seen by the handler. 77 @Override 78 public void handleMessage(Message msg) { 79 if (msg.what != ENTROPY_WHAT) { 80 Slog.e(TAG, "Will not process invalid message"); 81 return; 82 } 83 addHwRandomEntropy(); 84 writeEntropy(); 85 scheduleEntropyWriter(); 86 } 87 }; 88 89 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 90 @Override 91 public void onReceive(Context context, Intent intent) { 92 writeEntropy(); 93 } 94 }; 95 96 public EntropyMixer(Context context) { 97 this(context, getSystemDir() + "/entropy.dat", "/dev/urandom", "/dev/hw_random"); 98 } 99 100 /** Test only interface, not for public use */ 101 public EntropyMixer( 102 Context context, 103 String entropyFile, 104 String randomDevice, 105 String hwRandomDevice) { 106 if (randomDevice == null) { throw new NullPointerException("randomDevice"); } 107 if (hwRandomDevice == null) { throw new NullPointerException("hwRandomDevice"); } 108 if (entropyFile == null) { throw new NullPointerException("entropyFile"); } 109 110 this.randomDevice = randomDevice; 111 this.hwRandomDevice = hwRandomDevice; 112 this.entropyFile = entropyFile; 113 loadInitialEntropy(); 114 addDeviceSpecificEntropy(); 115 addHwRandomEntropy(); 116 writeEntropy(); 117 scheduleEntropyWriter(); 118 IntentFilter broadcastFilter = new IntentFilter(Intent.ACTION_SHUTDOWN); 119 broadcastFilter.addAction(Intent.ACTION_POWER_CONNECTED); 120 broadcastFilter.addAction(Intent.ACTION_REBOOT); 121 context.registerReceiver( 122 mBroadcastReceiver, 123 broadcastFilter, 124 null, // do not require broadcaster to hold any permissions 125 mHandler // process received broadcasts on the I/O thread instead of the main thread 126 ); 127 } 128 129 private void scheduleEntropyWriter() { 130 mHandler.removeMessages(ENTROPY_WHAT); 131 mHandler.sendEmptyMessageDelayed(ENTROPY_WHAT, ENTROPY_WRITE_PERIOD); 132 } 133 134 private void loadInitialEntropy() { 135 try { 136 RandomBlock.fromFile(entropyFile).toFile(randomDevice, false); 137 } catch (FileNotFoundException e) { 138 Slog.w(TAG, "No existing entropy file -- first boot?"); 139 } catch (IOException e) { 140 Slog.w(TAG, "Failure loading existing entropy file", e); 141 } 142 } 143 144 private void writeEntropy() { 145 try { 146 Slog.i(TAG, "Writing entropy..."); 147 RandomBlock.fromFile(randomDevice).toFile(entropyFile, true); 148 } catch (IOException e) { 149 Slog.w(TAG, "Unable to write entropy", e); 150 } 151 } 152 153 /** 154 * Add additional information to the kernel entropy pool. The 155 * information isn't necessarily "random", but that's ok. Even 156 * sending non-random information to {@code /dev/urandom} is useful 157 * because, while it doesn't increase the "quality" of the entropy pool, 158 * it mixes more bits into the pool, which gives us a higher degree 159 * of uncertainty in the generated randomness. Like nature, writes to 160 * the random device can only cause the quality of the entropy in the 161 * kernel to stay the same or increase. 162 * 163 * <p>For maximum effect, we try to target information which varies 164 * on a per-device basis, and is not easily observable to an 165 * attacker. 166 */ 167 private void addDeviceSpecificEntropy() { 168 PrintWriter out = null; 169 try { 170 out = new PrintWriter(new FileOutputStream(randomDevice)); 171 out.println("Copyright (C) 2009 The Android Open Source Project"); 172 out.println("All Your Randomness Are Belong To Us"); 173 out.println(START_TIME); 174 out.println(START_NANOTIME); 175 out.println(SystemProperties.get("ro.serialno")); 176 out.println(SystemProperties.get("ro.bootmode")); 177 out.println(SystemProperties.get("ro.baseband")); 178 out.println(SystemProperties.get("ro.carrier")); 179 out.println(SystemProperties.get("ro.bootloader")); 180 out.println(SystemProperties.get("ro.hardware")); 181 out.println(SystemProperties.get("ro.revision")); 182 out.println(SystemProperties.get("ro.build.fingerprint")); 183 out.println(new Object().hashCode()); 184 out.println(System.currentTimeMillis()); 185 out.println(System.nanoTime()); 186 } catch (IOException e) { 187 Slog.w(TAG, "Unable to add device specific data to the entropy pool", e); 188 } finally { 189 if (out != null) { 190 out.close(); 191 } 192 } 193 } 194 195 /** 196 * Mixes in the output from HW RNG (if present) into the Linux RNG. 197 */ 198 private void addHwRandomEntropy() { 199 if (!new File(hwRandomDevice).exists()) { 200 // HW RNG not present/exposed -- ignore 201 return; 202 } 203 204 try { 205 RandomBlock.fromFile(hwRandomDevice).toFile(randomDevice, false); 206 Slog.i(TAG, "Added HW RNG output to entropy pool"); 207 } catch (IOException e) { 208 Slog.w(TAG, "Failed to add HW RNG output to entropy pool", e); 209 } 210 } 211 212 private static String getSystemDir() { 213 File dataDir = Environment.getDataDirectory(); 214 File systemDir = new File(dataDir, "system"); 215 systemDir.mkdirs(); 216 return systemDir.toString(); 217 } 218 } 219