summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJorim Jaggi <jjaggi@google.com>2016-11-01 19:06:25 -0700
committerJorim Jaggi <jjaggi@google.com>2016-11-08 15:52:15 -0800
commit2fef6f7b155f2ff0d3531fe63b8e1c745bb3dc9d (patch)
treeef3e281589e62c57a88b6f0ac8b0fc67167a6bd2
parent1e1fd76a6729fcfd07371fa8f81817f6ec8f27db (diff)
Add ability to modify credential via shell command
Test: adb shell locksettings set-pattern 1234 Test: adb shell locksettings set-pin --old 1234 1234 Test: adb shell locksettings set-password --old 1234 1234 Test: adb shell locksettings clear --old 1234 Test: runtest frameworks-services -c com.android.server.LockSettingsShellCommandTest Change-Id: I8f541effc7eab0d7453cd9a9b46c280a6425e258
-rw-r--r--cmds/locksettings/Android.mk30
-rwxr-xr-xcmds/locksettings/locksettings5
-rw-r--r--cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java65
-rw-r--r--services/core/java/com/android/server/LockSettingsService.java28
-rw-r--r--services/core/java/com/android/server/LockSettingsShellCommand.java143
-rw-r--r--services/tests/servicestests/src/com/android/server/LockSettingsShellCommandTest.java142
6 files changed, 413 insertions, 0 deletions
diff --git a/cmds/locksettings/Android.mk b/cmds/locksettings/Android.mk
new file mode 100644
index 000000000000..76766c7c6955
--- /dev/null
+++ b/cmds/locksettings/Android.mk
@@ -0,0 +1,30 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+LOCAL_MODULE := locksettings
+LOCAL_MODULE_TAGS := optional
+include $(BUILD_JAVA_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := locksettings
+LOCAL_SRC_FILES := locksettings
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_TAGS := optional
+include $(BUILD_PREBUILT)
+
+
diff --git a/cmds/locksettings/locksettings b/cmds/locksettings/locksettings
new file mode 100755
index 000000000000..c963b238726b
--- /dev/null
+++ b/cmds/locksettings/locksettings
@@ -0,0 +1,5 @@
+# Script to start "locksettings" on the device
+#
+base=/system
+export CLASSPATH=$base/framework/locksettings.jar
+exec app_process $base/bin com.android.commands.locksettings.LockSettingsCmd "$@"
diff --git a/cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java b/cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java
new file mode 100644
index 000000000000..1e426d62e4e0
--- /dev/null
+++ b/cmds/locksettings/src/com/android/commands/locksettings/LockSettingsCmd.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.commands.locksettings;
+
+import android.os.ResultReceiver;
+import android.os.ServiceManager;
+import android.os.ShellCallback;
+
+import com.android.internal.os.BaseCommand;
+import com.android.internal.widget.ILockSettings;
+
+import java.io.FileDescriptor;
+import java.io.PrintStream;
+
+public final class LockSettingsCmd extends BaseCommand {
+
+ private static final String USAGE =
+ "usage: locksettings set-pattern [--old OLD_CREDENTIAL] NEW_PATTERN\n" +
+ " locksettings set-pin [--old OLD_CREDENTIAL] NEW_PIN\n" +
+ " locksettings set-password [--old OLD_CREDENTIAL] NEW_PASSWORD\n" +
+ " locksettings clear [--old OLD_CREDENTIAL]\n" +
+ "\n" +
+ "locksettings set-pattern: sets a pattern\n" +
+ " A pattern is specified by a non-separated list of numbers that index the cell\n" +
+ " on the pattern in a 1-based manner in left to right and top to bottom order,\n" +
+ " i.e. the top-left cell is indexed with 1, whereas the bottom-right cell\n" +
+ " is indexed with 9. Example: 1234\n" +
+ "\n" +
+ "locksettings set-pin: sets a PIN\n" +
+ "\n" +
+ "locksettings set-password: sets a password\n" +
+ "\n" +
+ "locksettings clear: clears the unlock credential\n";
+
+ public static void main(String[] args) {
+ (new LockSettingsCmd()).run(args);
+ }
+
+ @Override
+ public void onShowUsage(PrintStream out) {
+ out.println(USAGE);
+ }
+
+ @Override
+ public void onRun() throws Exception {
+ ILockSettings lockSettings = ILockSettings.Stub.asInterface(
+ ServiceManager.getService("lock_settings"));
+ lockSettings.asBinder().shellCommand(FileDescriptor.in, FileDescriptor.out,
+ FileDescriptor.err, getRawArgs(), new ShellCallback(), new ResultReceiver(null) {});
+ }
+}
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index a2207b259123..dd342c523e75 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -49,6 +49,8 @@ import android.os.IProgressListener;
import android.os.Parcel;
import android.os.Process;
import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
import android.os.storage.IMountService;
import android.os.storage.StorageManager;
import android.os.ServiceManager;
@@ -79,6 +81,7 @@ import com.android.server.LockSettingsStorage.CredentialHash;
import libcore.util.HexEncoding;
import java.io.ByteArrayOutputStream;
+import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@@ -1585,6 +1588,31 @@ public class LockSettingsService extends ILockSettings.Stub {
return mStrongAuthTracker.getStrongAuthForUser(userId);
}
+ private boolean isCallerShell() {
+ final int callingUid = Binder.getCallingUid();
+ return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID;
+ }
+
+ private void enforceShell() {
+ if (!isCallerShell()) {
+ throw new SecurityException("Caller must be shell");
+ }
+ }
+
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver)
+ throws RemoteException {
+ enforceShell();
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ (new LockSettingsShellCommand(mContext, new LockPatternUtils(mContext))).exec(
+ this, in, out, err, args, callback, resultReceiver);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
private static final String[] VALID_SETTINGS = new String[] {
LockPatternUtils.LOCKOUT_PERMANENT_KEY,
LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE,
diff --git a/services/core/java/com/android/server/LockSettingsShellCommand.java b/services/core/java/com/android/server/LockSettingsShellCommand.java
new file mode 100644
index 000000000000..0efdd510459a
--- /dev/null
+++ b/services/core/java/com/android/server/LockSettingsShellCommand.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static com.android.internal.widget.LockPatternUtils.stringToPattern;
+
+import android.app.ActivityManagerNative;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
+
+class LockSettingsShellCommand extends ShellCommand {
+
+ private static final String COMMAND_SET_PATTERN = "set-pattern";
+ private static final String COMMAND_SET_PIN = "set-pin";
+ private static final String COMMAND_SET_PASSWORD = "set-password";
+ private static final String COMMAND_CLEAR = "clear";
+
+ private int mCurrentUserId;
+ private final LockPatternUtils mLockPatternUtils;
+ private final Context mContext;
+ private String mOld = "";
+ private String mNew = "";
+
+ LockSettingsShellCommand(Context context, LockPatternUtils lockPatternUtils) {
+ mContext = context;
+ mLockPatternUtils = lockPatternUtils;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ try {
+ mCurrentUserId = ActivityManagerNative.getDefault().getCurrentUser().id;
+
+ parseArgs();
+ if (!checkCredential()) {
+ return -1;
+ }
+ switch (cmd) {
+ case COMMAND_SET_PATTERN:
+ runSetPattern();
+ break;
+ case COMMAND_SET_PASSWORD:
+ runSetPassword();
+ break;
+ case COMMAND_SET_PIN:
+ runSetPin();
+ break;
+ case COMMAND_CLEAR:
+ runClear();
+ break;
+ default:
+ getErrPrintWriter().println("Unknown command: " + cmd);
+ break;
+ }
+ return 0;
+ } catch (Exception e) {
+ getErrPrintWriter().println("Error while executing command: " + e);
+ return -1;
+ }
+ }
+
+ @Override
+ public void onHelp() {
+ }
+
+ private void parseArgs() {
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ if ("--old".equals(opt)) {
+ mOld = getNextArgRequired();
+ } else {
+ getErrPrintWriter().println("Unknown option: " + opt);
+ throw new IllegalArgumentException();
+ }
+ }
+ mNew = getNextArg();
+ }
+
+ private void runSetPattern() throws RemoteException {
+ mLockPatternUtils.saveLockPattern(stringToPattern(mNew), mOld, mCurrentUserId);
+ getOutPrintWriter().println("Pattern set to '" + mNew + "'");
+ }
+
+ private void runSetPassword() throws RemoteException {
+ mLockPatternUtils.saveLockPassword(mNew, mOld, PASSWORD_QUALITY_ALPHABETIC, mCurrentUserId);
+ getOutPrintWriter().println("Password set to '" + mNew + "'");
+ }
+
+ private void runSetPin() throws RemoteException {
+ mLockPatternUtils.saveLockPassword(mNew, mOld, PASSWORD_QUALITY_NUMERIC, mCurrentUserId);
+ getOutPrintWriter().println("Pin set to '" + mNew + "'");
+ }
+
+ private void runClear() throws RemoteException {
+ mLockPatternUtils.clearLock(mCurrentUserId);
+ getOutPrintWriter().println("Lock credential cleared");
+ }
+
+ private boolean checkCredential() throws RemoteException, RequestThrottledException {
+ final boolean havePassword = mLockPatternUtils.isLockPasswordEnabled(mCurrentUserId);
+ final boolean havePattern = mLockPatternUtils.isLockPatternEnabled(mCurrentUserId);
+ if (havePassword || havePattern) {
+ boolean result;
+ if (havePassword) {
+ result = mLockPatternUtils.checkPassword(mOld, mCurrentUserId);
+ } else {
+ result = mLockPatternUtils.checkPattern(stringToPattern(mOld),
+ mCurrentUserId);
+ }
+ if (result) {
+ return true;
+ } else {
+ getOutPrintWriter().println("Old password '" + mOld + "' didn't match");
+ return false;
+ }
+ } else {
+ return true;
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/LockSettingsShellCommandTest.java
new file mode 100644
index 000000000000..d6ee367e0bbd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/LockSettingsShellCommandTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+
+import static com.android.internal.widget.LockPatternUtils.stringToPattern;
+
+import static junit.framework.Assert.*;
+
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import static java.io.FileDescriptor.*;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.widget.LockPatternUtils;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.FileDescriptor;
+
+/**
+ * Test class for {@link LockSettingsShellCommand}.
+ *
+ * runtest frameworks-services -c com.android.server.LockSettingsShellCommandTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class LockSettingsShellCommandTest {
+
+ private LockSettingsShellCommand mCommand;
+
+ private @Mock LockPatternUtils mLockPatternUtils;
+ private int mUserId;
+ private final Binder mBinder = new Binder();
+ private final ShellCallback mShellCallback = new ShellCallback();
+ private final ResultReceiver mResultReceiver = new ResultReceiver(
+ new Handler(Looper.getMainLooper()));
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ final Context context = InstrumentationRegistry.getTargetContext();
+ mUserId = ActivityManager.getCurrentUser();
+ mCommand = new LockSettingsShellCommand(context, mLockPatternUtils);
+ }
+
+ @Test
+ public void testWrongPassword() throws Exception {
+ when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
+ when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
+ when(mLockPatternUtils.checkPassword("1234", mUserId)).thenReturn(false);
+ assertEquals(-1, mCommand.exec(mBinder, in, out, err,
+ new String[] { "set-pin", "--old", "1234" },
+ mShellCallback, mResultReceiver));
+ verify(mLockPatternUtils, never()).saveLockPassword(any(), any(), anyInt(), anyInt());
+ }
+
+ @Test
+ public void testChangePin() throws Exception {
+ when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
+ when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
+ when(mLockPatternUtils.checkPassword("1234", mUserId)).thenReturn(true);
+ assertEquals(0, mCommand.exec(new Binder(), in, out, err,
+ new String[] { "set-pin", "--old", "1234", "4321" },
+ mShellCallback, mResultReceiver));
+ verify(mLockPatternUtils).saveLockPassword("4321", "1234", PASSWORD_QUALITY_NUMERIC,
+ mUserId);
+ }
+
+ @Test
+ public void testChangePassword() throws Exception {
+ when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
+ when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
+ when(mLockPatternUtils.checkPassword("1234", mUserId)).thenReturn(true);
+ assertEquals(0, mCommand.exec(new Binder(), in, out, err,
+ new String[] { "set-password", "--old", "1234", "4321" },
+ mShellCallback, mResultReceiver));
+ verify(mLockPatternUtils).saveLockPassword("4321", "1234", PASSWORD_QUALITY_ALPHABETIC,
+ mUserId);
+ }
+
+ @Test
+ public void testChangePattern() throws Exception {
+ when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
+ when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
+ when(mLockPatternUtils.checkPattern(stringToPattern("1234"), mUserId)).thenReturn(true);
+ assertEquals(0, mCommand.exec(new Binder(), in, out, err,
+ new String[] { "set-pattern", "--old", "1234", "4321" },
+ mShellCallback, mResultReceiver));
+ verify(mLockPatternUtils).saveLockPattern(stringToPattern("4321"), "1234", mUserId);
+ }
+
+ @Test
+ public void testClear() throws Exception {
+ when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
+ when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
+ when(mLockPatternUtils.checkPattern(stringToPattern("1234"), mUserId)).thenReturn(true);
+ assertEquals(0, mCommand.exec(new Binder(), in, out, err,
+ new String[] { "clear", "--old", "1234" },
+ mShellCallback, mResultReceiver));
+ verify(mLockPatternUtils).clearLock(mUserId);
+ }
+}