Skip to content

Commit 8f4e49a

Browse files
Christopher TateAndroid (Google) Code Review
authored andcommitted
Merge "DO NOT MERGE - Require device encryption password for adb backup/restore" into ics-mr0
2 parents 0000538 + b9c1acf commit 8f4e49a

File tree

5 files changed

+171
-7
lines changed

5 files changed

+171
-7
lines changed

core/java/android/os/storage/IMountService.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,24 @@ public int changeEncryptionPassword(String password) throws RemoteException {
658658
return _result;
659659
}
660660

661+
@Override
662+
public int verifyEncryptionPassword(String password) throws RemoteException {
663+
Parcel _data = Parcel.obtain();
664+
Parcel _reply = Parcel.obtain();
665+
int _result;
666+
try {
667+
_data.writeInterfaceToken(DESCRIPTOR);
668+
_data.writeString(password);
669+
mRemote.transact(Stub.TRANSACTION_verifyEncryptionPassword, _data, _reply, 0);
670+
_reply.readException();
671+
_result = _reply.readInt();
672+
} finally {
673+
_reply.recycle();
674+
_data.recycle();
675+
}
676+
return _result;
677+
}
678+
661679
public Parcelable[] getVolumeList() throws RemoteException {
662680
Parcel _data = Parcel.obtain();
663681
Parcel _reply = Parcel.obtain();
@@ -761,6 +779,8 @@ public String getSecureContainerFilesystemPath(String id) throws RemoteException
761779

762780
static final int TRANSACTION_getEncryptionState = IBinder.FIRST_CALL_TRANSACTION + 31;
763781

782+
static final int TRANSACTION_verifyEncryptionPassword = IBinder.FIRST_CALL_TRANSACTION + 32;
783+
764784
/**
765785
* Cast an IBinder object into an IMountService interface, generating a
766786
* proxy if needed.
@@ -1285,6 +1305,12 @@ public void unmountVolume(String mountPoint, boolean force, boolean removeEncryp
12851305
*/
12861306
public int changeEncryptionPassword(String password) throws RemoteException;
12871307

1308+
/**
1309+
* Verify the encryption password against the stored volume. This method
1310+
* may only be called by the system process.
1311+
*/
1312+
public int verifyEncryptionPassword(String password) throws RemoteException;
1313+
12881314
/**
12891315
* Returns list of all mountable volumes.
12901316
*/

packages/BackupRestoreConfirmation/res/values/strings.xml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,12 @@
3535

3636
<!-- Text for message to user that they must enter their predefined backup password in order to perform this operation. -->
3737
<string name="current_password_text">Please enter your current backup password below:</string>
38+
<!-- Text for message to user that they must enter their device encryption password in order to perform this restore operation. -->
39+
<string name="device_encryption_restore_text">Please enter your device encryption password below.</string>
40+
<!-- Text for message to user that they must enter their device encryption password in order to perform this backup operation. -->
41+
<string name="device_encryption_backup_text">Please enter your device encryption password below. This will also be used to encrypt the backup archive.</string>
3842

39-
<!-- Text for message to user that they can must enter an encryption password to use for the full backup operation. -->
43+
<!-- Text for message to user that they must enter an encryption password to use for the full backup operation. -->
4044
<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>
4145
<!-- Text for message to user that they may optionally supply an encryption password to use for a full backup operation. -->
4246
<string name="backup_enc_password_optional">If you wish to encrypt the full backup data, enter a password below:</string>

packages/BackupRestoreConfirmation/src/com/android/backupconfirm/BackupRestoreConfirmation.java

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import android.os.Message;
2828
import android.os.RemoteException;
2929
import android.os.ServiceManager;
30+
import android.os.storage.IMountService;
31+
import android.util.Log;
3032
import android.util.Slog;
3133
import android.view.View;
3234
import android.widget.Button;
@@ -60,8 +62,10 @@ public class BackupRestoreConfirmation extends Activity {
6062

6163
Handler mHandler;
6264
IBackupManager mBackupManager;
65+
IMountService mMountService;
6366
FullObserver mObserver;
6467
int mToken;
68+
boolean mIsEncrypted;
6569
boolean mDidAcknowledge;
6670

6771
TextView mStatusView;
@@ -152,6 +156,7 @@ public void onCreate(Bundle icicle) {
152156
}
153157

154158
mBackupManager = IBackupManager.Stub.asInterface(ServiceManager.getService(Context.BACKUP_SERVICE));
159+
mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
155160

156161
mHandler = new ObserverHandler(getApplicationContext());
157162
final Object oldObserver = getLastNonConfigurationInstance();
@@ -174,8 +179,23 @@ public void onCreate(Bundle icicle) {
174179
mEncPassword = (TextView) findViewById(R.id.enc_password);
175180
TextView curPwDesc = (TextView) findViewById(R.id.password_desc);
176181

177-
// We vary the password prompt depending on whether one is predefined
178-
if (!haveBackupPassword()) {
182+
// We vary the password prompt depending on whether one is predefined, and whether
183+
// the device is encrypted.
184+
mIsEncrypted = deviceIsEncrypted();
185+
if (mIsEncrypted) {
186+
Log.d(TAG, "Device is encrypted: requiring encryption pw");
187+
TextView pwPrompt = (TextView) findViewById(R.id.password_desc);
188+
// this password is mandatory; we hide the other options during backup
189+
if (layoutId == R.layout.confirm_backup) {
190+
pwPrompt.setText(R.string.device_encryption_backup_text);
191+
TextView tv = (TextView) findViewById(R.id.enc_password);
192+
tv.setVisibility(View.GONE);
193+
tv = (TextView) findViewById(R.id.enc_password_desc);
194+
tv.setVisibility(View.GONE);
195+
} else {
196+
pwPrompt.setText(R.string.device_encryption_restore_text);
197+
}
198+
} else if (!haveBackupPassword()) {
179199
curPwDesc.setVisibility(View.GONE);
180200
mCurPassword.setVisibility(View.GONE);
181201
if (layoutId == R.layout.confirm_backup) {
@@ -226,17 +246,29 @@ void sendAcknowledgement(int token, boolean allow, IFullBackupRestoreObserver ob
226246
mDidAcknowledge = true;
227247

228248
try {
249+
CharSequence encPassword = (mIsEncrypted)
250+
? mCurPassword.getText() : mEncPassword.getText();
229251
mBackupManager.acknowledgeFullBackupOrRestore(mToken,
230252
allow,
231253
String.valueOf(mCurPassword.getText()),
232-
String.valueOf(mEncPassword.getText()),
254+
String.valueOf(encPassword),
233255
mObserver);
234256
} catch (RemoteException e) {
235257
// TODO: bail gracefully if we can't contact the backup manager
236258
}
237259
}
238260
}
239261

262+
boolean deviceIsEncrypted() {
263+
try {
264+
return (mMountService.getEncryptionState() != IMountService.ENCRYPTION_STATE_NONE);
265+
} catch (Exception e) {
266+
// If we can't talk to the mount service we have a serious problem; fail
267+
// "secure" i.e. assuming that the device is encrypted.
268+
return true;
269+
}
270+
}
271+
240272
boolean haveBackupPassword() {
241273
try {
242274
return mBackupManager.hasBackupPassword();

services/java/com/android/server/BackupManagerService.java

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,15 @@
6262
import android.os.PowerManager;
6363
import android.os.Process;
6464
import android.os.RemoteException;
65+
import android.os.ServiceManager;
6566
import android.os.SystemClock;
6667
import android.os.WorkSource;
68+
import android.os.storage.IMountService;
6769
import android.provider.Settings;
6870
import android.util.EventLog;
6971
import android.util.Log;
7072
import android.util.Slog;
7173
import android.util.SparseArray;
72-
import android.util.SparseIntArray;
7374
import android.util.StringBuilderPrinter;
7475

7576
import com.android.internal.backup.BackupConstants;
@@ -187,6 +188,7 @@ class BackupManagerService extends IBackupManager.Stub {
187188
private IActivityManager mActivityManager;
188189
private PowerManager mPowerManager;
189190
private AlarmManager mAlarmManager;
191+
private IMountService mMountService;
190192
IBackupManager mBackupManagerBinder;
191193

192194
boolean mEnabled; // access to this is synchronized on 'this'
@@ -660,6 +662,7 @@ public BackupManagerService(Context context) {
660662

661663
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
662664
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
665+
mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
663666

664667
mBackupManagerBinder = asInterface(asBinder());
665668

@@ -1037,6 +1040,40 @@ private byte[] randomBytes(int bits) {
10371040

10381041
// Backup password management
10391042
boolean passwordMatchesSaved(String candidatePw, int rounds) {
1043+
// First, on an encrypted device we require matching the device pw
1044+
final boolean isEncrypted;
1045+
try {
1046+
isEncrypted = (mMountService.getEncryptionState() != MountService.ENCRYPTION_STATE_NONE);
1047+
if (isEncrypted) {
1048+
if (DEBUG) {
1049+
Slog.i(TAG, "Device encrypted; verifying against device data pw");
1050+
}
1051+
// 0 means the password validated
1052+
// -2 means device not encrypted
1053+
// Any other result is either password failure or an error condition,
1054+
// so we refuse the match
1055+
final int result = mMountService.verifyEncryptionPassword(candidatePw);
1056+
if (result == 0) {
1057+
if (MORE_DEBUG) Slog.d(TAG, "Pw verifies");
1058+
return true;
1059+
} else if (result != -2) {
1060+
if (MORE_DEBUG) Slog.d(TAG, "Pw mismatch");
1061+
return false;
1062+
} else {
1063+
// ...else the device is supposedly not encrypted. HOWEVER, the
1064+
// query about the encryption state said that the device *is*
1065+
// encrypted, so ... we may have a problem. Log it and refuse
1066+
// the backup.
1067+
Slog.e(TAG, "verified encryption state mismatch against query; no match allowed");
1068+
return false;
1069+
}
1070+
}
1071+
} catch (Exception e) {
1072+
// Something went wrong talking to the mount service. This is very bad;
1073+
// assume that we fail password validation.
1074+
return false;
1075+
}
1076+
10401077
if (mPasswordHash == null) {
10411078
// no current password case -- require that 'currentPw' be null or empty
10421079
if (candidatePw == null || "".equals(candidatePw)) {
@@ -1114,7 +1151,15 @@ public boolean setBackupPassword(String currentPw, String newPw) {
11141151
public boolean hasBackupPassword() {
11151152
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
11161153
"hasBackupPassword");
1117-
return (mPasswordHash != null && mPasswordHash.length() > 0);
1154+
1155+
try {
1156+
return (mMountService.getEncryptionState() != IMountService.ENCRYPTION_STATE_NONE)
1157+
|| (mPasswordHash != null && mPasswordHash.length() > 0);
1158+
} catch (Exception e) {
1159+
// If we can't talk to the mount service we have a serious problem; fail
1160+
// "secure" i.e. assuming that we require a password
1161+
return true;
1162+
}
11181163
}
11191164

11201165
// Maintain persistent state around whether need to do an initialize operation.
@@ -5007,7 +5052,17 @@ public void acknowledgeFullBackupOrRestore(int token, boolean allow,
50075052

50085053
params.observer = observer;
50095054
params.curPassword = curPassword;
5010-
params.encryptPassword = encPpassword;
5055+
5056+
boolean isEncrypted;
5057+
try {
5058+
isEncrypted = (mMountService.getEncryptionState() != MountService.ENCRYPTION_STATE_NONE);
5059+
if (isEncrypted) Slog.w(TAG, "Device is encrypted; forcing enc password");
5060+
} catch (RemoteException e) {
5061+
// couldn't contact the mount service; fail "safe" and assume encryption
5062+
Slog.e(TAG, "Unable to contact mount service!");
5063+
isEncrypted = true;
5064+
}
5065+
params.encryptPassword = (isEncrypted) ? curPassword : encPpassword;
50115066

50125067
if (DEBUG) Slog.d(TAG, "Sending conf message with verb " + verb);
50135068
mWakelock.acquire();

services/java/com/android/server/MountService.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1897,6 +1897,53 @@ public int changeEncryptionPassword(String password) {
18971897
}
18981898
}
18991899

1900+
/**
1901+
* Validate a user-supplied password string with cryptfs
1902+
*/
1903+
@Override
1904+
public int verifyEncryptionPassword(String password) throws RemoteException {
1905+
// Only the system process is permitted to validate passwords
1906+
if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
1907+
throw new SecurityException("no permission to access the crypt keeper");
1908+
}
1909+
1910+
mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
1911+
"no permission to access the crypt keeper");
1912+
1913+
if (TextUtils.isEmpty(password)) {
1914+
throw new IllegalArgumentException("password cannot be empty");
1915+
}
1916+
1917+
waitForReady();
1918+
1919+
if (DEBUG_EVENTS) {
1920+
Slog.i(TAG, "validating encryption password...");
1921+
}
1922+
1923+
try {
1924+
ArrayList<String> response = mConnector.doCommand("cryptfs verifypw " + password);
1925+
String[] tokens = response.get(0).split(" ");
1926+
1927+
if (tokens == null || tokens.length != 2) {
1928+
String msg = "Unexpected result from cryptfs verifypw: {";
1929+
if (tokens == null) msg += "null";
1930+
else for (int i = 0; i < tokens.length; i++) {
1931+
if (i != 0) msg += ',';
1932+
msg += tokens[i];
1933+
}
1934+
msg += '}';
1935+
Slog.e(TAG, msg);
1936+
return -1;
1937+
}
1938+
1939+
Slog.i(TAG, "cryptfs verifypw => " + tokens[1]);
1940+
return Integer.parseInt(tokens[1]);
1941+
} catch (NativeDaemonConnectorException e) {
1942+
// Encryption failed
1943+
return e.getCode();
1944+
}
1945+
}
1946+
19001947
public Parcelable[] getVolumeList() {
19011948
synchronized(mVolumes) {
19021949
int size = mVolumes.size();

0 commit comments

Comments
 (0)