Require device encryption password to perform adb backup/restore

This supersedes any backup-password that the user might supply.  Per
design, the device encryption password is also always used to encrypt
the backup archive.

The CL introduces two new strings, used for prompting the user for
their device encryption password rather than their settings-defined
"backup password" when confirming a full backup or restore operation.

Bug 5382487

Change-Id: I0b03881b45437c944eaf636b6209278e1bba7a9f
This commit is contained in:
Christopher Tate
2011-10-10 13:51:12 -07:00
parent 3b16c9a5b4
commit 32418be49e
5 changed files with 171 additions and 7 deletions

View File

@@ -658,6 +658,24 @@ public interface IMountService extends IInterface {
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 @@ public interface IMountService extends IInterface {
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.
@@ -1285,6 +1305,12 @@ public interface IMountService extends IInterface {
*/
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.
*/

View File

@@ -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>

View File

@@ -27,6 +27,8 @@ import android.os.Handler;
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 @@ public class BackupRestoreConfirmation extends Activity {
Handler mHandler;
IBackupManager mBackupManager;
IMountService mMountService;
FullObserver mObserver;
int mToken;
boolean mIsEncrypted;
boolean mDidAcknowledge;
TextView mStatusView;
@@ -152,6 +156,7 @@ public class BackupRestoreConfirmation extends Activity {
}
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 @@ public class BackupRestoreConfirmation extends Activity {
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 @@ public class BackupRestoreConfirmation extends Activity {
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 @@ public class BackupRestoreConfirmation extends Activity {
}
}
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();

View File

@@ -62,14 +62,15 @@ import android.os.ParcelFileDescriptor;
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 @@ class BackupManagerService extends IBackupManager.Stub {
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 @@ class BackupManagerService extends IBackupManager.Stub {
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 @@ class BackupManagerService extends IBackupManager.Stub {
// 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 @@ class BackupManagerService extends IBackupManager.Stub {
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 @@ class BackupManagerService extends IBackupManager.Stub {
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();

View File

@@ -1897,6 +1897,53 @@ class MountService extends IMountService.Stub
}
}
/**
* 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();