Skip to content

Commit

Permalink
v5.28: Custom certificates can be delivered to the device and install…
Browse files Browse the repository at this point in the history
…ed (application settings `certificates`)
  • Loading branch information
vmayorow committed Jul 22, 2024
1 parent c684477 commit 158b506
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 11 deletions.
4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ android {
// To avoid extra step during initial setup, let's keep the target SDK version as 29 as long as possible
//noinspection ExpiredTargetSdkVersion
targetSdkVersion 29
versionCode 14270
versionName "5.27"
versionCode 14280
versionName "5.28"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
dataBinding {
enabled = true
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/com/hmdm/launcher/Const.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class Const {
public static final String ACTION_ENABLE_SETTINGS = "ENABLE_SETTINGS";
public static final String ACTION_PERMISSIVE_MODE = "PERMISSIVE_MODE"; // Temporary action
public static final String ACTION_TOGGLE_PERMISSIVE = "TOGGLE_PERMISSIVE"; // Permanent action
public static final String ACTION_EXIT_KIOSK = "EXIT_KIOSK";
public static final String ACTION_STOP_CONTROL = "STOP_CONTROL";
public static final String ACTION_EXIT = "EXIT";
public static final String ACTION_HIDE_SCREEN = "HIDE_SCREEN";
Expand Down
79 changes: 72 additions & 7 deletions app/src/main/java/com/hmdm/launcher/helper/CertInstaller.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.hmdm.launcher.Const;
import com.hmdm.launcher.R;
import com.hmdm.launcher.util.LegacyUtils;
import com.hmdm.launcher.util.RemoteLogger;
import com.hmdm.launcher.util.Utils;

import java.io.IOException;
Expand All @@ -23,17 +24,27 @@
import java.util.List;

public class CertInstaller {
public static List<String> getCertificates(Context context) {
static class CertEntry {
public String path;
public String cert;
public CertEntry() {}
public CertEntry(String path, String cert) {
this.path = path;
this.cert = cert;
}
}

public static List<CertEntry> getCertificatesFromAssets(Context context) {
String[] names = context.getResources().getStringArray(R.array.certificates);
if (names == null) {
return null;
}
List<String> result = new LinkedList<>();
List<CertEntry> result = new LinkedList<>();
for (String name : names) {
try {
String cert = Utils.loadStreamAsString(new InputStreamReader(context.getAssets().open(name)));
if (cert != null) {
result.add(cert);
result.add(new CertEntry(name, cert));
} else {
Log.e(Const.LOG_TAG, "Failed to read certificate " + name);
}
Expand All @@ -44,14 +55,53 @@ public static List<String> getCertificates(Context context) {
return result;
}

public static List<CertEntry> getCertificatesFromFiles(Context context, String paths) {
String[] names = paths.split("[;:,]");
if (names == null) {
return null;
}
List<CertEntry> result = new LinkedList<>();
for (String name : names) {
String adjustedName = name;
if (!adjustedName.startsWith("/storage/emulated/0/")) {
if (!adjustedName.startsWith("/")) {
adjustedName = "/" + adjustedName;
}
adjustedName = "/storage/emulated/0" + adjustedName;
}
try {
String cert = Utils.loadFileAsString(adjustedName);
if (cert != null) {
result.add(new CertEntry(adjustedName, cert));
} else {
RemoteLogger.log(context, Const.LOG_WARN, "Failed to read certificate " + adjustedName);
}
} catch (IOException e) {
RemoteLogger.log(context, Const.LOG_WARN, "Failed to read certificate " + adjustedName + ": " + e.getMessage());
e.printStackTrace();
}
}
return result;
}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public static boolean installCertificate(Context context, String cert) {
public static boolean installCertificate(Context context, String cert, String path) {
try {
DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
ComponentName adminComponentName = LegacyUtils.getAdminComponentName(context);
boolean res = dpm.installCaCert(adminComponentName, cert.getBytes());
if (path != null) {
if (res) {
RemoteLogger.log(context, Const.LOG_INFO, "Certificate installed: " + path);
} else {
RemoteLogger.log(context, Const.LOG_WARN, "Failed to install certificate " + path);
}
}
return res;
} catch (Exception e) {
if (path != null) {
RemoteLogger.log(context, Const.LOG_WARN, "Failed to install certificate " + path + ": " + e.getMessage());
}
e.printStackTrace();
return false;
}
Expand All @@ -61,12 +111,27 @@ public static void installCertificatesFromAssets(Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return;
}
List<String> certs = getCertificates(context);
List<CertEntry> certs = getCertificatesFromAssets(context);
if (certs == null || certs.size() == 0) {
return;
}
for (CertEntry cert : certs) {
// Do not log installation of certificates from assets
// because the remote logger is not yet initialized
installCertificate(context, cert.cert, null);
}
}

public static void installCertificatesFromFiles(Context context, String paths) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return;
}
List<CertEntry> certs = getCertificatesFromFiles(context, paths);
if (certs == null || certs.size() == 0) {
return;
}
for (String cert : certs) {
installCertificate(context, cert);
for (CertEntry cert : certs) {
installCertificate(context, cert.cert, cert.path);
}
}

Expand Down
22 changes: 21 additions & 1 deletion app/src/main/java/com/hmdm/launcher/helper/ConfigUpdater.java
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,27 @@ protected void onPostExecute(RemoteFileStatus fileStatus) {

}.execute(remoteFile);
} else {
Log.i(Const.LOG_TAG, "loadAndInstallFiles(): Proceed to application update");
Log.i(Const.LOG_TAG, "loadAndInstallFiles(): Proceed to certificate installation");
installCertificates();
}
}

private void installCertificates() {
final String certPaths = settingsHelper.getAppPreference(context.getPackageName(), "certificates");
if (certPaths != null) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
CertInstaller.installCertificatesFromFiles(context, certPaths.trim());
return null;
}

@Override
protected void onPostExecute(Void v) {
checkAndUpdateApplications();
}
}.execute();
} else {
checkAndUpdateApplications();
}
}
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/com/hmdm/launcher/json/PushMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public class PushMessage {
public static final String TYPE_PERMISSIVE_MODE = "permissiveMode";
public static final String TYPE_RUN_COMMAND = "runCommand";
public static final String TYPE_REBOOT = "reboot";
public static final String TYPE_EXIT_KIOSK = "exitKiosk";

public String getMessageType() {
return messageType;
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/java/com/hmdm/launcher/ui/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,14 @@ public void run() {
}
}
break;

case Const.ACTION_EXIT_KIOSK:
ServerConfig config = settingsHelper.getConfig();
if (config != null) {
config.setKioskMode(false);
RemoteLogger.log(MainActivity.this, Const.LOG_INFO, "Exit kiosk by admin command");
showContent(config);
}
}

}
Expand Down Expand Up @@ -410,6 +418,7 @@ private void initReceiver() {
intentFilter.addAction(Const.ACTION_HIDE_SCREEN);
intentFilter.addAction(Const.ACTION_EXIT);
intentFilter.addAction(Const.ACTION_POLICY_VIOLATION);
intentFilter.addAction(Const.ACTION_EXIT_KIOSK);
LocalBroadcastManager.getInstance(this).registerReceiver(receiver, intentFilter);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,12 @@ public static void process(PushMessage message, Context context) {
// Reboot a device
AsyncTask.execute(() -> reboot(context));
return;
}
} else if (message.getMessageType().equals(PushMessage.TYPE_EXIT_KIOSK)) {
// Temporarily exit kiosk mode
LocalBroadcastManager.getInstance(context).
sendBroadcast(new Intent(Const.ACTION_EXIT_KIOSK));
return;
}

// Send broadcast to all plugins
Intent intent = new Intent(Const.INTENT_PUSH_NOTIFICATION_PREFIX + message.getMessageType());
Expand Down

0 comments on commit 158b506

Please sign in to comment.