This is an automated email from the ASF dual-hosted git repository. shaojunwang pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-teaclave-java-tee-sdk.git
commit 7afe3a6d10eee8fb28149d9f34c26c0dba0e3d72 Author: jeffery.wsj <[email protected]> AuthorDate: Thu Jan 6 10:56:07 2022 +0800 [sdk] Implement JavaEnclave SDK fractional features of host-side Summary: 1. Define invocation interfaces between host-side and enclave-side. 2. Implement service load and invoking operation of the host-side. 3. Define and implement exceptions handling mechanism from the enclave-side. 4. Add a mock enclave for the unit test. Reviewers: lei.yul, cengfeng.lzy, sanhong.lsh Issue: https://aone.alibaba-inc.com/task/38948515 CR: https://code.aone.alibaba-inc.com/java-tee/JavaEnclave/codereview/7455358 --- sdk/{enclave => common}/pom.xml | 6 +- .../common/EnclaveInvocationContext.java | 70 ++++++ .../common/EnclaveInvocationResult.java | 41 ++++ .../common/SerializationHelper.java | 38 ++++ .../common/ServiceHandler.java | 57 +++++ sdk/enclave/pom.xml | 6 +- .../exception/ConfidentialComputingException.java | 8 + sdk/host/pom.xml | 79 ++++++- .../host/AbstractEnclave.java | 235 +++++++++++++++++++++ .../host/BaseEnclaveServicesRecycler.java | 18 ++ .../confidentialcomputing/host/Enclave.java | 4 +- .../host/EnclaveConfigure.java | 85 ++++++++ .../confidentialcomputing/host/EnclaveDebug.java | 29 +++ .../confidentialcomputing/host/EnclaveFactory.java | 4 +- .../host/EnclaveServicesRecycler.java | 62 ++++++ .../confidentialcomputing/host/EnclaveToken.java | 46 ++++ .../confidentialcomputing/host/ExtractLibrary.java | 34 +++ .../host/InnerNativeInvocationResult.java | 27 +++ .../host/MockInJvmEnclave.java | 32 +++ .../host/MockInSvmEnclave.java | 214 +++++++++++++++++++ .../host/ProxyEnclaveInvocationHandler.java | 87 ++++++++ .../confidentialcomputing/host/TeeSdkEnclave.java | 164 ++++++++++++++ .../exception/ConfidentialComputingException.java | 52 +++++ .../host/exception/EnclaveCreatingException.java | 12 +- .../host/exception/EnclaveDestroyingException.java | 12 +- .../exception/EnclaveMethodInvokingException.java | 30 +++ .../host/exception/ServicesLoadingException.java | 12 +- .../host/exception/ServicesUnloadingException.java | 30 +++ .../host/MockTestEnclave.java | 166 +++++++++++++++ .../confidentialcomputing/host/Service.java | 11 + .../host/ServiceExceptionTest.java | 7 + .../confidentialcomputing/host/ServiceImpl.java | 26 +++ .../host/TestAbstractEnclave.java | 46 ++++ .../host/TestEnclaveFactory.java | 16 ++ sdk/pom.xml | 28 ++- 35 files changed, 1776 insertions(+), 18 deletions(-) diff --git a/sdk/enclave/pom.xml b/sdk/common/pom.xml similarity index 95% copy from sdk/enclave/pom.xml copy to sdk/common/pom.xml index 7a81e07..1601dfb 100644 --- a/sdk/enclave/pom.xml +++ b/sdk/common/pom.xml @@ -8,9 +8,9 @@ <artifactId>JavaEnclave</artifactId> <version>0.1.0</version> </parent> - <artifactId>enclave</artifactId> + <artifactId>common</artifactId> <packaging>jar</packaging> - <name>JavaEnclave-Enclave</name> + <name>JavaEnclave-Common</name> <url></url> <build> <plugins> @@ -20,7 +20,7 @@ <version>0.8.3</version> <configuration> <includes> - <include>com/alibaba/confidentialcomputing/**/*</include> + <include>com/alibaba/confidentialcomputing/common/*</include> </includes> </configuration> <executions> diff --git a/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/EnclaveInvocationContext.java b/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/EnclaveInvocationContext.java new file mode 100644 index 0000000..9a1f3be --- /dev/null +++ b/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/EnclaveInvocationContext.java @@ -0,0 +1,70 @@ +package com.alibaba.confidentialcomputing.common; + +import java.io.Serializable; + +/** + * EnclaveInvocationInputMeta stores a method's necessary information for reflection + * call, include object's unique instanceIdentity、interface name、class name、method + * signature name, and its parameters. + */ +public final class EnclaveInvocationContext implements Serializable { + private static final long serialVersionUID = 6878585714134748604L; + + private final ServiceHandler serviceHandler; + private final String methodName; + private final String[] parameterTypes; + private final Object[] arguments; + + public EnclaveInvocationContext(ServiceHandler serviceHandler, + String methodName, + String[] parameterTypes, + Object[] arguments) { + this.serviceHandler = serviceHandler; + this.methodName = methodName; + this.parameterTypes = parameterTypes; + this.arguments = arguments; + } + + public EnclaveInvocationContext(ServiceHandler serviceHandler) { + this.methodName = null; + this.parameterTypes = null; + this.arguments = null; + this.serviceHandler = serviceHandler; + } + + /** + * get service handler. + * + * @return service handler. + */ + public ServiceHandler getServiceHandler() { + return serviceHandler; + } + + /** + * get the method's name. + * + * @return method's name. + */ + public String getMethodName() { + return methodName; + } + + /** + * get all parameters' type. + * + * @return parameters' type information. + */ + public String[] getParameterTypes() { + return parameterTypes; + } + + /** + * get all arguments' value. + * + * @return arguments' value information. + */ + public Object[] getArguments() { + return arguments; + } +} diff --git a/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/EnclaveInvocationResult.java b/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/EnclaveInvocationResult.java new file mode 100644 index 0000000..b664304 --- /dev/null +++ b/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/EnclaveInvocationResult.java @@ -0,0 +1,41 @@ +package com.alibaba.confidentialcomputing.common; + +import java.io.Serializable; + +/** + * EnclaveInvocationResult is a service's method invoking return value in an enclave. + * If an exception happened during the invocation, the exception will be stored in the + * EnclaveInvocationResult object's exception field, the result field will be null. + * If no exception happened during the invocation, the method invoking return value is + * stored in the EnclaveInvocationResult object's result field, and the exception field + * will be null. + */ +public final class EnclaveInvocationResult implements Serializable { + private static final long serialVersionUID = -571664787738930979L; + + private final Object resultedValue; + private final Throwable exception; + + public EnclaveInvocationResult(Object result, Throwable exception) { + this.resultedValue = result; + this.exception = exception; + } + + /** + * get method's return value. + * + * @return method's return value. + */ + public Object getResult() { + return this.resultedValue; + } + + /** + * get exception during method's invocation. + * + * @return exception during method's invocation if it has. + */ + public Throwable getException() { + return this.exception; + } +} \ No newline at end of file diff --git a/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/SerializationHelper.java b/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/SerializationHelper.java new file mode 100644 index 0000000..5b678ab --- /dev/null +++ b/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/SerializationHelper.java @@ -0,0 +1,38 @@ +package com.alibaba.confidentialcomputing.common; + +import java.io.IOException; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +/** + * SerializationHelper is a helper class to process Object's serialization and deserialization. + */ +public final class SerializationHelper { + /** + * serialization helper method. + * + * @return serialization result. + * @throws IOException {@link IOException} If serialization failed. + */ + public static byte[] serialize(Object data) throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + oos.writeObject(data); + return bos.toByteArray(); + } + + /** + * deserialization helper method. + * + * @return deserialization result. + * @throws IOException {@link IOException} If deserialization failed. + * @throws ClassNotFoundException {@link ClassNotFoundException} If deserialization failed. + */ + public static Object deserialize(byte[] data) throws IOException, ClassNotFoundException { + ByteArrayInputStream inputStream = new ByteArrayInputStream(data); + ObjectInputStream ois = new ObjectInputStream(inputStream); + return ois.readObject(); + } +} \ No newline at end of file diff --git a/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/ServiceHandler.java b/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/ServiceHandler.java new file mode 100644 index 0000000..b81632e --- /dev/null +++ b/sdk/common/src/main/java/com/alibaba/confidentialcomputing/common/ServiceHandler.java @@ -0,0 +1,57 @@ +package com.alibaba.confidentialcomputing.common; + +import java.io.Serializable; + +/** + * ServiceHandler is a handler object in host to the real service loaded in the enclave. + * A ServiceHandler object will be bound to a host proxy handler. + */ +public final class ServiceHandler implements Serializable { + private static final long serialVersionUID = -879933256236932801L; + + // instanceIdentity indicates the global unique service object's index in one enclave. + private final String instanceIdentity; + // serviceImplClass stores loaded service object's full signature in the enclave. + private final String serviceImplClass; + // serviceInterface stores loaded service object's implement interface's full signature in the enclave. + private final String serviceInterface; + + public ServiceHandler(String serviceInterfaceName, String serviceClassName, String instanceIdentity) { + this.serviceInterface = serviceInterfaceName; + this.serviceImplClass = serviceClassName; + this.instanceIdentity = instanceIdentity; + } + + public ServiceHandler(String interfaceName) { + this.instanceIdentity = null; + this.serviceImplClass = null; + this.serviceInterface = interfaceName; + } + + /** + * get service's unique identity. + * + * @return service's unique identity. + */ + public String getInstanceIdentity() { + return this.instanceIdentity; + } + + /** + * get service's interface name. + * + * @return service's interface name. + */ + public String getServiceInterfaceName() { + return this.serviceInterface; + } + + /** + * get service's class name. + * + * @return service's class name. + */ + public String getServiceImplClassName() { + return this.serviceImplClass; + } +} \ No newline at end of file diff --git a/sdk/enclave/pom.xml b/sdk/enclave/pom.xml index 7a81e07..9b47431 100644 --- a/sdk/enclave/pom.xml +++ b/sdk/enclave/pom.xml @@ -20,7 +20,7 @@ <version>0.8.3</version> <configuration> <includes> - <include>com/alibaba/confidentialcomputing/**/*</include> + <include>com/alibaba/confidentialcomputing/enclave/**/*</include> </includes> </configuration> <executions> @@ -57,5 +57,9 @@ <artifactId>junit-jupiter-engine</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>com.alibaba.confidentialcomputing</groupId> + <artifactId>common</artifactId> + </dependency> </dependencies> </project> \ No newline at end of file diff --git a/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/exception/ConfidentialComputingException.java b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/exception/ConfidentialComputingException.java index 51b4c93..f525dcf 100644 --- a/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/exception/ConfidentialComputingException.java +++ b/sdk/enclave/src/main/java/com/alibaba/confidentialcomputing/enclave/exception/ConfidentialComputingException.java @@ -20,4 +20,12 @@ public class ConfidentialComputingException extends Exception { public ConfidentialComputingException(Throwable e) { super(e); } + + /** + * @param info exception information. + * @param e exception. + */ + public ConfidentialComputingException(String info, Throwable e) { + super(info, e); + } } \ No newline at end of file diff --git a/sdk/host/pom.xml b/sdk/host/pom.xml index d017234..d2f5fc2 100644 --- a/sdk/host/pom.xml +++ b/sdk/host/pom.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.alibaba.confidentialcomputing</groupId> <parent> @@ -13,8 +14,82 @@ <url></url> <build> <plugins> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <version>0.8.3</version> + <configuration> + <includes> + <include>com/alibaba/confidentialcomputing/host/*</include> + <include>com/alibaba/confidentialcomputing/host/**/*</include> + </includes> + </configuration> + <executions> + <execution> + <id>pre-test</id> + <goals> + <goal>prepare-agent</goal> + </goals> + </execution> + <execution> + <id>post-test</id> + <phase>test</phase> + <goals> + <goal>report</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <version>3.0.0-M5</version> + </plugin> </plugins> </build> <dependencies> + <dependency> + <groupId>com.alibaba.confidentialcomputing</groupId> + <artifactId>common</artifactId> + </dependency> + <dependency> + <groupId>com.google.auto.service</groupId> + <artifactId>auto-service-annotations</artifactId> + <version>1.0-rc6</version> + <optional>true</optional> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.google.auto.service</groupId> + <artifactId>auto-service</artifactId> + <version>1.0-rc6</version> + <optional>true</optional> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.platform</groupId> + <artifactId>junit-platform-engine</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit-pioneer</groupId> + <artifactId>junit-pioneer</artifactId> + <scope>test</scope> + </dependency> </dependencies> -</project> \ No newline at end of file +</project> diff --git a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/AbstractEnclave.java b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/AbstractEnclave.java new file mode 100644 index 0000000..37c87a9 --- /dev/null +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/AbstractEnclave.java @@ -0,0 +1,235 @@ +package com.alibaba.confidentialcomputing.host; + +import java.io.IOException; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.ServiceLoader; + +import com.alibaba.confidentialcomputing.common.EnclaveInvocationResult; +import com.alibaba.confidentialcomputing.common.EnclaveInvocationContext; +import com.alibaba.confidentialcomputing.common.SerializationHelper; +import com.alibaba.confidentialcomputing.common.ServiceHandler; +import com.alibaba.confidentialcomputing.host.exception.EnclaveCreatingException; +import com.alibaba.confidentialcomputing.host.exception.EnclaveMethodInvokingException; +import com.alibaba.confidentialcomputing.host.exception.ServicesLoadingException; +import com.alibaba.confidentialcomputing.host.exception.ServicesUnloadingException; + +/** + * AbstractEnclave implements all kinds of enclave platform's common operation. + * Such as service loading、unloading and service method invocation. + * But the underlying native implements is very different between these enclave platforms, + * so enclave will implement these native calls in specific enclave platform. + */ +abstract class AbstractEnclave implements Enclave { + private final EnclaveContext enclaveContext; + + AbstractEnclave(EnclaveType type, + EnclaveDebug mode, + BaseEnclaveServicesRecycler recycler) throws EnclaveCreatingException { + if (type == EnclaveType.TEE_SDK && mode == EnclaveDebug.NONE) { + throw new EnclaveCreatingException("TEE SDK enclave's debug mode must be RELEASE or DEBUG."); + } + enclaveContext = new EnclaveContext(type, mode, recycler); + } + + AbstractEnclave(EnclaveType type, BaseEnclaveServicesRecycler recycler) { + enclaveContext = new EnclaveContext(type, EnclaveDebug.NONE, recycler); + } + + EnclaveContext getEnclaveContext() { + return enclaveContext; + } + + abstract InnerNativeInvocationResult loadServiceNative(byte[] payload); + + abstract InnerNativeInvocationResult unloadServiceNative(byte[] payload); + + abstract InnerNativeInvocationResult invokeMethodNative(byte[] payload); + + // load service by interface name. + ServiceHandler[] loadService(Class<?> service) throws ServicesLoadingException { + if (!getEnclaveContext().getEnclaveToken().tryAcquireToken()) { + throw new ServicesLoadingException("enclave was destroyed."); + } + try { + // Only need to provide service's interface name is enough to load service + // in enclave. + EnclaveInvocationContext parasWrapper = new EnclaveInvocationContext( + new ServiceHandler(service.getName())); + byte[] payload; + try { + payload = SerializationHelper.serialize(parasWrapper); + } catch (IOException e) { + throw new ServicesLoadingException("EnclaveInvokeMetaWrapper serialization failed.", e); + } + InnerNativeInvocationResult resultNativeWrapper = loadServiceNative(payload); + // If loadServiceNative native call return value is error, an ServicesLoadingException exception + // will be thrown. + if (resultNativeWrapper.getRet() != 0) { + throw new ServicesLoadingException("load service native call failed."); + } + EnclaveInvocationResult resultWrapper; + try { + resultWrapper = (EnclaveInvocationResult) SerializationHelper.deserialize(resultNativeWrapper.getPayload()); + } catch (IOException | ClassNotFoundException e) { + throw new ServicesLoadingException("EnclaveInvokeResultWrapper deserialization failed.", e); + } + Throwable exception = resultWrapper.getException(); + Object result = resultWrapper.getResult(); + // this exception is transformed from enclave, so throw it and handle it in proxy handler. + if (exception != null) { + throw new ServicesLoadingException("service load exception happened in enclave.", exception); + } + // result should never be null, at least it should have an empty ServiceMirror type array. + if (result == null) { + throw new ServicesLoadingException("service load with no any result."); + } + if (!(result instanceof ServiceHandler[])) { + throw new ServicesLoadingException("service load return type is not ServiceHandler[]."); + } + return (ServiceHandler[]) result; + } finally { + getEnclaveContext().getEnclaveToken().restoreToken(); + } + } + + // unload service by interface name、class name and service's unique identity in enclave. + // it was called if the service handler was recycled by gc in host side. + void unloadService(ServiceHandler service) throws ServicesUnloadingException { + if (!getEnclaveContext().getEnclaveToken().tryAcquireToken()) { + throw new ServicesUnloadingException("enclave was destroyed."); + } + try { + EnclaveInvocationContext parasWrapper = new EnclaveInvocationContext(service); + byte[] payload; + try { + payload = SerializationHelper.serialize(parasWrapper); + } catch (IOException e) { + throw new ServicesUnloadingException("EnclaveInvokeMetaWrapper serialization failed.", e); + } + InnerNativeInvocationResult resultNativeWrapper = unloadServiceNative(payload); + if (resultNativeWrapper.getRet() != 0) { + throw new ServicesUnloadingException("unload service native call failed."); + } + EnclaveInvocationResult resultWrapper; + try { + resultWrapper = (EnclaveInvocationResult) SerializationHelper.deserialize(resultNativeWrapper.getPayload()); + } catch (IOException | ClassNotFoundException e) { + throw new ServicesUnloadingException("EnclaveInvokeResultWrapper deserialization failed.", e); + } + Throwable exception = resultWrapper.getException(); + if (exception != null) { + throw new ServicesUnloadingException("service unload exception happened in enclave.", exception); + } + } finally { + getEnclaveContext().getEnclaveToken().restoreToken(); + } + } + + // it was called in service's proxy handler. + Object InvokeEnclaveMethod(EnclaveInvocationContext input) throws EnclaveMethodInvokingException { + if (!getEnclaveContext().getEnclaveToken().tryAcquireToken()) { + throw new EnclaveMethodInvokingException("enclave was destroyed."); + } + try { + byte[] payload; + try { + payload = SerializationHelper.serialize(input); + } catch (IOException e) { + throw new EnclaveMethodInvokingException("EnclaveInvokeMetaWrapper serialization failed.", e); + } + InnerNativeInvocationResult resultNativeWrapper = invokeMethodNative(payload); + if (resultNativeWrapper.getRet() != 0) { + throw new EnclaveMethodInvokingException("method invoke native call failed."); + } + EnclaveInvocationResult resultWrapper; + try { + resultWrapper = (EnclaveInvocationResult) SerializationHelper.deserialize(resultNativeWrapper.getPayload()); + } catch (IOException | ClassNotFoundException e) { + throw new EnclaveMethodInvokingException("EnclaveInvokeResultWrapper deserialization failed.", e); + } + Throwable exception = resultWrapper.getException(); + Object result = resultWrapper.getResult(); + if (exception != null) { + EnclaveMethodInvokingException e = new EnclaveMethodInvokingException("method invoke exception happened in enclave."); + e.initCause(exception); + throw e; + } + return result; + } finally { + getEnclaveContext().getEnclaveToken().restoreToken(); + } + } + + @Override + public <T> Iterator<T> load(Class<T> service) throws ServicesLoadingException { + // Check service must be an interface class. + if (!service.isInterface()) { + throw new ServicesLoadingException("service type: " + service.getTypeName() + " is not an interface type."); + } + + // If enclave type is MOCK_IN_JVM, loading services by JDK SPI mechanism directly. + if (enclaveContext.getEnclaveType() == EnclaveType.MOCK_IN_JVM) { + ServiceLoader<T> loader = ServiceLoader.load(service); + return loader.iterator(); + } + + // Loading services in enclave and creating proxy for them. + Class<?>[] serviceInterface = new Class[1]; + serviceInterface[0] = service; + + List<T> serviceProxies = new ArrayList<T>(); + ServiceHandler[] services = loadService(service); + for (ServiceHandler serviceHandler : services) { + ProxyEnclaveInvocationHandler handler = new ProxyEnclaveInvocationHandler(this, serviceHandler); + T proxy = (T) Proxy.newProxyInstance(service.getClassLoader(), serviceInterface, handler); + serviceProxies.add(proxy); + // Register proxy handler for enclave's corresponding service gc recycling. + enclaveContext.getEnclaveServicesRecycler().registerProxyHandler(handler); + } + return serviceProxies.iterator(); + } + + /** + * EnclaveContext cache an enclave's common information, such as + * enclave type and debug mode, and each enclave instance has a service + * resource recycle processor. + */ + class EnclaveContext { + // enclave's type. + private final EnclaveType type; + // enclave's debug mode. + private final EnclaveDebug mode; + // every enclave has a services' recycler. + private final BaseEnclaveServicesRecycler enclaveServicesRecycler; + // every enclave has an enclave token. + private final EnclaveToken enclaveToken; + + EnclaveContext(EnclaveType type, + EnclaveDebug mode, + BaseEnclaveServicesRecycler recycler) { + this.type = type; + this.mode = mode; + this.enclaveServicesRecycler = recycler; + this.enclaveToken = new EnclaveToken(); + } + + EnclaveType getEnclaveType() { + return type; + } + + EnclaveDebug getEnclaveDebugMode() { + return mode; + } + + BaseEnclaveServicesRecycler getEnclaveServicesRecycler() { + return enclaveServicesRecycler; + } + + EnclaveToken getEnclaveToken() { + return enclaveToken; + } + } +} diff --git a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/BaseEnclaveServicesRecycler.java b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/BaseEnclaveServicesRecycler.java new file mode 100644 index 0000000..0b51196 --- /dev/null +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/BaseEnclaveServicesRecycler.java @@ -0,0 +1,18 @@ +package com.alibaba.confidentialcomputing.host; + +/** + * BaseEnclaveServicesRecycler an empty enclave services recycler for MOCK_IN_JVM enclave. + */ +class BaseEnclaveServicesRecycler { + BaseEnclaveServicesRecycler() { + } + + void enqueueProxyHandler(ProxyEnclaveInvocationHandler handler) { + } + + void registerProxyHandler(ProxyEnclaveInvocationHandler handler) { + } + + void interruptServiceRecycler() { + } +} diff --git a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/Enclave.java b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/Enclave.java index e63541f..1baf92a 100644 --- a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/Enclave.java +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/Enclave.java @@ -58,7 +58,7 @@ public interface Enclave { /** * Returns all providers which implement service interface. It's similar to SPI - * ServiceLoader mechanism. It returns proxy providers which are mirrors to real + * ServiceLoader mechanism. It returns proxy providers which are handlers to real * services loaded in enclave. * <p> * @@ -66,7 +66,7 @@ public interface Enclave { * @param service Must be a service interface * @return An iterator of providers were discovered. * @throws ServicesLoadingException {@link ServicesLoadingException} If proxy providers created - * failed or mirrors services loaded failed in enclave. + * failed or service handlers loaded failed in enclave. */ <T> Iterator<T> load(Class<T> service) throws ServicesLoadingException; diff --git a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/EnclaveConfigure.java b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/EnclaveConfigure.java new file mode 100644 index 0000000..208e4de --- /dev/null +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/EnclaveConfigure.java @@ -0,0 +1,85 @@ +package com.alibaba.confidentialcomputing.host; + +import com.alibaba.confidentialcomputing.host.exception.EnclaveCreatingException; + +/** + * EnclaveConfigure decides a new created enclave's type and debug mode. + * If user creates an enclave with specific enclave type, that it is no + * matter what system variable is. If user creates an enclave with no + * specific enclave type, system variable is adapted to decide enclave's + * type and debug mode. Default enclave type is TEE_SDK and debug mode + * is RELEASE. + */ +class EnclaveConfigure { + private static final String ENCLAVE_TYPE = "com.alibaba.enclave.type"; + private static final String ENCLAVE_DEBUG = "com.alibaba.enclave.debug"; + private static final EnclaveType enclaveType; + private static final EnclaveDebug enclaveDebug; + + static { + // Three kinds of enclave is supported, TEE_SDK/MOCK_IN_JVM/MOCK_IN_SVM + String platform = System.getProperty(ENCLAVE_TYPE); + String mode = System.getProperty(ENCLAVE_DEBUG); + if (platform != null) { + switch (platform) { + case "TEE_SDK": + enclaveType = EnclaveType.TEE_SDK; + break; + case "MOCK_IN_JVM": + enclaveType = EnclaveType.MOCK_IN_JVM; + break; + case "MOCK_IN_SVM": + enclaveType = EnclaveType.MOCK_IN_SVM; + break; + case "NONE": + default: + enclaveType = EnclaveType.NONE; + } + } else { + // Default enclave type is tee sdk. + enclaveType = EnclaveType.TEE_SDK; + } + + if (mode != null) { + // Three kinds of enclave debug mode is supported, DEBUG/RELEASE + // If TEE_SDK enclave is created as RELEASE mode, it can't be debugged + // with GDB tool. + switch (mode) { + case "DEBUG": + enclaveDebug = EnclaveDebug.DEBUG; + break; + case "RELEASE": + enclaveDebug = EnclaveDebug.RELEASE; + break; + case "NONE": + default: + enclaveDebug = EnclaveDebug.NONE; + } + } else { + // Default debug mode is release. + enclaveDebug = EnclaveDebug.RELEASE; + } + } + + // create an enclave without specific enclave type. + // if -Dcom.alibaba.enclave.type is not set, TEE_SDK + // type enclave will be created. + static Enclave create() throws EnclaveCreatingException { + return create(enclaveType); + } + + // create an enclave with specific enclave type. + static Enclave create(EnclaveType type) throws EnclaveCreatingException { + switch (type) { + case MOCK_IN_JVM: + return new MockInJvmEnclave(); + case MOCK_IN_SVM: + return new MockInSvmEnclave(); + case TEE_SDK: + return new TeeSdkEnclave(enclaveDebug); + case NONE: + default: + throw new EnclaveCreatingException("enclave type is not supported."); + } + } +} diff --git a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/EnclaveDebug.java b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/EnclaveDebug.java new file mode 100644 index 0000000..bf8ab83 --- /dev/null +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/EnclaveDebug.java @@ -0,0 +1,29 @@ +package com.alibaba.confidentialcomputing.host; + +/** + * An enumeration of enclave debug mode. + */ +enum EnclaveDebug { + /** + * For MOCK_IN_JVM and MOCK_IN_SVM, there is no real enclave environment. + */ + NONE(0), + /** + * TEE_SDK could debug by gdb tool in this mode. + */ + DEBUG(1), + /** + * TEE_SDK could not debug by gdb tool in this mode. + */ + RELEASE(2); + + private final int value; + + EnclaveDebug(int value) { + this.value = value; + } + + int getValue() { + return value; + } +} diff --git a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/EnclaveFactory.java b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/EnclaveFactory.java index 9999de6..7df10f6 100644 --- a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/EnclaveFactory.java +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/EnclaveFactory.java @@ -33,7 +33,7 @@ public final class EnclaveFactory { * create failed. */ public static Enclave create() throws EnclaveCreatingException { - return null; + return EnclaveConfigure.create(); } /** @@ -43,6 +43,6 @@ public final class EnclaveFactory { * create failed. */ public static Enclave create(EnclaveType type) throws EnclaveCreatingException { - return null; + return EnclaveConfigure.create(type); } } \ No newline at end of file diff --git a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/EnclaveServicesRecycler.java b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/EnclaveServicesRecycler.java new file mode 100644 index 0000000..13de12b --- /dev/null +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/EnclaveServicesRecycler.java @@ -0,0 +1,62 @@ +package com.alibaba.confidentialcomputing.host; + +import com.alibaba.confidentialcomputing.host.exception.ServicesUnloadingException; + +import java.lang.ref.Cleaner; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * EnclaveServicesRecycler is responsible for an enclave's services resource recycling. + * If a service handler in host side was recycled by gc, EnclaveServicesRecycle will help + * to recycle the corresponding service loaded in enclave side. + * EnclaveServicesRecycle starts a new thread to recycle enclave's services asynchronously. + */ +class EnclaveServicesRecycler extends BaseEnclaveServicesRecycler { + private final Cleaner cleaner = Cleaner.create(); + // toBeReleasedEnclaveServices stores a service proxy handler when it's recycled + // by gc in host side. + private final BlockingQueue<ProxyEnclaveInvocationHandler> toBeReleasedEnclaveServices = new LinkedBlockingQueue<>(); + private final Thread recyclerThread; + + EnclaveServicesRecycler() { + recyclerThread = new Thread(() -> { + while (!Thread.interrupted()) { + try { + ProxyEnclaveInvocationHandler proxyHandler = toBeReleasedEnclaveServices.take(); + proxyHandler.getEnclave().unloadService(proxyHandler.getServiceHandler()); + } catch (InterruptedException e) { + break; // Recycle Thread should exit when enclave destroyed. + } catch (ServicesUnloadingException e) { + // Have to handle this exception locally, print to log later. + e.printStackTrace(); + } + } + }); + recyclerThread.setDaemon(true); + recyclerThread.start(); + } + + // enqueue the recycled proxy handler object of a service handler. + @Override + void enqueueProxyHandler(ProxyEnclaveInvocationHandler handler) { + try { + toBeReleasedEnclaveServices.add(handler); + } catch (IllegalStateException | ClassCastException | NullPointerException | IllegalArgumentException e) { + // Have to handle this exception locally. + e.printStackTrace(); + } + } + + // register service's proxy handler when it's created. + @Override + void registerProxyHandler(ProxyEnclaveInvocationHandler handler) { + cleaner.register(handler, handler); + } + + // interrupt enclave services' recycler thread exit. + @Override + void interruptServiceRecycler() { + recyclerThread.interrupt(); + } +} diff --git a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/EnclaveToken.java b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/EnclaveToken.java new file mode 100644 index 0000000..7d54dc0 --- /dev/null +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/EnclaveToken.java @@ -0,0 +1,46 @@ +package com.alibaba.confidentialcomputing.host; + +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * EnclaveToken avoids an enclave's invocation happen when it's being destroyed. + */ +class EnclaveToken { + private volatile AtomicBoolean alive = new AtomicBoolean(true); + private final int MAX_CONCURRENCY_INVOKER = 999999; + private final Semaphore tokens = new Semaphore(MAX_CONCURRENCY_INVOKER); + + /** + * tryAcquireToken try to get an enclave's token. + */ + boolean tryAcquireToken() { + if (alive.get()) { + return tokens.tryAcquire(); + } + return false; + } + + /** + * restoreToken restores the enclave token. + */ + void restoreToken() { + tokens.release(); + } + + /** + * destroyToken prevents an enclave invocation and waits for all + * ongoing enclave invocations finished. + */ + boolean destroyToken() { + if (alive.compareAndSet(true, false)) { + try { + tokens.acquire(MAX_CONCURRENCY_INVOKER); + } catch (InterruptedException e) { + ; // Should never happen, do nothing here. + } + return true; + } + return false; + } +} diff --git a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/ExtractLibrary.java b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/ExtractLibrary.java new file mode 100644 index 0000000..84fc41b --- /dev/null +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/ExtractLibrary.java @@ -0,0 +1,34 @@ +package com.alibaba.confidentialcomputing.host; + +import java.io.*; + +/** + * JavaEnclave building tool will put native .so files into a java .jar file, + * ExtractLibrary will extracts tee sdk's jni .so and enclave signed .so into + * a temp path from the jar file. it's very convenient for deployment. + */ +public final class ExtractLibrary { + /** + * get the temp file's full path. + * + * @param classLoader define the search scope for lib .so. + * @param name lib.so's name in the jar file. + * @return the temp file's full path. + */ + public static String extractLibrary(ClassLoader classLoader, String name) throws IOException { + int pos = name.lastIndexOf('.'); + File file = File.createTempFile(name.substring(0, pos), name.substring(pos)); + String fullPath = file.getAbsolutePath(); + try (InputStream in = classLoader.getResourceAsStream(name); + OutputStream out = new FileOutputStream(file)) { + byte[] buf = new byte[4096]; + int length; + while ((length = in.read(buf)) > 0) { + out.write(buf, 0, length); + } + } finally { + file.deleteOnExit(); + } + return fullPath; + } +} diff --git a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/InnerNativeInvocationResult.java b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/InnerNativeInvocationResult.java new file mode 100644 index 0000000..6e8e240 --- /dev/null +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/InnerNativeInvocationResult.java @@ -0,0 +1,27 @@ +package com.alibaba.confidentialcomputing.host; + +/** + * InnerNativeInvocationResult is load_service unload_service and invoke_method + * native call's return value. It not only contains enclave e_call's return value, + * also contains an EnclaveInvocationResult object's serialization payload from + * method invocation in enclave. + */ +class InnerNativeInvocationResult { + // enclave method native call's result. + private final int ret; + // payload is an EnclaveInvocationResult object's serialization data. + private final byte[] payload; + + InnerNativeInvocationResult(int ret, byte[] payload) { + this.ret = ret; + this.payload = payload; + } + + int getRet() { + return ret; + } + + byte[] getPayload() { + return payload; + } +} diff --git a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/MockInJvmEnclave.java b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/MockInJvmEnclave.java new file mode 100644 index 0000000..faa0a87 --- /dev/null +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/MockInJvmEnclave.java @@ -0,0 +1,32 @@ +package com.alibaba.confidentialcomputing.host; + +/** + * MockInJvmEnclave is a mock jvm enclave. Both host and enclave codes run + * in one jvm. It was used for test and debug. + */ +class MockInJvmEnclave extends AbstractEnclave { + MockInJvmEnclave() { + // Set EnclaveContext for this enclave instance. + super(EnclaveType.MOCK_IN_JVM, new BaseEnclaveServicesRecycler()); + } + + @Override + InnerNativeInvocationResult loadServiceNative(byte[] payload) { + return null; + } + + @Override + InnerNativeInvocationResult unloadServiceNative(byte[] payload) { + return null; + } + + @Override + InnerNativeInvocationResult invokeMethodNative(byte[] payload) { + return null; + } + + @Override + public void destroy() { + ; // Do nothing here. + } +} diff --git a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/MockInSvmEnclave.java b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/MockInSvmEnclave.java new file mode 100644 index 0000000..84e4618 --- /dev/null +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/MockInSvmEnclave.java @@ -0,0 +1,214 @@ +package com.alibaba.confidentialcomputing.host; + +import com.alibaba.confidentialcomputing.host.exception.EnclaveCreatingException; +import com.alibaba.confidentialcomputing.host.exception.EnclaveDestroyingException; + +import java.io.IOException; + +/** + * MockInSvmEnclave is a mock svm enclave. Host part code runs in jvm and enclave + * part code was compiled into native image, we could view enclave part as a service + * provider, and all service invocation will be implemented in enclave by reflection. + * The work mechanism in this mode is very closed to tee sdk enclave, so it's very + * important to debug issue. + */ +class MockInSvmEnclave extends AbstractEnclave { + private final static String JNI_EXTRACTED_PACKAGE_PATH = "jni/lib_jni_mock_svm.so"; + private final static String ENCLAVE_SVM_WRAPPER_PACKAGE_PATH = "libs/lib_enclave_mock_svm_wrapper.so"; + private final static String ENCLAVE_SVM_PACKAGE_PATH = "libs/lib_svm_sdk.so"; + private static volatile MockInSvmExtractTempPath extractTempPath; + private final EnclaveNativeContextCache nativeHandlerContext = new EnclaveNativeContextCache( + 0, 0, 0, 0); + + MockInSvmEnclave() throws EnclaveCreatingException { + // Set EnclaveContext for this enclave instance. + super(EnclaveType.MOCK_IN_SVM, new EnclaveServicesRecycler()); + + // Extract jni .so and svm sdk .so from .jar file. + if (extractTempPath == null) { + synchronized (MockInSvmEnclave.class) { + if (extractTempPath == null) { + try { + String jniTempFilePath = ExtractLibrary.extractLibrary( + MockInSvmEnclave.class.getClassLoader(), + JNI_EXTRACTED_PACKAGE_PATH); + String enclaveWrapperFilePath = ExtractLibrary.extractLibrary( + MockInSvmEnclave.class.getClassLoader(), + ENCLAVE_SVM_WRAPPER_PACKAGE_PATH); + String enclaveSvmFilePath = ExtractLibrary.extractLibrary( + MockInSvmEnclave.class.getClassLoader(), + ENCLAVE_SVM_PACKAGE_PATH); + extractTempPath = new MockInSvmEnclave.MockInSvmExtractTempPath( + jniTempFilePath, + enclaveWrapperFilePath, + enclaveSvmFilePath); + System.load(jniTempFilePath); + } catch (IOException e) { + throw new EnclaveCreatingException("extracting tee sdk jni .so or signed .so failed.", e); + } + } + } + } + + // Create svm sdk enclave by native call, enclaveWrapperHandle and enclaveSvmSdkHandle are set in jni in nativeHandlerContext. + int ret = nativeCreateEnclave(extractTempPath.getJniTempFilePath()); + if (ret != 0) { + throw new EnclaveCreatingException("create svm sdk enclave by native calling failed."); + } + // Create svm attach isolate and isolateThread, and they are set in jni in nativeHandlerContext. + ret = nativeSvmAttachIsolate(nativeHandlerContext.getEnclaveWrapperHandle(), + nativeHandlerContext.getEnclaveSvmSdkHandle()); + if (ret != 0) { + throw new EnclaveCreatingException("create svm isolate by native calling failed."); + } + } + + @Override + InnerNativeInvocationResult loadServiceNative(byte[] payload) { + return nativeLoadService( + nativeHandlerContext.getEnclaveWrapperHandle(), + nativeHandlerContext.getEnclaveSvmSdkHandle(), + nativeHandlerContext.getIsolateHandle(), + payload); + } + + @Override + InnerNativeInvocationResult unloadServiceNative(byte[] payload) { + return nativeUnloadService( + nativeHandlerContext.getEnclaveWrapperHandle(), + nativeHandlerContext.getEnclaveSvmSdkHandle(), + nativeHandlerContext.getIsolateHandle(), + payload); + } + + @Override + InnerNativeInvocationResult invokeMethodNative(byte[] payload) { + return nativeInvokeMethod( + nativeHandlerContext.getEnclaveWrapperHandle(), + nativeHandlerContext.getEnclaveSvmSdkHandle(), + nativeHandlerContext.getIsolateHandle(), + payload); + } + + @Override + public void destroy() throws EnclaveDestroyingException { + // destroyToken will wait for all ongoing enclave invocations finished. + if (this.getEnclaveContext().getEnclaveToken().destroyToken()) { + // interrupt enclave services' recycler firstly. + this.getEnclaveContext().getEnclaveServicesRecycler().interruptServiceRecycler(); + // destroy svm isolate. + int ret = nativeSvmDetachIsolate( + nativeHandlerContext.getEnclaveWrapperHandle(), + nativeHandlerContext.getEnclaveSvmSdkHandle(), + nativeHandlerContext.getIsolateThreadHandle()); + if (ret != 0) { + throw new EnclaveDestroyingException("isolate destroy native call failed."); + } + ret = nativeDestroyEnclave( + nativeHandlerContext.getEnclaveWrapperHandle(), + nativeHandlerContext.getEnclaveSvmSdkHandle()); + if (ret != 0) { + throw new EnclaveDestroyingException("enclave destroy native call failed."); + } + } + + } + + private native int nativeCreateEnclave(String path); + + private native int nativeSvmAttachIsolate( + long enclaveWrapperHandle, + long enclaveSvmSdkHandle); + + private native InnerNativeInvocationResult nativeLoadService( + long enclaveWrapperHandle, + long enclaveSvmSdkHandle, + long isolateHandler, + byte[] serviceHandler); + + private native InnerNativeInvocationResult nativeInvokeMethod( + long enclaveWrapperHandle, + long enclaveSvmSdkHandle, + long isolateHandler, + byte[] enclaveInvokeMetaWrapper); + + private native InnerNativeInvocationResult nativeUnloadService( + long enclaveWrapperHandle, + long enclaveSvmSdkHandle, + long isolateHandler, + byte[] serviceHandler); + + private native int nativeSvmDetachIsolate( + long enclaveWrapperHandle, + long enclaveSvmSdkHandle, + long isolateThreadHandler); + + private native int nativeDestroyEnclave( + long enclaveWrapperHandle, + long enclaveSvmSdkHandle); + + /** + * JavaEnclave will create svm isolate handle and isolateThread handle by native call, + * so EnclaveNativeContextCache will cache them for usage. + */ + class EnclaveNativeContextCache { + // enclaveHandle stores created enclave wrap .so file handler. + private final long enclaveWrapperHandle; + // enclaveHandle stores created enclave svm sdk .so file handler. + private final long enclaveSvmSdkHandle; + // isolate stores svm created isolate instance. + // In JavaEnclave only one isolateHandle instance will be created. + private final long isolateHandle; + // isolateThreadHandle stores the first attached isolateThread Handle. + private final long isolateThreadHandle; + + EnclaveNativeContextCache( + long enclaveWrapperHandle, long enclaveSvmSdkHandle, + long isolateHandle, long isolateThreadHandle) { + this.enclaveWrapperHandle = enclaveWrapperHandle; + this.enclaveSvmSdkHandle = enclaveSvmSdkHandle; + this.isolateHandle = isolateHandle; + this.isolateThreadHandle = isolateThreadHandle; + } + + long getEnclaveWrapperHandle() { + return enclaveWrapperHandle; + } + + long getEnclaveSvmSdkHandle() { + return enclaveSvmSdkHandle; + } + + long getIsolateHandle() { + return isolateHandle; + } + + long getIsolateThreadHandle() { + return isolateThreadHandle; + } + } + + class MockInSvmExtractTempPath { + private final String jniTempFilePath; + private final String enclaveWrapperFilePath; + private final String enclaveSvmFilePath; + + MockInSvmExtractTempPath(String jniTempFilePath, String enclaveWrapperFilePath, String enclaveSvmFilePath) { + this.jniTempFilePath = jniTempFilePath; + this.enclaveWrapperFilePath = enclaveWrapperFilePath; + this.enclaveSvmFilePath = enclaveSvmFilePath; + } + + String getJniTempFilePath() { + return jniTempFilePath; + } + + String getEnclaveWrapperFilePath() { + return enclaveWrapperFilePath; + } + + String getEnclaveSvmFilePath() { + return enclaveSvmFilePath; + } + } +} diff --git a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/ProxyEnclaveInvocationHandler.java b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/ProxyEnclaveInvocationHandler.java new file mode 100644 index 0000000..65eb630 --- /dev/null +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/ProxyEnclaveInvocationHandler.java @@ -0,0 +1,87 @@ +package com.alibaba.confidentialcomputing.host; + +import com.alibaba.confidentialcomputing.common.EnclaveInvocationContext; +import com.alibaba.confidentialcomputing.common.ServiceHandler; +import com.alibaba.confidentialcomputing.host.exception.EnclaveMethodInvokingException; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; + +/** + * ProxyEnclaveInvocationHandler define a service's proxy invocation handler. + */ +class ProxyEnclaveInvocationHandler implements InvocationHandler, Runnable { + private final AbstractEnclave enclave; + private final ServiceHandler serviceHandler; + + ProxyEnclaveInvocationHandler(AbstractEnclave enclave, ServiceHandler serviceHandler) { + this.enclave = enclave; + this.serviceHandler = serviceHandler; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + EnclaveInvocationContext methodInvokeMetaWrapper; + String[] parameterTypes; + // Building a method wrapper for enclave native invocation. + if (args != null) { + parameterTypes = new String[args.length]; + // Get a method's parameter type exactly. + Class<?>[] paraTypes = method.getParameterTypes(); + for (int index = 0x0; index < args.length; index++) { + parameterTypes[index] = paraTypes[index].getName(); + } + methodInvokeMetaWrapper = new EnclaveInvocationContext( + serviceHandler, + method.getName(), + parameterTypes, + args); + } else { + methodInvokeMetaWrapper = new EnclaveInvocationContext( + serviceHandler, + method.getName(), null, null); + } + + // Handle service method invocation exception. + Object result; + try { + result = enclave.InvokeEnclaveMethod(methodInvokeMetaWrapper); + } catch (EnclaveMethodInvokingException e) { + // Get cause exception if it has one. + Throwable cause = e.getCause(); + Class<?>[] exceptionTypes = method.getExceptionTypes(); + if (cause != null && cause instanceof InvocationTargetException) { + // Check whether cause exception matches one of the method's exception declaration. + // If it's true, it illustrates that an exception happened in enclave when the service + // method was invoked in enclave, we should throw this exception directly and user will + // handle it. + // If it's false, it illustrates that an exception happened in host side or enclave side, + // but the exception is not belong to the method's declaration. In the case we should throw + // EnclaveMethodInvokingException again. + for (Class<?> exception : exceptionTypes) { + if (exception == cause.getCause().getClass()) { + throw cause.getCause(); + } + } + } + throw e; + } + return result; + } + + AbstractEnclave getEnclave() { + return enclave; + } + + ServiceHandler getServiceHandler() { + return serviceHandler; + } + + // If a proxy handler object was recycled by gc in host side, tell EnclaveServicesRecycle to + // recycle the corresponding service loaded in enclave. + @Override + public void run() { + enclave.getEnclaveContext().getEnclaveServicesRecycler().enqueueProxyHandler(this); + } +} diff --git a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/TeeSdkEnclave.java b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/TeeSdkEnclave.java new file mode 100644 index 0000000..e7ee41e --- /dev/null +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/TeeSdkEnclave.java @@ -0,0 +1,164 @@ +package com.alibaba.confidentialcomputing.host; + +import com.alibaba.confidentialcomputing.host.exception.*; + +import java.io.IOException; + +/** + * TeeSdkEnclave is a sgx2 enclave based on Alibaba cloud's tee sdk. + */ +class TeeSdkEnclave extends AbstractEnclave { + private final static String JNI_EXTRACTED_PACKAGE_PATH = "jni/lib_jni_tee_sdk.so"; + private final static String TEE_SDK_SIGNED_PACKAGE_PATH = "libs/lib_enclave_tee_sdk.signed"; + private static volatile TeeSdkExtractTempPath extractTempPath; + private final EnclaveNativeContext nativeHandlerContext = new EnclaveNativeContext( + 0, 0, 0); + + TeeSdkEnclave(EnclaveDebug mode) throws EnclaveCreatingException { + // Set EnclaveContext for this enclave instance. + super(EnclaveType.TEE_SDK, mode, new EnclaveServicesRecycler()); + + // Extract jni .so and signed tee .so from .jar file. + // Only once extract and load operation. + if (extractTempPath == null) { + synchronized (TeeSdkEnclave.class) { + if (extractTempPath == null) { + try { + String jniTempFilePath = ExtractLibrary.extractLibrary( + TeeSdkEnclave.class.getClassLoader(), + JNI_EXTRACTED_PACKAGE_PATH); + String teeSdkSignedFilePath = ExtractLibrary.extractLibrary( + TeeSdkEnclave.class.getClassLoader(), + TEE_SDK_SIGNED_PACKAGE_PATH); + extractTempPath = new TeeSdkExtractTempPath(jniTempFilePath, teeSdkSignedFilePath); + System.load(jniTempFilePath); + } catch (IOException e) { + throw new EnclaveCreatingException("extracting tee sdk jni .so or signed .so failed.", e); + } + } + } + } + + // Create tee sdk enclave by native call, enclaveHandler is set in jni in nativeHandlerContext. + int ret = nativeCreateEnclave(mode.getValue(), extractTempPath.getTeeSdkSignedFilePath()); + if (ret != 0) { + throw new EnclaveCreatingException("create tee sdk enclave by native calling failed."); + } + // Create svm attach isolate and isolateThread, and they are set in jni in nativeHandlerContext. + ret = nativeSvmAttachIsolate(nativeHandlerContext.getEnclaveHandle()); + if (ret != 0) { + throw new EnclaveCreatingException("create svm isolate by native calling failed."); + } + } + + private native int nativeCreateEnclave(int mode, String path); + + private native int nativeSvmAttachIsolate(long enclaveHandler); + + private native InnerNativeInvocationResult nativeLoadService( + long enclaveHandler, long isolateHandler, byte[] serviceHandler); + + private native InnerNativeInvocationResult nativeInvokeMethod( + long enclaveHandler, long isolateHandler, byte[] enclaveInvokeMetaWrapper); + + private native InnerNativeInvocationResult nativeUnloadService( + long enclaveHandler, long isolateHandler, byte[] serviceHandler); + + private native int nativeSvmDetachIsolate(long enclaveHandler, long isolateThreadHandler); + + private native int nativeDestroyEnclave(long enclaveHandler); + + @Override + InnerNativeInvocationResult loadServiceNative(byte[] payload) { + return nativeLoadService( + nativeHandlerContext.getEnclaveHandle(), + nativeHandlerContext.getIsolateHandle(), + payload); + } + + @Override + InnerNativeInvocationResult unloadServiceNative(byte[] payload) { + return nativeUnloadService( + nativeHandlerContext.getEnclaveHandle(), + nativeHandlerContext.getIsolateHandle(), + payload); + } + + @Override + InnerNativeInvocationResult invokeMethodNative(byte[] payload) { + return nativeInvokeMethod( + nativeHandlerContext.getEnclaveHandle(), + nativeHandlerContext.getIsolateHandle(), + payload); + } + + @Override + public void destroy() throws EnclaveDestroyingException { + // destroyToken will wait for all ongoing enclave invocations finished. + if (this.getEnclaveContext().getEnclaveToken().destroyToken()) { + // interrupt enclave services' recycler firstly. + this.getEnclaveContext().getEnclaveServicesRecycler().interruptServiceRecycler(); + // destroy svm isolate. + int ret = nativeSvmDetachIsolate(nativeHandlerContext.getEnclaveHandle(), + nativeHandlerContext.getIsolateThreadHandle()); + if (ret != 0) { + throw new EnclaveDestroyingException("isolate destroy native call failed."); + } + // destroy the enclave. + ret = nativeDestroyEnclave(nativeHandlerContext.getEnclaveHandle()); + if (ret != 0) { + throw new EnclaveDestroyingException("enclave destroy native call failed."); + } + } + } + + class TeeSdkExtractTempPath { + private final String jniTempFilePath; + private final String teeSdkSignedFilePath; + + TeeSdkExtractTempPath(String jniTempFilePath, String teeSdkSignedFilePath) { + this.jniTempFilePath = jniTempFilePath; + this.teeSdkSignedFilePath = teeSdkSignedFilePath; + } + + String getJniTempFilePath() { + return jniTempFilePath; + } + + String getTeeSdkSignedFilePath() { + return teeSdkSignedFilePath; + } + } + + /** + * JavaEnclave will create svm isolate handle and isolateThread handle by native call, + * so EnclaveNativeContextCache will cache them for usage. + */ + class EnclaveNativeContext { + // enclaveHandle stores created enclave's handle id. + private final long enclaveHandle; + // isolate stores svm created isolate instance. + // In JavaEnclave only one isolateHandle instance will be created. + private final long isolateHandle; + // isolateThreadHandle stores the first attached isolateThread Handle. + private final long isolateThreadHandle; + + EnclaveNativeContext(long enclaveHandle, long isolateHandle, long isolateThreadHandle) { + this.enclaveHandle = enclaveHandle; + this.isolateHandle = isolateHandle; + this.isolateThreadHandle = isolateThreadHandle; + } + + long getEnclaveHandle() { + return enclaveHandle; + } + + long getIsolateHandle() { + return isolateHandle; + } + + long getIsolateThreadHandle() { + return isolateThreadHandle; + } + } +} diff --git a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/exception/ConfidentialComputingException.java b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/exception/ConfidentialComputingException.java index 3bedccf..f7af01a 100644 --- a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/exception/ConfidentialComputingException.java +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/exception/ConfidentialComputingException.java @@ -20,4 +20,56 @@ public class ConfidentialComputingException extends Exception { public ConfidentialComputingException(Throwable e) { super(e); } + + /** + * @param info exception message. + * @param e exception. + */ + public ConfidentialComputingException(String info, Throwable e) { + super(info, e); + } + + /** + * EnclaveNativeInvokingException defines all kinds of possible exceptions towards an + * enclave's native operation. Basically there are two kinds error about enclave operation, + * one kind is native calling return an unexpected value, the other kind is an exception + * happen in enclave and transform into host side. If a native invoking into enclave returns + * an error value, enum of EnclaveNativeInvokingException will add extra error message details + * for debugging; If an exception happened in enclave and transformed to host side, it will be + * thrown again in host side to user. + * Programmers need to handle EnclaveNativeInvokingException seriously. + */ + enum EnclaveNativeInvokingException { + // Enclave creating failed. + ENCLAVE_CREATING_ERROR("A0001", "creating enclave failed."), + // Enclave destroying failed. + ENCLAVE_DESTROYING_ERROR("A0002", "destroying enclave failed"), + // Services loading failed. + SERVICES_LOADING_ERROR("A0003", "services loading failed in enclave"), + // Services unloading failed. + SERVICES_UNLOADING_ERROR("A0004", "service unloading failed in enclave"), + // Service method invoking failed. + SERVICE_METHOD_INVOKING_ERROR("A0005", "service method invoking failed"); + + private final String errorCode; + private final String errorMessage; + + EnclaveNativeInvokingException(String errorCode, String errorMessage) { + this.errorCode = errorCode; + this.errorMessage = errorMessage; + } + + String buildExceptionMessage(String details) { + if (details != null) { + return this.toString() + " DetailErrorMessage: " + details; + } else { + return this.toString(); + } + } + + @Override + public String toString() { + return "ErrorCode: " + this.errorCode + " , " + " ErrorMessage: " + this.errorMessage; + } + } } \ No newline at end of file diff --git a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/exception/EnclaveCreatingException.java b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/exception/EnclaveCreatingException.java index 82cda63..cfbdd99 100644 --- a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/exception/EnclaveCreatingException.java +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/exception/EnclaveCreatingException.java @@ -10,13 +10,21 @@ public class EnclaveCreatingException extends ConfidentialComputingException { * @param info exception information. */ public EnclaveCreatingException(String info) { - super(info); + super(EnclaveNativeInvokingException.ENCLAVE_CREATING_ERROR.buildExceptionMessage(info)); } /** * @param e exception. */ public EnclaveCreatingException(Throwable e) { - super(e); + super(EnclaveNativeInvokingException.ENCLAVE_CREATING_ERROR.toString(), e); + } + + /** + * @param info exception message. + * @param e exception. + */ + public EnclaveCreatingException(String info, Throwable e) { + super(EnclaveNativeInvokingException.ENCLAVE_CREATING_ERROR.buildExceptionMessage(info), e); } } \ No newline at end of file diff --git a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/exception/EnclaveDestroyingException.java b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/exception/EnclaveDestroyingException.java index e83aedf..c46b1d5 100644 --- a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/exception/EnclaveDestroyingException.java +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/exception/EnclaveDestroyingException.java @@ -10,13 +10,21 @@ public class EnclaveDestroyingException extends ConfidentialComputingException { * @param info exception information. */ public EnclaveDestroyingException(String info) { - super(info); + super(EnclaveNativeInvokingException.ENCLAVE_DESTROYING_ERROR.buildExceptionMessage(info)); } /** * @param e exception. */ public EnclaveDestroyingException(Throwable e) { - super(e); + super(EnclaveNativeInvokingException.ENCLAVE_DESTROYING_ERROR.toString(), e); + } + + /** + * @param info exception message. + * @param e exception. + */ + public EnclaveDestroyingException(String info, Throwable e) { + super(EnclaveNativeInvokingException.ENCLAVE_DESTROYING_ERROR.buildExceptionMessage(info), e); } } \ No newline at end of file diff --git a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/exception/EnclaveMethodInvokingException.java b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/exception/EnclaveMethodInvokingException.java new file mode 100644 index 0000000..fcb534b --- /dev/null +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/exception/EnclaveMethodInvokingException.java @@ -0,0 +1,30 @@ +package com.alibaba.confidentialcomputing.host.exception; + +/** + * EnclaveMethodInvokingException {@link EnclaveMethodInvokingException} is thrown when exception happen + * during an enclave's method was invoking. + * Programmers need to handle EnclaveInvokeException seriously. + */ +public class EnclaveMethodInvokingException extends ConfidentialComputingException { + /** + * @param info exception information. + */ + public EnclaveMethodInvokingException(String info) { + super(EnclaveNativeInvokingException.SERVICE_METHOD_INVOKING_ERROR.buildExceptionMessage(info)); + } + + /** + * @param e exception. + */ + public EnclaveMethodInvokingException(Throwable e) { + super(EnclaveNativeInvokingException.SERVICE_METHOD_INVOKING_ERROR.toString(), e); + } + + /** + * @param info exception message. + * @param e exception. + */ + public EnclaveMethodInvokingException(String info, Throwable e) { + super(EnclaveNativeInvokingException.SERVICE_METHOD_INVOKING_ERROR.buildExceptionMessage(info), e); + } +} diff --git a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/exception/ServicesLoadingException.java b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/exception/ServicesLoadingException.java index dfb187c..7fda291 100644 --- a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/exception/ServicesLoadingException.java +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/exception/ServicesLoadingException.java @@ -10,13 +10,21 @@ public class ServicesLoadingException extends ConfidentialComputingException { * @param info exception information. */ public ServicesLoadingException(String info) { - super(info); + super(EnclaveNativeInvokingException.SERVICES_LOADING_ERROR.buildExceptionMessage(info)); } /** * @param e exception. */ public ServicesLoadingException(Throwable e) { - super(e); + super(EnclaveNativeInvokingException.SERVICES_LOADING_ERROR.toString(), e); + } + + /** + * @param info exception info. + * @param e exception. + */ + public ServicesLoadingException(String info, Throwable e) { + super(EnclaveNativeInvokingException.SERVICES_LOADING_ERROR.buildExceptionMessage(info), e); } } \ No newline at end of file diff --git a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/exception/ServicesUnloadingException.java b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/exception/ServicesUnloadingException.java new file mode 100644 index 0000000..4c0de9d --- /dev/null +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/exception/ServicesUnloadingException.java @@ -0,0 +1,30 @@ +package com.alibaba.confidentialcomputing.host.exception; + +/** + * ServicesUnloadingException {@link ServicesUnloadingException} is thrown when exception happen + * during an enclave's service was unloading. + * Programmers need to handle UnloadServiceException seriously. + */ +public class ServicesUnloadingException extends ConfidentialComputingException { + /** + * @param info exception information. + */ + public ServicesUnloadingException(String info) { + super(EnclaveNativeInvokingException.SERVICES_UNLOADING_ERROR.buildExceptionMessage(info)); + } + + /** + * @param e exception. + */ + public ServicesUnloadingException(Throwable e) { + super(EnclaveNativeInvokingException.SERVICES_UNLOADING_ERROR.toString(), e); + } + + /** + * @param info exception info. + * @param e exception. + */ + public ServicesUnloadingException(String info, Throwable e) { + super(EnclaveNativeInvokingException.SERVICES_UNLOADING_ERROR.buildExceptionMessage(info), e); + } +} diff --git a/sdk/host/src/test/java/com/alibaba/confidentialcomputing/host/MockTestEnclave.java b/sdk/host/src/test/java/com/alibaba/confidentialcomputing/host/MockTestEnclave.java new file mode 100644 index 0000000..e1a9b34 --- /dev/null +++ b/sdk/host/src/test/java/com/alibaba/confidentialcomputing/host/MockTestEnclave.java @@ -0,0 +1,166 @@ +package com.alibaba.confidentialcomputing.host; + +import com.alibaba.confidentialcomputing.common.*; +import com.alibaba.confidentialcomputing.host.exception.EnclaveCreatingException; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; + + +class MockTestEnclave extends AbstractEnclave { + private static final AtomicLong instanceIdentity = new AtomicLong(0); + private static final Map<String, Object> instancesRegisterCenter = new ConcurrentHashMap<>(); + private static final Queue<ServiceHandler> cacheServiceHandler = new LinkedList<>(); + + MockTestEnclave() throws EnclaveCreatingException { + super(EnclaveType.NONE, EnclaveDebug.NONE, new BaseEnclaveServicesRecycler()); + } + + private static Class<?>[] parseParamClass(String[] parameterTypes) { + if (parameterTypes == null) { + return null; + } + List<Class<?>> parametersClass = new ArrayList<>(); + Arrays.stream(parameterTypes).forEach(p -> { + try { + parametersClass.add(nameToType(p)); + } catch (ClassNotFoundException e) { + assertTrue(false); + } + }); + return parametersClass.toArray(new Class<?>[0]); + } + + private static Class<?> nameToType(String name) throws ClassNotFoundException { + if (name.indexOf('.') == -1) { + switch (name) { + case "boolean": + return boolean.class; + case "char": + return char.class; + case "float": + return float.class; + case "double": + return double.class; + case "byte": + return byte.class; + case "short": + return short.class; + case "int": + return int.class; + case "long": + return long.class; + case "void": + return void.class; + } + } + return Class.forName(name); + } + + @Override + InnerNativeInvocationResult loadServiceNative(byte[] payload) { + EnclaveInvocationContext invocationContext; + List<ServiceHandler> handlers = new ArrayList<>(); + Throwable exception = null; + EnclaveInvocationResult result; + try { + invocationContext = (EnclaveInvocationContext) SerializationHelper.deserialize(payload); + String interfaceName = invocationContext.getServiceHandler().getServiceInterfaceName(); + Class<?> service = Class.forName(interfaceName); + Iterator<?> services = ServiceLoader.load(service).iterator(); + while (services.hasNext()) { + String identity = String.valueOf(instanceIdentity.addAndGet(1)); + Object instance = services.next(); + ServiceHandler sm = new ServiceHandler(interfaceName, instance.getClass().getName(), identity); + handlers.add(sm); + cacheServiceHandler.add(sm); + instancesRegisterCenter.put(identity, instance); + } + } catch (IOException | ClassNotFoundException e) { + exception = e; + } finally { + result = new EnclaveInvocationResult(handlers.toArray(new ServiceHandler[0]), exception); + } + + try { + return new InnerNativeInvocationResult(0, SerializationHelper.serialize(result)); + } catch (IOException e) { + return new InnerNativeInvocationResult(-1, null); + } + } + + @Override + InnerNativeInvocationResult unloadServiceNative(byte[] payload) { + EnclaveInvocationContext invocationContext; + Throwable exception = null; + EnclaveInvocationResult result; + try { + invocationContext = (EnclaveInvocationContext) SerializationHelper.deserialize(payload); + instancesRegisterCenter.remove(invocationContext.getServiceHandler().getInstanceIdentity()); + } catch (IOException | ClassNotFoundException e) { + exception = e; + } finally { + result = new EnclaveInvocationResult(null, exception); + } + + try { + return new InnerNativeInvocationResult(0, SerializationHelper.serialize(result)); + } catch (IOException e) { + return new InnerNativeInvocationResult(-1, null); + } + } + + @Override + InnerNativeInvocationResult invokeMethodNative(byte[] payload) { + EnclaveInvocationContext invocationContext; + Throwable exception = null; + Object invokeRet = null; + EnclaveInvocationResult result; + try { + invocationContext = (EnclaveInvocationContext) SerializationHelper.deserialize(payload); + String className = invocationContext.getServiceHandler().getServiceImplClassName(); + String[] parameterTypes = invocationContext.getParameterTypes(); + String methodName = invocationContext.getMethodName(); + Object[] args = invocationContext.getArguments(); + Object instance = instancesRegisterCenter.get(invocationContext.getServiceHandler().getInstanceIdentity()); + assertNotNull(instance); + assertTrue(className.equals(instance.getClass().getName())); + Class<?> service = Class.forName(className); + Method method = service.getDeclaredMethod(methodName, parseParamClass(parameterTypes)); + method.setAccessible(true); + invokeRet = method.invoke(instance, args); + } catch (Throwable e) { + exception = e; + } finally { + result = new EnclaveInvocationResult(invokeRet, exception); + } + + try { + return new InnerNativeInvocationResult(0, SerializationHelper.serialize(result)); + } catch (IOException e) { + return new InnerNativeInvocationResult(-1, null); + } + } + + @Override + public void destroy() { + // destroyToken will wait for all ongoing enclave invocations finished. + if (this.getEnclaveContext().getEnclaveToken().destroyToken()) { + // interrupt enclave services' recycler firstly. + this.getEnclaveContext().getEnclaveServicesRecycler().interruptServiceRecycler(); + } + } + + int getServicesNum() { + return instancesRegisterCenter.size(); + } + + Queue<?> getCachedServiceHandler() { + return cacheServiceHandler; + } +} \ No newline at end of file diff --git a/sdk/host/src/test/java/com/alibaba/confidentialcomputing/host/Service.java b/sdk/host/src/test/java/com/alibaba/confidentialcomputing/host/Service.java new file mode 100644 index 0000000..13842ad --- /dev/null +++ b/sdk/host/src/test/java/com/alibaba/confidentialcomputing/host/Service.java @@ -0,0 +1,11 @@ +package com.alibaba.confidentialcomputing.host; + +public interface Service { + void doNothing(); + + int add(int a, int b); + + String saySomething(String words); + + void throwException(String code) throws ServiceExceptionTest; +} diff --git a/sdk/host/src/test/java/com/alibaba/confidentialcomputing/host/ServiceExceptionTest.java b/sdk/host/src/test/java/com/alibaba/confidentialcomputing/host/ServiceExceptionTest.java new file mode 100644 index 0000000..753f793 --- /dev/null +++ b/sdk/host/src/test/java/com/alibaba/confidentialcomputing/host/ServiceExceptionTest.java @@ -0,0 +1,7 @@ +package com.alibaba.confidentialcomputing.host; + +public class ServiceExceptionTest extends Exception { + public ServiceExceptionTest(String info) { + super(info); + } +} diff --git a/sdk/host/src/test/java/com/alibaba/confidentialcomputing/host/ServiceImpl.java b/sdk/host/src/test/java/com/alibaba/confidentialcomputing/host/ServiceImpl.java new file mode 100644 index 0000000..b364b9e --- /dev/null +++ b/sdk/host/src/test/java/com/alibaba/confidentialcomputing/host/ServiceImpl.java @@ -0,0 +1,26 @@ +package com.alibaba.confidentialcomputing.host; + +import com.google.auto.service.AutoService; + +@AutoService(Service.class) +public class ServiceImpl implements Service { + @Override + public void doNothing() { + ; // Do nothing; + } + + @Override + public int add(int a, int b) { + return a + b; + } + + @Override + public String saySomething(String words) { + return words; + } + + @Override + public void throwException(String code) throws ServiceExceptionTest { + throw new ServiceExceptionTest(code); + } +} diff --git a/sdk/host/src/test/java/com/alibaba/confidentialcomputing/host/TestAbstractEnclave.java b/sdk/host/src/test/java/com/alibaba/confidentialcomputing/host/TestAbstractEnclave.java new file mode 100644 index 0000000..a66aca7 --- /dev/null +++ b/sdk/host/src/test/java/com/alibaba/confidentialcomputing/host/TestAbstractEnclave.java @@ -0,0 +1,46 @@ +package com.alibaba.confidentialcomputing.host; + +import com.alibaba.confidentialcomputing.common.ServiceHandler; +import com.alibaba.confidentialcomputing.host.exception.EnclaveCreatingException; +import com.alibaba.confidentialcomputing.host.exception.EnclaveMethodInvokingException; +import com.alibaba.confidentialcomputing.host.exception.ServicesLoadingException; +import org.junit.jupiter.api.*; + +import java.lang.reflect.UndeclaredThrowableException; +import java.util.Iterator; +import java.util.Queue; + +import static org.junit.jupiter.api.Assertions.*; + +class TestAbstractEnclave { + private static Enclave enclave; + + @BeforeAll + static void create() throws EnclaveCreatingException { + enclave = new MockTestEnclave(); + } + + @Test + void testEnclave() throws Exception { + Iterator<?> services = enclave.load(Service.class); + assertEquals(1, ((MockTestEnclave) enclave).getServicesNum()); + assertNotNull(services); + assertTrue(services.hasNext()); + Service service = (Service) services.next(); + service.doNothing(); + assertEquals(200, service.add(20, 180)); + assertTrue("Hello World".equals(service.saySomething("Hello World"))); + assertThrows(ServiceExceptionTest.class, () -> service.throwException("something is wrong")); + Queue<?> queue = ((MockTestEnclave) enclave).getCachedServiceHandler(); + assertEquals(1, queue.size()); + ((MockTestEnclave) enclave).unloadService((ServiceHandler) queue.poll()); + assertEquals(0, ((MockTestEnclave) enclave).getServicesNum()); + enclave.destroy(); + assertThrows(ServicesLoadingException.class, () -> enclave.load(Service.class)); + try { + service.doNothing(); + } catch (UndeclaredThrowableException e) { + assertEquals(e.getCause().getClass(), EnclaveMethodInvokingException.class); + } + } +} diff --git a/sdk/host/src/test/java/com/alibaba/confidentialcomputing/host/TestEnclaveFactory.java b/sdk/host/src/test/java/com/alibaba/confidentialcomputing/host/TestEnclaveFactory.java new file mode 100644 index 0000000..410ece9 --- /dev/null +++ b/sdk/host/src/test/java/com/alibaba/confidentialcomputing/host/TestEnclaveFactory.java @@ -0,0 +1,16 @@ +package com.alibaba.confidentialcomputing.host; + +import com.alibaba.confidentialcomputing.host.exception.EnclaveCreatingException; +import com.alibaba.confidentialcomputing.host.exception.EnclaveDestroyingException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class TestEnclaveFactory { + @Test + void testEnclaveCreate() throws EnclaveCreatingException, EnclaveDestroyingException { + Enclave enclave = EnclaveFactory.create(EnclaveType.MOCK_IN_JVM); + assertTrue(enclave instanceof MockInJvmEnclave); + enclave.destroy(); + } +} diff --git a/sdk/pom.xml b/sdk/pom.xml index 24f2a05..3d93a44 100644 --- a/sdk/pom.xml +++ b/sdk/pom.xml @@ -13,6 +13,11 @@ </properties> <dependencyManagement> <dependencies> + <dependency> + <groupId>com.alibaba.confidentialcomputing</groupId> + <artifactId>common</artifactId> + <version>0.1.0</version> + </dependency> <dependency> <groupId>com.alibaba.confidentialcomputing</groupId> <artifactId>enclave</artifactId> @@ -29,16 +34,35 @@ <version>0.8.3</version> <scope>test</scope> </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <version>5.7.1</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.platform</groupId> + <artifactId>junit-platform-engine</artifactId> + <version>1.8.2</version> + <scope>test</scope> + </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-engine</artifactId> - <version>5.4.0</version> + <version>5.6.2</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit-pioneer</groupId> + <artifactId>junit-pioneer</artifactId> + <version>1.5.0</version> <scope>test</scope> </dependency> </dependencies> </dependencyManagement> <modules> + <module>common</module> <module>enclave</module> <module>host</module> </modules> -</project> \ No newline at end of file +</project> --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
