From 795a29b43deb4a6697f50c31ed5b80d31c793f37 Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Thu, 5 Mar 2026 15:51:43 +0900 Subject: [PATCH 01/12] =?UTF-8?q?`1.1.2`=20=EB=A6=B4=EB=A6=AC=EC=A6=88=20?= =?UTF-8?q?=EA=B0=9C=EB=B0=9C=20=EC=8B=9C=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 ++- README_EN.md | 3 ++- entlib-native | 2 +- .../space/qu4nt/entanglementlib/security/crypto/rng/RNG.java | 1 + 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 823687f..8b05581 100644 --- a/README.md +++ b/README.md @@ -58,10 +58,11 @@ Java 측에서 네이티브와 상호 작용할 때, 단순히 JNI(Java Native I 얽힘 라이브러리는 미래에 금융 및 보안 인프라 프로덕션에서 사용할 수 있도록 다음의 TODO를 명확히 하고자 합니다. +- [ ] 폐쇄망 환경 유용한 사용을 위한 Local Hosted 웹 개발 - [ ] TLS 통신 로직 추가 - [ ] 복합 검증 작업 준비 및 수행 - [ ] 커스텀 예외 최적화 -- [ ] JPMS 적용 +- [ ] JPMS 적용 (멀티모듈 내에서도 패키지 모듈화) - 안전한 캡슐화와 일관된 호출(또는 사용) 패턴이 완성되면 JPMS를 통해 캡슐화된 패키지를 모듈로서 관리하려고 합니다. - [ ] 외부 의존성 최소화 - 이제 `1.1.0` 릴리즈부턴 `BouncyCastle` 의존성을 최소화하며, 끝내 제거하는 데 성공했습니다. 현재 코드 작성에 필요한 몇 가지 유용한 도구를 제공하는 의존성은 여전히 남아 있지만, 이들도 끝내 최소화될 예정입니다. diff --git a/README_EN.md b/README_EN.md index 212c900..1ccd0f0 100644 --- a/README_EN.md +++ b/README_EN.md @@ -58,10 +58,11 @@ This project is currently `Alpha` version, and is still lacking a lot. We are al EntanglementLib wants to clarify the following TODOs so that it can be used in financial and security infrastructure production in the future. +- [ ] Develop Local Hosted Web for useful use in closed network environments - [ ] Add TLS communication logic - [ ] Prepare and perform complex verification tasks - [ ] Custom Exception Optimization -- [ ] Apply JPMS +- [ ] Apply JPMS (Package modularization within multi-modules) - Once secure encapsulation and consistent call (or usage) patterns are completed, we intend to manage encapsulated packages as modules through JPMS. - [ ] Minimize external dependencies - Now, from the `1.1.0` release, we have minimized `BouncyCastle` dependencies and finally succeeded in removing them. Dependencies that provide some useful tools needed for current code writing still remain, but these will also be minimized eventually. diff --git a/entlib-native b/entlib-native index 988dd79..c2ab2eb 160000 --- a/entlib-native +++ b/entlib-native @@ -1 +1 @@ -Subproject commit 988dd79552f2afddd47dda3404f7db106639e569 +Subproject commit c2ab2eb310943b09a921c9230c921c02208c02a3 diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/rng/RNG.java b/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/rng/RNG.java index e5e9473..7650b1a 100644 --- a/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/rng/RNG.java +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/rng/RNG.java @@ -75,6 +75,7 @@ public static SensitiveDataContainer generateRNG(final byte entropyStrategy, } } + // Q. T. Felix TODO: 좀 더 고차원적인 구현 private static void checkError(byte errorCode) { if (errorCode == 0) return; throw switch (errorCode) { From 448c558e8dee1f52f2560d830dba69406fa250f3 Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:04:09 +0900 Subject: [PATCH 02/12] =?UTF-8?q?=EB=84=A4=EC=9D=B4=ED=8B=B0=EB=B8=8C=20?= =?UTF-8?q?=EB=8F=99=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- entlib-native | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/entlib-native b/entlib-native index c2ab2eb..e5bfe81 160000 --- a/entlib-native +++ b/entlib-native @@ -1 +1 @@ -Subproject commit c2ab2eb310943b09a921c9230c921c02208c02a3 +Subproject commit e5bfe819c278c8eb9ed74e847cf8eaacc00f5cfd From 49c41054f9ec0d2619542dbb317599408a636438 Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:08:19 +0900 Subject: [PATCH 03/12] =?UTF-8?q?ds=20store=EA=B0=80=20=EC=99=9C=20?= =?UTF-8?q?=EB=93=A4=EC=96=B4=EA=B0=94=EB=8A=94=EC=A7=80=20=EB=AA=A8?= =?UTF-8?q?=EB=A5=BC=20=EC=BB=A4=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index dbea013..9e9a6fc 100644 --- a/.gitignore +++ b/.gitignore @@ -47,5 +47,5 @@ bin/ .vscode/ ### Mac OS ### -.DS_Store +.DS_* .temp.env From ec5e511cc87e7f18694c147e15715cb29438d653 Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:09:00 +0900 Subject: [PATCH 04/12] =?UTF-8?q?ds=20store=EA=B0=80=20=EC=99=9C=20?= =?UTF-8?q?=EB=93=A4=EC=96=B4=EA=B0=94=EB=8A=94=EC=A7=80=20=EB=AA=A8?= =?UTF-8?q?=EB=A5=BC=20=EC=BB=A4=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- entlib-native | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/entlib-native b/entlib-native index e5bfe81..2bccef1 160000 --- a/entlib-native +++ b/entlib-native @@ -1 +1 @@ -Subproject commit e5bfe819c278c8eb9ed74e847cf8eaacc00f5cfd +Subproject commit 2bccef170c024c03285d040d5a5310d8ca1648fb From 4f3b418fb0e08c2cdc231ed2d9bd91c8cebbdaaf Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:16:15 +0900 Subject: [PATCH 05/12] =?UTF-8?q?=EC=9B=B9=20=EC=84=9C=EB=B2=84=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal-shared-server/build.gradle.kts | 2 ++ settings.gradle.kts | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 internal-shared-server/build.gradle.kts diff --git a/internal-shared-server/build.gradle.kts b/internal-shared-server/build.gradle.kts new file mode 100644 index 0000000..571b417 --- /dev/null +++ b/internal-shared-server/build.gradle.kts @@ -0,0 +1,2 @@ +dependencies { +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index d219926..be63703 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,4 +2,5 @@ rootProject.name = "entanglementlib" include("core") include("security") include("annotations") -include("annotation-processor") \ No newline at end of file +include("annotation-processor") +include("internal-shared-server") \ No newline at end of file From 817b8ea65a0e860eb86b6bab6b8f7a68dadc3f5e Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Sat, 14 Mar 2026 17:52:02 +0900 Subject: [PATCH 06/12] =?UTF-8?q?=EC=9B=8C=ED=81=AC=ED=94=8C=EB=A1=9C=20?= =?UTF-8?q?=EB=B8=8C=EB=9E=9C=EC=B9=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/security-and-static-analysis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/security-and-static-analysis.yml b/.github/workflows/security-and-static-analysis.yml index cd91e21..df6f1bf 100644 --- a/.github/workflows/security-and-static-analysis.yml +++ b/.github/workflows/security-and-static-analysis.yml @@ -2,9 +2,7 @@ name: Security & Static Analysis on: push: - branches: [ "master", "feature/*" ] - pull_request: - branches: [ "master", "feature/*" ] + branches: [ "feature/*" ] schedule: - cron: '30 1 * * 0' # 매주 일요일 01:30 정기검진 From 95b541a8d32bed7c36c77d1c2175906193a308c5 Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Sat, 14 Mar 2026 17:52:41 +0900 Subject: [PATCH 07/12] =?UTF-8?q?=EC=B1=85=EC=9E=84=20=EC=A0=84=EA=B0=80?= =?UTF-8?q?=20=EC=96=B4=EB=85=B8=ED=85=8C=EC=9D=B4=EC=85=98=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EB=B2=95=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../qu4nt/entanglementlib/annotations/CallerResponsibility.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/annotations/src/main/java/space/qu4nt/entanglementlib/annotations/CallerResponsibility.java b/annotations/src/main/java/space/qu4nt/entanglementlib/annotations/CallerResponsibility.java index 3462f0a..9b6e578 100644 --- a/annotations/src/main/java/space/qu4nt/entanglementlib/annotations/CallerResponsibility.java +++ b/annotations/src/main/java/space/qu4nt/entanglementlib/annotations/CallerResponsibility.java @@ -24,6 +24,6 @@ * * @return 책임 전가 사유 또는 설명 */ - String value() default ""; + String[] value() default ""; } \ No newline at end of file From 35b550aefe9980ab0cbcf7e1a85b77b5eaf51df1 Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Sat, 14 Mar 2026 17:53:09 +0900 Subject: [PATCH 08/12] =?UTF-8?q?=EC=98=88=EC=99=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ELIBSecurityUnsafeUsageException.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityUnsafeUsageException.java diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityUnsafeUsageException.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityUnsafeUsageException.java new file mode 100644 index 0000000..cd6286c --- /dev/null +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityUnsafeUsageException.java @@ -0,0 +1,28 @@ +package space.qu4nt.entanglementlib.core.exception.security.checked; + +import java.io.Serial; + +public class ELIBSecurityUnsafeUsageException extends ELIBSecurityException { + + @Serial + private static final long serialVersionUID = 1672793311305065673L; + + public ELIBSecurityUnsafeUsageException() { + } + + public ELIBSecurityUnsafeUsageException(String message) { + super(message); + } + + public ELIBSecurityUnsafeUsageException(String message, Throwable cause) { + super(message, cause); + } + + public ELIBSecurityUnsafeUsageException(Throwable cause) { + super(cause); + } + + public ELIBSecurityUnsafeUsageException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} From 1cad64f2326e9cf8627917aaae417035973c457a Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Sat, 14 Mar 2026 17:53:36 +0900 Subject: [PATCH 09/12] =?UTF-8?q?=ED=8F=90=EC=87=84=EB=A7=9D=20=EC=84=9C?= =?UTF-8?q?=EB=B2=84=20=EB=AA=A8=EB=93=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal-shared-server/build.gradle.kts | 3 +++ .../java/space/qu4nt/entanglementlib/iss/ISSRunner.java | 8 ++++++++ 2 files changed, 11 insertions(+) create mode 100644 internal-shared-server/src/main/java/space/qu4nt/entanglementlib/iss/ISSRunner.java diff --git a/internal-shared-server/build.gradle.kts b/internal-shared-server/build.gradle.kts index 571b417..b345944 100644 --- a/internal-shared-server/build.gradle.kts +++ b/internal-shared-server/build.gradle.kts @@ -1,2 +1,5 @@ dependencies { + implementation(project(":core")) + implementation(project(":security")) + implementation(project(":annotations")) } \ No newline at end of file diff --git a/internal-shared-server/src/main/java/space/qu4nt/entanglementlib/iss/ISSRunner.java b/internal-shared-server/src/main/java/space/qu4nt/entanglementlib/iss/ISSRunner.java new file mode 100644 index 0000000..1f9be2b --- /dev/null +++ b/internal-shared-server/src/main/java/space/qu4nt/entanglementlib/iss/ISSRunner.java @@ -0,0 +1,8 @@ +package space.qu4nt.entanglementlib.iss; + +final class ISSRunner { + + static void main(String[] args) { + + } +} From ed82be0862d3c6a11fec57d5e174109aa13d51cc Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Sat, 14 Mar 2026 22:14:17 +0900 Subject: [PATCH 10/12] =?UTF-8?q?=EC=B4=88=EA=B8=B0=20=EB=AA=85=EC=84=B8?= =?UTF-8?q?=20=EB=AC=B8=EC=84=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/go.imports.xml | 11 + .idea/gradle.xml | 1 + .idea/markdown.xml | 8 + README_EN.md | 10 +- .../entanglementlib/annotations/Only.java | 9 + build.gradle.kts | 3 - core/build.gradle.kts | 1 + entlib-native | 2 +- .../EntanglementLibSecurityFacade.java | 2 +- .../security/communication/ExternalTLS.java | 6 + .../security/crypto/ChaCha20.java | 16 +- .../security/crypto/encode/Base64.java | 161 ++++--- .../security/crypto/encode/Hex.java | 104 +++++ .../security/crypto/hash/Hash.java | 120 ++--- .../security/crypto/rng/RNG.java | 14 +- .../security/data/SDCScopeContext.java | 13 +- .../security/data/SensitiveDataContainer.java | 87 ++-- .../entlibnative/ConstableFactory.java | 415 ++++++++++++++++++ .../entlibnative/EntLibNativeManager.java | 123 ------ .../entlibnative/FFIStandardWrapper.java | 25 ++ .../entlibnative/FFIStructEntLibResult.java | 4 + .../security/entlibnative/Function.java | 266 ----------- .../entlibnative/HandleProcessConsumer.java | 9 + .../entlibnative/NativeComponent.java | 169 +++++++ .../security/entlibnative/NativeLinker.java | 125 ++++++ .../security/entlibnative/NativeLoader.java | 4 +- .../entlibnative/NativeProcessResult.java | 101 +++++ .../entlibnative/NativeSpecContext.java | 75 +--- .../entlibnative/info/FunctionInfo.java | 57 +++ .../entlibnative/info/StructInfo.java | 72 +++ .../security/crypto/Base64Test.java | 14 +- .../security/crypto/ChaCha20Test.java | 14 +- .../security/crypto/RNGTest.java | 10 +- .../security/crypto/SHA2HashTest.java | 10 +- .../security/crypto/SHA3HashTest.java | 10 +- 35 files changed, 1385 insertions(+), 686 deletions(-) create mode 100644 .idea/go.imports.xml create mode 100644 .idea/markdown.xml create mode 100644 annotations/src/main/java/space/qu4nt/entanglementlib/annotations/Only.java create mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/communication/ExternalTLS.java create mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/crypto/encode/Hex.java create mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/ConstableFactory.java delete mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/EntLibNativeManager.java create mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/FFIStandardWrapper.java create mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/FFIStructEntLibResult.java delete mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/Function.java create mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/HandleProcessConsumer.java create mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeComponent.java create mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeLinker.java create mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeProcessResult.java create mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/info/FunctionInfo.java create mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/info/StructInfo.java diff --git a/.idea/go.imports.xml b/.idea/go.imports.xml new file mode 100644 index 0000000..d7202f0 --- /dev/null +++ b/.idea/go.imports.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 2547228..89fd185 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -12,6 +12,7 @@ diff --git a/.idea/markdown.xml b/.idea/markdown.xml new file mode 100644 index 0000000..c61ea33 --- /dev/null +++ b/.idea/markdown.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/README_EN.md b/README_EN.md index 1ccd0f0..408e76b 100644 --- a/README_EN.md +++ b/README_EN.md @@ -20,7 +20,7 @@ All designs of EntanglementLib prioritize security for 'military security' and ' EntanglementLib performs all security operations through [Rust-based native](https://github.com/Quant-Off/entlib-native). Native destroys all security weaknesses that can be triggered in the cleaning mechanism of the Garbage Collector (GC) due to `heap` memory allocation. It receives sensitive data from the Java side as `off-heap` to perform tasks, and immediately and safely erases the data of the pointer through the caller or callee pattern. -When interacting with Native from the Java side, simply JNI (Java Native Interface) functions are not used. The core technology is the Linker, FFM API (Foreign Function & Memory API), which is an advanced native call function based on [JEP 389](https://openjdk.org/jeps/389) and [JEP 454](https://openjdk.org/jeps/454) improvements, and on the Native side, it is linked via FFI (Foreign Function Interface) through encapsulated logic. +When interacting with Native from the Java side, simply JNI (Java Native Interface) nativeComponents are not used. The core technology is the Linker, FFM API (Foreign Function & Memory API), which is an advanced native call nativeComponent based on [JEP 389](https://openjdk.org/jeps/389) and [JEP 454](https://openjdk.org/jeps/454) improvements, and on the Native side, it is linked via FFI (Foreign Function Interface) through encapsulated logic. > [!TIP] > If you are curious about the background and overview of Native, please refer to [here](https://qu4nt.space/projects/entlib-native). @@ -35,8 +35,8 @@ EntanglementLib is now a multi-module project. The role of each module is divide | Module | Function | |-----------------|-------------------------------------------------------------------------------------------------------------------------------------------| -| `security` | Core security module. Provides logic for interaction with Native and various security functions linked via FFI. | -| `core` | Provides utility functions managing exceptions, internationalization and asynchronous, chunk operations, strings, and data structures. | +| `security` | Core security module. Provides logic for interaction with Native and various security nativeComponents linked via FFI. | +| `core` | Provides utility nativeComponents managing exceptions, internationalization and asynchronous, chunk operations, strings, and data structures. | | `annotations` | Includes annotations for easy code design and improving user's code understanding complexity. | @@ -46,7 +46,7 @@ Detailed technical specifications for EntanglementLib are being written. ## Benchmarking Records -We are performing bridge benchmarking for various operations such as nanosecond delays occurring when calling Rust native functions using FFM API in EntanglementLib. This work is directly related to performance and security, and plays an important role in creating optimal code. +We are performing bridge benchmarking for various operations such as nanosecond delays occurring when calling Rust native nativeComponents using FFM API in EntanglementLib. This work is directly related to performance and security, and plays an important role in creating optimal code. Many benchmarking tasks are scheduled in this alpha version. We plan to proceed with this work through JMH (Java Microbenchmark Harness), and we will organize it in a new document as soon as it is completed. @@ -82,7 +82,7 @@ EntanglementLib wants to clarify the following TODOs so that it can be used in f - In `ChaCha20Poly1305`, `InternalFactory.getSafeRandom()` is used to generate nonce value `Nonce`. If `Nonce` is reused with the same key, the security of `ChaCha20Poly1305` collapses completely. - **Resolution**: This problem was also solved with the `entlib-native` native library. Now, `ChaCha20`-based `CSPRNG` is created on the `Rust` side, and used only on the `Rust` side. In other words, all cryptographic operations are now performed only by `Rust`! - [X] Writing Technical Specifications for Security Functions - - **Resolution**: Important security-related functions of EntanglementLib have been written in a [separate document](TECHNICAL.md). + - **Resolution**: Important security-related nativeComponents of EntanglementLib have been written in a [separate document](TECHNICAL.md). ## License diff --git a/annotations/src/main/java/space/qu4nt/entanglementlib/annotations/Only.java b/annotations/src/main/java/space/qu4nt/entanglementlib/annotations/Only.java new file mode 100644 index 0000000..bf36125 --- /dev/null +++ b/annotations/src/main/java/space/qu4nt/entanglementlib/annotations/Only.java @@ -0,0 +1,9 @@ +package space.qu4nt.entanglementlib.annotations; + +import java.lang.annotation.Documented; + +@Documented +public @interface Only { + + String value() default ""; +} diff --git a/build.gradle.kts b/build.gradle.kts index ac994f7..f68ed6e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -45,9 +45,6 @@ subprojects { // Logging // https://mvnrepository.com/artifact/org.slf4j/slf4j-api implementation("org.slf4j:slf4j-api:2.0.17") - // bridger - // https://mvnrepository.com/artifact/org.slf4j/jul-to-slf4j - implementation("org.slf4j:jul-to-slf4j:2.0.17") // Logging Provider (Logback) // https://mvnrepository.com/artifact/ch.qos.logback/logback-classic implementation("ch.qos.logback:logback-classic:1.5.26") diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 195d444..2bd9281 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -1,6 +1,7 @@ dependencies { implementation(project(":annotations")) +// Q. T. Felix TODO: 암덩어리 jackson 제거 // Jackson // https://mvnrepository.com/artifact/tools.jackson.core/jackson-databind implementation("tools.jackson.core:jackson-databind:3.0.2") diff --git a/entlib-native b/entlib-native index 2bccef1..89debf5 160000 --- a/entlib-native +++ b/entlib-native @@ -1 +1 @@ -Subproject commit 2bccef170c024c03285d040d5a5310d8ca1648fb +Subproject commit 89debf599c51132ecee7c567f4384c6b5d18a5b3 diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/EntanglementLibSecurityFacade.java b/security/src/main/java/space/qu4nt/entanglementlib/security/EntanglementLibSecurityFacade.java index 66eb4ab..784ecb1 100644 --- a/security/src/main/java/space/qu4nt/entanglementlib/security/EntanglementLibSecurityFacade.java +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/EntanglementLibSecurityFacade.java @@ -11,7 +11,7 @@ private EntanglementLibSecurityFacade() { } public static void initialize(@NotNull EntanglementLibSecurityConfig config) { - NativeLoader.loadNativeLibrary(config); + NativeLoader.loadNativeLibrary(config); // TODO: entlib-native 기본 로더 로직 추가 HeuristicArenaFactory.setGlobalArenaMode(config.getArenaMode()); } } diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/communication/ExternalTLS.java b/security/src/main/java/space/qu4nt/entanglementlib/security/communication/ExternalTLS.java new file mode 100644 index 0000000..2f22a58 --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/communication/ExternalTLS.java @@ -0,0 +1,6 @@ +package space.qu4nt.entanglementlib.security.communication; + +public class ExternalTLS { + + +} diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/ChaCha20.java b/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/ChaCha20.java index 4882862..af674a8 100644 --- a/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/ChaCha20.java +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/ChaCha20.java @@ -7,8 +7,8 @@ import space.qu4nt.entanglementlib.security.data.InternalNativeBridge; import space.qu4nt.entanglementlib.security.data.SDCScopeContext; import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer; -import space.qu4nt.entanglementlib.security.entlibnative.EntLibNativeManager; -import space.qu4nt.entanglementlib.security.entlibnative.Function; +import space.qu4nt.entanglementlib.security.entlibnative.NativeLinker; +import space.qu4nt.entanglementlib.security.entlibnative.NativeComponent; import java.lang.foreign.MemorySegment; @@ -44,8 +44,8 @@ private ChaCha20() { } // Rust FFI 호출 (Callee-allocated Opaque Pointer 반환) - MemorySegment rustBufferPtr = (MemorySegment) EntLibNativeManager - .call(Function.ChaCha20_Poly1305_Encrypt) + MemorySegment rustBufferPtr = (MemorySegment) NativeLinker + .call(NativeComponent.ChaCha20_Poly1305_Encrypt) .invokeExact( InternalNativeBridge.unwrapMemorySegment(key), (long) InternalNativeBridge.unwrapMemorySegment(key).byteSize(), InternalNativeBridge.unwrapMemorySegment(nonce), (long) InternalNativeBridge.unwrapMemorySegment(nonce).byteSize(), @@ -57,7 +57,7 @@ private ChaCha20() { throw new ELIBSecurityProcessException("ChaCha20 암호화 실패: 유효하지 않은 입력 길이"); } - return EntLibNativeManager.transferNativeBufferBindToContext( + return NativeLinker.transferNativeBufferBindToContext( context, rustBufferPtr ); } catch (Throwable t) { @@ -84,8 +84,8 @@ private ChaCha20() { aadLen = InternalNativeBridge.unwrapMemorySegment(aad).byteSize(); } - MemorySegment rustBufferPtr = (MemorySegment) EntLibNativeManager - .call(Function.ChaCha20_Poly1305_Decrypt) + MemorySegment rustBufferPtr = (MemorySegment) NativeLinker + .call(NativeComponent.ChaCha20_Poly1305_Decrypt) .invokeExact( InternalNativeBridge.unwrapMemorySegment(key), (long) InternalNativeBridge.unwrapMemorySegment(key).byteSize(), InternalNativeBridge.unwrapMemorySegment(nonce), (long) InternalNativeBridge.unwrapMemorySegment(nonce).byteSize(), @@ -98,7 +98,7 @@ private ChaCha20() { throw new ELIBSecurityProcessException("ChaCha20 복호화 실패: 무결성 검증(MAC) 실패 또는 유효하지 않은 입력"); } - return EntLibNativeManager.transferNativeBufferBindToContext( + return NativeLinker.transferNativeBufferBindToContext( context, rustBufferPtr ); } catch (Throwable t) { diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/encode/Base64.java b/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/encode/Base64.java index 72d0eb7..1044882 100644 --- a/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/encode/Base64.java +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/encode/Base64.java @@ -1,107 +1,102 @@ package space.qu4nt.entanglementlib.security.crypto.encode; +import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityProcessException; +import space.qu4nt.entanglementlib.core.exception.security.unchecked.ELIBSecurityIllegalArgumentException; import space.qu4nt.entanglementlib.security.data.InternalNativeBridge; +import space.qu4nt.entanglementlib.security.data.SDCScopeContext; import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer; -import space.qu4nt.entanglementlib.security.entlibnative.EntLibNativeManager; -import space.qu4nt.entanglementlib.security.entlibnative.Function; +import space.qu4nt.entanglementlib.security.entlibnative.ConstableFactory; +import space.qu4nt.entanglementlib.security.entlibnative.NativeProcessResult; +import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; -/** - * 네이티브 메모리(native memory) 기반의 안전한 Base64 인코딩 및 디코딩 유틸리티입니다. - * 모든 입출력은 {@link SensitiveDataContainer}를 통해 소유권(ownership)이 통제되며, - * 가비지 컬렉터(garbage collector)가 관리하는 자바 힙(java heap) 메모리에 민감 데이터를 노출하지 않습니다. - */ +/// 네이티브 메모리 기반의 안전한 `Base64` 인코딩 및 디코딩 유틸리티입니다. +/// 모든 입출력은 [SensitiveDataContainer]를 통해 소유권(ownership)이 통제되며, +/// 가비지 컬렉터가 관리하는 자바 `heap` 메모리에 민감 데이터를 노출하지 않습니다. public final class Base64 { private Base64() { - throw new AssertionError("유틸리티 클래스는 인스턴스화할 수 없습니다."); + throw new AssertionError("cannot access"); } - /** - * 제공된 보안 컨테이너의 데이터를 Base64로 인코딩합니다. - * 반환된 새 컨테이너는 호출자(caller)가 소유권을 가지며, 세션 종료 시 명시적으로 소거해야 합니다. - * - * @param input 원본 데이터가 담긴 보안 컨테이너 - * @return Base64 인코딩 결과가 담긴 새로운 보안 컨테이너 - */ - public static SensitiveDataContainer encode(final SensitiveDataContainer input) { - if (input == null || !InternalNativeBridge.unwrapArena(input).scope().isAlive()) { - throw new IllegalArgumentException("유효하지 않거나 이미 소거된 입력 컨테이너입니다."); - } + /// 제공된 보안 컨테이너의 데이터를 Base64로 인코딩합니다. + /// Zero-Trust 원칙에 따라 JVM `Heap` 메모리를 거치지 않고 오직 `Off-Heap` 컨테이너 + /// 간의 연산만 허용합니다. + /// + /// # Security Note + /// 입력 컨테이너(input)와 출력 컨테이너(output)의 생명 주기는 이 메소드를 호출한 + /// 외부의 컨텍스트 [space.qu4nt.entanglementlib.security.data.SDCScopeContext] 또는 + /// `try-with-resources`가 책임집니다. + /// + /// @param scope 데이터 상호 작용을 수행할 보안 컨테이너 스코프 + /// @param input 인코딩 타겟 컨테이너 + /// @return Base64 인코딩 결과가 담긴 새로운 보안 컨테이너 + public static SensitiveDataContainer encode(final SDCScopeContext scope, final SensitiveDataContainer input) throws ELIBSecurityProcessException { + if (scope == null) + throw new ELIBSecurityIllegalArgumentException("유효하지 않은 스코프 컨텍스트입니다!"); + if (input == null || !InternalNativeBridge.unwrapArena(input).scope().isAlive()) + throw new ELIBSecurityIllegalArgumentException("유효하지 않거나 이미 소거된 입력 컨테이너입니다!"); final long inputLen = InternalNativeBridge.unwrapMemorySegment(input).byteSize(); - final int required = (int) (((inputLen + 2) / 3) * 4); - - SensitiveDataContainer output = new SensitiveDataContainer(required); - - try { - long result = (long) EntLibNativeManager.call(Function.Base64_encode).invokeExact( - InternalNativeBridge.unwrapMemorySegment(input), - inputLen, - InternalNativeBridge.unwrapMemorySegment(output), - (long) required - ); - - if (result < 0) { - output.close(); - throw new RuntimeException("인코딩 중 네이티브 오류 발생 (error code): " + result); - } - - return output; + if (inputLen > Long.MAX_VALUE / 2) + throw new ELIBSecurityProcessException("Base64 인코딩 허용 메모리 한계를 초과했습니다!"); + // Base64 인코딩 시 필요한 정확한 버퍼 크기 계산 (패딩 포함) + final int required = (int) (4 * ((inputLen + 2) / 3)); + final SensitiveDataContainer output = scope.allocate(required); + + // FFIStandard 구조체를 정의하기 위한 임시 Arena + try (Arena transientArena = Arena.ofConfined()) { + // FFIStandard 정의 + MemorySegment inputFFIStandard = ConstableFactory.Std.allocateJOStandard(transientArena, input); + MemorySegment outputFFIStandard = ConstableFactory.Std.allocateJOStandard(transientArena, output); + + NativeProcessResult result = ConstableFactory.Base64.base64Encode(inputFFIStandard, outputFFIStandard); + if (!result.isSuccess()) + throw new ELIBSecurityProcessException("Rust 네이티브 측 Base64 인코딩 실패 (상태 코드: " + result.getStatusCode() + ")"); } catch (Throwable t) { - output.close(); - throw new RuntimeException("FFM API 인코딩 호출 실패", t); + if (t instanceof ELIBSecurityProcessException) throw (ELIBSecurityProcessException) t; + throw new ELIBSecurityProcessException("Base64 인코딩 FFI 호출 중 치명적 예외가 발생했습니다!", t); } + return output; } - /** - * 제공된 보안 컨테이너의 Base64 데이터를 디코딩합니다. - * 반환된 새 컨테이너는 호출자(caller)가 소유권을 가지며, 세션 종료 시 명시적으로 소거해야 합니다. - * - * @param input Base64 인코딩 데이터가 담긴 보안 컨테이너 - * @return 디코딩된 원본 데이터가 담긴 새로운 보안 컨테이너 - */ - public static SensitiveDataContainer decode(final SensitiveDataContainer input) { - if (input == null || !InternalNativeBridge.unwrapArena(input).scope().isAlive()) { - throw new IllegalArgumentException("유효하지 않거나 이미 소거된 입력 컨테이너입니다."); - } + /// `Base64`로 인코딩된 민감 데이터를 디코딩하는 메소드입니다. + /// Zero-Trust 원칙에 따라 JVM `Heap` 메모리를 거치지 않고 오직 `Off-Heap` 컨테이너 + /// 간의 연산만 허용합니다. + /// + /// # Security Note + /// 입력 컨테이너(input)와 출력 컨테이너(output)의 생명 주기는 이 메소드를 호출한 + /// 외부의 컨텍스트 [SDCScopeContext] 또는 `try-with-resources`가 책임집니다. + /// + /// @param scope 데이터 생명주기를 통제할 UCA 스코프 컨텍스트 + /// @param input Base64로 인코딩된 데이터를 담고 있는 입력 컨테이너 + /// @return 디코딩된 원본 데이터가 담긴 새로운 보안 컨테이너 (스코프에 귀속됨) + /// @throws ELIBSecurityProcessException 네이티브 연산 실패 또는 메모리 할당 오류 시 발생 + public static SensitiveDataContainer decode(final SDCScopeContext scope, final SensitiveDataContainer input) throws ELIBSecurityProcessException { + if (scope == null) + throw new ELIBSecurityIllegalArgumentException("유효하지 않은 스코프 컨텍스트입니다!"); + if (input == null || !InternalNativeBridge.unwrapArena(input).scope().isAlive()) + throw new ELIBSecurityIllegalArgumentException("유효하지 않거나 이미 소거된 입력 컨테이너입니다!"); final long inputLen = InternalNativeBridge.unwrapMemorySegment(input).byteSize(); - final int maxRequired = (int) ((inputLen / 4 + 1) * 3); - - // 임시 컨테이너 생성 (try-with-resources로 자동 소거 보장) - try (SensitiveDataContainer tempOutput = new SensitiveDataContainer(maxRequired)) { - - long result = (long) EntLibNativeManager.call(Function.Base64_decode).invokeExact( - InternalNativeBridge.unwrapMemorySegment(input), - inputLen, - InternalNativeBridge.unwrapMemorySegment(tempOutput), - (long) maxRequired - ); - - if (result < 0) { - throw new RuntimeException("디코딩 중 네이티브 오류 발생 (error code): " + result); - } - - // 실제 크기에 맞는 반환용 컨테이너 생성 (소유권 이전용) - // result(실제 길이)만큼만 할당 - SensitiveDataContainer exactOutput = new SensitiveDataContainer((int) result); - - try { - // 데이터 복사 - MemorySegment src = InternalNativeBridge.unwrapMemorySegment(tempOutput); - MemorySegment dst = InternalNativeBridge.unwrapMemorySegment(exactOutput); - MemorySegment.copy(src, 0, dst, 0, result); - return exactOutput; - } catch (Exception e) { - exactOutput.close(); // 복사 중 예외 시 반환용 컨테이너도 닫음 - throw e; - } - // 메소드 종료 시 tempOutput.close()가 자동 호출 - + // Base64 디코딩 시 필요한 최대 버퍼 크기 계산 (패딩 길이를 무시한 최대 보수값) + final int maxRequired = (int) ((inputLen * 3) / 4); + final SensitiveDataContainer output = scope.allocate(maxRequired); + + // FFIStandard 구조체를 정의하기 위한 임시 Arena + try (Arena transientArena = Arena.ofConfined()) { + MemorySegment inputFFIStandard = ConstableFactory.Std.allocateJOStandard(transientArena, input); + MemorySegment outputFFIStandard = ConstableFactory.Std.allocateJOStandard(transientArena, output); + + NativeProcessResult result = ConstableFactory.Base64.base64Decode(inputFFIStandard, outputFFIStandard); + if (!result.isSuccess()) + throw new ELIBSecurityProcessException("Rust 네이티브 측 Base64 디코딩 실패 (상태 코드: " + result.getStatusCode() + ")"); } catch (Throwable t) { - throw new RuntimeException("FFM API 디코딩 호출 실패", t); + if (t instanceof ELIBSecurityProcessException) throw (ELIBSecurityProcessException) t; + throw new ELIBSecurityProcessException("Base64 디코딩 FFI 호출 중 치명적 예외가 발생했습니다!", t); } + + return output; } } \ No newline at end of file diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/encode/Hex.java b/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/encode/Hex.java new file mode 100644 index 0000000..f8275e7 --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/encode/Hex.java @@ -0,0 +1,104 @@ +package space.qu4nt.entanglementlib.security.crypto.encode; + +import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityProcessException; +import space.qu4nt.entanglementlib.core.exception.security.unchecked.ELIBSecurityIllegalArgumentException; +import space.qu4nt.entanglementlib.security.data.InternalNativeBridge; +import space.qu4nt.entanglementlib.security.data.SDCScopeContext; +import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer; +import space.qu4nt.entanglementlib.security.entlibnative.ConstableFactory; +import space.qu4nt.entanglementlib.security.entlibnative.NativeProcessResult; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; + +/// 네이티브 메모리 기반의 안전한 `Hex` 인코딩 및 디코딩 유틸리티입니다. +/// 모든 입출력은 [SensitiveDataContainer]를 통해 소유권(ownership)이 통제되며, +/// 가비지 컬렉터가 관리하는 자바 `heap` 메모리에 민감 데이터를 노출하지 않습니다. +public final class Hex { + + private Hex() { + throw new AssertionError("cannot access"); + } + + /// 제공된 보안 컨테이너의 데이터를 Hex로 인코딩합니다. + /// Zero-Trust 원칙에 따라 JVM `Heap` 메모리를 거치지 않고 오직 `Off-Heap` 컨테이너 + /// 간의 연산만 허용합니다. + /// + /// # Security Note + /// 입력 컨테이너(input)와 출력 컨테이너(output)의 생명 주기는 이 메소드를 호출한 + /// 외부의 컨텍스트 [SDCScopeContext] 또는 `try-with-resources`가 책임집니다. + /// + /// @param scope 데이터 상호 작용을 수행할 보안 컨테이너 스코프 + /// @param input 인코딩 타겟 컨테이너 + /// @return Hex 인코딩 결과가 담긴 새로운 보안 컨테이너 + public static SensitiveDataContainer encode(final SDCScopeContext scope, final SensitiveDataContainer input) throws ELIBSecurityProcessException { + if (scope == null) + throw new ELIBSecurityIllegalArgumentException("유효하지 않은 스코프 컨텍스트입니다!"); + if (input == null || !InternalNativeBridge.unwrapArena(input).scope().isAlive()) + throw new ELIBSecurityIllegalArgumentException("유효하지 않거나 이미 소거된 입력 컨테이너입니다!"); + + final long inputLen = InternalNativeBridge.unwrapMemorySegment(input).byteSize(); + if (inputLen > Long.MAX_VALUE / 2) + throw new ELIBSecurityProcessException("Base64 인코딩 허용 메모리 한계를 초과했습니다!"); + // Hex 인코딩 시 필요한 정확한 버퍼 크기 계산 (원본 * 2) + final long required = inputLen * 2; + final SensitiveDataContainer output = scope.allocate(required); + + try (Arena transientArena = Arena.ofConfined()) { + MemorySegment inputFFIStandard = ConstableFactory.Std.allocateJOStandard(transientArena, input); + MemorySegment outputFFIStandard = ConstableFactory.Std.allocateJOStandard(transientArena, output); + + NativeProcessResult result = ConstableFactory.Hex.hexEncode(inputFFIStandard, outputFFIStandard); + if (!result.isSuccess()) + throw new ELIBSecurityProcessException("Rust 네이티브 측 Hex 인코딩 실패 (상태 코드: " + result.getStatusCode() + ")"); + } catch (Throwable t) { + if (t instanceof ELIBSecurityProcessException) throw (ELIBSecurityProcessException) t; + throw new ELIBSecurityProcessException("Hex 인코딩 FFI 호출 중 치명적 예외가 발생했습니다!", t); + } + return output; + } + + /// `Base64`로 인코딩된 민감 데이터를 디코딩하는 메소드입니다. + /// Zero-Trust 원칙에 따라 JVM `Heap` 메모리를 거치지 않고 오직 `Off-Heap` 컨테이너 + /// 간의 연산만 허용합니다. + /// + /// # Security Note + /// 입력 컨테이너(input)와 출력 컨테이너(output)의 생명 주기는 이 메소드를 호출한 + /// 외부의 컨텍스트 [SDCScopeContext] 또는 `try-with-resources`가 책임집니다. + /// + /// @param scope 데이터 생명주기를 통제할 UCA 스코프 컨텍스트 + /// @param input Base64로 인코딩된 데이터를 담고 있는 입력 컨테이너 + /// @return 디코딩된 원본 데이터가 담긴 새로운 보안 컨테이너 (스코프에 귀속됨) + /// @throws ELIBSecurityProcessException 네이티브 연산 실패 또는 메모리 할당 오류 시 발생 + public static SensitiveDataContainer decode(final SDCScopeContext scope, final SensitiveDataContainer input) throws ELIBSecurityProcessException { + if (scope == null) + throw new ELIBSecurityIllegalArgumentException("유효하지 않은 스코프 컨텍스트입니다!"); + if (input == null || !InternalNativeBridge.unwrapArena(input).scope().isAlive()) + throw new ELIBSecurityIllegalArgumentException("유효하지 않거나 이미 소거된 입력 컨테이너입니다!"); + + final long inputLen = InternalNativeBridge.unwrapMemorySegment(input).byteSize(); + + // 비밀 데이터가 아니므로 일반 분기문을 사용 + // 홀수 길이를 즉각 거부하여 침묵적 절삭(Truncation) 방지 + if (inputLen % 2 != 0) + throw new ELIBSecurityIllegalArgumentException("유효하지 않은 Hex 인코딩 데이터입니다 (홀수 길이)!"); + + // Hex 디코딩 시 필요한 정확한 버퍼 크기 계산 (원본 / 2) + // (디코딩은 크기가 줄어들므로 inputLen에 대한 최대 한계(오버플로우) 체크가 생략됩니다) + final long required = inputLen / 2; + final SensitiveDataContainer output = scope.allocate(required); + + try (Arena transientArena = Arena.ofConfined()) { + MemorySegment inputFFIStandard = ConstableFactory.Std.allocateJOStandard(transientArena, input); + MemorySegment outputFFIStandard = ConstableFactory.Std.allocateJOStandard(transientArena, output); + + NativeProcessResult result = ConstableFactory.Hex.hexDecode(inputFFIStandard, outputFFIStandard); + if (!result.isSuccess()) + throw new ELIBSecurityProcessException("Rust 네이티브 측 Hex 디코딩 실패 (상태 코드: " + result.getStatusCode() + ")"); + } catch (Throwable t) { + if (t instanceof ELIBSecurityProcessException) throw (ELIBSecurityProcessException) t; + throw new ELIBSecurityProcessException("Hex 디코딩 FFI 호출 중 치명적 예외가 발생했습니다!", t); + } + return output; + } +} \ No newline at end of file diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/hash/Hash.java b/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/hash/Hash.java index 46f024a..8f9e7cb 100644 --- a/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/hash/Hash.java +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/hash/Hash.java @@ -5,8 +5,8 @@ import space.qu4nt.entanglementlib.security.data.InternalNativeBridge; import space.qu4nt.entanglementlib.security.data.SDCScopeContext; import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer; -import space.qu4nt.entanglementlib.security.entlibnative.EntLibNativeManager; -import space.qu4nt.entanglementlib.security.entlibnative.Function; +import space.qu4nt.entanglementlib.security.entlibnative.NativeLinker; +import space.qu4nt.entanglementlib.security.entlibnative.NativeComponent; import java.lang.foreign.MemorySegment; import java.lang.invoke.MethodHandle; @@ -24,42 +24,42 @@ public static SensitiveDataContainer sha2( return null; // 길이에 맞는 함수 등록 - Function newContextFunc, updateFunc, finalizeFunc, freeFunc; + NativeComponent newContextFunc, updateFunc, finalizeFunc, freeFunc; switch (length) { case 224 -> { - newContextFunc = Function.SHA2_224_New; - updateFunc = Function.SHA2_224_Update; - finalizeFunc = Function.SHA2_224_Finalize; - freeFunc = Function.SHA2_224_Free; + newContextFunc = NativeComponent.SHA2_224_New; + updateFunc = NativeComponent.SHA2_224_Update; + finalizeFunc = NativeComponent.SHA2_224_Finalize; + freeFunc = NativeComponent.SHA2_224_Free; } case 256 -> { - newContextFunc = Function.SHA2_256_New; - updateFunc = Function.SHA2_256_Update; - finalizeFunc = Function.SHA2_256_Finalize; - freeFunc = Function.SHA2_256_Free; + newContextFunc = NativeComponent.SHA2_256_New; + updateFunc = NativeComponent.SHA2_256_Update; + finalizeFunc = NativeComponent.SHA2_256_Finalize; + freeFunc = NativeComponent.SHA2_256_Free; } case 384 -> { - newContextFunc = Function.SHA2_384_New; - updateFunc = Function.SHA2_384_Update; - finalizeFunc = Function.SHA2_384_Finalize; - freeFunc = Function.SHA2_384_Free; + newContextFunc = NativeComponent.SHA2_384_New; + updateFunc = NativeComponent.SHA2_384_Update; + finalizeFunc = NativeComponent.SHA2_384_Finalize; + freeFunc = NativeComponent.SHA2_384_Free; } case 512 -> { - newContextFunc = Function.SHA2_512_New; - updateFunc = Function.SHA2_512_Update; - finalizeFunc = Function.SHA2_512_Finalize; - freeFunc = Function.SHA2_512_Free; + newContextFunc = NativeComponent.SHA2_512_New; + updateFunc = NativeComponent.SHA2_512_Update; + finalizeFunc = NativeComponent.SHA2_512_Finalize; + freeFunc = NativeComponent.SHA2_512_Free; } default -> throw new ELIBSecurityProcessException("불가능한 길이"); } // 컨텍스트 생성 - MemorySegment ctx = (MemorySegment) EntLibNativeManager.call(newContextFunc).invokeExact(); + MemorySegment ctx = (MemorySegment) NativeLinker.call(newContextFunc).invokeExact(); boolean isCtxConsumed = false; // double-free 방지를 위한 상태 플래그 try { // 데이터 업데이트 - MethodHandle updateMH = EntLibNativeManager.call(updateFunc); + MethodHandle updateMH = NativeLinker.call(updateFunc); MemorySegment dataSeg = InternalNativeBridge.unwrapMemorySegment(input); int status = (int) updateMH.invokeExact(ctx, dataSeg, dataSeg.byteSize()); if (status != 0) { @@ -67,20 +67,20 @@ public static SensitiveDataContainer sha2( } // 연산 수행 -> SecureBuffer* 반환 (ctx 소유권 소비됨) - MemorySegment secureBufferPtr = (MemorySegment) EntLibNativeManager.call(finalizeFunc) + MemorySegment secureBufferPtr = (MemorySegment) NativeLinker.call(finalizeFunc) .invokeExact(ctx); isCtxConsumed = true; // 성공적으로 finalize 되었으므로 플래그 전환 if (secureBufferPtr.equals(MemorySegment.NULL)) throw new ELIBSecurityProcessException("해시 연산 결과가 null입니다!"); - return EntLibNativeManager.transferNativeBufferBindToContext( + return NativeLinker.transferNativeBufferBindToContext( scope, secureBufferPtr ); // 이 작업 내에서 버퍼 소거가 진행됨 } finally { // 예외 발생 등으로 인해 finalize가 호출되지 않은 경우에만 early free if (!isCtxConsumed && ctx != null && !ctx.equals(MemorySegment.NULL)) { - EntLibNativeManager.call(freeFunc).invokeExact(ctx); + NativeLinker.call(freeFunc).invokeExact(ctx); } } } @@ -95,42 +95,42 @@ public static SensitiveDataContainer sha3( return null; // 길이에 맞는 함수 등록 - Function newContextFunc, updateFunc, finalizeFunc, freeFunc; + NativeComponent newContextFunc, updateFunc, finalizeFunc, freeFunc; switch (length) { case 224 -> { - newContextFunc = Function.SHA3_224_New; - updateFunc = Function.SHA3_224_Update; - finalizeFunc = Function.SHA3_224_Finalize; - freeFunc = Function.SHA3_224_Free; + newContextFunc = NativeComponent.SHA3_224_New; + updateFunc = NativeComponent.SHA3_224_Update; + finalizeFunc = NativeComponent.SHA3_224_Finalize; + freeFunc = NativeComponent.SHA3_224_Free; } case 256 -> { - newContextFunc = Function.SHA3_256_New; - updateFunc = Function.SHA3_256_Update; - finalizeFunc = Function.SHA3_256_Finalize; - freeFunc = Function.SHA3_256_Free; + newContextFunc = NativeComponent.SHA3_256_New; + updateFunc = NativeComponent.SHA3_256_Update; + finalizeFunc = NativeComponent.SHA3_256_Finalize; + freeFunc = NativeComponent.SHA3_256_Free; } case 384 -> { - newContextFunc = Function.SHA3_384_New; - updateFunc = Function.SHA3_384_Update; - finalizeFunc = Function.SHA3_384_Finalize; - freeFunc = Function.SHA3_384_Free; + newContextFunc = NativeComponent.SHA3_384_New; + updateFunc = NativeComponent.SHA3_384_Update; + finalizeFunc = NativeComponent.SHA3_384_Finalize; + freeFunc = NativeComponent.SHA3_384_Free; } case 512 -> { - newContextFunc = Function.SHA3_512_New; - updateFunc = Function.SHA3_512_Update; - finalizeFunc = Function.SHA3_512_Finalize; - freeFunc = Function.SHA3_512_Free; + newContextFunc = NativeComponent.SHA3_512_New; + updateFunc = NativeComponent.SHA3_512_Update; + finalizeFunc = NativeComponent.SHA3_512_Finalize; + freeFunc = NativeComponent.SHA3_512_Free; } default -> throw new ELIBSecurityProcessException("불가능한 길이"); } // 컨텍스트 생성 - MemorySegment ctx = (MemorySegment) EntLibNativeManager.call(newContextFunc).invokeExact(); + MemorySegment ctx = (MemorySegment) NativeLinker.call(newContextFunc).invokeExact(); boolean isCtxConsumed = false; // double-free 방지를 위한 상태 플래그 try { // 데이터 업데이트 - MethodHandle updateMH = EntLibNativeManager.call(updateFunc); + MethodHandle updateMH = NativeLinker.call(updateFunc); MemorySegment dataSeg = InternalNativeBridge.unwrapMemorySegment(input); int status = (int) updateMH.invokeExact(ctx, dataSeg, dataSeg.byteSize()); if (status != 0) { @@ -138,20 +138,20 @@ public static SensitiveDataContainer sha3( } // 연산 수행 -> SecureBuffer* 반환 (ctx 소유권 소비됨) - MemorySegment secureBufferPtr = (MemorySegment) EntLibNativeManager.call(finalizeFunc) + MemorySegment secureBufferPtr = (MemorySegment) NativeLinker.call(finalizeFunc) .invokeExact(ctx); isCtxConsumed = true; // 성공적으로 finalize 되었으므로 플래그 전환 if (secureBufferPtr.equals(MemorySegment.NULL)) throw new ELIBSecurityProcessException("해시 연산 결과가 null입니다!"); - return EntLibNativeManager.transferNativeBufferBindToContext( + return NativeLinker.transferNativeBufferBindToContext( scope, secureBufferPtr ); // 이 작업 내에서 버퍼 소거가 진행됨 } finally { // 예외 발생 등으로 인해 finalize가 호출되지 않은 경우에만 early free if (!isCtxConsumed && ctx != null && !ctx.equals(MemorySegment.NULL)) { - EntLibNativeManager.call(freeFunc).invokeExact(ctx); + NativeLinker.call(freeFunc).invokeExact(ctx); } } } @@ -167,30 +167,30 @@ public static SensitiveDataContainer sha3Shake( return null; // 길이에 맞는 함수 등록 - Function newContextFunc, updateFunc, finalizeFunc, freeFunc; + NativeComponent newContextFunc, updateFunc, finalizeFunc, freeFunc; switch (length) { case 128 -> { - newContextFunc = Function.SHA3_SHAKE128_New; - updateFunc = Function.SHA3_SHAKE128_Update; - finalizeFunc = Function.SHA3_SHAKE128_Finalize; - freeFunc = Function.SHA3_SHAKE128_Free; + newContextFunc = NativeComponent.SHA3_SHAKE128_New; + updateFunc = NativeComponent.SHA3_SHAKE128_Update; + finalizeFunc = NativeComponent.SHA3_SHAKE128_Finalize; + freeFunc = NativeComponent.SHA3_SHAKE128_Free; } case 256 -> { - newContextFunc = Function.SHA3_SHAKE256_New; - updateFunc = Function.SHA3_SHAKE256_Update; - finalizeFunc = Function.SHA3_SHAKE256_Finalize; - freeFunc = Function.SHA3_SHAKE256_Free; + newContextFunc = NativeComponent.SHA3_SHAKE256_New; + updateFunc = NativeComponent.SHA3_SHAKE256_Update; + finalizeFunc = NativeComponent.SHA3_SHAKE256_Finalize; + freeFunc = NativeComponent.SHA3_SHAKE256_Free; } default -> throw new ELIBSecurityProcessException("불가능한 길이"); } // 컨텍스트 생성 - MemorySegment ctx = (MemorySegment) EntLibNativeManager.call(newContextFunc).invokeExact(); + MemorySegment ctx = (MemorySegment) NativeLinker.call(newContextFunc).invokeExact(); boolean isCtxConsumed = false; // double-free 방지를 위한 상태 플래그 try { // 데이터 업데이트 - MethodHandle updateMH = EntLibNativeManager.call(updateFunc); + MethodHandle updateMH = NativeLinker.call(updateFunc); MemorySegment dataSeg = InternalNativeBridge.unwrapMemorySegment(input); int status = (int) updateMH.invokeExact(ctx, dataSeg, dataSeg.byteSize()); if (status != 0) { @@ -198,20 +198,20 @@ public static SensitiveDataContainer sha3Shake( } // 연산 수행 -> SecureBuffer* 반환 (ctx 소유권 소비됨) - MemorySegment secureBufferPtr = (MemorySegment) EntLibNativeManager.call(finalizeFunc) + MemorySegment secureBufferPtr = (MemorySegment) NativeLinker.call(finalizeFunc) .invokeExact(ctx, byteOutLen); isCtxConsumed = true; // 성공적으로 finalize 되었으므로 플래그 전환 if (secureBufferPtr.equals(MemorySegment.NULL)) throw new ELIBSecurityProcessException("해시 연산 결과가 null입니다."); - return EntLibNativeManager.transferNativeBufferBindToContext( + return NativeLinker.transferNativeBufferBindToContext( scope, secureBufferPtr ); // 이 작업 내에서 버퍼 소거가 진행됨 } finally { // 예외 발생 등으로 인해 finalize가 호출되지 않은 경우에만 early free if (!isCtxConsumed && ctx != null && !ctx.equals(MemorySegment.NULL)) { - EntLibNativeManager.call(freeFunc).invokeExact(ctx); + NativeLinker.call(freeFunc).invokeExact(ctx); } } } diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/rng/RNG.java b/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/rng/RNG.java index 7650b1a..c5dfa20 100644 --- a/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/rng/RNG.java +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/rng/RNG.java @@ -6,8 +6,8 @@ import space.qu4nt.entanglementlib.security.data.InternalNativeBridge; import space.qu4nt.entanglementlib.security.data.SDCScopeContext; import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer; -import space.qu4nt.entanglementlib.security.entlibnative.EntLibNativeManager; -import space.qu4nt.entanglementlib.security.entlibnative.Function; +import space.qu4nt.entanglementlib.security.entlibnative.NativeLinker; +import space.qu4nt.entanglementlib.security.entlibnative.NativeComponent; import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; @@ -38,19 +38,19 @@ public static SensitiveDataContainer generateRNG(final byte entropyStrategy, MemorySegment errFlag = localArena.allocate(ValueLayout.JAVA_BYTE); // 혼합 난수 생성기(mixed rng) 인스턴스 초기화 - MemorySegment rngPtr = (MemorySegment) EntLibNativeManager.call(Function.RNG_MIXED_New_With_Strategy) + MemorySegment rngPtr = (MemorySegment) NativeLinker.call(NativeComponent.RNG_MIXED_New_With_Strategy) .invokeExact(entropyStrategy, errFlag); checkError(errFlag.get(ValueLayout.JAVA_BYTE, 0)); MemorySegment secureBufPtr = null; try { // 난수 버퍼 생성 - secureBufPtr = (MemorySegment) EntLibNativeManager.call(Function.RNG_MIXED_Generate) + secureBufPtr = (MemorySegment) NativeLinker.call(NativeComponent.RNG_MIXED_Generate) .invokeExact(rngPtr, length, errFlag); checkError(errFlag.get(ValueLayout.JAVA_BYTE, 0)); // 러스트 영역의 보안 버퍼(secure buffer)에서 실제 데이터 포인터 추출 - MemorySegment dataPtr = (MemorySegment) EntLibNativeManager.call(Function.Callee_Secure_Buffer_Data) + MemorySegment dataPtr = (MemorySegment) NativeLinker.call(NativeComponent.Callee_Secure_Buffer_Data) .invokeExact(secureBufPtr); MemorySegment nativeDataSegment = dataPtr.reinterpret(length); @@ -64,10 +64,10 @@ public static SensitiveDataContainer generateRNG(final byte entropyStrategy, } finally { // 러스트 힙에 할당된 포인터의 강제 해제 및 소거 유도 if (secureBufPtr != null && !secureBufPtr.equals(MemorySegment.NULL)) { - EntLibNativeManager.call(Function.Callee_Secure_Buffer_Free).invokeExact(secureBufPtr); + NativeLinker.call(NativeComponent.Callee_Secure_Buffer_Free).invokeExact(secureBufPtr); } if (rngPtr != null && !rngPtr.equals(MemorySegment.NULL)) { - EntLibNativeManager.call(Function.RNG_MIXED_Free).invokeExact(rngPtr); + NativeLinker.call(NativeComponent.RNG_MIXED_Free).invokeExact(rngPtr); } } } catch (Throwable e) { diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/data/SDCScopeContext.java b/security/src/main/java/space/qu4nt/entanglementlib/security/data/SDCScopeContext.java index c08aa8d..02c1317 100644 --- a/security/src/main/java/space/qu4nt/entanglementlib/security/data/SDCScopeContext.java +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/data/SDCScopeContext.java @@ -2,10 +2,12 @@ import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; +import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityProcessException; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; /// 보안 작업 흐름 내에서 생성되는 모든 민감 데이터 컨테이너를 추적하고, /// 스코프 종료 시 일괄 소거(zeroize)를 보장하는 컨텍스트 클래스입니다. @@ -20,7 +22,14 @@ public final class SDCScopeContext implements AutoCloseable { /// 스코프 내에서 새로운 [SensitiveDataContainer]를 할당하는 메소드입니다. /// 생성된 컨테이너는 자동으로 현재 스코프에 바인딩됩니다. - public SensitiveDataContainer allocate(int size) { + public SensitiveDataContainer allocate(int size) throws ELIBSecurityProcessException { + checkAlive(); + SensitiveDataContainer container = new SensitiveDataContainer(size); + trackedContainers.add(container); + return container; + } + + public SensitiveDataContainer allocate(final long size) throws ELIBSecurityProcessException { checkAlive(); SensitiveDataContainer container = new SensitiveDataContainer(size); trackedContainers.add(container); @@ -28,7 +37,7 @@ public SensitiveDataContainer allocate(int size) { } /// 기존 바이트 배열로부터 데이터 소유권을 이전받는 컨테이너를 생성하는 메소드입니다. - public SensitiveDataContainer allocate(byte[] from, boolean forceWipe) { + public SensitiveDataContainer allocate(byte[] from, boolean forceWipe) throws ELIBSecurityProcessException { checkAlive(); SensitiveDataContainer container = new SensitiveDataContainer(from, forceWipe); trackedContainers.add(container); diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/data/SensitiveDataContainer.java b/security/src/main/java/space/qu4nt/entanglementlib/security/data/SensitiveDataContainer.java index 459f5f7..e4ff33e 100644 --- a/security/src/main/java/space/qu4nt/entanglementlib/security/data/SensitiveDataContainer.java +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/data/SensitiveDataContainer.java @@ -11,8 +11,10 @@ import org.jetbrains.annotations.NotNull; import space.qu4nt.entanglementlib.annotations.CallerResponsibility; import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityProcessException; -import space.qu4nt.entanglementlib.security.entlibnative.EntLibNativeManager; -import space.qu4nt.entanglementlib.security.entlibnative.Function; +import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityUnsafeUsageException; +import space.qu4nt.entanglementlib.core.exception.security.critical.ELIBSecurityCritical; +import space.qu4nt.entanglementlib.security.entlibnative.ConstableFactory; +import space.qu4nt.entanglementlib.security.entlibnative.NativeProcessResult; import java.io.IOException; import java.lang.foreign.Arena; @@ -102,7 +104,7 @@ public class SensitiveDataContainer implements AutoCloseable { @Getter(AccessLevel.PACKAGE) private final MemorySegment memorySegment; - /// 네이티브 메모리에 전달받은 정수 값(바이트 크기) 만큼의 메모리 세그먼트를 + /// 네이티브 메모리에 전달받은 정수 `int` 값(바이트 크기) 만큼의 메모리 세그먼트를 /// 생성하여 이 인스턴스를 생성합니다. /// /// # Safety @@ -112,13 +114,34 @@ public class SensitiveDataContainer implements AutoCloseable { /// 또한, 이 생성자는 제거 예정은 없으나 권장되는 사용이 아닙니다. [SDCScopeContext]를 /// 통해 세션식 보안 작업을 수행하세요. /// - /// @param allocateSIze 바이트 크기 + /// @param allocateSize `int` 바이트 크기 /// @see #runScope(int, SDCConsumer) 스코프 작업 종료 시 자원 소거를 보장하는 정적 메소드 /// @see #callScope(int, SDCFunction) 스코프 작업 종료 후 자원을 소거하고 가공된 결과를 반환하는 정적 메소드 @CallerResponsibility("try-with-resource 사용 또는 close 메소드 직접 호출 필수") - public SensitiveDataContainer(final int allocateSIze) { + public SensitiveDataContainer(final int allocateSize) throws ELIBSecurityProcessException { this.arena = HeuristicArenaFactory.intelligenceCreateArena(); - this.memorySegment = Objects.requireNonNull(arena.allocate(allocateSIze)); + this.memorySegment = Objects.requireNonNull(arena.allocate(allocateSize)); + ConstableFactory.Std.systemCallMemoryLock(false, memorySegment); // TODO: 실제 OS 수정 + } + + /// 네이티브 메모리에 전달받은 정수 `long` 값(바이트 크기) 만큼의 메모리 세그먼트를 + /// 생성하여 이 인스턴스를 생성합니다. + /// + /// # Safety + /// 이 생성자를 통해 인스턴스를 생성하면 호출자가 부담하는 보안 책임이 발생합니다. + /// 특별한 경우가 아닌 이상 이 방식을 통한 생성은 권장하지 않습니다. + /// + /// 또한, 이 생성자는 제거 예정은 없으나 권장되는 사용이 아닙니다. [SDCScopeContext]를 + /// 통해 세션식 보안 작업을 수행하세요. + /// + /// @param allocateSize `long` 바이트 크기 + /// @see #runScope(int, SDCConsumer) 스코프 작업 종료 시 자원 소거를 보장하는 정적 메소드 + /// @see #callScope(int, SDCFunction) 스코프 작업 종료 후 자원을 소거하고 가공된 결과를 반환하는 정적 메소드 + @CallerResponsibility("try-with-resource 사용 또는 close 메소드 직접 호출 필수") + public SensitiveDataContainer(final long allocateSize) throws ELIBSecurityProcessException { + this.arena = HeuristicArenaFactory.intelligenceCreateArena(); + this.memorySegment = Objects.requireNonNull(arena.allocate(allocateSize)); + ConstableFactory.Std.systemCallMemoryLock(false, memorySegment); // TODO: 실제 OS 수정 } /// 원본 바이트 배열을 전달받고 네이티브 메모리에 바인딩하여 이 인스턴스를 생성합니다. @@ -130,7 +153,8 @@ public SensitiveDataContainer(final int allocateSIze) { /// # Safety /// 이 생성자를 통해 인스턴스를 생성하면 호출자가 부담하는 보안 책임이 발생합니다. /// 특별한 경우가 아닌 이상 이 방식을 통한 생성은 권장하지 않습니다. 또한, 결국 - /// `heap` 메모리에 데이터를 노출하는 것은 위험합니다. + /// `heap` 메모리에 데이터를 노출하는 것은 위험합니다. `forceWipe` 플래그를 + /// `true`로 설정하는 편이 권장됩니다. /// /// 또한, 이 생성자는 제거 예정은 없으나 권장되는 사용이 아닙니다. [SDCScopeContext]를 /// 통해 세션식 보안 작업을 수행하세요. @@ -139,21 +163,24 @@ public SensitiveDataContainer(final int allocateSIze) { /// @param forceWipe 인스턴스에 소유권 이전 여부 /// @see #runScope(int, SDCConsumer) 스코프 작업 종료 시 자원 소거를 보장하는 정적 메소드 /// @see #callScope(int, SDCFunction) 스코프 작업 종료 후 자원을 소거하고 가공된 결과를 반환하는 정적 메소드 - @CallerResponsibility("try-with-resource 사용 또는 close 메소드 직접 호출 필수") - public SensitiveDataContainer(final byte @NotNull [] from, boolean forceWipe) { + @CallerResponsibility({ + "try-with-resource 사용 또는 close 메소드 직접 호출 필수", + "원본 byte[] 입력 권장" + }) + public SensitiveDataContainer(final byte @NotNull [] from, boolean forceWipe) throws ELIBSecurityProcessException { this.arena = HeuristicArenaFactory.intelligenceCreateArena(); this.memorySegment = Objects.requireNonNull(arena.allocateFrom(ValueLayout.JAVA_BYTE, from)); if (forceWipe) Arrays.fill(from, (byte) 0); + ConstableFactory.Std.systemCallMemoryLock(false, memorySegment); // TODO: 실제 OS 수정 } - /** - * 보안 컨테이너의 생명주기를 자동으로 관리하는 실행 메소드입니다. - * Execute-Around-Pattern이 적용되어 작업 완료 즉시 메모리가 소거됨을 보장합니다. - * - * @param allocateSize 할당할 버퍼 크기 - * @param action 컨테이너를 사용하여 수행할 보안 로직 - */ + /// 보안 컨테이너의 생명주기를 자동으로 관리하는 실행 메소드입니다. + /// Execute-Around-Pattern이 적용되어 작업 완료 즉시 메모리가 소거됨을 보장합니다. + /// + /// @param allocateSize 할당할 버퍼 크기 + /// @param action 컨테이너를 사용하여 수행할 보안 로직 + /// @throws ELIBSecurityProcessException 스코프 내 작업 수행 중 발생 가능한 예외 public static void runScope(int allocateSize, SDCConsumer action) throws ELIBSecurityProcessException { try (SensitiveDataContainer sdc = new SensitiveDataContainer(allocateSize)) { action.accept(sdc); @@ -166,13 +193,18 @@ public static void runScope(int allocateSize, SDCConsumer action) throws ELIBSec /// @param allocateSize 할당할 버퍼 크기 /// @param action 컨테이너를 사용하여 수행할 계산 로직 /// @return 계산 결과 - public static R callScope(int allocateSize, SDCFunction action) throws ELIBSecurityProcessException { + /// @throws ELIBSecurityProcessException 스코프 내 작업 수행 중 발생 가능한 예외 + /// @throws ELIBSecurityUnsafeUsageException 올바르지 않거나 위험한 반환 시 발생하는 예외 + public static R callScope(int allocateSize, SDCFunction action) throws ELIBSecurityProcessException, ELIBSecurityUnsafeUsageException { try (SensitiveDataContainer sdc = new SensitiveDataContainer(allocateSize)) { - return action.apply(sdc); + R r = action.apply(sdc); + if (r instanceof byte[]) + throw new ELIBSecurityUnsafeUsageException("SDC 작업 수행에 따른 반환값이 바이트 배열입니다!"); + return r; } } - public static void transmitZeroCopy(SensitiveDataContainer sdc, WritableByteChannel channel) throws ELIBSecurityProcessException { + public static void transmitZeroCopy(final SensitiveDataContainer sdc, final WritableByteChannel channel) throws ELIBSecurityProcessException { if (!sdc.getArena().scope().isAlive()) { throw new IllegalStateException("이미 소거 완료되었거나 유효하지 않은 컨테이너입니다!"); } @@ -198,7 +230,6 @@ public static void transmitZeroCopy(SensitiveDataContainer sdc, WritableByteChan @Override public void close() { - // 더 이상 하위 바인딩(bindings)을 관리하지 않아 로직 수정 // // 스레드 안전성과 다중 호출 시의 멱등성을 보장하기 위해 인스턴스 락 사용 synchronized (this) { // 이미 닫힌 경우 early-return @@ -208,15 +239,15 @@ public void close() { } try { - // 호출자 측 네이티브 메모리 완벽 소거 & 할당 해제 - EntLibNativeManager - .call(Function.Caller_Secure_Buffer_Wipe) - .invokeExact(this.memorySegment, this.memorySegment.byteSize()); - } catch (Throwable e) { - // 네이티브 소거 실패는 치명적 보안 이벤트이기 떄문에 에러 출력 - log.error("치명적 보안 예외가 발생했습니다!", e); + // TODO: 아래 메소드는 FFIStandard 구조체 레이아웃만 받음. 하지만 현재 sdc는 this.memorySegement 값이 해당 구조체만 들어오는게 아님. 이에따라 추가 조치 필요 + NativeProcessResult result = ConstableFactory.Std.joepOrder(this.memorySegment); + if (result.isSuccess()) + log.debug("'{}' 스레드 JOEP 명령 -> Rust측 소거 완료", Thread.currentThread().getName()); + } catch (ELIBSecurityProcessException e) { + throw new ELIBSecurityCritical(e); } finally { - // 예외 발생 여부와 상관없이 접근 채널은 반드시 닫음 + // 소거 후 메모리 락 해제 + ConstableFactory.Std.systemCallMemoryUnlock(false, memorySegment); // TODO: 실제 OS 수정 this.arena.close(); } } diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/ConstableFactory.java b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/ConstableFactory.java new file mode 100644 index 0000000..3426d18 --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/ConstableFactory.java @@ -0,0 +1,415 @@ +package space.qu4nt.entanglementlib.security.entlibnative; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityProcessException; +import space.qu4nt.entanglementlib.core.exception.security.critical.ELIBSecurityCritical; +import space.qu4nt.entanglementlib.core.exception.security.critical.ELIBSecurityNativeCritical; +import space.qu4nt.entanglementlib.security.data.InternalNativeBridge; +import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.StructLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.VarHandle; +import java.util.Map; +import java.util.Objects; + +/// 내부적으로 핫 패스 최적화를 위한 핵심 [java.lang.constant.Constable] 정적 캐시를 수행하기 위한 클래스를 정의한 클래스 +/// +/// 개별 클래스는 지연 로딩 패턴을 사용 +/// +/// TODO: 특정 constable은 entlib-native 바이너리가 없으면 사용 불가능하도록 +@ApiStatus.Internal +public final class ConstableFactory { + + static final Map withoutOSMethodHandles = NativeLinker.withoutOSMethodHandles; + + public static @Nullable MethodHandle getImportedComponentMethodHandle(final @NotNull NativeComponent component) { + if (withoutOSMethodHandles.containsKey(component)) + return withoutOSMethodHandles.get(component); + return null; + } + + public static void processImportedComponentMethodHandle(final @NotNull MethodHandle handle, final HandleProcessConsumer process) throws ELIBSecurityProcessException { + if (handle == null) return; + try { + Objects.requireNonNull(process).accept(handle); + } catch (Throwable e) { + throw new ELIBSecurityProcessException("네이티브 함수 핸들링 작업 중 예외가 발생했습니다!", e); + } + } + + public static void processImportedComponentMethodHandle(final @NotNull NativeComponent component, final HandleProcessConsumer process) throws ELIBSecurityProcessException { + @Nullable MethodHandle handle = getImportedComponentMethodHandle(component); + if (handle == null) return; + try { + Objects.requireNonNull(process).accept(handle); + } catch (Throwable e) { + throw new ELIBSecurityProcessException("네이티브 함수 '" + component.getFunctionInfo().getFunctionName() + "' 핸들링 작업 중 예외가 발생했습니다!", e); + } + } + + static Object wrapInvokeGlobal(final T o, final Object... val) throws ELIBSecurityProcessException { + try { + return o.invokeExact(val); + } catch (Throwable e) { + throw new ELIBSecurityProcessException("글로벌 네이티브 함수 '" + o.toString() + "' 실행 중 예외가 발생했습니다!", e); + } + } + + static NativeProcessResult wrapInvoke(final T o, final @Nullable Class additionalDataType, final Object... val) throws ELIBSecurityProcessException { + try { + MemorySegment resultPtr = (MemorySegment) o.invokeExact(val); + return new NativeProcessResult<>(new FFIStructEntLibResult<>( + (byte) Std.STRUCT_ENTLIB_RESULT_type_id.get(resultPtr), + (byte) Std.STRUCT_ENTLIB_RESULT_status.get(resultPtr), + additionalDataType == null ? null : additionalDataType.cast(Std.STRUCT_ENTLIB_RESULT_data.get(resultPtr)) + )); + } catch (Throwable e) { + if (e.getClass().equals(ClassCastException.class) && additionalDataType != null) + throw new ELIBSecurityProcessException("네이티브 함수 '" + o + "'의 data 값을 '" + + additionalDataType.getCanonicalName() + "' 타입으로 캐스팅 할 수 없습니다!", e); + throw new ELIBSecurityProcessException("네이티브 함수 '" + o.toString() + "' 실행 중 예외가 발생했습니다!", e); + } + } + + static Object wrapInvoke(final T o, final @Nullable Class additionalDataType, final @NotNull String additionalLog, final Object... val) throws ELIBSecurityProcessException { + try { + MemorySegment resultPtr = (MemorySegment) o.invokeExact(val); + return new NativeProcessResult<>(new FFIStructEntLibResult<>( + (byte) Std.STRUCT_ENTLIB_RESULT_type_id.get(resultPtr), + (byte) Std.STRUCT_ENTLIB_RESULT_status.get(resultPtr), + additionalDataType == null ? null : additionalDataType.cast(Std.STRUCT_ENTLIB_RESULT_data.get(resultPtr)) + )); + } catch (Throwable e) { + if (e.getClass().equals(ClassCastException.class) && additionalDataType != null) + throw new ELIBSecurityProcessException("네이티브 함수 '" + o + "'의 data 값을 '" + + additionalDataType.getCanonicalName() + "' 타입으로 캐스팅 할 수 없습니다!", e); + throw new ELIBSecurityProcessException("네이티브 함수 '" + o.toString() + "' 실행 중 예외가 발생했습니다! 상세: " + additionalLog, e); + } + } + + public static final class Custom { +// static final MethodHandle FUNC_SUM_GET; + + static { +// FUNC_SUM_GET = entlibMethodHandles.get(NativeComponent.FUNC_SUM_GET); + } + } + + /// FFI 경계 호출에서 기본적으로(자주) 사용되는 컴포넌트의 핸들러를 정의한 클래스입니다. + public static final class Std { + // os std + static final MethodHandle OS_SC_VIRTUAL_LOCK; + static final MethodHandle OS_SC_VIRTUAL_UNLOCK; + static final MethodHandle OS_SC_MLOCK; + static final MethodHandle OS_SC_MUNLOCK; + + // 네이티브 함수 결과 래핑 + static final VarHandle STRUCT_ENTLIB_RESULT_type_id; + static final VarHandle STRUCT_ENTLIB_RESULT_status; + static final VarHandle STRUCT_ENTLIB_RESULT_data; + + // JO 패턴의 경우, 작업 종료 시 이 핸들러를 사용하여 Rust 에게 메모리 할당 해제 지시 + static final MethodHandle FUNC_JOEP; + + // JO 패턴의 경우, 데이터를 래핑하는 데 이 핸들러를 사용 (Rust로 전송) + public static final VarHandle STRUCT_FFI_STANDARD_ptr; + public static final VarHandle STRUCT_FFI_STANDARD_len; + public static final VarHandle STRUCT_FFI_STANDARD_is_rust_owned; + + static { + // OSStd + final Map osDefaultMethodHandles = NativeLinker.osDefaultMethodHandles; + OS_SC_VIRTUAL_LOCK = osDefaultMethodHandles.get(NativeComponent.OS_SC_VIRTUAL_LOCK); + OS_SC_VIRTUAL_UNLOCK = osDefaultMethodHandles.get(NativeComponent.OS_SC_VIRTUAL_UNLOCK); + OS_SC_MLOCK = osDefaultMethodHandles.get(NativeComponent.OS_SC_MLOCK); + OS_SC_MUNLOCK = osDefaultMethodHandles.get(NativeComponent.OS_SC_MUNLOCK); + + // EntLibResult + final StructLayout entlibResult = NativeComponent.STRUCT_ENTLIB_RESULT.getStructInfo().toStructLayout(); + STRUCT_ENTLIB_RESULT_type_id = entlibResult.varHandle(MemoryLayout.PathElement.groupElement("type_id")); + STRUCT_ENTLIB_RESULT_status = entlibResult.varHandle(MemoryLayout.PathElement.groupElement("status")); + STRUCT_ENTLIB_RESULT_data = entlibResult.varHandle(MemoryLayout.PathElement.groupElement("data")); + + // JOEP order + FUNC_JOEP = getImportedComponentMethodHandle(NativeComponent.FUNC_JOEP); + + // FFIStandard + final StructLayout ffiStandard = NativeComponent.STRUCT_FFI_STANDARD.getStructInfo().toStructLayout(); + STRUCT_FFI_STANDARD_ptr = ffiStandard.varHandle(MemoryLayout.PathElement.groupElement("ptr")); + STRUCT_FFI_STANDARD_len = ffiStandard.varHandle(MemoryLayout.PathElement.groupElement("len")); + STRUCT_FFI_STANDARD_is_rust_owned = ffiStandard.varHandle(MemoryLayout.PathElement.groupElement("is_rust_owned")); + } + + public static void systemCallMemoryLock(boolean windows, final @NotNull MemorySegment target) { + int result; + try { + if (windows) { + result = (int) wrapInvokeGlobal(OS_SC_VIRTUAL_LOCK, target, target.byteSize()); + if (result == 0) + throw new ELIBSecurityCritical(""" + Windows OS System Call -> VirtualLock 실패! 다음의 지침을 참고할 수 있습니다. + - 프로세스의 작업 집합(Working Set) 제한을 확인하세요. + - VirtualLock을 호출할 메모리 영역은 반드시 VirtualAlloc 등을 통해 MEM_COMMIT 상태로 커밋되어 있어야 합니다. + - PAGE_NOACCESS로 보호된 메모리 영역은 잠글 수 없습니다. 읽기/쓰기 권한이 있어야 합니다. + - VirtualLock은 페이지 단위로 작동합니다. 지정한 주소(lpAddress)와 크기(dwSize)가 페이지 경계에 걸쳐 있으면 관련 페이지가 모두 잠겨야 합니다. + - 특수 권한(SE_LOCK_MEMORY_PRIVILEGE)이 필요할 수 있습니다."""); + } else { + result = (int) wrapInvokeGlobal(OS_SC_MLOCK, target, target.byteSize()); + if (result != 0) + throw new ELIBSecurityNativeCritical(""" + Unix OS System Call -> mlock 실패! 다음의 지침을 참고할 수 있습니다. + - RLIMIT_MEMLOCK (사용자별 잠금 가능 메모리 제한)을 초과했는지 확인하세요. + - 시스템 전체 메모리가 부족하여 page를 잠그지 못할 수 있습니다. + - 프로세스에 CAP_IPC_LOCK 기능이 없거나 루트(root) 권한이 아닐 수 있습니다."""); + } + } catch (ELIBSecurityProcessException e) { + throw new ELIBSecurityNativeCritical("치명 오류"); + } + } + + public static void systemCallMemoryUnlock(boolean windows, final @NotNull MemorySegment target) { + int result; + try { + if (windows) { + result = (int) wrapInvokeGlobal(OS_SC_VIRTUAL_UNLOCK, target, target.byteSize()); + if (result == 0) + throw new ELIBSecurityCritical(String.format(""" + Windows OS System Call -> VirtualLock 실패! (code: %d) 다음의 지침을 따를 수 있습니다. + - 해당 메모리 영역이 잠겨 있지 않은 상태일 수 있습니다. + - lpAddress (시작 주소)와 dwSize (크기)가 유효한 메모리 범위를 가리키지 않을 수 있습니다. + - VirtualUnlock은 해당 함수를 호출하는 프로세스의 작업 집합(Working Set)에 있는 메모리가 아닐 수 있습니다. + - Windows 버전마다 프로세스가 잠글 수 있는 페이지 수에 제한이 있으며, 이 제한을 초과했을 수 있습니다.""", result)); + } else { + result = (int) wrapInvokeGlobal(OS_SC_MUNLOCK, target, target.byteSize()); + if (result != 0) + throw new ELIBSecurityNativeCritical(String.format(""" + Unix OS System Call -> mlock 실패! (code: %d) 다음의 지침을 따를 수 있습니다. + - RLIMIT_MEMLOCK (사용자별 잠금 가능 메모리 제한)을 초과했는지 확인하세요. + - 시스템 전체 메모리가 부족하여 page를 잠그지 못할 수 있습니다. + - 프로세스에 CAP_IPC_LOCK 기능이 없거나 루트(root) 권한이 아닐 수 있습니다.""", result)); + } + } catch (ELIBSecurityProcessException e) { + throw new ELIBSecurityNativeCritical("치명 오류"); + } + } + + public static NativeProcessResult joepOrder(final @NotNull MemorySegment target) throws ELIBSecurityProcessException { + return wrapInvoke(FUNC_JOEP, null, target); + } + + /// Rust FFI 통신을 위한 표준 규격 구조체를 안전하게 할당하고 초기화하는 메소드입니다. + /// 직접적인 메모리 오프셋 접근을 차단하고 [VarHandle]을 통한 캡슐화된 주입을 보장합니다. + /// + /// @param arena FFI 구조체가 할당될 단기 생명주기 제어 객체 + /// @param targetPtr 민감 데이터가 위치한 네이티브 메모리 포인터 + /// @param length 데이터의 실제 크기 (오버플로우 방지를 위해 `long` 타입 사용) + /// @param isRustOwned 소유권 플래그 + /// @return 초기화가 완료된 FFIStandard 구조체의 메모리 세그먼트 + public static MemorySegment allocateFFIStandard(final @NotNull Arena arena, + final @NotNull MemorySegment targetPtr, + final long length, + final boolean isRustOwned) throws ELIBSecurityProcessException { + try { + // FFIStandard 구조체 레이아웃에 맞춰 안전하게 메모리 할당 + final MemorySegment ffiStruct = arena.allocate( + NativeComponent.STRUCT_FFI_STANDARD.getStructInfo().toStructLayout() + ); + + // 오프셋 휴먼 에러를 방지하기 위해 미리 초기화된 VarHandle을 통한 안전한 주입 + STRUCT_FFI_STANDARD_ptr.set(ffiStruct, 0L, targetPtr); + STRUCT_FFI_STANDARD_len.set(ffiStruct, 0L, length); + STRUCT_FFI_STANDARD_is_rust_owned.set(ffiStruct, 0L, isRustOwned); + + return ffiStruct; + } catch (IllegalArgumentException | UnsupportedOperationException e) { + throw new ELIBSecurityProcessException("FFIStandard 구조체 메모리 할당 및 초기화 중 치명적 오류가 발생했습니다. (레이아웃/타입 불일치)", e); + } + } + + /// [SensitiveDataContainer]를 기반으로 JO 패턴의 FFIStandard 구조체를 생성하는 헬퍼 메소드입니다. + /// 비즈니스 로직에서 반복되는 보일러플레이트를 제거하고 소유권 정책을 강제합니다. + public static MemorySegment allocateJOStandard(final @NotNull Arena arena, + final @NotNull SensitiveDataContainer sdc) throws ELIBSecurityProcessException { + // UCA 대원칙에 따라 Java가 주도하여 생성한 데이터는 반드시 is_rust_owned = false + // 패키지 내부 브릿지를 통해 내부 포인터에 안전하게 접근 + MemorySegment ptr = InternalNativeBridge.unwrapMemorySegment(sdc); + return allocateFFIStandard( + arena, + ptr, + ptr.byteSize(), + false + ); + } + } + + public static final class Base64 { + static final MethodHandle FUNC_BASE64_ENCODE; + static final MethodHandle FUNC_BASE64_DECODE; + + static { + FUNC_BASE64_ENCODE = withoutOSMethodHandles.get(NativeComponent.FUNC_BASE64_ENCODE); + FUNC_BASE64_DECODE = withoutOSMethodHandles.get(NativeComponent.FUNC_BASE64_DECODE); + } + + public static NativeProcessResult base64Encode(final @NotNull MemorySegment input, final @NotNull MemorySegment output) + throws ELIBSecurityProcessException { + return wrapInvoke(FUNC_BASE64_ENCODE, Long.class, input, output); + } + + public static NativeProcessResult base64Decode(final @NotNull MemorySegment input, final @NotNull MemorySegment output) + throws ELIBSecurityProcessException { + return wrapInvoke(FUNC_BASE64_DECODE, Long.class, input, output); + } + } + + public static final class Hex { + static final MethodHandle FUNC_HEX_ENCODE; + static final MethodHandle FUNC_HEX_DECODE; + + static { + FUNC_HEX_ENCODE = withoutOSMethodHandles.get(NativeComponent.FUNC_HEX_ENCODE); + FUNC_HEX_DECODE = withoutOSMethodHandles.get(NativeComponent.FUNC_HEX_DECODE); + } + + public static NativeProcessResult hexEncode(final @NotNull MemorySegment input, final @NotNull MemorySegment output) + throws ELIBSecurityProcessException { + return wrapInvoke(FUNC_HEX_ENCODE, Long.class, input, output); + } + + public static NativeProcessResult hexDecode(final @NotNull MemorySegment input, final @NotNull MemorySegment output) + throws ELIBSecurityProcessException { + return wrapInvoke(FUNC_HEX_DECODE, Long.class, input, output); + } + } + + public static final class Hash { + + static NativeProcessResult hash(final @NotNull MethodHandle handle, final @NotNull MemorySegment input, final @NotNull MemorySegment output) + throws ELIBSecurityProcessException { + return wrapInvoke(handle, Long.class, input, output); + } + + static NativeProcessResult hashBits(final @NotNull MethodHandle handle, final @NotNull MemorySegment input, final @NotNull MemorySegment output, final byte last, final long bits) + throws ELIBSecurityProcessException { + return wrapInvoke(handle, Long.class, input, output, last, bits); + } + + public static final class SHA2 { + static final MethodHandle FUNC_HASH_SHA2_224; + static final MethodHandle FUNC_HASH_SHA2_256; + static final MethodHandle FUNC_HASH_SHA2_384; + static final MethodHandle FUNC_HASH_SHA2_512; + + static { + FUNC_HASH_SHA2_224 = withoutOSMethodHandles.get(NativeComponent.FUNC_HASH_SHA2_224); + FUNC_HASH_SHA2_256 = withoutOSMethodHandles.get(NativeComponent.FUNC_HASH_SHA2_256); + FUNC_HASH_SHA2_384 = withoutOSMethodHandles.get(NativeComponent.FUNC_HASH_SHA2_384); + FUNC_HASH_SHA2_512 = withoutOSMethodHandles.get(NativeComponent.FUNC_HASH_SHA2_512); + } + + public static NativeProcessResult sha224(final @NotNull MemorySegment input, final @NotNull MemorySegment output) throws ELIBSecurityProcessException { + return hash(FUNC_HASH_SHA2_224, input, output); + } + + public static NativeProcessResult sha256(final @NotNull MemorySegment input, final @NotNull MemorySegment output) throws ELIBSecurityProcessException { + return hash(FUNC_HASH_SHA2_256, input, output); + } + + public static NativeProcessResult sha384(final @NotNull MemorySegment input, final @NotNull MemorySegment output) throws ELIBSecurityProcessException { + return hash(FUNC_HASH_SHA2_384, input, output); + } + + public static NativeProcessResult sha512(final @NotNull MemorySegment input, final @NotNull MemorySegment output) throws ELIBSecurityProcessException { + return hash(FUNC_HASH_SHA2_512, input, output); + } + } + + public static final class SHA3 { + static final MethodHandle FUNC_HASH_SHA3_224; + static final MethodHandle FUNC_HASH_SHA3_256; + static final MethodHandle FUNC_HASH_SHA3_384; + static final MethodHandle FUNC_HASH_SHA3_512; + static final MethodHandle FUNC_HASH_SHA3_224_BITS; + static final MethodHandle FUNC_HASH_SHA3_256_BITS; + static final MethodHandle FUNC_HASH_SHA3_384_BITS; + static final MethodHandle FUNC_HASH_SHA3_512_BITS; + + static final MethodHandle FUNC_HASH_SHA3_SHAKE128; + static final MethodHandle FUNC_HASH_SHA3_SHAKE256; + static final MethodHandle FUNC_HASH_SHA3_SHAKE128_BITS; + static final MethodHandle FUNC_HASH_SHA3_SHAKE256_BITS; + + static { + FUNC_HASH_SHA3_224 = withoutOSMethodHandles.get(NativeComponent.FUNC_HASH_SHA3_224); + FUNC_HASH_SHA3_256 = withoutOSMethodHandles.get(NativeComponent.FUNC_HASH_SHA3_256); + FUNC_HASH_SHA3_384 = withoutOSMethodHandles.get(NativeComponent.FUNC_HASH_SHA3_384); + FUNC_HASH_SHA3_512 = withoutOSMethodHandles.get(NativeComponent.FUNC_HASH_SHA3_512); + FUNC_HASH_SHA3_224_BITS = withoutOSMethodHandles.get(NativeComponent.FUNC_HASH_SHA3_224_BITS); + FUNC_HASH_SHA3_256_BITS = withoutOSMethodHandles.get(NativeComponent.FUNC_HASH_SHA3_256_BITS); + FUNC_HASH_SHA3_384_BITS = withoutOSMethodHandles.get(NativeComponent.FUNC_HASH_SHA3_384_BITS); + FUNC_HASH_SHA3_512_BITS = withoutOSMethodHandles.get(NativeComponent.FUNC_HASH_SHA3_512_BITS); + + FUNC_HASH_SHA3_SHAKE128 = withoutOSMethodHandles.get(NativeComponent.FUNC_HASH_SHA3_SHAKE128); + FUNC_HASH_SHA3_SHAKE256 = withoutOSMethodHandles.get(NativeComponent.FUNC_HASH_SHA3_SHAKE256); + FUNC_HASH_SHA3_SHAKE128_BITS = withoutOSMethodHandles.get(NativeComponent.FUNC_HASH_SHA3_SHAKE128_BITS); + FUNC_HASH_SHA3_SHAKE256_BITS = withoutOSMethodHandles.get(NativeComponent.FUNC_HASH_SHA3_SHAKE256_BITS); + } + + // pure + public static NativeProcessResult sha224(final @NotNull MemorySegment input, final @NotNull MemorySegment output) throws ELIBSecurityProcessException { + return hash(FUNC_HASH_SHA3_224, input, output); + } + + public static NativeProcessResult sha256(final @NotNull MemorySegment input, final @NotNull MemorySegment output) throws ELIBSecurityProcessException { + return hash(FUNC_HASH_SHA3_256, input, output); + } + + public static NativeProcessResult sha384(final @NotNull MemorySegment input, final @NotNull MemorySegment output) throws ELIBSecurityProcessException { + return hash(FUNC_HASH_SHA3_384, input, output); + } + + public static NativeProcessResult sha512(final @NotNull MemorySegment input, final @NotNull MemorySegment output) throws ELIBSecurityProcessException { + return hash(FUNC_HASH_SHA3_512, input, output); + } + + public static NativeProcessResult shake128(final @NotNull MemorySegment input, final @NotNull MemorySegment output) throws ELIBSecurityProcessException { + return hash(FUNC_HASH_SHA3_SHAKE128, input, output); + } + + public static NativeProcessResult shake256(final @NotNull MemorySegment input, final @NotNull MemorySegment output) throws ELIBSecurityProcessException { + return hash(FUNC_HASH_SHA3_SHAKE256, input, output); + } + + // bits + public static NativeProcessResult sha224Bits(final @NotNull MemorySegment input, final @NotNull MemorySegment output, final byte last, final long valid) throws ELIBSecurityProcessException { + return hashBits(FUNC_HASH_SHA3_224_BITS, input, output, last, valid); + } + + public static NativeProcessResult sha256Bits(final @NotNull MemorySegment input, final @NotNull MemorySegment output, final byte last, final long valid) throws ELIBSecurityProcessException { + return hashBits(FUNC_HASH_SHA3_256_BITS, input, output, last, valid); + } + + public static NativeProcessResult sha384Bits(final @NotNull MemorySegment input, final @NotNull MemorySegment output, final byte last, final long valid) throws ELIBSecurityProcessException { + return hashBits(FUNC_HASH_SHA3_384_BITS, input, output, last, valid); + } + + public static NativeProcessResult sha512Bits(final @NotNull MemorySegment input, final @NotNull MemorySegment output, final byte last, final long valid) throws ELIBSecurityProcessException { + return hashBits(FUNC_HASH_SHA3_512_BITS, input, output, last, valid); + } + + public static NativeProcessResult shake128Bits(final @NotNull MemorySegment input, final @NotNull MemorySegment output, final byte last, final long valid) throws ELIBSecurityProcessException { + return hashBits(FUNC_HASH_SHA3_SHAKE128_BITS, input, output, last, valid); + } + + public static NativeProcessResult shake256Bits(final @NotNull MemorySegment input, final @NotNull MemorySegment output, final byte last, final long valid) throws ELIBSecurityProcessException { + return hashBits(FUNC_HASH_SHA3_SHAKE256_BITS, input, output, last, valid); + } + } + } +} \ No newline at end of file diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/EntLibNativeManager.java b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/EntLibNativeManager.java deleted file mode 100644 index 91763ef..0000000 --- a/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/EntLibNativeManager.java +++ /dev/null @@ -1,123 +0,0 @@ -package space.qu4nt.entanglementlib.security.entlibnative; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityProcessException; -import space.qu4nt.entanglementlib.core.exception.security.critical.ELIBSecurityCritical; -import space.qu4nt.entanglementlib.core.exception.security.critical.ELIBSecurityNativeCritical; -import space.qu4nt.entanglementlib.security.data.InternalNativeBridge; -import space.qu4nt.entanglementlib.security.data.SDCScopeContext; -import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer; - -import java.lang.foreign.Linker; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.SymbolLookup; -import java.lang.invoke.MethodHandle; -import java.util.HashMap; -import java.util.Map; - -/// 해당 클래스를 통해 네이티브 함수를 호출하는 경우, 반드시 [NativeLoader]에 의해 -/// 타겟 네이티브 라이브러리가 시스템에 등록(선행)되어 있어야 합니다. -/// -/// @author Q. T. Felix -/// @since 1.1.1 -public final class EntLibNativeManager { - - private static final SymbolLookup lookup; - private static final Linker linker = Linker.nativeLinker(); - - // 동시성 문제 및 런타임 조작을 원천 차단하기 위한 불변 맵(Immutable Map) - private static Map methodHandles; - - // 핫 패스(Hot Path) 최적화를 위한 핵심 MethodHandle 정적 캐시 - @SuppressWarnings({"FieldCanBeLocal", "unused"}) - private static MethodHandle MH_CALLEE_SECURE_BUFFER_DATA; - private static MethodHandle MH_CALLEE_SECURE_BUFFER_LEN; - private static MethodHandle MH_CALLEE_SECURE_BUFFER_FREE; - private static MethodHandle MH_CALLEE_SECURE_BUFFER_COPY_AND_FREE; - - static { - lookup = SymbolLookup.loaderLookup(); - } - - private EntLibNativeManager() { - throw new AssertionError("cannot access"); - } - - static synchronized void setup() { - if (methodHandles != null) return; // 중복 호출 방지 - - Map tempMap = new HashMap<>(); - for (Function function : Function.LOADED) - tempMap.put(function, linkExact(function)); - - // 맵을 불변 상태로 봉인 - methodHandles = Map.copyOf(tempMap); - - // 핵심 기능은 O(1)의 Map 탐색 비용조차 없애기 위해 정적 필드에 다이렉트 바인딩 - MH_CALLEE_SECURE_BUFFER_DATA = methodHandles.get(Function.Callee_Secure_Buffer_Data); - MH_CALLEE_SECURE_BUFFER_LEN = methodHandles.get(Function.Callee_Secure_Buffer_Len); - MH_CALLEE_SECURE_BUFFER_FREE = methodHandles.get(Function.Callee_Secure_Buffer_Free); - MH_CALLEE_SECURE_BUFFER_COPY_AND_FREE = methodHandles.get(Function.SecureBuffer_CopyAndFree); - } - - private static MethodHandle linkExact(final @NotNull Function function) { - return linker.downcallHandle( - lookup.find(function.getFunctionName()).orElseThrow(() -> - new ELIBSecurityNativeCritical("네이티브에서 함수 '" + function.getFunctionName() + "'을(를) 찾을 수 없습니다.")), - function.getDescriptor() - ); - } - - public static MethodHandle call(final @NotNull Function function) { - MethodHandle handle = methodHandles.get(function); - if (handle == null) - throw new ELIBSecurityNativeCritical("네이티브에서 함수 '" + function.getFunctionName() + "'이(가) 등록되지 않았습니다."); - return handle; - } - - public static @NotNull SensitiveDataContainer transferNativeBufferBindToContext( - final @NotNull SDCScopeContext context, - final @NotNull MemorySegment data - ) throws ELIBSecurityProcessException { - // Rust 측에서 메모리 해제가 완료되었는지 추적하여 Double-Free를 방지하기 위한 플래그 - boolean isFreedByNative = false; - - try { - long len = (long) MH_CALLEE_SECURE_BUFFER_LEN.invokeExact(data); - - // Off-heap 메모리 할당 (OutOfMemoryError 등 예외 발생 가능 구간) - SensitiveDataContainer result = context.allocate((int) len); - - // 네이티브에서 직접 복사 및 원본 즉시 소거 (단 1회의 FFI 호출로 압축) - long copied = (long) MH_CALLEE_SECURE_BUFFER_COPY_AND_FREE.invokeExact( - data, - InternalNativeBridge.unwrapMemorySegment(result), - len - ); - - // invokeExact가 예외 없이 통과했다면 Rust의 Box::from_raw에 의해 무조건 소멸 - isFreedByNative = true; - - // 용량 불일치 등 논리적 오류 검증 - if (copied != len) - throw new ELIBSecurityCritical("네이티브 버퍼 복사 중 크기 불일치 또는 오류가 발생했습니다."); - - return result; - } catch (Throwable e) { - // context.allocate() 실패 등 Rust로 제어권이 넘어가기 전에 예외가 발생한 경우 메모리 누수 방지 - if (!isFreedByNative) { - try { - MH_CALLEE_SECURE_BUFFER_FREE.invokeExact(data); - } catch (Throwable ex) { - // 원본 예외 유실을 막기 위해 억제된 예외로 병합 - e.addSuppressed(ex); - throw new ELIBSecurityCritical("네이티브 데이터를 소거하는 중 치명적 오류가 발생했습니다!", e); - } - } - - // 이미 Critical 예외인 경우 그대로 던짐 - if (e instanceof ELIBSecurityCritical) throw (ELIBSecurityCritical) e; - throw new ELIBSecurityProcessException("네이티브 버퍼 획득 및 전송 중 예외가 발생했습니다!", e); - } - } -} \ No newline at end of file diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/FFIStandardWrapper.java b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/FFIStandardWrapper.java new file mode 100644 index 0000000..e2197e6 --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/FFIStandardWrapper.java @@ -0,0 +1,25 @@ +package space.qu4nt.entanglementlib.security.entlibnative; + +import lombok.Getter; +import lombok.Setter; +import space.qu4nt.entanglementlib.security.data.InternalNativeBridge; +import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer; + +import java.lang.foreign.MemorySegment; +import java.util.Objects; + +@Getter +@Setter +public class FFIStandardWrapper { + private MemorySegment target; + private long length; + + private FFIStandardWrapper(MemorySegment target) { + this.target = target; + this.length = target.byteSize(); + } + + public static FFIStandardWrapper createFFIStruct(final SensitiveDataContainer sdc) { + return new FFIStandardWrapper(InternalNativeBridge.unwrapMemorySegment(Objects.requireNonNull(sdc, "SDC"))); + } +} diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/FFIStructEntLibResult.java b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/FFIStructEntLibResult.java new file mode 100644 index 0000000..011eeb7 --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/FFIStructEntLibResult.java @@ -0,0 +1,4 @@ +package space.qu4nt.entanglementlib.security.entlibnative; + +public record FFIStructEntLibResult(byte typeId, byte status, A data) { +} diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/Function.java b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/Function.java deleted file mode 100644 index 430dfa5..0000000 --- a/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/Function.java +++ /dev/null @@ -1,266 +0,0 @@ -package space.qu4nt.entanglementlib.security.entlibnative; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import org.jetbrains.annotations.NotNull; - -import java.lang.foreign.FunctionDescriptor; -import java.lang.foreign.MemoryLayout; -import java.lang.foreign.ValueLayout; -import java.util.ArrayList; -import java.util.List; - -/// NOTE: 사용자는 확장되거나 구체화된 entlib-native 바이너리를 제공했을 수 있음. 따라서 이 클래스 형식은 유효 -@Getter -@AllArgsConstructor(access = AccessLevel.PRIVATE) -public class Function { - - protected static final List LOADED = new ArrayList<>(); - - // 보안 버퍼 엔드포인트 - // 피호출자 할당 - public static final Function Callee_Secure_Buffer_Data = Function.of("entlib_secure_buffer_data", ValueLayout.ADDRESS, ValueLayout.ADDRESS); - public static final Function Callee_Secure_Buffer_Len = Function.of("entlib_secure_buffer_len", ValueLayout.JAVA_LONG, ValueLayout.ADDRESS); - public static final Function Callee_Secure_Buffer_Free = Function.ofVoid("entlib_secure_buffer_free", ValueLayout.ADDRESS); // 보안 작업에서는 공통적으로 사용 - // 호출자 할당 - public static final Function Caller_Secure_Buffer_Wipe = Function.ofVoid("entanglement_secure_wipe", ValueLayout.ADDRESS, ValueLayout.JAVA_LONG); - - // 데이터 상호 작용을 위한 엔드포인트 - public static final Function SecureBuffer_Data = Function.of("entlib_secure_buffer_data", ValueLayout.ADDRESS, ValueLayout.ADDRESS); - public static final Function SecureBuffer_Len = Function.of("entlib_secure_buffer_len", ValueLayout.JAVA_LONG, ValueLayout.ADDRESS); - public static final Function SecureBuffer_Free = Function.ofVoid("entlib_secure_buffer_free", ValueLayout.ADDRESS); - public static final Function SecureBuffer_View = Function.of("entlib_secure_buffer_view", - MemoryLayout.structLayout( - ValueLayout.ADDRESS.withName("data"), - ValueLayout.JAVA_LONG.withName("len") - ), - ValueLayout.ADDRESS - ); - public static final Function SecureBuffer_CopyAndFree = Function.of("entlib_secure_buffer_copy_and_free", ValueLayout.JAVA_LONG, - ValueLayout.ADDRESS, - ValueLayout.ADDRESS, - ValueLayout.JAVA_LONG - ); - - // Base64 엔드포인트 - public static final Function Base64_encode = Function.of("entlib_b64_encode_caller_alloc", ValueLayout.JAVA_LONG, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG); - public static final Function Base64_decode = Function.of("entlib_b64_decode_caller_alloc", ValueLayout.JAVA_LONG, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG); // todo; err_flag 파라미터 추가 확인 필요 - - // Hash 엔드포인트 - // SHA2 - public static final Function SHA2_224_New = Function.of("entlib_sha224_new", ValueLayout.ADDRESS); - public static final Function SHA2_224_Update = Function.of("entlib_sha224_update", ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG); - public static final Function SHA2_224_Finalize = Function.of("entlib_sha224_finalize", ValueLayout.ADDRESS, ValueLayout.ADDRESS); - public static final Function SHA2_224_Free = Function.ofVoid("entlib_sha224_free", ValueLayout.ADDRESS); - - public static final Function SHA2_256_New = Function.of("entlib_sha256_new", ValueLayout.ADDRESS); - public static final Function SHA2_256_Update = Function.of("entlib_sha256_update", ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG); - public static final Function SHA2_256_Finalize = Function.of("entlib_sha256_finalize", ValueLayout.ADDRESS, ValueLayout.ADDRESS); - public static final Function SHA2_256_Free = Function.ofVoid("entlib_sha256_free", ValueLayout.ADDRESS); - - public static final Function SHA2_384_New = Function.of("entlib_sha384_new", ValueLayout.ADDRESS); - public static final Function SHA2_384_Update = Function.of("entlib_sha384_update", ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG); - public static final Function SHA2_384_Finalize = Function.of("entlib_sha384_finalize", ValueLayout.ADDRESS, ValueLayout.ADDRESS); - public static final Function SHA2_384_Free = Function.ofVoid("entlib_sha384_free", ValueLayout.ADDRESS); - - public static final Function SHA2_512_New = Function.of("entlib_sha512_new", ValueLayout.ADDRESS); - public static final Function SHA2_512_Update = Function.of("entlib_sha512_update", ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG); - public static final Function SHA2_512_Finalize = Function.of("entlib_sha512_finalize", ValueLayout.ADDRESS, ValueLayout.ADDRESS); - public static final Function SHA2_512_Free = Function.ofVoid("entlib_sha512_free", ValueLayout.ADDRESS); - - // SHA3 - public static final Function SHA3_224_New = Function.of("entlib_sha3_224_new", ValueLayout.ADDRESS); - public static final Function SHA3_224_Update = Function.of("entlib_sha3_224_update", ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG); - public static final Function SHA3_224_Finalize = Function.of("entlib_sha3_224_finalize", ValueLayout.ADDRESS, ValueLayout.ADDRESS); - public static final Function SHA3_224_Free = Function.ofVoid("entlib_sha3_224_free", ValueLayout.ADDRESS); - - public static final Function SHA3_256_New = Function.of("entlib_sha3_256_new", ValueLayout.ADDRESS); - public static final Function SHA3_256_Update = Function.of("entlib_sha3_256_update", ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG); - public static final Function SHA3_256_Finalize = Function.of("entlib_sha3_256_finalize", ValueLayout.ADDRESS, ValueLayout.ADDRESS); - public static final Function SHA3_256_Free = Function.ofVoid("entlib_sha3_256_free", ValueLayout.ADDRESS); - - public static final Function SHA3_384_New = Function.of("entlib_sha3_384_new", ValueLayout.ADDRESS); - public static final Function SHA3_384_Update = Function.of("entlib_sha3_384_update", ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG); - public static final Function SHA3_384_Finalize = Function.of("entlib_sha3_384_finalize", ValueLayout.ADDRESS, ValueLayout.ADDRESS); - public static final Function SHA3_384_Free = Function.ofVoid("entlib_sha3_384_free", ValueLayout.ADDRESS); - - public static final Function SHA3_512_New = Function.of("entlib_sha3_512_new", ValueLayout.ADDRESS); - public static final Function SHA3_512_Update = Function.of("entlib_sha3_512_update", ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG); - public static final Function SHA3_512_Finalize = Function.of("entlib_sha3_512_finalize", ValueLayout.ADDRESS, ValueLayout.ADDRESS); - public static final Function SHA3_512_Free = Function.ofVoid("entlib_sha3_512_free", ValueLayout.ADDRESS); - - public static final Function SHA3_SHAKE128_New = Function.of("entlib_sha3_shake128_new", ValueLayout.ADDRESS); - public static final Function SHA3_SHAKE128_Update = Function.of("entlib_sha3_shake128_update", ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG); - public static final Function SHA3_SHAKE128_Finalize = Function.of("entlib_sha3_shake128_finalize", ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG); - public static final Function SHA3_SHAKE128_Free = Function.ofVoid("entlib_sha3_shake128_free", ValueLayout.ADDRESS); - - public static final Function SHA3_SHAKE256_New = Function.of("entlib_sha3_shake256_new", ValueLayout.ADDRESS); - public static final Function SHA3_SHAKE256_Update = Function.of("entlib_sha3_shake256_update", ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG); - public static final Function SHA3_SHAKE256_Finalize = Function.of("entlib_sha3_shake256_finalize", ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG); - public static final Function SHA3_SHAKE256_Free = Function.ofVoid("entlib_sha3_shake256_free", ValueLayout.ADDRESS); - - // RNG 엔드포인트 - public static final Function RNG_HW_Generate = Function.of("entlib_rng_hw_generate", ValueLayout.ADDRESS, ValueLayout.JAVA_LONG, ValueLayout.ADDRESS); - public static final Function RNG_HW_Next_Generate = Function.of("entlib_rng_hw_next_generate", ValueLayout.JAVA_BYTE, ValueLayout.ADDRESS); - public static final Function RNG_ANU_Generate = Function.of("entlib_rng_anu_generate", ValueLayout.ADDRESS, ValueLayout.JAVA_LONG, ValueLayout.ADDRESS); - public static final Function RNG_MIXED_New_With_Strategy = Function.of("entlib_rng_mixed_new_with_strategy", ValueLayout.ADDRESS, ValueLayout.JAVA_BYTE, ValueLayout.ADDRESS); - public static final Function RNG_MIXED_New = Function.of("entlib_rng_mixed_new", ValueLayout.ADDRESS, ValueLayout.ADDRESS); - public static final Function RNG_MIXED_Generate = Function.of("entlib_rng_mixed_generate", ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG, ValueLayout.ADDRESS); - public static final Function RNG_MIXED_Free = Function.ofVoid("entlib_rng_mixed_free", ValueLayout.ADDRESS); - - // ChaCha20 엔드포인트 - public static final Function ChaCha20_Process = Function.of("process_chacha20_ffi", ValueLayout.ADDRESS, - ValueLayout.ADDRESS, - ValueLayout.JAVA_LONG, - ValueLayout.ADDRESS, - ValueLayout.JAVA_LONG, - ValueLayout.JAVA_INT, - ValueLayout.ADDRESS, - ValueLayout.JAVA_LONG); - public static final Function ChaCha20_Poly1305_MAC_Generate = Function.of("generate_poly1305_ffi", ValueLayout.ADDRESS, - ValueLayout.ADDRESS, - ValueLayout.JAVA_LONG, - ValueLayout.ADDRESS, - ValueLayout.JAVA_LONG); - public static final Function ChaCha20_Poly1305_Encrypt = Function.of("chacha20_poly1305_encrypt_ffi", ValueLayout.ADDRESS, - ValueLayout.ADDRESS, - ValueLayout.JAVA_LONG, - ValueLayout.ADDRESS, - ValueLayout.JAVA_LONG, - ValueLayout.ADDRESS, - ValueLayout.JAVA_LONG, - ValueLayout.ADDRESS, - ValueLayout.JAVA_LONG); - public static final Function ChaCha20_Poly1305_Decrypt = Function.of("chacha20_poly1305_decrypt_ffi", ValueLayout.ADDRESS, - ValueLayout.ADDRESS, - ValueLayout.JAVA_LONG, - ValueLayout.ADDRESS, - ValueLayout.JAVA_LONG, - ValueLayout.ADDRESS, - ValueLayout.JAVA_LONG, - ValueLayout.ADDRESS, - ValueLayout.JAVA_LONG); - - private final @NotNull String functionName; - private final FunctionDescriptor descriptor; - - public static Function of(final @NotNull String functionName, MemoryLayout returnType, MemoryLayout... args) { - Function r = new Function(functionName, FunctionDescriptor.of(returnType, args)); - LOADED.add(r); - return r; - } - - public static Function ofVoid(final @NotNull String functionName, MemoryLayout... args) { - Function r = new Function(functionName, FunctionDescriptor.ofVoid(args)); - LOADED.add(r); - return r; - } - - public static Function[] chain(Function[] source, Function[]... additional) { - int size = source.length; - for (Function[] specs : additional) { - size += specs.length; - } - - Function[] result = new Function[size]; - - System.arraycopy(source, 0, result, 0, source.length); - int currentPosition = source.length; - - for (Function[] specs : additional) { - System.arraycopy(specs, 0, result, currentPosition, specs.length); - currentPosition += specs.length; - } - - return result; - } - - public static Function[] withCallerSecureBuffer() { - return new Function[]{ - Callee_Secure_Buffer_Data, - Callee_Secure_Buffer_Len, - Callee_Secure_Buffer_Free, - Caller_Secure_Buffer_Wipe - }; - } - - public static Function[] withCalleeSecureBuffer() { - return new Function[]{ - SecureBuffer_Data, - SecureBuffer_Len, - SecureBuffer_Free, - SecureBuffer_View, - SecureBuffer_CopyAndFree - }; - } - - public static Function[] withRNG() { - return new Function[]{ - RNG_HW_Generate, - RNG_HW_Next_Generate, - RNG_ANU_Generate, - RNG_MIXED_New_With_Strategy, - RNG_MIXED_New, - RNG_MIXED_Generate, - RNG_MIXED_Free - }; - } - - public static Function[] withHash(boolean sha2) { - if (sha2) - return new Function[]{ - SHA2_224_New, - SHA2_224_Update, - SHA2_224_Finalize, - SHA2_224_Free, - SHA2_256_New, - SHA2_256_Update, - SHA2_256_Finalize, - SHA2_256_Free, - SHA2_384_New, - SHA2_384_Update, - SHA2_384_Finalize, - SHA2_384_Free, - SHA2_512_New, - SHA2_512_Update, - SHA2_512_Finalize, - SHA2_512_Free - }; - return new Function[] { - SHA3_224_New, - SHA3_224_Update, - SHA3_224_Finalize, - SHA3_224_Free, - SHA3_256_New, - SHA3_256_Update, - SHA3_256_Finalize, - SHA3_256_Free, - SHA3_384_New, - SHA3_384_Update, - SHA3_384_Finalize, - SHA3_384_Free, - SHA3_512_New, - SHA3_512_Update, - SHA3_512_Finalize, - SHA3_512_Free, - SHA3_SHAKE128_New, - SHA3_SHAKE128_Update, - SHA3_SHAKE128_Finalize, - SHA3_SHAKE128_Free, - SHA3_SHAKE256_New, - SHA3_SHAKE256_Update, - SHA3_SHAKE256_Finalize, - SHA3_SHAKE256_Free - }; - } - - public static Function[] withChaCha20() { - return new Function[]{ - ChaCha20_Process, - ChaCha20_Poly1305_MAC_Generate, - ChaCha20_Poly1305_Encrypt, - ChaCha20_Poly1305_Decrypt - }; - } -} diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/HandleProcessConsumer.java b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/HandleProcessConsumer.java new file mode 100644 index 0000000..2523e51 --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/HandleProcessConsumer.java @@ -0,0 +1,9 @@ +package space.qu4nt.entanglementlib.security.entlibnative; + +import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityProcessException; + +@FunctionalInterface +public interface HandleProcessConsumer { + + void accept(T t) throws Throwable; +} diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeComponent.java b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeComponent.java new file mode 100644 index 0000000..9c483a1 --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeComponent.java @@ -0,0 +1,169 @@ +package space.qu4nt.entanglementlib.security.entlibnative; + +import lombok.Getter; +import org.jetbrains.annotations.NotNull; +import space.qu4nt.entanglementlib.annotations.Only; +import space.qu4nt.entanglementlib.security.entlibnative.info.FunctionInfo; +import space.qu4nt.entanglementlib.security.entlibnative.info.StructInfo; + +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.ValueLayout; +import java.util.ArrayList; +import java.util.List; + +/// NOTE: 사용자는 확장되거나 구체화된 entlib-native 바이너리를 제공했을 수 있음. 따라서 이 클래스 형식은 유효 +@Getter +public class NativeComponent { + + protected static final List LOADED = new ArrayList<>(); + + @Only("windows") + static final NativeComponent OS_SC_VIRTUAL_LOCK = NativeComponent.ofOSFunction( + FunctionInfo.of("VirtualLock", ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG)); + @Only("windows") + static final NativeComponent OS_SC_VIRTUAL_UNLOCK = NativeComponent.ofOSFunction( + FunctionInfo.of("VirtualUnlock", ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG)); + @Only("unix") + static final NativeComponent OS_SC_MLOCK = NativeComponent.ofOSFunction( + FunctionInfo.of("mlock", ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG)); + @Only("unix") + static final NativeComponent OS_SC_MUNLOCK = NativeComponent.ofOSFunction( + FunctionInfo.of("munlock", ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG)); + + /// FFI 함수 수행 결과를 받는 구조체 컴포넌트입니다. + static final NativeComponent STRUCT_ENTLIB_RESULT = NativeComponent.ofStruct( + StructInfo.of("EntLibResult", + "type_id", ValueLayout.JAVA_BYTE, + "status", ValueLayout.JAVA_BYTE, + "_PADDING", MemoryLayout.paddingLayout(6), + "data", ValueLayout.ADDRESS)); + /// Java와 Rust 간의 FFI 통신 표준 구조체 컴포넌트입니다. + public static final NativeComponent STRUCT_FFI_STANDARD = NativeComponent.ofStruct( + StructInfo.of("FFIStandard", + "ptr", ValueLayout.ADDRESS, + "len", ValueLayout.JAVA_BYTE, + "is_rust_owned", ValueLayout.JAVA_BOOLEAN)); + /// Java-Owned End Process order 함수 컴포넌트입니다. Java 측 연산 종료 후, Rust에게 메모리 소거를 지시할 때 사용됩니다. + static final NativeComponent FUNC_JOEP = NativeComponent.ofFunction( + FunctionInfo.of("joep", STRUCT_ENTLIB_RESULT.getStructInfo().toStructLayout(), STRUCT_FFI_STANDARD.getStructInfo().toStructLayout())); + + /// Base64 인/디코딩 함수 컴포넌트입니다. + static final NativeComponent FUNC_BASE64_ENCODE; + static final NativeComponent FUNC_BASE64_DECODE; + + static { + final FunctionInfo defaultHashFuncLayout = FunctionInfo.of(null, STRUCT_ENTLIB_RESULT.getStructInfo().toStructLayout(), STRUCT_FFI_STANDARD.getStructInfo().toStructLayout(), STRUCT_FFI_STANDARD.getStructInfo().toStructLayout()); + FUNC_BASE64_ENCODE = NativeComponent.ofFunction(FunctionInfo.of("ffi_base64_encode", defaultHashFuncLayout)); + FUNC_BASE64_DECODE = NativeComponent.ofFunction(FunctionInfo.of("ffi_base64_decode", defaultHashFuncLayout)); + } + + /// Hex 인/디코딩 함수 컴포넌트입니다. + static final NativeComponent FUNC_HEX_ENCODE; + static final NativeComponent FUNC_HEX_DECODE; + + static { + final FunctionInfo defaultHashFuncLayout = FunctionInfo.of(null, STRUCT_ENTLIB_RESULT.getStructInfo().toStructLayout(), STRUCT_FFI_STANDARD.getStructInfo().toStructLayout(), STRUCT_FFI_STANDARD.getStructInfo().toStructLayout()); + FUNC_HEX_ENCODE = NativeComponent.ofFunction(FunctionInfo.of("ffi_hex_encode", defaultHashFuncLayout)); + FUNC_HEX_DECODE = NativeComponent.ofFunction(FunctionInfo.of("ffi_hex_decode", defaultHashFuncLayout)); + } + + /// Hash SHA-2, 3 해시 함수 컴포넌트입니다. + static final NativeComponent FUNC_HASH_SHA2_224; + static final NativeComponent FUNC_HASH_SHA2_256; + static final NativeComponent FUNC_HASH_SHA2_384; + static final NativeComponent FUNC_HASH_SHA2_512; + static final NativeComponent FUNC_HASH_SHA3_224; + static final NativeComponent FUNC_HASH_SHA3_256; + static final NativeComponent FUNC_HASH_SHA3_384; + static final NativeComponent FUNC_HASH_SHA3_512; + static final NativeComponent FUNC_HASH_SHA3_224_BITS; + static final NativeComponent FUNC_HASH_SHA3_256_BITS; + static final NativeComponent FUNC_HASH_SHA3_384_BITS; + static final NativeComponent FUNC_HASH_SHA3_512_BITS; + static final NativeComponent FUNC_HASH_SHA3_SHAKE128; + static final NativeComponent FUNC_HASH_SHA3_SHAKE256; + static final NativeComponent FUNC_HASH_SHA3_SHAKE128_BITS; + static final NativeComponent FUNC_HASH_SHA3_SHAKE256_BITS; + + static { + final FunctionInfo defaultHashFuncLayout = FunctionInfo.of(null, STRUCT_ENTLIB_RESULT.getStructInfo().toStructLayout(), STRUCT_FFI_STANDARD.getStructInfo().toStructLayout(), STRUCT_FFI_STANDARD.getStructInfo().toStructLayout()); + final FunctionInfo defaultHashBitsFuncLayout = FunctionInfo.of(null, defaultHashFuncLayout).andArg(ValueLayout.JAVA_BYTE).andArg(ValueLayout.JAVA_LONG); + FUNC_HASH_SHA2_224 = NativeComponent.ofFunction(FunctionInfo.of("ffi_sha2_224", defaultHashFuncLayout)); + FUNC_HASH_SHA2_256 = NativeComponent.ofFunction(FunctionInfo.of("ffi_sha2_256", defaultHashFuncLayout)); + FUNC_HASH_SHA2_384 = NativeComponent.ofFunction(FunctionInfo.of("ffi_sha2_384", defaultHashFuncLayout)); + FUNC_HASH_SHA2_512 = NativeComponent.ofFunction(FunctionInfo.of("ffi_sha2_512", defaultHashFuncLayout)); + + FUNC_HASH_SHA3_224 = NativeComponent.ofFunction(FunctionInfo.of("ffi_sha3_224", defaultHashFuncLayout)); + FUNC_HASH_SHA3_256 = NativeComponent.ofFunction(FunctionInfo.of("ffi_sha3_256", defaultHashFuncLayout)); + FUNC_HASH_SHA3_384 = NativeComponent.ofFunction(FunctionInfo.of("ffi_sha3_384", defaultHashFuncLayout)); + FUNC_HASH_SHA3_512 = NativeComponent.ofFunction(FunctionInfo.of("ffi_sha3_512", defaultHashFuncLayout)); + FUNC_HASH_SHA3_224_BITS = NativeComponent.ofFunction(FunctionInfo.of("ffi_sha3_224_bits", defaultHashBitsFuncLayout)); + FUNC_HASH_SHA3_256_BITS = NativeComponent.ofFunction(FunctionInfo.of("ffi_sha3_256_bits", defaultHashBitsFuncLayout)); + FUNC_HASH_SHA3_384_BITS = NativeComponent.ofFunction(FunctionInfo.of("ffi_sha3_384_bits", defaultHashBitsFuncLayout)); + FUNC_HASH_SHA3_512_BITS = NativeComponent.ofFunction(FunctionInfo.of("ffi_sha3_512_bits", defaultHashBitsFuncLayout)); + + FUNC_HASH_SHA3_SHAKE128 = NativeComponent.ofFunction(FunctionInfo.of("ffi_shake128", defaultHashFuncLayout)); + FUNC_HASH_SHA3_SHAKE256 = NativeComponent.ofFunction(FunctionInfo.of("ffi_shake256", defaultHashFuncLayout)); + FUNC_HASH_SHA3_SHAKE128_BITS = NativeComponent.ofFunction(FunctionInfo.of("ffi_shake128_bits", defaultHashBitsFuncLayout)); + FUNC_HASH_SHA3_SHAKE256_BITS = NativeComponent.ofFunction(FunctionInfo.of("ffi_shake256_bits", defaultHashBitsFuncLayout)); + } + + @Getter + private final boolean isOsDefault; + private final FunctionInfo functionInfo; + private final StructInfo structInfo; + + private NativeComponent(final boolean isOsDefault, final FunctionInfo functionInfo, final StructInfo structInfo) { + this.isOsDefault = isOsDefault; + this.functionInfo = functionInfo; + this.structInfo = structInfo; + } + + public static NativeComponent ofOSFunction(final @NotNull FunctionInfo functionInfo) { + NativeComponent r = new NativeComponent(true, functionInfo, null); + LOADED.add(r); + return r; + } + + public static NativeComponent ofOSStruct(final @NotNull StructInfo structInfo) { + NativeComponent r = new NativeComponent(true, null, structInfo); + LOADED.add(r); + return r; + } + + public static NativeComponent ofFunction(final @NotNull FunctionInfo functionInfo) { + NativeComponent r = new NativeComponent(false, functionInfo, null); + LOADED.add(r); + return r; + } + + // TODO: Struct Info 클래스 결국 만들어야 겠다... + public static NativeComponent ofStruct(final @NotNull StructInfo structInfo) { + NativeComponent r = new NativeComponent(false, null, structInfo); + LOADED.add(r); + return r; + } + + public static NativeComponent[] chain(NativeComponent[] source, NativeComponent[]... additional) { + int size = source.length; + for (NativeComponent[] specs : additional) { + size += specs.length; + } + + NativeComponent[] result = new NativeComponent[size]; + + System.arraycopy(source, 0, result, 0, source.length); + int currentPosition = source.length; + + for (NativeComponent[] specs : additional) { + System.arraycopy(specs, 0, result, currentPosition, specs.length); + currentPosition += specs.length; + } + + return result; + } + + public boolean isStructComponent() { + return functionInfo == null && structInfo != null; + } +} diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeLinker.java b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeLinker.java new file mode 100644 index 0000000..83bf6b2 --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeLinker.java @@ -0,0 +1,125 @@ +package space.qu4nt.entanglementlib.security.entlibnative; + +import org.jetbrains.annotations.NotNull; +import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityProcessException; +import space.qu4nt.entanglementlib.core.exception.security.critical.ELIBSecurityNativeCritical; +import space.qu4nt.entanglementlib.security.data.SDCScopeContext; +import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer; +import space.qu4nt.entanglementlib.security.entlibnative.info.FunctionInfo; + +import java.lang.foreign.Linker; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SymbolLookup; +import java.lang.invoke.MethodHandle; +import java.util.HashMap; +import java.util.Map; + +/// 해당 클래스를 통해 네이티브 함수를 호출하는 경우, 반드시 [NativeLoader]에 의해 +/// 타겟 네이티브 라이브러리가 시스템에 등록(선행)되어 있어야 합니다. +/// +/// @author Q. T. Felix +/// @since 1.1.1 +public final class NativeLinker { + + private static final Linker linker = Linker.nativeLinker(); + private static final SymbolLookup osDefaultLookup; + private static final SymbolLookup withoutOSLookup; + + // 동시성 문제 및 런타임 조작을 원천 차단하기 위한 불변 맵(Immutable Map) + static Map withoutOSMethodHandles; + static Map osDefaultMethodHandles; + + static { + osDefaultLookup = linker.defaultLookup(); + withoutOSLookup = SymbolLookup.loaderLookup(); + } + + private NativeLinker() { + throw new AssertionError("cannot access"); + } + + /// 이 메소드를 수행하여 정의된 함수 맵은 [ConstableFactory]에서 사용됨 + static synchronized void setup() { + if (withoutOSMethodHandles != null) return; // 중복 호출 방지 + + Map withoutOSFunctionMap = new HashMap<>(); + Map tempOSDefaultFunctionMap = new HashMap<>(); + for (NativeComponent nativeComponent : NativeComponent.LOADED) { + if (nativeComponent.isStructComponent()) continue; // 구조체는 직접 핸들링 + if (nativeComponent.isOsDefault()) { + tempOSDefaultFunctionMap.put(nativeComponent, downcall(true, nativeComponent)); + continue; + } + withoutOSFunctionMap.put(nativeComponent, downcall(false, nativeComponent)); + } + + // 맵을 불변 상태로 봉인 + withoutOSMethodHandles = Map.copyOf(withoutOSFunctionMap); + osDefaultMethodHandles = Map.copyOf(tempOSDefaultFunctionMap); + } + + private static MethodHandle downcall(boolean isOSDefault, final @NotNull NativeComponent nativeComponent) { + final FunctionInfo functionInfo = nativeComponent.getFunctionInfo(); + final String functionName = functionInfo.getFunctionName(); + return linker.downcallHandle(isOSDefault ? + osDefaultLookup.find(functionName).orElseThrow(() -> new ELIBSecurityNativeCritical("OS 기본 바이너리에서 함수 '" + functionName + "'을(를) 찾을 수 없습니다.")) : + withoutOSLookup.find(functionName).orElseThrow(() -> new ELIBSecurityNativeCritical("네이티브에서 함수 '" + functionName + "'을(를) 찾을 수 없습니다.")), + functionInfo.toFunctionDescriptor() + ); + } + + @Deprecated + public static MethodHandle call(final @NotNull NativeComponent nativeComponent) { + MethodHandle handle = withoutOSMethodHandles.get(nativeComponent); + if (handle == null) + throw new ELIBSecurityNativeCritical("네이티브에서 함수 '" + nativeComponent.getFunctionInfo().getFunctionName() + "'이(가) 등록되지 않았습니다."); + return handle; + } + + public static @NotNull SensitiveDataContainer transferNativeBufferBindToContext( + final @NotNull SDCScopeContext context, + final @NotNull MemorySegment data + ) throws ELIBSecurityProcessException { + return null; +// // Rust 측에서 메모리 해제가 완료되었는지 추적하여 Double-Free를 방지하기 위한 플래그 +// boolean isFreedByNative = false; +// +// try { +// long len = (long) MH_CALLEE_SECURE_BUFFER_LEN.invokeExact(data); +// +// // Off-heap 메모리 할당 (OutOfMemoryError 등 예외 발생 가능 구간) +// SensitiveDataContainer result = context.allocate((int) len); +// +// // 네이티브에서 직접 복사 및 원본 즉시 소거 (단 1회의 FFI 호출로 압축) +// long copied = (long) MH_CALLEE_SECURE_BUFFER_COPY_AND_FREE.invokeExact( +// data, +// InternalNativeBridge.unwrapMemorySegment(result), +// len +// ); +// +// // invokeExact가 예외 없이 통과했다면 Rust의 Box::from_raw에 의해 무조건 소멸 +// isFreedByNative = true; +// +// // 용량 불일치 등 논리적 오류 검증 +// if (copied != len) +// throw new ELIBSecurityCritical("네이티브 버퍼 복사 중 크기 불일치 또는 오류가 발생했습니다."); +// +// return result; +// } catch (Throwable e) { +// // context.allocate() 실패 등 Rust로 제어권이 넘어가기 전에 예외가 발생한 경우 메모리 누수 방지 +// if (!isFreedByNative) { +// try { +// MH_CALLEE_SECURE_BUFFER_FREE.invokeExact(data); +// } catch (Throwable ex) { +// // 원본 예외 유실을 막기 위해 억제된 예외로 병합 +// e.addSuppressed(ex); +// throw new ELIBSecurityCritical("네이티브 데이터를 소거하는 중 치명적 오류가 발생했습니다!", e); +// } +// } +// +// // 이미 Critical 예외인 경우 그대로 던짐 +// if (e instanceof ELIBSecurityCritical) throw (ELIBSecurityCritical) e; +// throw new ELIBSecurityProcessException("네이티브 버퍼 획득 및 전송 중 예외가 발생했습니다!", e); +// } + } +} \ No newline at end of file diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeLoader.java b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeLoader.java index c30d2fa..5507d72 100644 --- a/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeLoader.java +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeLoader.java @@ -29,7 +29,7 @@ private NativeLoader() { private static synchronized void loadSuccess() { loaded = true; - EntLibNativeManager.setup(); + NativeLinker.setup(); } public static synchronized void loadNativeLibrary(final @NotNull EntanglementLibSecurityConfig config) { @@ -49,7 +49,7 @@ public static synchronized void loadNativeLibrary(final @NotNull EntanglementLib log.info(exactExternalFile.toString()); - // 사용자가 파일명까지 포함한 절대 경로를 입력했거나, 디렉터리 경로를 입력한 경우 모두 처리 + // 사용자가 파일명까지 포함한 절대 경로를 입력했거나, 디렉토리 경로를 입력한 경우 모두 처리 if (Files.exists(externalPath) && !Files.isDirectory(externalPath)) { System.load(externalPath.toAbsolutePath().toString()); loadSuccess(); diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeProcessResult.java b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeProcessResult.java new file mode 100644 index 0000000..1755a9b --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeProcessResult.java @@ -0,0 +1,101 @@ +package space.qu4nt.entanglementlib.security.entlibnative; + +import lombok.Getter; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; + +@Getter +public class NativeProcessResult { + + private final FFIStructEntLibResult result; + private final boolean success; + private final byte typeId; + private final byte statusCode; + private final String message; + private final @Nullable A additionalData; + + public NativeProcessResult(final FFIStructEntLibResult result) { + this.result = result; + this.success = result.status() == 0; + this.typeId = result.typeId(); + this.statusCode = result.status(); + this.message = message(); + this.additionalData = result.data(); + } + + private String message() { + return MatchMessage.resolve(this.typeId, this.statusCode); + } + + /// typeId(크레이트 식별자) + statusCode(결과 코드)를 메시지 문자열로 매핑하는 클래스입니다. + /// + /// 각 크레이트별 `status` 코드는 정적 초기화 블록에서 + /// [#register(byte, byte, String)]으로 등록합니다. + /// + /// - `status == 0`은 항상 성공으로 판단합니다. + /// - `status != 0`은 할당 등록된 메시지를 반환합니다. + /// + /// @author Q. T. Felix + /// @since 1.1.2 + private static final class MatchMessage { + + // Q. T. Felix TODO: 국제화 너무 힘들다... + + /** + * key: typeId → (key: statusCode → message) + */ + private static final Map> TABLE = new HashMap<>(); + + // Q. T. Felix NOTE: 안티포렌식 관점에서 스테이터스 코드를 통해 오류를 정의하는 건 내부 상태를 유추할 수 있음을 뜻함. + // 이 기능은 수정될 수 있음. + static { + // entlib-native-secure-buffer + register((byte) 0x00, (byte) -1, "외부로부터 받은 포인터가 null입니다!"); + + // entlib-native-ffi (base64) + register((byte) 0x01, (byte) -1, "FFI 입력 또는 출력 FFIStandard 포인터가 null입니다!"); + register((byte) 0x01, (byte) -2, "필요 버퍼 크기 산출 도중 산술 오버플로우가 발생했거나 OS 메모리 lock에 실패했습니다!"); + register((byte) 0x01, (byte) -3, "호출자가 할당한 출력 용량이 필요 크기(((input_len + 2) / 3) * 4)보다 작습니다!"); + register((byte) 0x01, (byte) -4, "호출자가 할당한 출력 용량이 최대 필요 크기((input_len / 4 + 1) * 3)보다 작습니다!"); + register((byte) 0x01, (byte) -5, "유효하지 않은 Base64 문자열(잘못된 길이/패딩/문자) 이거나 메모리 lock에 실패했습니다!"); + register((byte) 0x01, (byte) -1, "into_domain_buffer 함수 실행 결과가 유효하지 않습니다!"); + + // entlib-native-ffi (hex) + register((byte) 0x02, (byte) -1, "FFI 입력 또는 출력 FFIStandard 포인터가 null입니다!"); + + // entlib-native-ffi (sha2) + register((byte) 0x03, (byte) -1, "FFI 입력 또는 출력 FFIStandard 포인터가 null입니다!"); + + // entlib-native-ffi (sha3, shake) + register((byte) 0x04, (byte) -1, "FFI 입력 또는 출력 FFIStandard 포인터가 null입니다!"); + } + + /// 크레이트의 특정 상태 코드에 대한 메시지를 등록하는 메소드입니다. + /// + /// @param typeId 크레이트 식별자 + /// @param status 상태 코드 (음수) + /// @param message 사람이 읽을 수 있는 메시지 + static void register(byte typeId, byte status, String message) { + TABLE.computeIfAbsent(typeId, k -> new HashMap<>()).put(status, message); + } + + /// 크레이트 + 상태 코드 조합으로 메시지를 조회하는 메소드입니다. + /// + /// @param typeId 크레이트 식별자 + /// @param status 상태 코드 + /// @return 매핑된 메시지, 또는 fallback 메시지 + static String resolve(byte typeId, byte status) { + if (status == 0) + return "OK"; + Map statusMap = TABLE.get(typeId); + if (statusMap == null) + return "알 수 없는 크레이트 [typeId=0x" + String.format("%02X", typeId) + "]"; + return statusMap.getOrDefault( + status, + "정의되지 않은 에러 [typeId=0x" + String.format("%02X", typeId) + ", status=" + status + "]" + ); + } + } +} diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeSpecContext.java b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeSpecContext.java index 8765f1a..1a9927f 100644 --- a/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeSpecContext.java +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeSpecContext.java @@ -5,7 +5,7 @@ import java.util.Set; -import static space.qu4nt.entanglementlib.security.entlibnative.Function.*; +import static space.qu4nt.entanglementlib.security.entlibnative.NativeComponent.*; @Getter @Setter @@ -13,84 +13,23 @@ public class NativeSpecContext { private String nativeDirName; private String nativeFilename; - private Set functions; + private Set nativeComponents; - public NativeSpecContext(String nativeDirName, String nativeFilename, Set functions) { + public NativeSpecContext(String nativeDirName, String nativeFilename, Set nativeComponents) { this.nativeDirName = nativeDirName; this.nativeFilename = nativeFilename; - this.functions = functions; + this.nativeComponents = nativeComponents; } - public NativeSpecContext(String nativeDirName, String nativeFilename, Function... functions) { + public NativeSpecContext(String nativeDirName, String nativeFilename, NativeComponent... nativeComponents) { this.nativeDirName = nativeDirName; this.nativeFilename = nativeFilename; - this.functions = Set.of(functions); + this.nativeComponents = Set.of(nativeComponents); } public static NativeSpecContext defaults() { return new NativeSpecContext("/native", "entlib_native_ffi", Set.of( - Callee_Secure_Buffer_Data, - Callee_Secure_Buffer_Len, - Callee_Secure_Buffer_Free, - Caller_Secure_Buffer_Wipe, - SecureBuffer_Data, - SecureBuffer_Len, - SecureBuffer_Free, - SecureBuffer_View, - SecureBuffer_CopyAndFree, - Base64_encode, - Base64_decode, - SHA2_224_New, - SHA2_224_Update, - SHA2_224_Finalize, - SHA2_224_Free, - SHA2_256_New, - SHA2_256_Update, - SHA2_256_Finalize, - SHA2_256_Free, - SHA2_384_New, - SHA2_384_Update, - SHA2_384_Finalize, - SHA2_384_Free, - SHA2_512_New, - SHA2_512_Update, - SHA2_512_Finalize, - SHA2_512_Free, - SHA3_224_New, - SHA3_224_Update, - SHA3_224_Finalize, - SHA3_224_Free, - SHA3_256_New, - SHA3_256_Update, - SHA3_256_Finalize, - SHA3_256_Free, - SHA3_384_New, - SHA3_384_Update, - SHA3_384_Finalize, - SHA3_384_Free, - SHA3_512_New, - SHA3_512_Update, - SHA3_512_Finalize, - SHA3_512_Free, - SHA3_SHAKE128_New, - SHA3_SHAKE128_Update, - SHA3_SHAKE128_Finalize, - SHA3_SHAKE128_Free, - SHA3_SHAKE256_New, - SHA3_SHAKE256_Update, - SHA3_SHAKE256_Finalize, - SHA3_SHAKE256_Free, - RNG_HW_Generate, - RNG_HW_Next_Generate, - RNG_ANU_Generate, - RNG_MIXED_New_With_Strategy, - RNG_MIXED_New, - RNG_MIXED_Generate, - RNG_MIXED_Free, - ChaCha20_Process, - ChaCha20_Poly1305_MAC_Generate, - ChaCha20_Poly1305_Encrypt, - ChaCha20_Poly1305_Decrypt + // TODO: )); } } diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/info/FunctionInfo.java b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/info/FunctionInfo.java new file mode 100644 index 0000000..ee55ff9 --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/info/FunctionInfo.java @@ -0,0 +1,57 @@ +package space.qu4nt.entanglementlib.security.entlibnative.info; + +import lombok.Getter; +import lombok.Setter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.constant.Constable; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.MemoryLayout; +import java.util.List; + +public class FunctionInfo { + + @Getter + @Setter + private String functionName; + private @Nullable MemoryLayout returnType; + private List<@NotNull MemoryLayout> argLayouts; + + private FunctionInfo(String functionName, @Nullable MemoryLayout returnType, List argLayouts) { + this.functionName = functionName; + this.returnType = returnType; + this.argLayouts = argLayouts; + } + + public static FunctionInfo of(final String functionName, @NotNull FunctionInfo ref) { + return new FunctionInfo(functionName, ref.returnType, ref.argLayouts); + } + + public static FunctionInfo ofVoid(final String functionName, @NotNull FunctionInfo ref) { + return new FunctionInfo(functionName, null, ref.argLayouts); + } + + public static FunctionInfo of(final String functionName, @NotNull MemoryLayout returnType, MemoryLayout... args) { + return new FunctionInfo(functionName, returnType, calibration(args)); + } + + public static FunctionInfo ofVoid(final @NotNull String functionName, MemoryLayout... args) { + return new FunctionInfo(functionName, null, calibration(args)); + } + + public FunctionInfo andArg(final @NotNull MemoryLayout additional) { + this.argLayouts.add(additional); + return this; + } + + public FunctionDescriptor toFunctionDescriptor() { + if (returnType == null) + return FunctionDescriptor.ofVoid(argLayouts.toArray(MemoryLayout[]::new)); + return FunctionDescriptor.of(returnType, argLayouts.toArray(MemoryLayout[]::new)); + } + + private static List calibration(MemoryLayout[] args) { + return args.length == 0 ? null : List.of(args); + } +} diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/info/StructInfo.java b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/info/StructInfo.java new file mode 100644 index 0000000..43cf4e1 --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/info/StructInfo.java @@ -0,0 +1,72 @@ +package space.qu4nt.entanglementlib.security.entlibnative.info; + +import lombok.Getter; +import lombok.Setter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.constant.Constable; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.StructLayout; +import java.lang.invoke.VarHandle; +import java.util.Map; + +public class StructInfo { + + @Getter + @Setter + private @NotNull String structName; + private final Map params; + + private StructInfo(@NotNull String structName, Map params) { + this.structName = structName; + this.params = params; + } + + public static StructInfo of(final @NotNull String structName, final @NotNull String paramName, final @NotNull MemoryLayout paramType) { + return new StructInfo(structName, Map.of(paramName, paramType)); + } + + public static StructInfo of(final @NotNull String structName, + final @NotNull String paramName1, final @NotNull MemoryLayout paramType1, + final @NotNull String paramName2, final @NotNull MemoryLayout paramType2) { + return new StructInfo(structName, Map.of(paramName1, paramType1, paramName2, paramType2)); + } + + public static StructInfo of(final @NotNull String structName, + final @NotNull String paramName1, final @NotNull MemoryLayout paramType1, + final @NotNull String paramName2, final @NotNull MemoryLayout paramType2, + final @NotNull String paramName3, final @NotNull MemoryLayout paramType3) { + return new StructInfo(structName, Map.of(paramName1, paramType1, paramName2, paramType2, paramName3, paramType3)); + } + + public static StructInfo of(@NotNull String structName, + final @NotNull String paramName1, final @NotNull MemoryLayout paramType1, + final @NotNull String paramName2, final @NotNull MemoryLayout paramType2, + final @NotNull String paramName3, final @NotNull MemoryLayout paramType3, + final @NotNull String paramName4, final @NotNull MemoryLayout paramType4) { + return new StructInfo(structName, Map.of(paramName1, paramType1, paramName2, paramType2, paramName3, paramType3, paramName4, paramType4)); + } + + public @Nullable MemoryLayout getParameterLayout(final @NotNull String paramName) { + if (params.containsKey(paramName)) + return params.get(paramName); + return null; + } + + public StructLayout toStructLayout() { + return MemoryLayout.structLayout( + params.entrySet().stream() + .map(e -> { + final String key = e.getKey(); + if (key.equals("_PADDING")) return e.getValue(); + return e.getValue().withName(e.getKey()); + }) + .toArray(MemoryLayout[]::new) + ).withName(structName); + } + + public VarHandle accessField(final @NotNull String paramName) { + return toStructLayout().varHandle(MemoryLayout.PathElement.groupElement(paramName)); + } +} diff --git a/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/Base64Test.java b/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/Base64Test.java index d37e76c..4a6248b 100644 --- a/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/Base64Test.java +++ b/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/Base64Test.java @@ -10,7 +10,7 @@ import space.qu4nt.entanglementlib.security.data.InternalNativeBridge; import space.qu4nt.entanglementlib.security.data.SDCScopeContext; import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer; -import space.qu4nt.entanglementlib.security.entlibnative.Function; +import space.qu4nt.entanglementlib.security.entlibnative.NativeComponent; import space.qu4nt.entanglementlib.security.entlibnative.NativeSpecContext; import java.lang.foreign.MemorySegment; @@ -29,12 +29,12 @@ void test() { EntanglementLibSecurityFacade.initialize( EntanglementLibSecurityConfig.create( new NativeSpecContext(System.getenv("ENTLIB_NATIVE_BIN"), "entlib_native_ffi", - Function.Callee_Secure_Buffer_Data, - Function.Callee_Secure_Buffer_Len, - Function.Callee_Secure_Buffer_Free, - Function.Caller_Secure_Buffer_Wipe, - Function.Base64_encode, - Function.Base64_decode), + NativeComponent.Callee_Secure_Buffer_Data, + NativeComponent.Callee_Secure_Buffer_Len, + NativeComponent.Callee_Secure_Buffer_Free, + NativeComponent.Caller_Secure_Buffer_Wipe, + NativeComponent.Base64_encode, + NativeComponent.Base64_decode), HeuristicArenaFactory.ArenaMode.CONFINED) ); diff --git a/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/ChaCha20Test.java b/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/ChaCha20Test.java index 518dfcc..c9a2cee 100644 --- a/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/ChaCha20Test.java +++ b/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/ChaCha20Test.java @@ -10,7 +10,7 @@ import space.qu4nt.entanglementlib.security.data.InternalNativeBridge; import space.qu4nt.entanglementlib.security.data.SDCScopeContext; import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer; -import space.qu4nt.entanglementlib.security.entlibnative.Function; +import space.qu4nt.entanglementlib.security.entlibnative.NativeComponent; import space.qu4nt.entanglementlib.security.entlibnative.NativeSpecContext; import java.lang.foreign.MemorySegment; @@ -27,12 +27,12 @@ static void setUp() { EntanglementLibSecurityFacade.initialize( EntanglementLibSecurityConfig.create( new NativeSpecContext(System.getenv("ENTLIB_NATIVE_BIN"), "entlib_native_ffi", - Function.Callee_Secure_Buffer_Data, - Function.Callee_Secure_Buffer_Len, - Function.Callee_Secure_Buffer_Free, - Function.Caller_Secure_Buffer_Wipe, - Function.ChaCha20_Poly1305_Encrypt, - Function.ChaCha20_Poly1305_Decrypt), + NativeComponent.Callee_Secure_Buffer_Data, + NativeComponent.Callee_Secure_Buffer_Len, + NativeComponent.Callee_Secure_Buffer_Free, + NativeComponent.Caller_Secure_Buffer_Wipe, + NativeComponent.ChaCha20_Poly1305_Encrypt, + NativeComponent.ChaCha20_Poly1305_Decrypt), HeuristicArenaFactory.ArenaMode.CONFINED) ); } diff --git a/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/RNGTest.java b/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/RNGTest.java index 22b3476..dc330a5 100644 --- a/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/RNGTest.java +++ b/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/RNGTest.java @@ -13,7 +13,7 @@ import space.qu4nt.entanglementlib.security.data.InternalNativeBridge; import space.qu4nt.entanglementlib.security.data.SDCScopeContext; import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer; -import space.qu4nt.entanglementlib.security.entlibnative.Function; +import space.qu4nt.entanglementlib.security.entlibnative.NativeComponent; import space.qu4nt.entanglementlib.security.entlibnative.NativeSpecContext; import java.lang.foreign.MemorySegment; @@ -29,10 +29,10 @@ static void setUp() { EntanglementLibSecurityFacade.initialize( EntanglementLibSecurityConfig.create( new NativeSpecContext(System.getenv("ENTLIB_NATIVE_BIN"), "entlib_native_ffi", - Function.chain( - Function.withCalleeSecureBuffer(), - Function.withCallerSecureBuffer(), - Function.withRNG())), + NativeComponent.chain( + NativeComponent.withCalleeSecureBuffer(), + NativeComponent.withCallerSecureBuffer(), + NativeComponent.withRNG())), HeuristicArenaFactory.ArenaMode.CONFINED) ); } diff --git a/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/SHA2HashTest.java b/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/SHA2HashTest.java index d092042..2d720bb 100644 --- a/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/SHA2HashTest.java +++ b/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/SHA2HashTest.java @@ -10,7 +10,7 @@ import space.qu4nt.entanglementlib.security.data.InternalNativeBridge; import space.qu4nt.entanglementlib.security.data.SDCScopeContext; import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer; -import space.qu4nt.entanglementlib.security.entlibnative.Function; +import space.qu4nt.entanglementlib.security.entlibnative.NativeComponent; import space.qu4nt.entanglementlib.security.entlibnative.NativeSpecContext; import java.lang.foreign.MemorySegment; @@ -28,10 +28,10 @@ static void setUp() { EntanglementLibSecurityFacade.initialize( EntanglementLibSecurityConfig.create( new NativeSpecContext(System.getenv("ENTLIB_NATIVE_BIN"), "entlib_native_ffi", - Function.chain( - Function.withCalleeSecureBuffer(), - Function.withCallerSecureBuffer(), - Function.withHash(true))), + NativeComponent.chain( + NativeComponent.withCalleeSecureBuffer(), + NativeComponent.withCallerSecureBuffer(), + NativeComponent.withHash(true))), HeuristicArenaFactory.ArenaMode.CONFINED) ); } diff --git a/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/SHA3HashTest.java b/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/SHA3HashTest.java index ed32ec8..83d9b15 100644 --- a/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/SHA3HashTest.java +++ b/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/SHA3HashTest.java @@ -10,7 +10,7 @@ import space.qu4nt.entanglementlib.security.data.InternalNativeBridge; import space.qu4nt.entanglementlib.security.data.SDCScopeContext; import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer; -import space.qu4nt.entanglementlib.security.entlibnative.Function; +import space.qu4nt.entanglementlib.security.entlibnative.NativeComponent; import space.qu4nt.entanglementlib.security.entlibnative.NativeSpecContext; import java.lang.foreign.MemorySegment; @@ -28,10 +28,10 @@ static void setUp() { EntanglementLibSecurityFacade.initialize( EntanglementLibSecurityConfig.create( new NativeSpecContext(System.getenv("ENTLIB_NATIVE_BIN"), "entlib_native_ffi", - Function.chain( - Function.withCalleeSecureBuffer(), - Function.withCallerSecureBuffer(), - Function.withHash(false)) + NativeComponent.chain( + NativeComponent.withCalleeSecureBuffer(), + NativeComponent.withCallerSecureBuffer(), + NativeComponent.withHash(false)) ), HeuristicArenaFactory.ArenaMode.CONFINED) ); From 5109c800112ea5cba336461cc5065c206933c502 Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Mon, 16 Mar 2026 17:09:18 +0900 Subject: [PATCH 11/12] =?UTF-8?q?https://github.com/Quant-Off/entlib-nativ?= =?UTF-8?q?e/=20`1.1.2`=20=EB=A6=B4=EB=A6=AC=EC=A6=88=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/copilot.data.migration.ask2agent.xml | 6 ++++++ entlib-native | 2 +- native-benchmark/src/lib.rs | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 .idea/copilot.data.migration.ask2agent.xml diff --git a/.idea/copilot.data.migration.ask2agent.xml b/.idea/copilot.data.migration.ask2agent.xml new file mode 100644 index 0000000..1f2ea11 --- /dev/null +++ b/.idea/copilot.data.migration.ask2agent.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/entlib-native b/entlib-native index 89debf5..c0e696f 160000 --- a/entlib-native +++ b/entlib-native @@ -1 +1 @@ -Subproject commit 89debf599c51132ecee7c567f4384c6b5d18a5b3 +Subproject commit c0e696fdc7332c922d6faaea40c4cd86ffce49ab diff --git a/native-benchmark/src/lib.rs b/native-benchmark/src/lib.rs index d6aceea..68a2ac0 100644 --- a/native-benchmark/src/lib.rs +++ b/native-benchmark/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg(target_arch = "x86_64")] use jni::sys::*; use std::arch::x86_64::{ __m256i, _mm256_add_epi32, _mm256_and_si256, _mm256_cmpgt_epi32, _mm256_loadu_si256, From 68a65bb409b161b8ab864ff52e652cf9310b81ef Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Mon, 16 Mar 2026 17:09:58 +0900 Subject: [PATCH 12/12] =?UTF-8?q?`1.1.2`=20=EB=A6=B4=EB=A6=AC=EC=A6=88=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index f68ed6e..bd12f5f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,7 +14,7 @@ val quantPublicDir = project.findProperty("quantPublicDir") as? String val lombokVersion = "org.projectlombok:lombok:1.18.42" -val entLibVersion = "1.1.2-Alpha3" +val entLibVersion = "1.1.2" allprojects { group = "{$commonGroupId}.entanglementlib"