Merge "DO NOT MERGE - Require device encryption password for adb backup/restore" into ics-mr0
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index d1dc6e5..0640d7e 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -658,6 +658,24 @@
                 return _result;
             }
 
+            @Override
+            public int verifyEncryptionPassword(String password) throws RemoteException {
+                Parcel _data = Parcel.obtain();
+                Parcel _reply = Parcel.obtain();
+                int _result;
+                try {
+                    _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeString(password);
+                    mRemote.transact(Stub.TRANSACTION_verifyEncryptionPassword, _data, _reply, 0);
+                    _reply.readException();
+                    _result = _reply.readInt();
+                } finally {
+                    _reply.recycle();
+                    _data.recycle();
+                }
+                return _result;
+            }
+
             public Parcelable[] getVolumeList() throws RemoteException {
                 Parcel _data = Parcel.obtain();
                 Parcel _reply = Parcel.obtain();
@@ -761,6 +779,8 @@
 
         static final int TRANSACTION_getEncryptionState = IBinder.FIRST_CALL_TRANSACTION + 31;
 
+        static final int TRANSACTION_verifyEncryptionPassword = IBinder.FIRST_CALL_TRANSACTION + 32;
+
         /**
          * Cast an IBinder object into an IMountService interface, generating a
          * proxy if needed.
@@ -1286,6 +1306,12 @@
     public int changeEncryptionPassword(String password) throws RemoteException;
 
     /**
+     * Verify the encryption password against the stored volume.  This method
+     * may only be called by the system process.
+     */
+    public int verifyEncryptionPassword(String password) throws RemoteException;
+
+    /**
      * Returns list of all mountable volumes.
      */
     public Parcelable[] getVolumeList() throws RemoteException;
diff --git a/packages/BackupRestoreConfirmation/res/values/strings.xml b/packages/BackupRestoreConfirmation/res/values/strings.xml
index e91c6e2..5c90fd0 100644
--- a/packages/BackupRestoreConfirmation/res/values/strings.xml
+++ b/packages/BackupRestoreConfirmation/res/values/strings.xml
@@ -35,8 +35,12 @@
 
     <!-- Text for message to user that they must enter their predefined backup password in order to perform this operation. -->
     <string name="current_password_text">Please enter your current backup password below:</string>
+    <!-- Text for message to user that they must enter their device encryption password in order to perform this restore operation. -->
+    <string name="device_encryption_restore_text">Please enter your device encryption password below.</string>
+    <!-- Text for message to user that they must enter their device encryption password in order to perform this backup operation. -->
+    <string name="device_encryption_backup_text">Please enter your device encryption password below. This will also be used to encrypt the backup archive.</string>
 
-    <!-- Text for message to user that they can must enter an encryption password to use for the full backup operation. -->
+    <!-- Text for message to user that they must enter an encryption password to use for the full backup operation. -->
     <string name="backup_enc_password_text">Please enter a password to use for encrypting the full backup data. If this is left blank, your current backup password will be used:</string>
     <!-- Text for message to user that they may optionally supply an encryption password to use for a full backup operation. -->
     <string name="backup_enc_password_optional">If you wish to encrypt the full backup data, enter a password below:</string>
diff --git a/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java b/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java
index fbdf3cc..7f1d059 100644
--- a/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java
+++ b/packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java
@@ -27,6 +27,8 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.storage.IMountService;
+import android.util.Log;
 import android.util.Slog;
 import android.view.View;
 import android.widget.Button;
@@ -60,8 +62,10 @@
 
     Handler mHandler;
     IBackupManager mBackupManager;
+    IMountService mMountService;
     FullObserver mObserver;
     int mToken;
+    boolean mIsEncrypted;
     boolean mDidAcknowledge;
 
     TextView mStatusView;
@@ -152,6 +156,7 @@
         }
 
         mBackupManager = IBackupManager.Stub.asInterface(ServiceManager.getService(Context.BACKUP_SERVICE));
+        mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
 
         mHandler = new ObserverHandler(getApplicationContext());
         final Object oldObserver = getLastNonConfigurationInstance();
@@ -174,8 +179,23 @@
         mEncPassword = (TextView) findViewById(R.id.enc_password);
         TextView curPwDesc = (TextView) findViewById(R.id.password_desc);
 
-        // We vary the password prompt depending on whether one is predefined
-        if (!haveBackupPassword()) {
+        // We vary the password prompt depending on whether one is predefined, and whether
+        // the device is encrypted.
+        mIsEncrypted = deviceIsEncrypted();
+        if (mIsEncrypted) {
+            Log.d(TAG, "Device is encrypted: requiring encryption pw");
+            TextView pwPrompt = (TextView) findViewById(R.id.password_desc);
+            // this password is mandatory; we hide the other options during backup
+            if (layoutId == R.layout.confirm_backup) {
+                pwPrompt.setText(R.string.device_encryption_backup_text);
+                TextView tv = (TextView) findViewById(R.id.enc_password);
+                tv.setVisibility(View.GONE);
+                tv = (TextView) findViewById(R.id.enc_password_desc);
+                tv.setVisibility(View.GONE);
+            } else {
+                pwPrompt.setText(R.string.device_encryption_restore_text);
+            }
+        } else if (!haveBackupPassword()) {
             curPwDesc.setVisibility(View.GONE);
             mCurPassword.setVisibility(View.GONE);
             if (layoutId == R.layout.confirm_backup) {
@@ -226,10 +246,12 @@
             mDidAcknowledge = true;
 
             try {
+                CharSequence encPassword = (mIsEncrypted)
+                        ? mCurPassword.getText() : mEncPassword.getText();
                 mBackupManager.acknowledgeFullBackupOrRestore(mToken,
                         allow,
                         String.valueOf(mCurPassword.getText()),
-                        String.valueOf(mEncPassword.getText()),
+                        String.valueOf(encPassword),
                         mObserver);
             } catch (RemoteException e) {
                 // TODO: bail gracefully if we can't contact the backup manager
@@ -237,6 +259,16 @@
         }
     }
 
+    boolean deviceIsEncrypted() {
+        try {
+            return (mMountService.getEncryptionState() != IMountService.ENCRYPTION_STATE_NONE);
+        } catch (Exception e) {
+            // If we can't talk to the mount service we have a serious problem; fail
+            // "secure" i.e. assuming that the device is encrypted.
+            return true;
+        }
+    }
+
     boolean haveBackupPassword() {
         try {
             return mBackupManager.hasBackupPassword();
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index f9f5458..4ef8837 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -62,14 +62,15 @@
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.WorkSource;
+import android.os.storage.IMountService;
 import android.provider.Settings;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.SparseIntArray;
 import android.util.StringBuilderPrinter;
 
 import com.android.internal.backup.BackupConstants;
@@ -187,6 +188,7 @@
     private IActivityManager mActivityManager;
     private PowerManager mPowerManager;
     private AlarmManager mAlarmManager;
+    private IMountService mMountService;
     IBackupManager mBackupManagerBinder;
 
     boolean mEnabled;   // access to this is synchronized on 'this'
@@ -660,6 +662,7 @@
 
         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
 
         mBackupManagerBinder = asInterface(asBinder());
 
@@ -1037,6 +1040,40 @@
 
     // Backup password management
     boolean passwordMatchesSaved(String candidatePw, int rounds) {
+        // First, on an encrypted device we require matching the device pw
+        final boolean isEncrypted;
+        try {
+            isEncrypted = (mMountService.getEncryptionState() != MountService.ENCRYPTION_STATE_NONE);
+            if (isEncrypted) {
+                if (DEBUG) {
+                    Slog.i(TAG, "Device encrypted; verifying against device data pw");
+                }
+                // 0 means the password validated
+                // -2 means device not encrypted
+                // Any other result is either password failure or an error condition,
+                // so we refuse the match
+                final int result = mMountService.verifyEncryptionPassword(candidatePw);
+                if (result == 0) {
+                    if (MORE_DEBUG) Slog.d(TAG, "Pw verifies");
+                    return true;
+                } else if (result != -2) {
+                    if (MORE_DEBUG) Slog.d(TAG, "Pw mismatch");
+                    return false;
+                } else {
+                    // ...else the device is supposedly not encrypted.  HOWEVER, the
+                    // query about the encryption state said that the device *is*
+                    // encrypted, so ... we may have a problem.  Log it and refuse
+                    // the backup.
+                    Slog.e(TAG, "verified encryption state mismatch against query; no match allowed");
+                    return false;
+                }
+            }
+        } catch (Exception e) {
+            // Something went wrong talking to the mount service.  This is very bad;
+            // assume that we fail password validation.
+            return false;
+        }
+
         if (mPasswordHash == null) {
             // no current password case -- require that 'currentPw' be null or empty
             if (candidatePw == null || "".equals(candidatePw)) {
@@ -1114,7 +1151,15 @@
     public boolean hasBackupPassword() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                 "hasBackupPassword");
-        return (mPasswordHash != null && mPasswordHash.length() > 0);
+
+        try {
+            return (mMountService.getEncryptionState() != IMountService.ENCRYPTION_STATE_NONE)
+                || (mPasswordHash != null && mPasswordHash.length() > 0);
+        } catch (Exception e) {
+            // If we can't talk to the mount service we have a serious problem; fail
+            // "secure" i.e. assuming that we require a password
+            return true;
+        }
     }
 
     // Maintain persistent state around whether need to do an initialize operation.
@@ -5007,7 +5052,17 @@
 
                         params.observer = observer;
                         params.curPassword = curPassword;
-                        params.encryptPassword = encPpassword;
+
+                        boolean isEncrypted;
+                        try {
+                            isEncrypted = (mMountService.getEncryptionState() != MountService.ENCRYPTION_STATE_NONE);
+                            if (isEncrypted) Slog.w(TAG, "Device is encrypted; forcing enc password");
+                        } catch (RemoteException e) {
+                            // couldn't contact the mount service; fail "safe" and assume encryption
+                            Slog.e(TAG, "Unable to contact mount service!");
+                            isEncrypted = true;
+                        }
+                        params.encryptPassword = (isEncrypted) ? curPassword : encPpassword;
 
                         if (DEBUG) Slog.d(TAG, "Sending conf message with verb " + verb);
                         mWakelock.acquire();
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index 582f0ed..5425813 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -1897,6 +1897,53 @@
         }
     }
 
+    /**
+     * Validate a user-supplied password string with cryptfs
+     */
+    @Override
+    public int verifyEncryptionPassword(String password) throws RemoteException {
+        // Only the system process is permitted to validate passwords
+        if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
+            throw new SecurityException("no permission to access the crypt keeper");
+        }
+
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
+            "no permission to access the crypt keeper");
+
+        if (TextUtils.isEmpty(password)) {
+            throw new IllegalArgumentException("password cannot be empty");
+        }
+
+        waitForReady();
+
+        if (DEBUG_EVENTS) {
+            Slog.i(TAG, "validating encryption password...");
+        }
+
+        try {
+            ArrayList<String> response = mConnector.doCommand("cryptfs verifypw " + password);
+            String[] tokens = response.get(0).split(" ");
+
+            if (tokens == null || tokens.length != 2) {
+                String msg = "Unexpected result from cryptfs verifypw: {";
+                if (tokens == null) msg += "null";
+                else for (int i = 0; i < tokens.length; i++) {
+                    if (i != 0) msg += ',';
+                    msg += tokens[i];
+                }
+                msg += '}';
+                Slog.e(TAG, msg);
+                return -1;
+            }
+
+            Slog.i(TAG, "cryptfs verifypw => " + tokens[1]);
+            return Integer.parseInt(tokens[1]);
+        } catch (NativeDaemonConnectorException e) {
+            // Encryption failed
+            return e.getCode();
+        }
+    }
+
     public Parcelable[] getVolumeList() {
         synchronized(mVolumes) {
             int size = mVolumes.size();