Home | History | Annotate | Download | only in server
      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 &quot;randomness&quot; 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