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