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() {
     74         @Override
     75         public void handleMessage(Message msg) {
     76             if (msg.what != ENTROPY_WHAT) {
     77                 Slog.e(TAG, "Will not process invalid message");
     78                 return;
     79             }
     80             addHwRandomEntropy();
     81             writeEntropy();
     82             scheduleEntropyWriter();
     83         }
     84     };
     85 
     86     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
     87         @Override
     88         public void onReceive(Context context, Intent intent) {
     89             writeEntropy();
     90         }
     91     };
     92 
     93     public EntropyMixer(Context context) {
     94         this(context, getSystemDir() + "/entropy.dat", "/dev/urandom", "/dev/hw_random");
     95     }
     96 
     97     /** Test only interface, not for public use */
     98     public EntropyMixer(
     99             Context context,
    100             String entropyFile,
    101             String randomDevice,
    102             String hwRandomDevice) {
    103         if (randomDevice == null) { throw new NullPointerException("randomDevice"); }
    104         if (hwRandomDevice == null) { throw new NullPointerException("hwRandomDevice"); }
    105         if (entropyFile == null) { throw new NullPointerException("entropyFile"); }
    106 
    107         this.randomDevice = randomDevice;
    108         this.hwRandomDevice = hwRandomDevice;
    109         this.entropyFile = entropyFile;
    110         loadInitialEntropy();
    111         addDeviceSpecificEntropy();
    112         addHwRandomEntropy();
    113         writeEntropy();
    114         scheduleEntropyWriter();
    115         IntentFilter broadcastFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
    116         broadcastFilter.addAction(Intent.ACTION_POWER_CONNECTED);
    117         broadcastFilter.addAction(Intent.ACTION_REBOOT);
    118         context.registerReceiver(mBroadcastReceiver, broadcastFilter);
    119     }
    120 
    121     private void scheduleEntropyWriter() {
    122         mHandler.removeMessages(ENTROPY_WHAT);
    123         mHandler.sendEmptyMessageDelayed(ENTROPY_WHAT, ENTROPY_WRITE_PERIOD);
    124     }
    125 
    126     private void loadInitialEntropy() {
    127         try {
    128             RandomBlock.fromFile(entropyFile).toFile(randomDevice, false);
    129         } catch (FileNotFoundException e) {
    130             Slog.w(TAG, "No existing entropy file -- first boot?");
    131         } catch (IOException e) {
    132             Slog.w(TAG, "Failure loading existing entropy file", e);
    133         }
    134     }
    135 
    136     private void writeEntropy() {
    137         try {
    138             Slog.i(TAG, "Writing entropy...");
    139             RandomBlock.fromFile(randomDevice).toFile(entropyFile, true);
    140         } catch (IOException e) {
    141             Slog.w(TAG, "Unable to write entropy", e);
    142         }
    143     }
    144 
    145     /**
    146      * Add additional information to the kernel entropy pool.  The
    147      * information isn't necessarily "random", but that's ok.  Even
    148      * sending non-random information to {@code /dev/urandom} is useful
    149      * because, while it doesn't increase the "quality" of the entropy pool,
    150      * it mixes more bits into the pool, which gives us a higher degree
    151      * of uncertainty in the generated randomness.  Like nature, writes to
    152      * the random device can only cause the quality of the entropy in the
    153      * kernel to stay the same or increase.
    154      *
    155      * <p>For maximum effect, we try to target information which varies
    156      * on a per-device basis, and is not easily observable to an
    157      * attacker.
    158      */
    159     private void addDeviceSpecificEntropy() {
    160         PrintWriter out = null;
    161         try {
    162             out = new PrintWriter(new FileOutputStream(randomDevice));
    163             out.println("Copyright (C) 2009 The Android Open Source Project");
    164             out.println("All Your Randomness Are Belong To Us");
    165             out.println(START_TIME);
    166             out.println(START_NANOTIME);
    167             out.println(SystemProperties.get("ro.serialno"));
    168             out.println(SystemProperties.get("ro.bootmode"));
    169             out.println(SystemProperties.get("ro.baseband"));
    170             out.println(SystemProperties.get("ro.carrier"));
    171             out.println(SystemProperties.get("ro.bootloader"));
    172             out.println(SystemProperties.get("ro.hardware"));
    173             out.println(SystemProperties.get("ro.revision"));
    174             out.println(SystemProperties.get("ro.build.fingerprint"));
    175             out.println(new Object().hashCode());
    176             out.println(System.currentTimeMillis());
    177             out.println(System.nanoTime());
    178         } catch (IOException e) {
    179             Slog.w(TAG, "Unable to add device specific data to the entropy pool", e);
    180         } finally {
    181             if (out != null) {
    182                 out.close();
    183             }
    184         }
    185     }
    186 
    187     /**
    188      * Mixes in the output from HW RNG (if present) into the Linux RNG.
    189      */
    190     private void addHwRandomEntropy() {
    191         try {
    192             RandomBlock.fromFile(hwRandomDevice).toFile(randomDevice, false);
    193             Slog.i(TAG, "Added HW RNG output to entropy pool");
    194         } catch (FileNotFoundException ignored) {
    195             // HW RNG not present/exposed -- ignore
    196         } catch (IOException e) {
    197             Slog.w(TAG, "Failed to add HW RNG output to entropy pool", e);
    198         }
    199     }
    200 
    201     private static String getSystemDir() {
    202         File dataDir = Environment.getDataDirectory();
    203         File systemDir = new File(dataDir, "system");
    204         systemDir.mkdirs();
    205         return systemDir.toString();
    206     }
    207 }
    208