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 bf26f1d3e6e7a36bb940fb52f3050f97e51db80e Author: jeffery.wsj <[email protected]> AuthorDate: Tue Feb 22 21:10:50 2022 +0800 [sdk] Add enclave remote attestation api for JavaEnclave Summary: Add two enclave remote attestation api, one api is enclave report generation, another api is enclave report verification. Test Plan: all tests pass Reviewers: lei.yul, cengfeng.lzy, sanhong.lsh Issue: https://aone.alibaba-inc.com/task/39713494 CR: https://code.aone.alibaba-inc.com/java-tee/JavaEnclave/codereview/7817874 --- .../host/AbstractEnclave.java | 4 ++ .../host/AttestationReport.java | 78 ++++++++++++++++++++++ .../confidentialcomputing/host/Enclave.java | 6 +- .../confidentialcomputing/host/ExtractLibrary.java | 6 +- .../host/MockInJvmEnclave.java | 11 +++ .../host/MockInSvmEnclave.java | 10 +++ .../host/RemoteAttestation.java | 62 +++++++++++++++++ .../confidentialcomputing/host/TeeSdkEnclave.java | 28 +++++++- .../exception/ConfidentialComputingException.java | 4 +- .../host/exception/RemoteAttestationException.java | 30 +++++++++ .../host/MockTestEnclave.java | 10 +++ .../host/TestRemoteAttestation.java | 53 +++++++++++++++ 12 files changed, 298 insertions(+), 4 deletions(-) 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 index 37c87a9..50a92f6 100644 --- a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/AbstractEnclave.java +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/AbstractEnclave.java @@ -13,9 +13,11 @@ 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.RemoteAttestationException; 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. @@ -163,6 +165,8 @@ abstract class AbstractEnclave implements Enclave { } } + abstract AttestationReport generateAttestationReport(byte[] userData) throws RemoteAttestationException; + @Override public <T> Iterator<T> load(Class<T> service) throws ServicesLoadingException { // Check service must be an interface class. diff --git a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/AttestationReport.java b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/AttestationReport.java new file mode 100644 index 0000000..4dd574d --- /dev/null +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/AttestationReport.java @@ -0,0 +1,78 @@ +package com.alibaba.confidentialcomputing.host; + +import java.io.Serializable; + +/** + * AttestationReport wraps enclave's type and generated remote attestation report. + */ +public final class AttestationReport implements Serializable { + private static final long serialVersionUID = -2781780414647128479L; + + private final EnclaveType enclaveType; + private final byte[] report; + + AttestationReport(EnclaveType enclaveType, byte[] report) { + this.enclaveType = enclaveType; + this.report = report; + } + + /** + * Get enclave type from an AttestationReport instance. + * <p> + * + * @return Enclave type. + */ + public EnclaveType getEnclaveType() { + return enclaveType; + } + + /** + * Get enclave report from an AttestationReport instance. + * <p> + * + * @return Remote attestation report data. + */ + public byte[] getReport() { + return report; + } + + /** + * Bind an AttestationReport's type and report into a buffer for rpc transmission. + * <p> + * + * @return Serialized buffer. + */ + public byte[] toByteArray() { + byte[] bindReport = new byte[1 + report.length]; + bindReport[0] = (byte) enclaveType.ordinal(); + System.arraycopy(report, 0, bindReport, 1, report.length); + return bindReport; + } + + /** + * Build an AttestationReport instance from a bind buffer which contains its type and report. + * <p> + * + * @return AttestationReport instance. + */ + public static AttestationReport fromByteArray(byte[] attestationReport) { + EnclaveType enclaveType = EnclaveType.NONE; + byte[] report = new byte[attestationReport.length - 1]; + switch (attestationReport[0]) { + case 0: + enclaveType = EnclaveType.NONE; + break; + case 1: + enclaveType = EnclaveType.MOCK_IN_JVM; + break; + case 2: + enclaveType = EnclaveType.MOCK_IN_SVM; + break; + case 3: + enclaveType = EnclaveType.TEE_SDK; + break; + } + System.arraycopy(attestationReport, 1, report, 0, report.length); + return new AttestationReport(enclaveType, report); + } +} 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 1baf92a..f0a3b52 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 @@ -42,6 +42,11 @@ import com.alibaba.confidentialcomputing.host.exception.EnclaveDestroyingExcepti * <pre> * try { * Enclave enclave = EnclaveFactory.create(); + * AttestationReport report = RemoteAttestation.generateAttestationReport(enclave, new byte[64]); + * int valid = RemoteAttestation.verifyAttestationReport(report); + * if (valid == 0) { + * ... ... ... + * } * ... ... ... * Service provider = enclave.load(Service.class); * ... ... ... @@ -55,7 +60,6 @@ import com.alibaba.confidentialcomputing.host.exception.EnclaveDestroyingExcepti * </pre> */ public interface Enclave { - /** * Returns all providers which implement service interface. It's similar to SPI * ServiceLoader mechanism. It returns proxy providers which are handlers to real 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 index 84fc41b..1ce65e0 100644 --- a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/ExtractLibrary.java +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/ExtractLibrary.java @@ -1,6 +1,10 @@ package com.alibaba.confidentialcomputing.host; -import java.io.*; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; /** * JavaEnclave building tool will put native .so files into a java .jar file, 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 index faa0a87..030feaf 100644 --- a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/MockInJvmEnclave.java +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/MockInJvmEnclave.java @@ -1,5 +1,7 @@ package com.alibaba.confidentialcomputing.host; +import com.alibaba.confidentialcomputing.host.exception.RemoteAttestationException; + /** * MockInJvmEnclave is a mock jvm enclave. Both host and enclave codes run * in one jvm. It was used for test and debug. @@ -10,6 +12,15 @@ class MockInJvmEnclave extends AbstractEnclave { super(EnclaveType.MOCK_IN_JVM, new BaseEnclaveServicesRecycler()); } + @Override + AttestationReport generateAttestationReport(byte[] userData) throws RemoteAttestationException { + throw new RemoteAttestationException("MOCK_IN_JVM enclave doesn't support remote attestation generation."); + } + + static int verifyAttestationReport(byte[] report) throws RemoteAttestationException { + throw new RemoteAttestationException("MOCK_IN_JVM enclave doesn't support remote attestation verification."); + } + @Override InnerNativeInvocationResult loadServiceNative(byte[] payload) { return null; 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 index 84e4618..3b676ea 100644 --- a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/MockInSvmEnclave.java +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/MockInSvmEnclave.java @@ -2,6 +2,7 @@ package com.alibaba.confidentialcomputing.host; import com.alibaba.confidentialcomputing.host.exception.EnclaveCreatingException; import com.alibaba.confidentialcomputing.host.exception.EnclaveDestroyingException; +import com.alibaba.confidentialcomputing.host.exception.RemoteAttestationException; import java.io.IOException; @@ -63,6 +64,15 @@ class MockInSvmEnclave extends AbstractEnclave { } } + @Override + AttestationReport generateAttestationReport(byte[] userData) throws RemoteAttestationException { + throw new RemoteAttestationException("MOCK_IN_SVM enclave doesn't support remote attestation generation."); + } + + static int verifyAttestationReport(byte[] report) throws RemoteAttestationException { + throw new RemoteAttestationException("MOCK_IN_SVM enclave doesn't support remote attestation verification."); + } + @Override InnerNativeInvocationResult loadServiceNative(byte[] payload) { return nativeLoadService( diff --git a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/RemoteAttestation.java b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/RemoteAttestation.java new file mode 100644 index 0000000..c5bd9df --- /dev/null +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/RemoteAttestation.java @@ -0,0 +1,62 @@ +package com.alibaba.confidentialcomputing.host; + +import com.alibaba.confidentialcomputing.host.exception.RemoteAttestationException; + +import java.util.Random; + +/** + * RemoteAttestation mainly provides enclave's remote attestation generation and verification. + */ +public final class RemoteAttestation { + // normalizeUserData format userData to make sure it's a buffer with 64 bytes. + private static byte[] normalizeUserData(byte[] userData) throws RemoteAttestationException { + byte[] result = null; + if (userData == null) { + result = new byte[64]; + new Random().nextBytes(result); + } else if (userData.length < 64) { + result = new byte[64]; + System.arraycopy(userData, 0, result, 0, userData.length); + } else if (userData.length > 64) { + throw new RemoteAttestationException("enclave remote attestation user data length exceeds 64 bytes."); + } + return result; + } + + /** + * Generate enclave's remote attestation report, the report is signed by the enclave platform + * in TEE, it's used to verify enclave's validation. + * <p> + * + * @param enclave an enclave instance. + * @param userData provided as user identification, its length must be 64 bytes. + * If userData is null, JavaEnclave will generate a random buffer + * with 64 length bytes for it. + * If userData's length exceeds 64 bytes, RemoteAttestationException + * will be thrown. + * If userData's length is less than 64 bytes, padding was filled in the tail. + * @return Remote attestation report data. + * @throws RemoteAttestationException {@link RemoteAttestationException} If enclave remote + * attestation generated failed. + */ + public static AttestationReport generateAttestationReport(Enclave enclave, byte[] userData) throws RemoteAttestationException { + if (!(enclave instanceof AbstractEnclave)) { + throw new RemoteAttestationException("enclave instance class type is not AbstractEnclave."); + } + return ((AbstractEnclave) enclave).generateAttestationReport(normalizeUserData(userData)); + } + + /** + * Verify an enclave's validation according to giving report data signed by one enclave. + * <p> + * + * @param report signed data in an enclave and its tee type info. + * @return Zero means enclave is valid, Other value means enclave is invalid. + */ + public static int verifyAttestationReport(AttestationReport report) throws RemoteAttestationException { + if (report.getEnclaveType() != EnclaveType.TEE_SDK) { + throw new RemoteAttestationException("enclaveType must be TEE_SDK."); + } + return TeeSdkEnclave.verifyAttestationReport(report.getReport()); + } +} 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 index e7ee41e..2cd03f7 100644 --- a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/TeeSdkEnclave.java +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/TeeSdkEnclave.java @@ -1,6 +1,8 @@ package com.alibaba.confidentialcomputing.host; -import com.alibaba.confidentialcomputing.host.exception.*; +import com.alibaba.confidentialcomputing.host.exception.EnclaveCreatingException; +import com.alibaba.confidentialcomputing.host.exception.EnclaveDestroyingException; +import com.alibaba.confidentialcomputing.host.exception.RemoteAttestationException; import java.io.IOException; @@ -53,6 +55,10 @@ class TeeSdkEnclave extends AbstractEnclave { private native int nativeCreateEnclave(int mode, String path); + private native InnerNativeInvocationResult nativeGenerateAttestationReport(byte[] userData); + + private static native InnerNativeInvocationResult nativeVerifyAttestationReport(byte[] report); + private native int nativeSvmAttachIsolate(long enclaveHandler); private native InnerNativeInvocationResult nativeLoadService( @@ -68,6 +74,26 @@ class TeeSdkEnclave extends AbstractEnclave { private native int nativeDestroyEnclave(long enclaveHandler); + @Override + AttestationReport generateAttestationReport(byte[] userData) throws RemoteAttestationException { + InnerNativeInvocationResult result = nativeGenerateAttestationReport(userData); + if (result.getRet() != 0) { + throw new RemoteAttestationException("TEE_SDK's attestation report generation native call error code: " + result.getRet()); + } + return new AttestationReport(EnclaveType.TEE_SDK, result.getPayload()); + } + + static int verifyAttestationReport(byte[] report) throws RemoteAttestationException { + InnerNativeInvocationResult result = nativeVerifyAttestationReport(report); + if (result.getRet() != 0) { + throw new RemoteAttestationException("TEE_SDK's attestation verification native call error code: " + result.getRet()); + } + if (result.getPayload() == null) { + return 0; // Remote Attestation Verification result is succeed. + } + return 1; // Remote Attestation Verification result is failed. + } + @Override InnerNativeInvocationResult loadServiceNative(byte[] payload) { return nativeLoadService( 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 f7af01a..85ab3fe 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 @@ -49,7 +49,9 @@ public class ConfidentialComputingException extends Exception { // 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"); + SERVICE_METHOD_INVOKING_ERROR("A0005", "service method invoking failed"), + // Enclave remote attestation exception. + ENCLAVE_REMOTE_ATTESTATION_ERROR("A0006", "tee remote attestation failed"); private final String errorCode; private final String errorMessage; diff --git a/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/exception/RemoteAttestationException.java b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/exception/RemoteAttestationException.java new file mode 100644 index 0000000..d5faaab --- /dev/null +++ b/sdk/host/src/main/java/com/alibaba/confidentialcomputing/host/exception/RemoteAttestationException.java @@ -0,0 +1,30 @@ +package com.alibaba.confidentialcomputing.host.exception; + +/** + * RemoteAttestationException {@link RemoteAttestationException} is thrown when an enclave generates remote + * attestation report and returns an error value. + * Programmers need to handle RemoteAttestationException seriously. + */ +public class RemoteAttestationException extends ConfidentialComputingException { + /** + * @param info exception information. + */ + public RemoteAttestationException(String info) { + super(EnclaveNativeInvokingException.ENCLAVE_REMOTE_ATTESTATION_ERROR.buildExceptionMessage(info)); + } + + /** + * @param e exception. + */ + public RemoteAttestationException(Throwable e) { + super(EnclaveNativeInvokingException.ENCLAVE_REMOTE_ATTESTATION_ERROR.toString(), e); + } + + /** + * @param info exception message. + * @param e exception. + */ + public RemoteAttestationException(String info, Throwable e) { + super(EnclaveNativeInvokingException.ENCLAVE_REMOTE_ATTESTATION_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 index e1a9b34..04faf7b 100644 --- a/sdk/host/src/test/java/com/alibaba/confidentialcomputing/host/MockTestEnclave.java +++ b/sdk/host/src/test/java/com/alibaba/confidentialcomputing/host/MockTestEnclave.java @@ -2,6 +2,7 @@ package com.alibaba.confidentialcomputing.host; import com.alibaba.confidentialcomputing.common.*; import com.alibaba.confidentialcomputing.host.exception.EnclaveCreatingException; +import com.alibaba.confidentialcomputing.host.exception.RemoteAttestationException; import static org.junit.jupiter.api.Assertions.*; @@ -62,6 +63,15 @@ class MockTestEnclave extends AbstractEnclave { return Class.forName(name); } + @Override + AttestationReport generateAttestationReport(byte[] userData) throws RemoteAttestationException { + throw new RemoteAttestationException("MockTestEnclave enclave doesn't support remote attestation generation."); + } + + static int verifyAttestationReport(byte[] report) throws RemoteAttestationException { + throw new RemoteAttestationException("MockTestEnclave enclave doesn't support remote attestation verification."); + } + @Override InnerNativeInvocationResult loadServiceNative(byte[] payload) { EnclaveInvocationContext invocationContext; diff --git a/sdk/host/src/test/java/com/alibaba/confidentialcomputing/host/TestRemoteAttestation.java b/sdk/host/src/test/java/com/alibaba/confidentialcomputing/host/TestRemoteAttestation.java new file mode 100644 index 0000000..4773b0c --- /dev/null +++ b/sdk/host/src/test/java/com/alibaba/confidentialcomputing/host/TestRemoteAttestation.java @@ -0,0 +1,53 @@ +package com.alibaba.confidentialcomputing.host; + +import com.alibaba.confidentialcomputing.host.exception.EnclaveCreatingException; +import com.alibaba.confidentialcomputing.host.exception.RemoteAttestationException; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import static org.junit.jupiter.api.Assertions.*; + +class TestRemoteAttestation { + @Test + void testRemoteAttestation() throws EnclaveCreatingException { + Enclave mockInJvmEnclave = new MockInJvmEnclave(); + assertThrows(RemoteAttestationException.class, () -> RemoteAttestation.generateAttestationReport(mockInJvmEnclave, null)); + assertThrows(RemoteAttestationException.class, () -> RemoteAttestation.verifyAttestationReport(new AttestationReport(EnclaveType.MOCK_IN_JVM, null))); + assertThrows(RemoteAttestationException.class, () -> RemoteAttestation.verifyAttestationReport(new AttestationReport(EnclaveType.MOCK_IN_SVM, null))); + } + + @Test + void testNormalizeUserData() throws Exception { + Class<RemoteAttestation> clazz = RemoteAttestation.class; + Method method = clazz.getDeclaredMethod("normalizeUserData", byte[].class); + method.setAccessible(true); + + byte[] parameter = null; + Object result = method.invoke(null, (Object) parameter); + assertEquals(((byte[]) result).length, 64); + + parameter = new byte[32]; + result = method.invoke(null, parameter); + assertEquals(((byte[]) result).length, 64); + + byte[] finalParameter = new byte[65]; + assertThrows(InvocationTargetException.class, () -> method.invoke(null, finalParameter)); + } + + @Test + void testAttestationReport() throws Exception { + byte[] quote = new byte[4]; + for (int index = 0; index < quote.length; index++) { + quote[index] = (byte) 0x5f; + } + AttestationReport report = new AttestationReport(EnclaveType.TEE_SDK, quote); + byte[] serializedReport = report.toByteArray(); + AttestationReport deserializedReport = AttestationReport.fromByteArray(serializedReport); + assertEquals(EnclaveType.TEE_SDK, deserializedReport.getEnclaveType()); + for (int index = 0; index < quote.length; index++) { + assertEquals(quote[index], (deserializedReport.getReport())[index]); + } + } +} --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
