Merge "Require device encryption password to perform adb backup/restore"
This commit is contained in:
committed by
Android (Google) Code Review
commit
28e9046ed4
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user