The Java Development Kit (JDK) library's java.security
package is one of the most important packages, yet despite consistent updates, it remains vastly underutilized.
In light of the increased emphasis on cybersecurity frameworks, including zero trust, it's imperative for Java developers to become familiar with Java SE's security libraries.
As with any other field in information technology, cybersecurity has a capricious nature. After all, it has to keep up with the latest trends in cybercrime. One way Java security has evolved (or devolved, depending on who you ask) is by removing the SecurityManager. But why is this significant? Is it any real cause for concern? This article will help you answer these questions.
The necessity of Java Security
Java is considered an inherently secure language, thanks to features like the Java virtual machine (JVM), memory management, strong exception handling, and lack of explicit pointers (*ie* references). Despite these built-in features, users may be less inclined to use it because of high-profile vulnerabilities, such as Log4j.
However, it's important to note that many of these vulnerabilities are product, third-party, and/or developer related (*ie* developers aren't writing secure code). Although Java does (or did) have vulnerabilities specific to the JDK, such as sensitive data exposure and deserialization issues, many of these vulnerabilities have either been addressed or are currently being addressed. For instance, JDK 20 was released with improved optimization for cryptography intrinsics for specific platforms.
Of course, this isn't to say that Java automatically handles all security. It's still the developer's job to write secure code. This means understanding the capabilities and limitations of Java's built-in security packages and sourcing third-party security APIs and libraries are often unnecessary.
The JDK includes features for platform security, cryptography, access control and authentication, communications, and public key infrastructure. All these features can be explored in the official Security Developer's Guide.
Improvement of any platform or tool also entails removing insecure or superfluous features. One of these features happens to be the SecurityManager.
What is the SecurityManager?
The Java SecurityManager was designed as a federated authority for application security. It is (or was) used to specify security policies and rules for Java applications to ensure that certain operations are safe. The Java.lang.SecurityManager
class has been around since JDK 1.0.
Originally, the SecurityManager was an abstract class. However, eventually, it was made non-abstract to make it easier to access and work with. While a new SecurityManager
object could be instantiated with each class, it wasn't in line with Java's best coding practices. Instead, you were required to set a system property when running your application:
java -Djava.security.manager MyAPP
Alternatively, you could set it by directly instantiating a SecurityManager
object and then setting it as a system property from your code:
SecurityManager sm = new SecurityManager();
System.setSecurityManager(sm);
The system SecurityManager
could then be fetched in another class using the following:
SecurityManager securityManager = System.getSecurityManager();
The SecurityManager
class mostly consists of a collection of check methods. These methods verify if specific application operations are allowed to access certain resources. Arguably, the most useful is the checkPermission
method, which verifies if an operation is permitted to use certain resources. Permission policies are dictated and controlled using (direct and inherited) subclasses of the java.security.Permission
class.
Ultimately, the SecurityManager simplifies Java application security and is easy to use. So why has it been deprecated?
The history of the SecurityManager
Each new release of Java SE comes with a host of improvements, many of which are thanks to the Java Community Process (JCP) and JDK Enhancement Proposals. The latter is the key reason the SecurityManager has been deprecated for removal.
In the early stages of its usage, the SecurityManager
class was chiefly implemented to dictate security policies for applets, which most web platforms no longer support. Each applet's SecurityManager
was provided by the web browser.
The Java Applet API was finally deprecated with the release of Java 9 in 2017, as dictated by JEP289. However, while it was deprecated, it was not deprecated for removal. With the release of Java SE 17, the Applet API and its related classes were finally deprecated for removal, as specified by JEP398.
This was a way to move the programming language and API forward. Legacy APIs such as these have reached a level of irrelevancy that made them unnecessary for all future JDK releases. And because the SecurityManager is so closely tied to applet architecture, it's being removed for similar reasons.
Why is the SecurityManager being removed?
Today, the SecurityManager is no longer the most efficient way to secure client-side and server-side Java code. In fact, the Java platform provides more effective mechanisms for these tasks.
Each new release of the JDK aims to be more intrinsically secure than the last. This means many of the accidental vulnerabilities that the SecurityManager attempts to address are no longer relevant.
Because the SecurityManager's functionality has remained untouched over the last decade, it cannot address many of the current common threats, weaknesses, and vulnerabilities faced by programmers. In truth, it was never intended or created to tackle the security issues of today. And yet because it still exists, it must be tested with all the classes it's connected to.
This creates an unnecessary maintenance burden. The time and effort required to preserve the SecurityManager could be dedicated to testing classes and features that are currently more relevant to developers. While one of the biggest advantages of the SecurityManager's architecture is its intuitiveness, configuring it can be difficult.
As a reminder, the SecurityManager mainly consists of a collection of check methods. For instance, a useful security feature is CheckPermission
, which enables you to validate permissions for an application or system via your Java policy file. The structure for a policy that grants an application's code read access to a directory would look like this:
grant codeBase "file:/home/source/" {
permission java.io.FilePermission "/resources/files", "read";
};
This configuration grants the codebase (every class file) located in file:/home/source/
access to every resource in the relative files
directory. To check specific permission, you must instantiate a subclass of the Permission
or BasicPermission
class. It ultimately represents the permission set in the policy file. In this case, it's the FilePermission
object:
FilePermission filePerm = new java.io.FilePermission("/resources/files", "read");
Since policies are often contextual, you have to fetch an object representing the current access control context of the code you're running. Then you can use SecurityManager
to check the permissions against the context:
AccessControlContext accessCon = AccessController.getContext();
SecurityManager securityManager = System.getSecurityManager();
securityManager.checkPermission (filePerm, accessCon);
If your file-read policies don't permit read access from your codebase to the resource file, this code will throw a SecurityException
— the AccessControlException
.
While this process seems simple, it has a few disadvantages. Nuanced granular permission and policy setting can be difficult. For instance, what if you want certain operations or classes to have write access to a specified file in a directory that your policies prevent?
You could separate directories for your policy, source, and resource files as a workaround. This would require you to carefully match and keep track of how your permissions relate to each of your policies. This is highly inefficient, especially when you're trying to build an application with nuanced policies and permissions.
Likewise, developing libraries and APIs that use SecurityManager
isn't easier. Libraries using SecurityManager architecture require that you grant your code some of the same permissions they do. This means scouring the library's documentation to see which permissions it grants. Assuming that the library has detailed documentation.
Alternatively, you can simply grant all permission to the library dependency's files. However, this isn't in line with modern zero-trust security principles (*ie* least privilege).
Your applications must be functional, secure, and able to use resources efficiently to ensure portability. The SecurityManager is a resource hog, especially in situations where you're using or writing an application with a complex permission and policy network. In fact, the JVM always runs with the SecurityManager disabled by default.
Not only is SecurityManager functionally obsolete, but it's also architecturally irrelevant, too. This means there will be no direct replacements for it. So now what?
The future of the SecurityManager
At the time of writing this article, JDK 20 was just made generally available with much of the SecurityManager's architecture still intact. Three editions have been released since the removal of the SecurityManager was proposed. Because it intersects with so many Java classes and libraries, it's difficult to remove without breaking the entire Java API. Even Jakarta EE has many functions that rely on the SecurityManager's architecture.
This means complete removal won't be easy, and the plan is to perform it in safe increments that have already started with the SecurityManager labeled for removal in JDK 17. Additionally, JVM warns users if a SecurityManager
is dynamically created or allowed.
By JDK 18, the SecurityManager was disallowed by default. If developers attempted to create and set a SecurityManager (setSecurityManger
), it would throw an UnsupportedOperationException
error. While the functionality to support legacy code that uses SecurityManager
still exists, anyone still using this architecture should consider moving away from it.
The JDK has alternatives to many of the SecurityManager's functions. For instance, those looking to control permissions for native code access can use the Foreign Function and Memory API (FFM), which was introduced in JDK 20.
Alternatives to the SecurityManager
The first step to separating your code from the Java Security Manager is identifying where it's used. While it's still available in the latest long-term support (LTS) releases of the JDK (17 and 21), it's deprecated. Organizations planning to upgrade from previous LTS versions, such as JDK 11 or 8, need to be aware of this. Ultimately, all deprecated code should be removed or updated since it isn't well-maintained and can present a security risk. You can use jdeprscan to find all deprecated code in your class files.
The only true out-of-the-box alternative to the SecurityManager is the JDK's Security Library, and developers must acquaint themselves with Oracle's Secure Coding Guidelines for Java SE. Ultimately, writing secure code from the get-go is the first step to minimizing your project's vulnerabilities.
While scanning for deprecated APIs is important, it's not the only security weakness that you should be scanning for. It's also crucial to check your project's dependencies and libraries. Developers often use the SecurityManager to monitor and log what resources their applications access. A third-party solution, such as Snyk, can be utilized for this purpose.
Snyk is a developer security tool that can be integrated into your development environment, helping you analyze code and container security. It scans through your project's configuration files and manifests to find and flag direct and indirect dependencies with known vulnerabilities. Snyk automatically fixes your vulnerabilities by patching your configuration files and updating all dependencies. It can be integrated into a variety of development tools and workflows.
Snyk provides a command line interface (CLI) that enables developers to use it in their terminal or in a continuous integration, continuous delivery (CI/CD) workflow. You can download and install the stand-alone Snyk CLI tool, and once you've configured it and authenticated your account, you can test your local projects using the snyk test
command.
Additionally, you can stay up-to-date with any new vulnerabilities that may arise using the snyk monitor
command. With this command, Snyk takes a snapshot of your project (which can be viewed on the web application) and sends you email notifications, letting you know about any newly revealed problems related to your components.
Here is a small demonstration of the snyk test
and the snyk monitor
commands:
Nonetheless, while Snyk can find vulnerabilities, it's still important to adhere to the basic secure coding principles for Java. As the old adage goes, "Prevention is better than cure." So it's important to use Snyk CLI for development along with a CI tool, which is part of a healthy DevSecOps process.
In the last few years, the industry has seen a substantial increase in open source vulnerabilities. To mitigate and prevent attacks, developers should routinely review the security posture of their projects and workflows. Tools like Snyk are regularly updated and allow you to automate the quality and security check process by integrating them into your CI/CD pipeline.
With SecurityManager standing on its last legs, you might be tempted to use a third-party Java library or API that has a similar architecture or utilizes a similar approach. Third-party libraries have always posed one of the highest security risks. If you do elect to go this route, consider using Snyk Open Source to ensure that you're using safe dependencies for your project.
Conclusion
Ultimately, the SecurityManager is obsolete. It's a wonder that they've taken this long to officially remove it. And this isn't bad news. Quite the contrary, it will make the Java platform far more secure and less superfluous. The SecurityManager was never meant to be depended on as a general tool for cybersecurity.
If you want a more effective tool to improve your projects' overall security posture, you should consider using a tool like Snyk. It can help you embed zero trust into your development workflow. In addition, you can add the Snyk Code IDE plugin to IntelliJ to secure your code and ensure its quality in real-time.