Define a Security Policy
A security policy allows the Kernel to prevent an Application from accessing resources or calling specific APIs. Whereas APIs exposed by the Kernel allows to control at build-time, the security policy here controls the APIs called at runtime.
Defining a security policy is done by implementing the standard SecurityManager class. Basically, all sensitive APIs exposed by the Kernel have already been protected by a Permission Check. Each time an Application calls such API, the Security Manager SecurityManager.checkPermission(Permission) implementation verifies that the requested permission has been granted to the Application.
Register a Security Manager
The first step for the Kernel is to register a SecurityManager implementation. Usually this is done in the Kernel boot sequence, before starting to run Applications. However, the Security Manager can be updated at any-time, and a Kernel can also register different implementations during its execution.
For the purpose of ROM footprint optimization, permission checks calls are disabled by default to avoid extra code processing if the system owner does not want to use the Security Manager. In order to activate this feature, set the Option(checkbox): Enable SecurityManager checks option.
Once the Security Manager checks are enabled, you can then implement your own security policy.
To apply a security policy, instantiate a SecurityManager and register it with System.setSecurityManager(SecurityManager) method.
// create an instance of SecurityManager
SecurityManager sm = new SecurityManager() {
@Override
public void checkPermission(java.security.Permission perm) {
// implement here the Application Security Policy
};
};
// set the Security Manager
System.setSecurityManager(sm);
The next section will guide you to implement the desired security policy.
Implement a Security Manager
The implementation of the SecurityManager.checkPermission(Permission) method first retrieves the owner of the requested Permission, then checks if it is a Feature (not the Kernel), and finally, checks the permission according to the given Feature.
The following code snippet shows the typical implementation:
public class MyKernelSecurityManager extends SecurityManager {
@Override
public void checkPermission(Permission perm) {
Module caller = Kernel.getContextOwner();
if(caller == Kernel.getInstance()) {
// Kernel has all the rights: no checks
}
else{
Feature callerApp = (Feature)caller;
Kernel.enter();
if(!isAllowed(callerApp, perm)) {
throw new SecurityException();
}
}
}
private boolean isAllowed(Feature callerApp, Permission perm) {
// implement here the Application Security Policy
}
}
You are now ready to implement your policy to decide whether the given permission is granted to the given Application.
The KF-Util module provides ready-to-use implementations, described in the next sections. Examples of integration are also available in the Kernel-GREEN project on GitHub.
Security Manager with Application Declared Permissions
This Security Manager provides a ready-to-use implementation based on permissions declared by the Application itself. It assumes the application and its permissions file have been approved beforehand by a moderator.
Principle
Basically, Applications embed a policy resource file that describes the permissions they need at runtime. This file is then loaded when the Application is installed. Finally, this Security Manager checks if the permission has been granted to the calling Application. If a permission check is triggered but has not been declared, the Security Manager throws a SecurityException.
Here is a sequence diagram to describe the entire flow from Feature installation to uninstallation:
Policy File Format
An Application must define its Application policy file as a resource.
By default, the resource name must be /feature.policy.json
.
The policy file format is described in JSON, which is the default syntax supported by this Security Manager.
Before going further we strongly advise to take a look to the java.security.Permission specification and its class hierarchy to fully understand the way permissions work (name, action).
The Application policy file declares the list of required java.security.Permission classes, names and actions as following:
{
"permissions": {
"<permissionClassName1>":{
"<permissionName1>":["<permissionAction1>","<permissionAction2>"],
"<permissionName2>":["<permissionAction1>"]
},
"<permissionClassName2>":{
"<permissionName3>":["<permissionAction3>"]
}
}
}
The permission name
and action
attributes are specific to the permission implementation.
Therefore each permission has its own definition of what a name is.
The following keywords allow more flexibility over the content of the file:
the
*
(wildcard) symbol means “any”. It can be used for permission class name, permission name and permission actions.the
null
keyword represents a Javanull
value. It can be used for permission name and permission actions.
Policy File Example
Here is now an example of what a real JSON Application policy file can look like:
{
"permissions": {
"ej.microui.display.DisplayPermission":{
"*":[]
},
"ej.microui.event.EventPermission":{
"null":["null"]
},
"ej.microui.display.FontPermission":{},
"ej.microui.display.ImagePermission":{
"null":["*"]
},"ej.microui.MicroUIPermission":{
"*":["start"]
},"java.net.SocketPermission":{
"www.microej.com":["connect","resolve"]
},"java.util.PropertyPermission":{
"property":["write","read"]
},"java.lang.RuntimePermission":{
"exit":[]
}
}
}
To simplify the file structure you can also choose to have an empty object value for permission class name or/and permission actions such as shown in the example above:
{
"permissions": {
"ej.microui.display.DisplayPermission":{
"*":[]
},
"ej.microui.display.FontPermission":{},
"java.lang.RuntimePermission":{
"exit":[]
}
}
}
This example:
allows the usage of any permission name and any actions for the
ej.microui.display.DisplayPermission
permission.allows the usage of any permission name and any actions for the
ej.microui.display.FontPermission
permission.allows the
exit
permission name and any actions for thejava.lang.RuntimePermission
permission.
Using an empty value or the *
wildcard is left to the developer preference and should be processed in the exact same way by the security policy resource loader.
Kernel Implementation
Here are the steps to integrate this Security Manager in your Kernel:
Add the dependency to the KF-Util library in the Kernel build file
implementation("com.microej.library.util:kf-util:2.8.0")<dependency org="com.microej.library.util" name="kf-util" rev="2.8.0"/>
Make sure to embed java.security.Permission class names
If the Kernel does not embed all class names (see Stripping Class Names from an Application), the specified Permission class names must be embedded by declaring them as Required Types. Any permission check done on a permission class without embedded name will result in a SecurityException.
Create the policy resource loader. By default, the library comes with a policy resource loader for the JSON format.
SecurityPolicyResourceLoader loader = new JsonSecurityPolicyLoader();
You can also define your own format for the policy resource file by implementing the _SecurityPolicyResourceLoader` interface. Optionally, you can change the Application file policy name, by setting the System Property
feature.policy.name
(defaults to/feature.policy.json
).Create the KernelSecurityPolicyManager instance with the policy resource loader
SecurityManager sm = new KernelSecurityPolicyManager(loader);
Register this instance as the current Security Manager
System.setSecurityManager(sm);
Note
To log every authorized access, change the logger level to FINE
in the Kernel system properties such as
.level=FINE
.
Security Manager with Permission Dispatch
This Security Manager provides a template for dispatching the permission check per kind of java.security.Permission class. The Kernel implementation must provide instances of FeaturePermissionCheckDelegate to specify the behavior of the SecurityManager.checkPermission(Permission) for each permission class. If a permission check is done and no delegate for its permission is found, a SecurityException is thrown. The policy grants all applications the permission for a list of permission classes and logs all protected accesses by Applications.
Here are the steps to integrate this Security Manager in your Kernel:
Add the dependency to the KF-Util library in the Kernel build file
implementation("com.microej.library.util:kf-util:2.8.0")<dependency org="com.microej.library.util" name="kf-util" rev="2.8.0"/>
Create the KernelSecurityManager instance
KernelSecurityManager sm = new KernelSecurityManager(loader);
Create a new class that implements the FeaturePermissionCheckDelegate interface like
MySocketPermissionCheckDelegate
below.public class CustomPermissionCheckDelegate implements FeaturePermissionCheckDelegate { @Override public void checkPermission(Permission permission, Feature feature) { SocketPermission sPerm = (SocketPermission)permission; // implement here the SocketPermission check for this Application } }
Associate an instance of this FeaturePermissionCheckDelegate subclass with the java.security.Permission to be checked (like
SocketPermission
in the example below) by means of the Security Manager.sm.setFeaturePermissionDelegate(SocketPermission.class, new MySocketPermissionCheckDelegate());
This code will apply the logic inside of the
MySocketPermissionCheckDelegate#checkPermission(Permission permission, Feature feature)
method to all mapped permissions (such asSocketPermission.class
for this specific example).Repeat the two previous steps for each supported java.security.Permission class.
Register this instance as the current Security Manager
System.setSecurityManager(sm);
Note
The Kernel-GREEN uses this Security Manager template to log all the Permission checks on the standard output.