By Ziyi Lin, Cloud-native Confidential Computing SIG Maintainer
"How can I ensure the security of passwords in Java program memory?"
The standard solution to this problem is Java confidential computing technology, which significantly enhances the security of Java programs. In this regard, the cloud-native confidential computing SIG of the OpenAnolis community has introduced the implementation technology for Java confidential computing called Teaclave Java TEE SDK, hereinafter referred to as Teaclave Java. This technology offers the following notable advantages:
• Comprehensive security in various scenarios: Teaclave Java enables you to implement trusted computing at the highest security level when you have confidential computing hardware. If you don't have the necessary hardware, it still provides trusted computing at the security sandbox isolation level. This effectively protects your sensitive data and the security of the computing process.
• Easy development and building: Teaclave Java adopts an SPI-based pure Java programming model, allowing for one-click building, thus significantly reducing the threshold for developing and building Java confidential computing.
Teaclave Java has been validated in enterprise-level internal scenarios and is open-sourced in the Apache community. The paper on this technology was published at the International Conference on Software Engineering ICSE 2023 by the cloud-native confidential computing SIG of the OpenAnolis community, in collaboration with Shanghai Jiao Tong University and Dalian University of Technology, winning the ACM SIGSOFT distinguished paper award.
The essence of the problem is how to safely use sensitive data in a risky runtime environment. When we decrypt the password at runtime, the password will exist in memory in plain text, specifically on the Java heap, as shown in the left half of Figure 1. If the system is attacked, such as the notorious log4j vulnerability attack in 2021, the sensitive contents on the Java heap can be compromised. Even if it is not attacked, sensitive information may be leaked when a heap dump is performed for performance diagnosis. Therefore, exposing sensitive information such as passwords to common execution environments is highly risky.
(Solution Figure 1: Native Java password protection diagram)
One idea for protection is to minimize the storage time of plaintext passwords in memory to reduce the time window for exposure of sensitive information. As shown in the right half of Figure 1, after the password is used, it should be promptly destroyed from memory, which is more secure than before. Since passwords are text messages, they are stored using the String class in Java. The Java String is an immutable type, meaning its content cannot be changed after creation, making it impossible to reset the content through any API. The only way to destroy the password is to use reflection to empty the array inside the String class that holds the character contents. By doing so, the password can be erased from the Java heap. Setting the password string to null directly is ineffective as it only sets the pointer of the String variable to null, which does not impact the password data on the Java heap. The password data can only be cleared from the heap during the next garbage collection. Another method is to use a char array instead of the String class to hold the password, which eliminates the need for reflection and makes destruction easier. Alternatively, byte arrays can be used to hold the password because the plaintext is in character encoding rather than readable characters for humans, making it more difficult for people to understand.
These are the solutions currently available on the web, and this article refers to them as "naive" Java password protection schemes. These schemes only shorten the lifetime of plaintext passwords on the Java heap and do not provide actual protection for the plaintext passwords. Additionally, the term "timely" is subjective, and developers may not be able to accurately determine when it is timely.
A more typical case is the well-known log4j vulnerability problem. An attacker can exploit a vulnerability in log4j 2.14 to upload a malicious class file to the server and run it through Java's dynamic class loading mechanism to steal the server's private key stored in the Java heap. With the private key, all communication between the server and the client becomes plain text to the attacker.
In the above examples, the passwords and keys that need to be protected at runtime are security-sensitive data. However, in actual scenarios, the scope of protection is not limited to sensitive data and may extend to the computing process. For example, in authentication scenarios, it is necessary to ensure the trustworthiness of the authentication process and prevent tampering by attackers. Another example is when cloud service users deploy their own algorithms to the cloud. Although the deployed products can be encrypted for secure transmission and storage, and cloud vendors provide robust security protection against external attacks, users may still be concerned about whether the cloud vendors are snooping on their computing process at runtime or if there is a possibility of insider threats.
It is evident that protecting security-sensitive data and computing in Java applications is not a distant requirement but an urgent and practical one. For cloud computing providers, convincing users that their sensitive data and computing are invisible to cloud vendors holds significant commercial value.
Protecting sensitive data at runtime is not a new topic, but rather a technology that has been around for more than 20 years - confidential computing technology. Confidential computing provides hardware-level system isolation to ensure data security and program security during runtime. It divides the execution environment into two parts: the rich execution environment (REE) and the trusted execution environment (TEE). It is believed that the REE and TEE should be isolated from each other, with the TEE being encrypted by hardware to prevent the outside world from accessing its contents. Security-sensitive content should be run in the TEE, while other content should be executed in the REE.
The core concept of confidential computing is to provide a secure region within the runtime environment for security-sensitive programs to execute, reducing the risk of attacks. It ensures the security and trustworthiness of transmitting, storing, and computing security-sensitive data and programs. Currently, confidential computing has a wide range of applications and promising prospects in privacy security, blockchain, multi-party computing, IoT, edge devices, and personal computing devices.
Confidential computing technology appears to be the standard solution to the security problems of Java programs. So, can we apply it to Java applications?
SGX and TrustZone provide the hardware foundation for general-purpose confidential computing, while open-source drivers and SDKs provide the software foundation. With these foundations, developers can use confidential computing in software applications. However, confidential computing is not compatible with Java applications since only native programs can run in the TEE. To run a Java program in the TEE, you must first start a JVM in the TEE and then execute the Java program on the JVM. Is it possible to run a JVM in the TEE? The answer is yes, and Occlum allows you to run a JVM in the TEE. The principle is shown in Figure 2.
Occlum acts as an operating system, sitting between the TEE's underlying SDK and the JVM, to support the execution of JVM in the TEE. Users can deploy the entire Java program, including confidential code, in the TEE, and Occlum supports JVM execution. The deployment structure is shown in the right half of Figure 2. The yellow APP represents the Java application and its required third-party libraries, while the red circle represents the trusted code. The application starts its execution through a launcher in the REE, which is usually a small command line tool. This solution offers good compatibility, allowing users to obtain confidential computing support without modifying the original code. However, there are also obvious disadvantages. Putting too much code into the TEE will cause two problems:
(Figure 2: Schematic diagram of Occlum)
To sum up, although the Occlum solution has the advantages of simplicity and feasibility, its shortcomings in security and performance are the main obstacles to its practical application.
The approach of supporting JVM and all applications in the TEE executes too much code, leading to decreased security and performance degradation, which makes it difficult to implement in practice. Can we change our thinking and only put trusted code into the TEE? Considering that only native code can be executed in the TEE, is it possible to directly compile trusted code from Java code into native code and run it in the TEE? The answer is yes, and we can achieve that with the Teaclave Java TEE SDK, referred to as Teaclave Java.
Teaclave Java is a Java confidential computing development framework and build toolchain developed by the JVM team. It allows for the development and building of Java confidential computing applications in one place. Even if the user does not have a hardware environment that supports confidential computing, Teaclave Java can implement security sandbox isolation to effectively ensure the security of sensitive data and programs at runtime.
The key technical features of Teaclave Java are as follows:
With the support of these technologies, Teaclave Java can convert Java inter-module service calls from ordinary modules to confidential modules into function calls from ordinary modules to confidential native libraries, as shown in Figure 4.
Teaclave Java divides application code into three modules: Host, Enclave, and Common. Host is a common security-non-sensitive program, Enclave is a security-sensitive program, and Common is a common code used by the first two programs. This module division is to make developers aware of the security division of the code and to make it easier to use different toolchains for different modules during construction.
Host and Enclave are decoupled. They can only interact with each other through Java's SPI (Service Provider Interface) mechanism but cannot be called directly. The implementation of confidential computing is encapsulated as a service in the Enclave module. Its interface declaration is defined in the Common module and identified by the @EnclaveService annotation. When the program in the Host needs to use a confidential computing task, it can load the service instance first and then call the corresponding function. This structural organization is shown in Figure 3.
(Figure 3: Development view of Teaclave Java)
For example, we can declare a confidential computing service interface as shown in code block 1 in Common, which provides an API for authenticating whether the encrypted password is valid. This is the authenticate function. This function accepts an encrypted password passed in by a user and returns the authentication result of the password.
@EnclaveService
public interface AuthenticationService {
/**
* Given an encrypted input password, check if it is the correct password.
* @param inputPwd the encrypted password to be authenticated
* @return true if the given password is correct.
*/
boolean authenticate(String inputPwd);
}
Code block 1: Example of defining an interface declaration of the confidential computing service in the Common module
The implementation of the AuthenticationService
interface is defined in the AuthenticationServiceImpl
class of the Enclave module, as shown in code block 2. The authenticate
function of this class first uses the private key to decrypt the input encrypted string to obtain the plaintext result, then compares it with the correct password stored in memory, and then returns the result of the consistency check. The correct password value and private key stored in this class are security-sensitive data, and the implementation of the authenticate function is also a security-sensitive operation. They will all run in the TEE and be provided for external use in the form of a black box. You can only see the encrypted input data and the returned judgment results from the outside, but you cannot spy on the actual running process and data.
public class AuthenticationServiceImpl implements AuthenticationService {
private String pwd = "somePwd"; // assume it's got at runtime.
@Override
public boolean authenticate(String inputPwd) {
String decryptedInputPwd = decrypt(inputPwd);
return pwd.equals(decryptedInputPwd);
}
private static String decrypt(String inputPwd) {
return inputPwd; // assume it's decrypted with private key
}
}
Code block 2: Example of defining an interface declaration of the confidential computing service in the Enclave module
The code example of the Host module using confidential computing services is shown in code block 3, from which you can see that the use of the AuthenticationService
interface for confidential computing services is no different from the ordinary SPI interface, which is still the process of loading services, calling functions, and performing different actions based on the results. The slight difference is that you must first create an instance of the confidential computing environment Enclave, then load a confidential computing service instance from it, bind the confidential computing service instance to the environment instance, and then destroy the environment. The APIs for the lifecycle management of these confidential computing environments are provided by Teaclave Java. As can be seen from code block 3, it is unnecessary to perceive what the password and private key are in the Host module, nor to understand the authentication process, but to call the authentication function a black box service.
public class Main {
public static void main(String[] args) throws Exception {
Enclave enclave = EnclaveFactory.create();
Iterator<AuthenticationService> services = enclave.load(AuthenticationService.class);
String pwd = "encryptedPwd"; // assume this is an encrypted password
while (services.hasNext()) {
AuthenticationService authenticationService = services.next();
if (authenticationService.authenticate(pwd)) {
System.out.println("Passed");
} else {
System.out.println("Rejected");
}
}
enclave.destroy();
}
}
Code block 3: Example of using confidential compute services from the Host module
The above three parts of code constitute a complete Java confidential computing application. From a development perspective, it looks similar to writing an application that calls an ordinary SPI service. You only need to focus on the development of business logic and do not need to learn the underlying details of confidential computing. Therefore, thanks to Teaclave Java, there is no development threshold for Java confidential computing.
Teaclave Java provides a complete set of build toolchains to support the programming model described above. Users only need to enter a few simple Maven commands to complete all build tasks. The build toolchains compile non-confidential and confidential code into Java bytecode products and native libraries that can be deployed in SGX. They also automatically generate all auxiliary code required for completing confidential computing service calls.
Figure 4 shows the build deployment view of Teaclave Java, which consists of three main aspects:
(Figure 4: Teaclave Java deployment view)
The code for these adaptation and conversion calls is automatically generated during the build process and deployed in the normal environment and SGX. In Figure 4, they are marked in blue.
An important step in the build process is Java static compilation, which compiles the confidential section's Java code into native code.
Initially, Java programs required the Java Virtual Machine (JVM) to run. However, with the help of Java static compilation technology, Java programs (including JDK library dependencies and third-party library dependencies) can be compiled into native code at runtime. This allows Java programs to be executed directly without the need for a JVM, resulting in lightweight execution.
Teaclave Java utilizes the most mature Java static compilation technology called GraalVM. GraalVM analyzes a Java program by determining the reachable code from the program entry point. Only the reachable code is compiled to produce a native image. The program entry point refers to the main function for executable programs or the exposed public API for library files. In the case of Teaclave Java, the entry point is the implementation function of the confidential computing service interface defined in the Enclave module. These interface implementations rely on three types of dependencies: code in the Common module, certain JDK libraries, and other third-party Java libraries. However, only the code that is actually used is compiled into a native image, along with the implementation code of the confidential computing service and the support provided by GraalVM at runtime (referred to as Substrate VM). Additionally, Teaclave Java provides specific adaptations for the SGX hardware platform and optimizations for confidential computing requirements, as GraalVM is primarily designed for common scenarios and hardware platforms.
When the native image is compiled, it possesses unique properties.
• Decreased Trusted Computing Base (TCB): GraalVM only compiles the code that is reachable from the confidential computing service entry. As a result, the TCB is significantly reduced compared to Occlum's solution, where LibOS, JVM, and Java applications are all placed within the TEE.
• Improved security: At runtime, a native image has its own native memory heap that is isolated from the Java heap, making it difficult for Java applications to access it. (Java can still access native memory through the Unsafe interface, but it becomes much more challenging.) Additionally, Java static compilation eliminates the dynamic characteristics of Java. Only reflection and dynamic class loading explicitly configured at compile time will be effective, rendering other dynamic behaviors at runtime invalid. Log4j vulnerability attacks are inherently ineffective on native images. Therefore, the native image can be seen as a security sandbox. Even without the SGX hardware environment, the native image improves security compared to Java programs. When deployed in SGX, the security of the TEE is further enhanced as the threat posed by Java's dynamic characteristics to TEE security is eliminated.
• Improved performance: GraalVM's Java static compilation considerably optimizes the code, reaching a runtime performance level similar to JVM's C1 optimization. Moreover, there is no need to start the JVM, perform class loading, execute interpretation, or consume JIT resources. Consequently, compared to Java programs, the performance of short tasks can be improved by an order of magnitude, with a significant reduction in memory usage.
These properties effectively enhance the security of confidential programs and improve the practicality of Teaclave Java.
The above section covers technical aspects such as the Java confidential computing programming model provided by Teaclave Java and the adopted build methods. But what is the final outcome? This article examines the functional effectiveness of Teaclave Java by using the log4j vulnerability attack as an example.
In terms of TCB improvement and performance analysis at runtime, we prepared 10 tests, as shown in Table 1. The first four applications with "app-" as prefixes are simple applications written by ourselves, treating them as confidential programs and using their main entry as normal programs. The last six use cases with "ct-" as prefixes use unit tests of the well-known open-source encryption framework BouncyCastle. We provide a single entry point to invoke these tests, treating the entry point as a normal program and the unit tests as confidential programs.
The Java confidential computing framework compares OcclumJ and Teaclave Java. OcclumJ is a confidential computing model we implemented that bridges the gap between Occlum and Teaclave Java. It adopts the modular and service-oriented confidential computing model of Teaclave Java but does not utilize Java static compilation. Instead, it runs confidential computing services in Occlum mode within the TEE.
Figure 5 shows the principle of log4j vulnerability attacks (sub-figure a) and the principle of Teaclave Java's prevention of log4j vulnerability attacks (sub-figure b). In a typical Java application service, it interacts with the client through three steps:
With the server's private key, all communication between the server and the client appears to the attacker as plain text.
Figure 5-b demonstrates how Teaclave Java protects the application server from log4j vulnerability attacks. In Teaclave Java, the common code of the application is placed in the Rich Execution Environment (REE), while the security-sensitive decryption and private key are stored in the Trusted Execution Environment (TEE). When the client sends an encrypted message, it is transferred to the TEE for decryption by the proxy service in the REE. In the case of a log4j attack, the Log4j is deployed in the REE, and the malicious code can only run in the REE, unable to access the private key stored in the TEE memory, rendering the attack ineffective.
However, if the confidential code also uses log4j for logging and causes log4j to run in the TEE, log4j will download the attacker's malicious code into the TEE. Nevertheless, due to Teaclave Java's Java static compilation technology, the malicious code is unknown during compilation and will not be included in the native image. Furthermore, the Java static compilation technology does not support the dynamic loading and execution of code that does not exist in native images. Hence, even if the malicious classes are downloaded into the TEE, they will not be executed. Therefore, the confidential computing supported by Teaclave Java remains secure in this scenario. However, if the Occlum solution is used, as the JVM is available in the TEE, the malicious code can be dynamically loaded and executed, resulting in a successful attack.
Furthermore, even without SGX hardware for encrypting the TEE, the native image still functions as a native sandbox, making it difficult for malicious Java code to access security-sensitive content in the native memory.
Teaclave Java no longer requires Library Operating System (LibOS) and Java Virtual Machine (JVM), and the confidential code is compiled and deployed on demand. Although the OcclumJ scheme adopts a sub-module model, it does not perform static analysis, resulting in only a module-level code division. While this is an improvement over Occlum's lack of division, it still falls considerably short compared to Teaclave Java's function-level division. Figure 6 illustrates the size comparison of the binary compilation products of OcclumJ and Teaclave Java deployed in the TEE. The blue bar represents the result of OcclumJ, the orange bar represents the result of Teaclave Java, and "Lejacon" in the figure is the code name for Teaclave Java as mentioned in the paper.
(Figure 6: TCB comparison of Occlum and Teaclave Java schemes. Lejacon is the code name of Teaclave Java in the paper)
The comparison data in Figure 6 reveals that the compiled TCB size of Teaclave Java is approximately 1/20 to 1/10 of that of Occlum. Taking into account the expansion of native code during compilation, the actual difference in the number of functions between the two is even greater. As a result, the TCB of Teaclave Java is one order of magnitude lower than that of Occlum, leading to higher security.
The native image runs directly as native code, eliminating the cold start process of the Java program, including JVM startup, class loading, and interpretive execution steps. Consequently, the startup is extremely fast. If there is less confidential code to be executed, it will be executed quickly. However, the code compilation quality of the native image is not as good as the C2 compiler in JVM. Therefore, as the program execution time increases and the Java code is fully compiled by JIT (Just-In-Time) compilation, the runtime performance of the native image gradually approaches that of the Java program and may even be surpassed. Consequently, Teaclave Java demonstrates superior performance compared to OcclumJ in small-scale applications, but this advantage diminishes in long-term applications.
Figure 7 shows this feature. The blue line represents the execution time of the confidential code using the OcclumJ model, while the yellow line represents the execution time of the Teaclave Java model. The green line represents the execution time of the Teaclave Java model running in a normal environment on a standard JVM. The program's execution time in the TEE is longer than in the normal environment primarily due to the increased overhead of creating a confidential environment and allocating confidential memory, referred to collectively as the confidential environment overhead. In scenarios with a short execution time, the yellow line maintains performance close to that of the green line, indicating that the cold start overhead of Java programs is almost equal to the confidential environment overhead of native images. However, when the program execution time is long, the impact of cold start overhead diminishes, while the confidential environment overhead remains proportional to the TEE memory usage. Consequently, the yellow line becomes steeper than the green line in the last three test cases.
Figure 8 shows a comparison of the runtime memory usage between OcclumJ and Teaclave Java. The memory consumption of OcclumJ includes LibOS, JVM, and the application itself, whereas the memory consumption of the Teaclave Java model consists solely of the lightweight runtime within the application and the native image. The simplified structure of Teaclave Java results in lower memory consumption for confidential computing.
(Figure 7: Performance comparison chart between OcclumJ and Teaclave Java at runtime. (Lejacon is the code name of Teaclave Java in the paper)
(Figure 8: Memory consumption chart between OcclumJ and Teaclave Java at runtime. (Lejacon is the code name of Teaclave Java in the paper)
Teaclave Java is a Java confidential computing solution that is simple to use, effective, and offers high-performance. It is designed to protect security-sensitive content and computing in Java applications. Teaclave Java is hardware tolerant, meaning that when the SGX hardware environment is available, Java users can benefit from the highest level of runtime security protection, similar to users of other native languages. Even without a hardware environment for confidential computing, Teaclave Java can still provide security sandboxes to implement memory isolation for confidential code, ensuring that security-sensitive content is not directly exposed. In essence, Teaclave Java is the standard solution for safeguarding sensitive data and ensuring computing security in Java applications.
In the future, we intend to submit a document to the Java community proposing the addition of a confidential computing specification. Our goal is to elevate Teaclave Java's confidential computing model to a Java-native confidential computing solution.
The published paper is: Xinyuan Miao, Ziyi Lin, Shaojun Wang, Lei Yu, Sanhong Li, Zihan Wang, Pengbo Nie, Yuting Chen, Beijun Shen, He Jiang. Lejacon: A Lightweight and Efficient Approach to Java Confidential Computing on SGX. ICSE 2023.
Paper link: https://ddst.sjtu.edu.cn/Management/Upload/[News]a845acae286b470bb55013c1b5e425e2/20232101456536725sSV.pdf
The source code of the Teaclave Java project has been contributed to the Apache community.
Project link:
https://github.com/apache/incubator-teaclave-java-tee-sdk
Cloud-native confidential computing SIG home page of the OpenAnolis community:
https://openanolis.cn/sig/coco
OpenAnolis Whitepaper: A Smooth Migration Solution for CentOS
85 posts | 5 followers
FollowAlibaba Cloud Community - September 19, 2024
Alipay Technology - November 6, 2019
OpenAnolis - September 26, 2022
Data Geek - July 25, 2024
Alibaba Clouder - October 27, 2020
Alibaba Developer - March 28, 2019
85 posts | 5 followers
FollowAlibaba Cloud is committed to safeguarding the cloud security for every business.
Learn MoreAccelerate and secure the development, deployment, and management of containerized applications cost-effectively.
Learn MoreSimple, secure, and intelligent services.
Learn MoreThis solution helps you easily build a robust data security framework to safeguard your data assets throughout the data security lifecycle with ensured confidentiality, integrity, and availability of your data.
Learn MoreMore Posts by OpenAnolis