From e56ac0b7994320dcd98c1dcdc93be5b3f83d0700 Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Sat, 14 Feb 2026 01:16:40 +0900 Subject: [PATCH 01/13] =?UTF-8?q?`fix/ci-test`=20draft=20PR=20=EB=B0=98?= =?UTF-8?q?=EC=98=81=20=EB=B0=8F=20=EB=B9=8C=EB=93=9C=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=20=EC=88=98=EC=A0=95,=20=EB=82=B4=EB=B6=80=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=20=EB=B3=80=EC=88=98=EB=AA=85=20=EA=B3=A0=EC=B9=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 51 ++++++++++++++++--- entlib-native | 2 +- native-benchmark/src/lib.rs | 3 +- .../NativeAVXCall_JMHBenchmark.java} | 6 +-- .../benchmark/NativeCall_JMHBenchmark.java} | 6 +-- src/benchmark/resources/logback.xml | 49 ++++++++++++++++++ .../entanglementlib/EntanglementLibEnvs.java | 2 +- 7 files changed, 102 insertions(+), 17 deletions(-) rename src/{test/java/space/qu4nt/entanglementlib/benchmarks/NativeAVXCallBenchmark.java => benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeAVXCall_JMHBenchmark.java} (98%) rename src/{test/java/space/qu4nt/entanglementlib/benchmarks/NativeCallBenchmark.java => benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeCall_JMHBenchmark.java} (98%) create mode 100644 src/benchmark/resources/logback.xml diff --git a/build.gradle.kts b/build.gradle.kts index 132bf4c..6ac1a06 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,25 +9,59 @@ plugins { id("me.champeau.jmh") version "0.7.3" } +val commonGroupId = project.findProperty("commonGroupId") as? String ?: "space.qu4nt" +val quantPublicDir = project.findProperty("quantPublicDir") as? String + ?: layout.buildDirectory.dir("dummy-resources").get().asFile.absolutePath + val lombokVersion = "org.projectlombok:lombok:1.18.42" -val quantPublicDir: String by project -val commonGroupId: String by project val bouncyCastleVer = "1.83" -val entLibVersion = "1.1.2-Alpha2" +val entLibVersion = "1.1.2-Alpha3" group = commonGroupId version = entLibVersion sourceSets { main { + java { + srcDirs("src/main/java") + } resources { - srcDirs += File("${quantPublicDir}/entanglementlib") + srcDirs("src/main/resources") + + if (quantPublicDir.isNotEmpty()) { + val extraResourceDir = File("${quantPublicDir}/entanglementlib") + if (extraResourceDir.exists()) { + srcDir(extraResourceDir) + } else { + logger.warn("Warning: External resource directory not found: $extraResourceDir. Skipping...") + } + } } } + test { + java { + srcDirs("src/test/java") + } + resources { + srcDirs("src/test/resources") + + if (quantPublicDir.isNotEmpty()) { + val extraTestResourceDir = File("${quantPublicDir}/entanglementlib-test") + if (extraTestResourceDir.exists()) { + srcDir(extraTestResourceDir) + } + } + } + } + + named("jmh") { + java { + srcDirs("src/benchmark/java") + } resources { - srcDirs += File("${quantPublicDir}/entanglementlib-test") + srcDirs("src/benchmark/resources") } } } @@ -96,6 +130,7 @@ dependencies { testImplementation("org.openjdk.jmh:jmh-core:1.37") // Source: https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-generator-annprocess testImplementation("org.openjdk.jmh:jmh-generator-annprocess:1.37") + jmhAnnotationProcessor(lombokVersion) } tasks.test { @@ -111,6 +146,10 @@ tasks.jar { } } +tasks.withType { + duplicatesStrategy = DuplicatesStrategy.INCLUDE +} + mavenPublishing { signAllPublications() @@ -160,7 +199,7 @@ jmh { // 테스트 벤치마킹 클래스 등록 includeTests.set(true) - includes.set(listOf(".*Benchmark")) + includes.set(listOf(".*_JMHBenchmark")) // 벤치마크 실행 시 필요한 jvm 인자 중앙 제어 jvmArgs.set(listOf( diff --git a/entlib-native b/entlib-native index a2ca93a..5e58861 160000 --- a/entlib-native +++ b/entlib-native @@ -1 +1 @@ -Subproject commit a2ca93a6ef616f5ac97143baa8c4f4ae989ff34f +Subproject commit 5e588617098544f9f98e5ab15616996e697737dd diff --git a/native-benchmark/src/lib.rs b/native-benchmark/src/lib.rs index bd79178..ae16675 100644 --- a/native-benchmark/src/lib.rs +++ b/native-benchmark/src/lib.rs @@ -119,7 +119,8 @@ pub unsafe extern "C" fn bless_poly_modular_add( /// jni 단순 ++++ #[unsafe(no_mangle)] -#[unsafe(export_name = "Java_space_qu4nt_entanglementlib_benchmarks_NativeCallBenchmark_jni_1add_1numbers")] +// TODO: 사실 이 JNI 테스트가 작동되는지 모르겠음. 아마 안 될거임. 클래스명까지 "_1"로 언더스코어를 포함할 수 없는걸로 알고 있긴 함. +#[unsafe(export_name = "Java_space_qu4nt_entanglementlib_benchmark_NativeCall_1JMHBenchmark_jni_1add_1numbers")] pub extern "C" fn jni_add_numbers_impl( mut _env: JNIEnv, _class: jclass, diff --git a/src/test/java/space/qu4nt/entanglementlib/benchmarks/NativeAVXCallBenchmark.java b/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeAVXCall_JMHBenchmark.java similarity index 98% rename from src/test/java/space/qu4nt/entanglementlib/benchmarks/NativeAVXCallBenchmark.java rename to src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeAVXCall_JMHBenchmark.java index 2999ea2..6a0bab6 100644 --- a/src/test/java/space/qu4nt/entanglementlib/benchmarks/NativeAVXCallBenchmark.java +++ b/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeAVXCall_JMHBenchmark.java @@ -1,10 +1,8 @@ -/* +package space.qu4nt.entanglementlib.benchmark;/* * Copyright © 2025-2026 Quant. * Under License "PolyForm Noncommercial License 1.0.0". */ -package space.qu4nt.entanglementlib.benchmarks; - import org.openjdk.jmh.annotations.*; import java.lang.foreign.*; @@ -23,7 +21,7 @@ }) @Warmup(iterations = 5, time = 1) @Measurement(iterations = 5, time = 1) -public class NativeAVXCallBenchmark { +public class NativeAVXCall_JMHBenchmark { private static final Linker LINKER = Linker.nativeLinker(); diff --git a/src/test/java/space/qu4nt/entanglementlib/benchmarks/NativeCallBenchmark.java b/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeCall_JMHBenchmark.java similarity index 98% rename from src/test/java/space/qu4nt/entanglementlib/benchmarks/NativeCallBenchmark.java rename to src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeCall_JMHBenchmark.java index 7580fac..82f5979 100644 --- a/src/test/java/space/qu4nt/entanglementlib/benchmarks/NativeCallBenchmark.java +++ b/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeCall_JMHBenchmark.java @@ -1,10 +1,8 @@ -/* +package space.qu4nt.entanglementlib.benchmark;/* * Copyright © 2025-2026 Quant. * Under License "PolyForm Noncommercial License 1.0.0". */ -package space.qu4nt.entanglementlib.benchmarks; - import org.openjdk.jmh.annotations.*; import java.lang.foreign.*; @@ -19,7 +17,7 @@ @Fork(value = 1, jvmArgs = {"--enable-native-access=ALL-UNNAMED", "--enable-preview"}) @Warmup(iterations = 3, time = 1) @Measurement(iterations = 5, time = 1) -public class NativeCallBenchmark { +public class NativeCall_JMHBenchmark { private static final Linker LINKER = Linker.nativeLinker(); diff --git a/src/benchmark/resources/logback.xml b/src/benchmark/resources/logback.xml new file mode 100644 index 0000000..5f5b457 --- /dev/null +++ b/src/benchmark/resources/logback.xml @@ -0,0 +1,49 @@ + + + + + + + + + + ${LOG_PATTERN_CONSOLE} + + + + + ${LOG_DIR}/benchmark.log + + ${LOG_DIR}/benchmark-%d{yyyy-MM-dd}.log + 30 + + + ${LOG_PATTERN_FILE} + + + + + + 512 + 0 false + + + + 1024 + 0 + false + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/space/qu4nt/entanglementlib/EntanglementLibEnvs.java b/src/main/java/space/qu4nt/entanglementlib/EntanglementLibEnvs.java index 7c73113..6b303f3 100644 --- a/src/main/java/space/qu4nt/entanglementlib/EntanglementLibEnvs.java +++ b/src/main/java/space/qu4nt/entanglementlib/EntanglementLibEnvs.java @@ -28,7 +28,7 @@ sealed class EntanglementLibEnvs extends WrapEnv permits InternalFactory { static { entanglementPublicDir = new EntanglementLibEnvs("ENTANGLEMENT_PUBLIC_DIR" , true); entanglementHomeDir = new EntanglementLibEnvs("ENTANGLEMENT_HOME_DIR" , true); - entLibNativeDir = new EntanglementLibEnvs("ENTLIB_NATIVE_DIR" , false, "native"); + entLibNativeDir = new EntanglementLibEnvs("ENTLIB_NATIVE_BIN" , false, "native"); } EntanglementLibEnvs() { From 30ea9cde1f8f6b269661925a31fe4d57e348a4b1 Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Sat, 14 Feb 2026 01:33:47 +0900 Subject: [PATCH 02/13] =?UTF-8?q?=EB=84=A4=EC=9D=B4=ED=8B=B0=EB=B8=8C=20?= =?UTF-8?q?=EB=B2=A4=EC=B9=98=EB=A7=88=ED=82=B9=20=EB=AA=A8=EB=93=88=20?= =?UTF-8?q?=EB=8C=80=EC=9D=91=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=9B=8C?= =?UTF-8?q?=ED=81=AC=ED=94=8C=EB=A1=9C=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20?= =?UTF-8?q?=EC=84=9C=EB=B8=8C=EB=AA=A8=EB=93=88=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/security-and-static-analysis.yml | 9 +++++++-- .gitmodules | 3 +++ 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 .gitmodules diff --git a/.github/workflows/security-and-static-analysis.yml b/.github/workflows/security-and-static-analysis.yml index 4cb0876..a3a8706 100644 --- a/.github/workflows/security-and-static-analysis.yml +++ b/.github/workflows/security-and-static-analysis.yml @@ -49,7 +49,12 @@ jobs: # 정적 분석 및 린트 (Clippy) - 보안 취약점 및 잠재적 버그 탐지 - name: Run Clippy (Linting) working-directory: ./${{ matrix.project-path }} - run: cargo clippy --all-targets --all-features -- -D warnings + run: | + if [ "${{ matrix.project-path }}" == "native-benchmark" ]; then + cargo clippy --all-targets --all-features + else + cargo clippy --all-targets --all-features -- -D warnings + fi # 의존성 보안 검사 (Audit) - RustSec 데이터베이스 기반 - name: Run Security Audit @@ -100,4 +105,4 @@ jobs: - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 with: - category: "/language:${{ matrix.language }}" \ No newline at end of file + category: "/language:${{ matrix.language }}" diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..26990c1 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "entlib-native"] + path = entlib-native + url = https://github.com/Quant-Off/entlib-native.git \ No newline at end of file From adda9b77a5f9423232c59e476677907bac32b120 Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Sat, 14 Feb 2026 01:42:31 +0900 Subject: [PATCH 03/13] =?UTF-8?q?=EB=84=A4=EC=9D=B4=ED=8B=B0=EB=B8=8C=20?= =?UTF-8?q?=EB=B2=A4=EC=B9=98=EB=A7=88=ED=82=B9=20=EB=AA=A8=EB=93=88=20?= =?UTF-8?q?=EB=A6=B0=ED=84=B0=20=EC=9E=AC=EC=88=98=EC=A0=95,=20=EC=84=9C?= =?UTF-8?q?=EB=B8=8C=EB=AA=A8=EB=93=88=20=EC=9E=AC=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitmodules | 2 +- native-benchmark/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 26990c1..9541551 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "entlib-native"] path = entlib-native - url = https://github.com/Quant-Off/entlib-native.git \ No newline at end of file + url = https://github.com/Quant-Off/entlib-native.git diff --git a/native-benchmark/src/lib.rs b/native-benchmark/src/lib.rs index ae16675..d6aceea 100644 --- a/native-benchmark/src/lib.rs +++ b/native-benchmark/src/lib.rs @@ -1,7 +1,7 @@ use jni::sys::*; use std::arch::x86_64::{ __m256i, _mm256_add_epi32, _mm256_and_si256, _mm256_cmpgt_epi32, _mm256_loadu_si256, - _mm256_set1_epi32, _mm256_set1_epi8, _mm256_storeu_si256, _mm256_sub_epi32, _mm256_xor_si256, + _mm256_set1_epi8, _mm256_set1_epi32, _mm256_storeu_si256, _mm256_sub_epi32, _mm256_xor_si256, }; use std::slice; From 6483ec0d9fefd16ad4f99000080a06cdcd28441e Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Sat, 14 Feb 2026 01:59:01 +0900 Subject: [PATCH 04/13] =?UTF-8?q?=EC=84=9C=EB=B8=8C=EB=AA=A8=EB=93=88=20?= =?UTF-8?q?=EC=B2=B4=ED=81=AC=EC=95=84=EC=9B=83=20=EC=88=98=ED=96=89?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=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 | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/security-and-static-analysis.yml b/.github/workflows/security-and-static-analysis.yml index a3a8706..d70a23a 100644 --- a/.github/workflows/security-and-static-analysis.yml +++ b/.github/workflows/security-and-static-analysis.yml @@ -24,6 +24,11 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + # 서브모듈을 재귀호출 + submodules: recursive + # 비공개 저장소의 경우 토큰 설정 필요 + # token: ${{ secrets.GITHUB_TOKEN }} # Rust 툴체인 설치 (clippy, rustfmt 포함) - name: Install Rust toolchain @@ -47,6 +52,7 @@ jobs: run: cargo fmt -- --check # 정적 분석 및 린트 (Clippy) - 보안 취약점 및 잠재적 버그 탐지 + # 벤치마킹 모듈은 검사 강도 하향 가능 - name: Run Clippy (Linting) working-directory: ./${{ matrix.project-path }} run: | @@ -80,6 +86,9 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + # Java 빌드 시에도 entlib-native 참조 가능성을 위해 서브모듈 체크아웃 + submodules: recursive # Java 25 환경 설정 (프로젝트 요구사항) - name: Set up JDK 25 @@ -105,4 +114,4 @@ jobs: - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 with: - category: "/language:${{ matrix.language }}" + category: "/language:${{ matrix.language }}" \ No newline at end of file From e9cd043c6286b261ca493d9acf884df50914bf42 Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Sat, 14 Feb 2026 02:04:29 +0900 Subject: [PATCH 05/13] =?UTF-8?q?=EB=84=A4=EC=9D=B4=ED=8B=B0=EB=B8=8C=20?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= 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 5e58861..988dd79 160000 --- a/entlib-native +++ b/entlib-native @@ -1 +1 @@ -Subproject commit 5e588617098544f9f98e5ab15616996e697737dd +Subproject commit 988dd79552f2afddd47dda3404f7db106639e569 From 4ae23e965876d451d896d2f9b2e758aba236fedd Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Sat, 14 Feb 2026 13:06:10 +0900 Subject: [PATCH 06/13] =?UTF-8?q?https://github.com/Quant-Off/entanglement?= =?UTF-8?q?lib/security/code-scanning/2=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../communication/session/Session.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/main/java/space/qu4nt/entanglementlib/security/communication/session/Session.java b/src/main/java/space/qu4nt/entanglementlib/security/communication/session/Session.java index d363220..03c813f 100644 --- a/src/main/java/space/qu4nt/entanglementlib/security/communication/session/Session.java +++ b/src/main/java/space/qu4nt/entanglementlib/security/communication/session/Session.java @@ -13,6 +13,7 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Consumer; @@ -94,7 +95,8 @@ private Session(String sessionId, SessionConfig config) { this.participants = new ConcurrentHashMap<>(); this.participantsByRole = new ConcurrentHashMap<>(); this.participantsLock = new ReentrantReadWriteLock(); - this.eventListeners = Collections.synchronizedList(new ArrayList<>()); + // 순회 시 락이 필요 없는 CopyOnWrite 병렬 컬렉션 사용 변경 + this.eventListeners = new CopyOnWriteArrayList<>(); log.debug("세션 생성됨: {}", sessionId); } @@ -318,21 +320,27 @@ public void close() { if (state.compareAndSet(current, SessionState.CLOSING)) { try { - // 모든 참여자에게 종료 알림 notifyListeners(listener -> listener.onSessionClosing(this)); - // 참여자 정리 + // 방어적 복사 로컬 리스트 + List participantsToClose; + + // lock scope 최소화 -> 내부 컬렉션 상태만 조작 participantsLock.writeLock().lock(); try { - participants.values().forEach(p -> - p.transitionState(ConnectionState.CLOSING)); + // 스냅샷 생성 + participantsToClose = new ArrayList<>(participants.values()); participants.clear(); participantsByRole.clear(); } finally { participantsLock.writeLock().unlock(); } - // 보안 컨텍스트 정리 + // 락이 해제된 안전한 상태에서 외부 메소드(alien method) 호출 + participantsToClose.forEach(p -> + p.transitionState(ConnectionState.CLOSING) + ); + if (sessionSecurityContext != null) { sessionSecurityContext.clear(); } From bc2b9e1c1beaaec908b1d043faee7385dd139946 Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Sat, 14 Feb 2026 13:06:25 +0900 Subject: [PATCH 07/13] =?UTF-8?q?Qodana=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/qodana_code_quality.yml | 40 +++++++++++++++++++ qodana.yaml | 48 +++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 .github/workflows/qodana_code_quality.yml create mode 100644 qodana.yaml diff --git a/.github/workflows/qodana_code_quality.yml b/.github/workflows/qodana_code_quality.yml new file mode 100644 index 0000000..36f0a13 --- /dev/null +++ b/.github/workflows/qodana_code_quality.yml @@ -0,0 +1,40 @@ +#-------------------------------------------------------------------------------# +# Discover additional configuration options in our documentation # +# https://www.jetbrains.com/help/qodana/github.html # +#-------------------------------------------------------------------------------# + +name: Qodana +on: + workflow_dispatch: + pull_request: + push: + branches: + - master + - feature/1.1.0-Alpha + +jobs: + qodana: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + checks: write + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + - name: 'Qodana Scan' + uses: JetBrains/qodana-action@v2025.3 + env: + QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} + with: + # When pr-mode is set to true, Qodana analyzes only the files that have been changed + pr-mode: false + use-caches: true + post-pr-comment: true + use-annotations: true + # Upload Qodana results (SARIF, other artifacts, logs) as an artifact to the job + upload-result: false + # quick-fixes available in Ultimate and Ultimate Plus plans + push-fixes: 'none' \ No newline at end of file diff --git a/qodana.yaml b/qodana.yaml new file mode 100644 index 0000000..4cfb240 --- /dev/null +++ b/qodana.yaml @@ -0,0 +1,48 @@ +#-------------------------------------------------------------------------------# +# Qodana analysis is configured by qodana.yaml file # +# https://www.jetbrains.com/help/qodana/qodana-yaml.html # +#-------------------------------------------------------------------------------# + +################################################################################# +# WARNING: Do not store sensitive information in this file, # +# as its contents will be included in the Qodana report. # +################################################################################# +version: "1.0" + +#Specify inspection profile for code analysis +profile: + name: qodana.starter + +#Enable inspections +#include: +# - name: + +#Disable inspections +#exclude: +# - name: +# paths: +# - + +projectJDK: "25" #(Applied in CI/CD pipeline) + +#Execute shell command before Qodana execution (Applied in CI/CD pipeline) +#bootstrap: sh ./prepare-qodana.sh + +#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline) +#plugins: +# - id: #(plugin id can be found at https://plugins.jetbrains.com) + +# Quality gate. Will fail the CI/CD pipeline if any condition is not met +# severityThresholds - configures maximum thresholds for different problem severities +# testCoverageThresholds - configures minimum code coverage on a whole project and newly added code +# Code Coverage is available in Ultimate and Ultimate Plus plans +#failureConditions: +# severityThresholds: +# any: 15 +# critical: 5 +# testCoverageThresholds: +# fresh: 70 +# total: 50 + +#Specify Qodana linter for analysis (Applied in CI/CD pipeline) +linter: jetbrains/qodana-jvm:2025.3 From ebd205f2988c884762eb42cac0c570f5345dcca8 Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Sat, 14 Feb 2026 13:19:02 +0900 Subject: [PATCH 08/13] =?UTF-8?q?Qodana=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=20=EC=9E=90=EB=8F=99=ED=99=94=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD(=EC=9D=B4=20PR=EC=97=90=20=ED=8F=AC=ED=95=A8=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=9D=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/qodana_code_quality.yml | 40 ------------------- qodana.yaml | 48 ----------------------- 2 files changed, 88 deletions(-) delete mode 100644 .github/workflows/qodana_code_quality.yml delete mode 100644 qodana.yaml diff --git a/.github/workflows/qodana_code_quality.yml b/.github/workflows/qodana_code_quality.yml deleted file mode 100644 index 36f0a13..0000000 --- a/.github/workflows/qodana_code_quality.yml +++ /dev/null @@ -1,40 +0,0 @@ -#-------------------------------------------------------------------------------# -# Discover additional configuration options in our documentation # -# https://www.jetbrains.com/help/qodana/github.html # -#-------------------------------------------------------------------------------# - -name: Qodana -on: - workflow_dispatch: - pull_request: - push: - branches: - - master - - feature/1.1.0-Alpha - -jobs: - qodana: - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - checks: write - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 0 - - name: 'Qodana Scan' - uses: JetBrains/qodana-action@v2025.3 - env: - QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} - with: - # When pr-mode is set to true, Qodana analyzes only the files that have been changed - pr-mode: false - use-caches: true - post-pr-comment: true - use-annotations: true - # Upload Qodana results (SARIF, other artifacts, logs) as an artifact to the job - upload-result: false - # quick-fixes available in Ultimate and Ultimate Plus plans - push-fixes: 'none' \ No newline at end of file diff --git a/qodana.yaml b/qodana.yaml deleted file mode 100644 index 4cfb240..0000000 --- a/qodana.yaml +++ /dev/null @@ -1,48 +0,0 @@ -#-------------------------------------------------------------------------------# -# Qodana analysis is configured by qodana.yaml file # -# https://www.jetbrains.com/help/qodana/qodana-yaml.html # -#-------------------------------------------------------------------------------# - -################################################################################# -# WARNING: Do not store sensitive information in this file, # -# as its contents will be included in the Qodana report. # -################################################################################# -version: "1.0" - -#Specify inspection profile for code analysis -profile: - name: qodana.starter - -#Enable inspections -#include: -# - name: - -#Disable inspections -#exclude: -# - name: -# paths: -# - - -projectJDK: "25" #(Applied in CI/CD pipeline) - -#Execute shell command before Qodana execution (Applied in CI/CD pipeline) -#bootstrap: sh ./prepare-qodana.sh - -#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline) -#plugins: -# - id: #(plugin id can be found at https://plugins.jetbrains.com) - -# Quality gate. Will fail the CI/CD pipeline if any condition is not met -# severityThresholds - configures maximum thresholds for different problem severities -# testCoverageThresholds - configures minimum code coverage on a whole project and newly added code -# Code Coverage is available in Ultimate and Ultimate Plus plans -#failureConditions: -# severityThresholds: -# any: 15 -# critical: 5 -# testCoverageThresholds: -# fresh: 70 -# total: 50 - -#Specify Qodana linter for analysis (Applied in CI/CD pipeline) -linter: jetbrains/qodana-jvm:2025.3 From 2f706953a28c8b78004efdf947ccf7b6ea688f1a Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Sat, 14 Feb 2026 13:39:22 +0900 Subject: [PATCH 09/13] =?UTF-8?q?Qodana=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/code_quality.yml | 28 ++++++++++++++++++++++++++++ qodana.yaml | 10 ++++++++++ 2 files changed, 38 insertions(+) create mode 100644 .github/workflows/code_quality.yml create mode 100644 qodana.yaml diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml new file mode 100644 index 0000000..60ba925 --- /dev/null +++ b/.github/workflows/code_quality.yml @@ -0,0 +1,28 @@ +name: Qodana +on: + workflow_dispatch: + pull_request: + push: + branches: + - master + - 'feature/*' # The release branches + +jobs: + qodana: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + checks: write + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit + fetch-depth: 0 # a full history is required for pull request analysis + - name: 'Qodana Scan' + uses: JetBrains/qodana-action@v2025.3 + with: + pr-mode: false + env: + QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} + QODANA_ENDPOINT: 'https://qodana.cloud' \ No newline at end of file diff --git a/qodana.yaml b/qodana.yaml new file mode 100644 index 0000000..8bc8ec1 --- /dev/null +++ b/qodana.yaml @@ -0,0 +1,10 @@ +#################################################################################################################### +# WARNING: Do not store sensitive information in this file, as its contents will be included in the Qodana report. # +#################################################################################################################### + +version: "1.0" +linter: jetbrains/qodana-jvm-community:2025.3 +profile: + name: qodana.recommended +include: + - name: CheckDependencyLicenses \ No newline at end of file From 1398dbddc689131dd9092a3468828423279f1438 Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Sat, 14 Feb 2026 14:11:34 +0900 Subject: [PATCH 10/13] =?UTF-8?q?Qodana=20=EC=B2=B4=ED=81=AC=EC=95=84?= =?UTF-8?q?=EC=9B=83=20->=204?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/code_quality.yml | 4 ++-- .github/workflows/security-and-static-analysis.yml | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml index 60ba925..366acc3 100644 --- a/.github/workflows/code_quality.yml +++ b/.github/workflows/code_quality.yml @@ -5,7 +5,7 @@ on: push: branches: - master - - 'feature/*' # The release branches + - 'feature/*' jobs: qodana: @@ -15,7 +15,7 @@ jobs: pull-requests: write checks: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit fetch-depth: 0 # a full history is required for pull request analysis diff --git a/.github/workflows/security-and-static-analysis.yml b/.github/workflows/security-and-static-analysis.yml index d70a23a..e1dcd68 100644 --- a/.github/workflows/security-and-static-analysis.yml +++ b/.github/workflows/security-and-static-analysis.yml @@ -2,7 +2,6 @@ name: Security & Static Analysis on: push: - branches: [ "master", "develop" ] pull_request: branches: [ "master", "develop" ] schedule: From 5d1ce383061da009a5c90c217ba1898c51fc6836 Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Wed, 4 Mar 2026 00:20:16 +0900 Subject: [PATCH 11/13] =?UTF-8?q?JPMS=20=EB=8F=84=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/code_quality.yml | 28 - .../security-and-static-analysis.yml | 3 +- .idea/gradle.xml | 4 + README.md | 142 +-- README_EN.md | 178 +--- annotation-processor/build.gradle.kts | 3 + annotations/build.gradle.kts | 3 + .../annotations/CallerResponsibility.java | 29 + .../annotations}/ExternalPattern.java | 13 +- .../entanglementlib/annotations}/Unsafe.java | 23 +- build.gradle.kts | 293 +++--- core/build.gradle.kts | 12 + .../core/EntanglementLibCoreContext.java | 27 + .../core/exception/ELIBException.java | 55 ++ .../exception/ELIBUncheckedException.java | 44 + .../core}/exception/ExceptionLogger.java | 2 +- .../exception/core/ELIBCoreException.java | 28 + .../ELIBCoreIllegalArgumentException.java | 28 + .../core/ELIBCoreUtilityException.java | 28 + .../checked/ELIBSecurityException.java | 28 + .../checked/ELIBSecurityIOException.java | 28 + .../ELIBSecurityIllegalStateException.java | 28 + .../checked/ELIBSecurityProcessException.java | 28 + .../critical/ELIBSecurityCritical.java | 28 + .../critical/ELIBSecurityNativeCritical.java | 28 + .../ELIBSecurityIllegalArgumentException.java | 28 + .../core/i18n/EntanglementLibCoreI18n.java | 42 + .../entanglementlib/core}/util/Async.java | 6 +- .../entanglementlib/core}/util/Nill.java | 6 +- .../core}/util/StringUtil.java | 9 +- .../util/chunk/ByteArrayChunkProcessor.java | 2 +- .../core}/util/chunk/ChunkProcessor.java | 2 +- .../core}/util/chunk/FileChunkProcessor.java | 2 +- .../util/chunk/StringChunkProcessor.java | 2 +- .../core}/util/collection/SafeList.java | 12 +- .../core}/util/wrapper/Arrays.java | 2 +- .../core}/util/wrapper/Hex.java | 4 +- .../core}/util/wrapper/Pair.java | 2 +- .../core}/util/wrapper/Tuple.java | 2 +- entlib-native | 2 +- qodana.yaml | 10 - security/build.gradle.kts | 73 ++ .../benchmark/NativeAVXCall_JMHBenchmark.java | 3 +- .../benchmark/NativeCall_JMHBenchmark.java | 5 +- .../src}/benchmark/resources/logback.xml | 0 .../EntanglementLibSecurityConfig.java | 32 + .../EntanglementLibSecurityFacade.java | 17 + .../security/crypto/ChaCha20.java | 109 +++ .../security/crypto/encode/Base64.java | 93 ++ .../security/crypto/hash/Hash.java | 218 +++++ .../security/crypto/rng/RNG.java | 90 ++ .../security/data}/HeuristicArenaFactory.java | 2 +- .../security/data/InternalNativeBridge.java | 24 + .../security/data/SDCConsumer.java | 9 + .../security/data/SDCFunction.java | 9 + .../security/data/SDCScopeContext.java | 61 ++ .../security/data/SensitiveDataContainer.java | 224 +++++ .../entlibnative/EntLibNativeManager.java | 122 +++ .../security/entlibnative/Function.java | 266 ++++++ .../security/entlibnative/NativeLoader.java | 117 +++ .../security/entlibnative/NativePlatform.java | 38 + .../entlibnative/NativeSpecContext.java | 96 ++ .../src}/main/resources/logback.xml | 0 .../security/crypto/Base64Test.java | 38 + .../security/crypto/ChaCha20Test.java | 122 +++ .../security/crypto/RNGTest.java | 96 ++ .../security/crypto/SHA2HashTest.java | 189 ++++ .../security/crypto/SHA3HashTest.java | 266 ++++++ settings.gradle.kts | 6 +- .../entanglementlib/CallerResponsibility.java | 34 - .../EntanglementLibBootstrap.java | 118 --- .../entanglementlib/EntanglementLibEnvs.java | 57 -- .../entanglementlib/InternalFactory.java | 234 ----- .../space/qu4nt/entanglementlib/WrapEnv.java | 67 -- .../entlibnative/NativeLinkerManager.java | 174 ---- .../entlibnative/ProgressResult.java | 35 - .../entlibnative/SensitiveDataContainer.java | 335 ------- .../exception/EntLibException.java | 99 -- .../exception/EntLibUncheckedException.java | 88 -- .../exception/critical/EntLibEnvError.java | 18 - .../exception/critical/EntLibError.java | 52 -- .../exception/critical/EntLibNativeError.java | 22 - .../critical/EntLibSecurityError.java | 33 - .../EntLibSecureCertProcessException.java | 33 - .../secure/EntLibSecureException.java | 45 - .../EntLibSecureIllegalArgumentException.java | 41 - .../EntLibSecureIllegalStateException.java | 37 - ...tLibSecureJCAJCEStoreProcessException.java | 33 - .../secure/EntLibSensitiveDataException.java | 33 - ...ibCryptoCipherIllegalIVStateException.java | 33 - .../EntLibCryptoCipherProcessException.java | 41 - .../secure/crypto/EntLibCryptoException.java | 33 - .../EntLibCryptoKEMProcessingException.java | 37 - ...LibCryptoSignatureProcessingException.java | 37 - ...LibCryptoStreamCipherProcessException.java | 37 - .../server/EntLibServerException.java | 34 - .../EntLibServerIllegalStateException.java | 32 - .../EntLibServerSecurityWarningException.java | 33 - .../session/EntLibSessionException.java | 37 - .../EntLibSessionIllegalStateException.java | 37 - .../util/EntLibUtilityException.java | 29 - ...EntLibUtilityIllegalArgumentException.java | 29 - .../experimental/package-info.java | 13 - .../resource/ResourceCaller.java | 156 ---- .../resource/ResourceHandler.java | 83 -- .../resource/SupportedFormat.java | 11 - .../resource/config/Configer.java | 195 ---- .../config/ConfigerInstanceBased.java | 191 ---- .../resource/config/PublicConfigTree.java | 64 -- .../resource/config/PublicConfiguration.java | 144 --- ...icJSONFileSystemResourceBundleControl.java | 65 -- ...icYamlFileSystemResourceBundleControl.java | 63 -- .../resource/language/Language.java | 326 ------- .../language/LanguageInstanceBased.java | 117 --- .../resource/language/SupportedLanguage.java | 36 - .../security/EntLibParameterSpec.java | 19 - .../security/KeyDestroyHelper.java | 204 ----- .../security/PostQuantumParameterSpec.java | 19 - .../entanglementlib/security/auth/TOTP.java | 112 --- .../security/auth/package-info.java | 12 - .../security/communication/README.md | 3 - .../session/ConnectionState.java | 25 - .../communication/session/Participant.java | 129 --- .../session/ParticipantRole.java | 14 - .../session/ParticipantSecurityContext.java | 52 -- .../communication/session/Session.java | 514 ----------- .../communication/session/SessionConfig.java | 163 ---- .../session/SessionEventListener.java | 82 -- .../session/SessionSecurityContext.java | 215 ----- .../communication/session/SessionState.java | 32 - .../communication/tls/HandshakeMessage.java | 157 ---- .../security/communication/tls/Server.java | 859 ------------------ .../communication/tls/ServerConfig.java | 98 -- .../tls/ServerEventListener.java | 88 -- .../communication/tls/ServerState.java | 32 - .../crypto/AbstractStrategyBundle.java | 167 ---- .../security/crypto/CipherType.java | 149 --- .../security/crypto/CryptoFamily.java | 67 -- .../security/crypto/CryptoMethod.java | 49 - .../security/crypto/Digest.java | 78 -- .../security/crypto/EntLibAlgorithmType.java | 58 -- .../security/crypto/EntLibCryptoCategory.java | 60 -- .../security/crypto/EntLibCryptoRegistry.java | 119 --- .../security/crypto/KEMType.java | 112 --- .../entanglementlib/security/crypto/Mode.java | 71 -- .../security/crypto/Padding.java | 42 - .../security/crypto/ParameterSizeDetail.java | 120 --- .../entanglementlib/security/crypto/README.md | 345 ------- .../security/crypto/RegistrableStrategy.java | 38 - .../security/crypto/SignatureType.java | 124 --- .../entanglementlib/security/crypto/USAGE.md | 119 --- .../crypto/bundle/AESStrategyBundle.java | 42 - .../crypto/bundle/ARIAStrategyBundle.java | 43 - .../crypto/bundle/BundleStaticCaller.java | 25 - .../crypto/bundle/ChaCha20StrategyBundle.java | 48 - .../crypto/bundle/MLDSAStrategyBundle.java | 104 --- .../crypto/bundle/MLKEMStrategyBundle.java | 97 -- .../crypto/bundle/SLHDSAStrategyBundle.java | 52 -- .../bundle/X25519MLKEM768StrategyBundle.java | 36 - .../crypto/bundle/X25519StrategyBundle.java | 82 -- .../security/crypto/key/EntLibCryptoKey.java | 137 --- .../security/crypto/key/EntLibKey.java | 9 - .../security/crypto/key/KeyWiper.java | 51 -- .../strategy/EntLibAsymmetricKeyStrategy.java | 32 - .../strategy/EntLibSymmetricKeyStrategy.java | 30 - .../detail/AESSymmetricKeyStrategy.java | 67 -- .../detail/ARIASymmetricKeyStrategy.java | 66 -- .../ChaCha20Poly1305SymmetricKeyStrategy.java | 53 -- .../detail/ChaCha20SymmetricKeyStrategy.java | 53 -- .../strategy/detail/InternalKeyGenerator.java | 43 - .../key/strategy/detail/MLDSAKeyStrategy.java | 61 -- .../key/strategy/detail/MLKEMKeyStrategy.java | 58 -- .../strategy/detail/SLHDSAKeyStrategy.java | 209 ----- .../strategy/detail/X25519KeyStrategy.java | 50 - .../detail/X25519MLKEM768KeyStrategy.java | 109 --- .../crypto/strategy/AEADCipherStrategy.java | 29 - .../crypto/strategy/BlockCipherStrategy.java | 71 -- .../crypto/strategy/CipherStrategy.java | 70 -- .../crypto/strategy/EntLibCryptoStrategy.java | 36 - .../crypto/strategy/NativeECDHStrategy.java | 22 - .../crypto/strategy/NativeKEMStrategy.java | 43 - .../strategy/NativeSignatureStrategy.java | 46 - .../crypto/strategy/SignatureStrategy.java | 40 - .../crypto/strategy/StreamCipherStrategy.java | 47 - .../crypto/strategy/detail/AESStrategy.java | 201 ---- .../crypto/strategy/detail/ARIAStrategy.java | 172 ---- .../strategy/detail/AbstractBlockCipher.java | 229 ----- .../strategy/detail/AbstractStreamCipher.java | 132 --- .../detail/ChaCha20Poly1305Strategy.java | 419 --------- .../strategy/detail/ChaCha20Strategy.java | 177 ---- .../crypto/strategy/detail/MLDSAStrategy.java | 141 --- .../crypto/strategy/detail/MLKEMStrategy.java | 111 --- .../strategy/detail/SLHDSAStrategy.java | 122 --- .../strategy/detail/X25519Strategy.java | 210 ----- .../crypto/strategy/detail/hybrid/README.md | 53 -- .../detail/hybrid/X25519MLKEM768Strategy.java | 247 ----- .../security/legacy/EntTCP.java | 296 ------ .../entanglementlib/security/legacy/README.md | 223 ----- .../legacy/certificate/Certificator.java | 131 --- .../security/legacy/certificate/EntSSL.java | 125 --- .../legacy/certificate/KeyStoreManager.java | 290 ------ .../legacy/certificate/SubjectString.java | 69 -- .../security/legacy/certificate/TBSData.java | 122 --- .../entanglementlib/util/io/EntFile.java | 364 -------- .../qu4nt/entanglementlib/util/io/Hash.java | 162 ---- .../entanglementlib/util/io/PemUtil.java | 197 ---- .../util/security/Password.java | 268 ------ .../util/security/SecureCharBuffer.java | 225 ----- .../qu4nt/entanglementlib/NativeTest.java | 66 -- .../SDCBindingsConcurrencyTest.java | 155 ---- .../entlibnative/SDCSemiPerformanceTest.java | 42 - .../security/communication/SessionTest.java | 57 -- .../communication/tls/ServerTest.java | 193 ---- .../strategy/detail/AESStrategyTest.java | 49 - .../strategy/detail/ChaCha20StrategyTest.java | 45 - .../strategy/detail/MLKEMStrategyTest.java | 41 - .../hybrid/X25519MLKEM768StrategyTest.java | 152 ---- src/test/resources/logback.xml | 20 - 218 files changed, 3050 insertions(+), 16034 deletions(-) delete mode 100644 .github/workflows/code_quality.yml create mode 100644 annotation-processor/build.gradle.kts create mode 100644 annotations/build.gradle.kts create mode 100644 annotations/src/main/java/space/qu4nt/entanglementlib/annotations/CallerResponsibility.java rename {src/main/java/space/qu4nt/entanglementlib => annotations/src/main/java/space/qu4nt/entanglementlib/annotations}/ExternalPattern.java (62%) rename {src/main/java/space/qu4nt/entanglementlib => annotations/src/main/java/space/qu4nt/entanglementlib/annotations}/Unsafe.java (79%) create mode 100644 core/build.gradle.kts create mode 100644 core/src/main/java/space/qu4nt/entanglementlib/core/EntanglementLibCoreContext.java create mode 100644 core/src/main/java/space/qu4nt/entanglementlib/core/exception/ELIBException.java create mode 100644 core/src/main/java/space/qu4nt/entanglementlib/core/exception/ELIBUncheckedException.java rename {src/main/java/space/qu4nt/entanglementlib => core/src/main/java/space/qu4nt/entanglementlib/core}/exception/ExceptionLogger.java (96%) create mode 100644 core/src/main/java/space/qu4nt/entanglementlib/core/exception/core/ELIBCoreException.java create mode 100644 core/src/main/java/space/qu4nt/entanglementlib/core/exception/core/ELIBCoreIllegalArgumentException.java create mode 100644 core/src/main/java/space/qu4nt/entanglementlib/core/exception/core/ELIBCoreUtilityException.java create mode 100644 core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityException.java create mode 100644 core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityIOException.java create mode 100644 core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityIllegalStateException.java create mode 100644 core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityProcessException.java create mode 100644 core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/critical/ELIBSecurityCritical.java create mode 100644 core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/critical/ELIBSecurityNativeCritical.java create mode 100644 core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/unchecked/ELIBSecurityIllegalArgumentException.java create mode 100644 core/src/main/java/space/qu4nt/entanglementlib/core/i18n/EntanglementLibCoreI18n.java rename {src/main/java/space/qu4nt/entanglementlib => core/src/main/java/space/qu4nt/entanglementlib/core}/util/Async.java (94%) rename {src/main/java/space/qu4nt/entanglementlib => core/src/main/java/space/qu4nt/entanglementlib/core}/util/Nill.java (94%) rename {src/main/java/space/qu4nt/entanglementlib => core/src/main/java/space/qu4nt/entanglementlib/core}/util/StringUtil.java (97%) rename {src/main/java/space/qu4nt/entanglementlib => core/src/main/java/space/qu4nt/entanglementlib/core}/util/chunk/ByteArrayChunkProcessor.java (98%) rename {src/main/java/space/qu4nt/entanglementlib => core/src/main/java/space/qu4nt/entanglementlib/core}/util/chunk/ChunkProcessor.java (93%) rename {src/main/java/space/qu4nt/entanglementlib => core/src/main/java/space/qu4nt/entanglementlib/core}/util/chunk/FileChunkProcessor.java (99%) rename {src/main/java/space/qu4nt/entanglementlib => core/src/main/java/space/qu4nt/entanglementlib/core}/util/chunk/StringChunkProcessor.java (98%) rename {src/main/java/space/qu4nt/entanglementlib => core/src/main/java/space/qu4nt/entanglementlib/core}/util/collection/SafeList.java (85%) rename {src/main/java/space/qu4nt/entanglementlib => core/src/main/java/space/qu4nt/entanglementlib/core}/util/wrapper/Arrays.java (98%) rename {src/main/java/space/qu4nt/entanglementlib => core/src/main/java/space/qu4nt/entanglementlib/core}/util/wrapper/Hex.java (84%) rename {src/main/java/space/qu4nt/entanglementlib => core/src/main/java/space/qu4nt/entanglementlib/core}/util/wrapper/Pair.java (91%) rename {src/main/java/space/qu4nt/entanglementlib => core/src/main/java/space/qu4nt/entanglementlib/core}/util/wrapper/Tuple.java (92%) delete mode 100644 qodana.yaml create mode 100644 security/build.gradle.kts rename {src => security/src}/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeAVXCall_JMHBenchmark.java (98%) rename {src => security/src}/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeCall_JMHBenchmark.java (99%) rename {src => security/src}/benchmark/resources/logback.xml (100%) create mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/EntanglementLibSecurityConfig.java create mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/EntanglementLibSecurityFacade.java create mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/crypto/ChaCha20.java create mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/crypto/encode/Base64.java create mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/crypto/hash/Hash.java create mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/crypto/rng/RNG.java rename {src/main/java/space/qu4nt/entanglementlib => security/src/main/java/space/qu4nt/entanglementlib/security/data}/HeuristicArenaFactory.java (98%) create mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/data/InternalNativeBridge.java create mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/data/SDCConsumer.java create mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/data/SDCFunction.java create mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/data/SDCScopeContext.java create mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/data/SensitiveDataContainer.java create 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/Function.java create mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeLoader.java create mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativePlatform.java create mode 100644 security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeSpecContext.java rename {src => security/src}/main/resources/logback.xml (100%) create mode 100644 security/src/test/java/space/qu4nt/entanglementlib/security/crypto/Base64Test.java create mode 100644 security/src/test/java/space/qu4nt/entanglementlib/security/crypto/ChaCha20Test.java create mode 100644 security/src/test/java/space/qu4nt/entanglementlib/security/crypto/RNGTest.java create mode 100644 security/src/test/java/space/qu4nt/entanglementlib/security/crypto/SHA2HashTest.java create mode 100644 security/src/test/java/space/qu4nt/entanglementlib/security/crypto/SHA3HashTest.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/CallerResponsibility.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/EntanglementLibBootstrap.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/EntanglementLibEnvs.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/InternalFactory.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/WrapEnv.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/entlibnative/NativeLinkerManager.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/entlibnative/ProgressResult.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/entlibnative/SensitiveDataContainer.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/exception/EntLibException.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/exception/EntLibUncheckedException.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/exception/critical/EntLibEnvError.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/exception/critical/EntLibError.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/exception/critical/EntLibNativeError.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/exception/critical/EntLibSecurityError.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/exception/secure/EntLibSecureCertProcessException.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/exception/secure/EntLibSecureException.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/exception/secure/EntLibSecureIllegalArgumentException.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/exception/secure/EntLibSecureIllegalStateException.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/exception/secure/EntLibSecureJCAJCEStoreProcessException.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/exception/secure/EntLibSensitiveDataException.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/exception/secure/crypto/EntLibCryptoCipherIllegalIVStateException.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/exception/secure/crypto/EntLibCryptoCipherProcessException.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/exception/secure/crypto/EntLibCryptoException.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/exception/secure/crypto/EntLibCryptoKEMProcessingException.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/exception/secure/crypto/EntLibCryptoSignatureProcessingException.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/exception/secure/crypto/EntLibCryptoStreamCipherProcessException.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/exception/server/EntLibServerException.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/exception/server/EntLibServerIllegalStateException.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/exception/server/EntLibServerSecurityWarningException.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/exception/session/EntLibSessionException.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/exception/session/EntLibSessionIllegalStateException.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/exception/util/EntLibUtilityException.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/exception/util/EntLibUtilityIllegalArgumentException.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/experimental/package-info.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/resource/ResourceCaller.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/resource/ResourceHandler.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/resource/SupportedFormat.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/resource/config/Configer.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/resource/config/ConfigerInstanceBased.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/resource/config/PublicConfigTree.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/resource/config/PublicConfiguration.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/resource/control/PublicJSONFileSystemResourceBundleControl.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/resource/control/PublicYamlFileSystemResourceBundleControl.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/resource/language/Language.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/resource/language/LanguageInstanceBased.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/resource/language/SupportedLanguage.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/EntLibParameterSpec.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/KeyDestroyHelper.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/PostQuantumParameterSpec.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/auth/TOTP.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/auth/package-info.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/communication/README.md delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/communication/session/ConnectionState.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/communication/session/Participant.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/communication/session/ParticipantRole.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/communication/session/ParticipantSecurityContext.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/communication/session/Session.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/communication/session/SessionConfig.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/communication/session/SessionEventListener.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/communication/session/SessionSecurityContext.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/communication/session/SessionState.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/communication/tls/HandshakeMessage.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/communication/tls/Server.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/communication/tls/ServerConfig.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/communication/tls/ServerEventListener.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/communication/tls/ServerState.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/AbstractStrategyBundle.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/CipherType.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/CryptoFamily.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/CryptoMethod.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/Digest.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/EntLibAlgorithmType.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/EntLibCryptoCategory.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/EntLibCryptoRegistry.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/KEMType.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/Mode.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/Padding.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/ParameterSizeDetail.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/README.md delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/RegistrableStrategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/SignatureType.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/USAGE.md delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/AESStrategyBundle.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/ARIAStrategyBundle.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/BundleStaticCaller.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/ChaCha20StrategyBundle.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/MLDSAStrategyBundle.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/MLKEMStrategyBundle.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/SLHDSAStrategyBundle.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/X25519MLKEM768StrategyBundle.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/X25519StrategyBundle.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/key/EntLibCryptoKey.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/key/EntLibKey.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/key/KeyWiper.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/EntLibAsymmetricKeyStrategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/EntLibSymmetricKeyStrategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/AESSymmetricKeyStrategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/ARIASymmetricKeyStrategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/ChaCha20Poly1305SymmetricKeyStrategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/ChaCha20SymmetricKeyStrategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/InternalKeyGenerator.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/MLDSAKeyStrategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/MLKEMKeyStrategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/SLHDSAKeyStrategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/X25519KeyStrategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/X25519MLKEM768KeyStrategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/AEADCipherStrategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/BlockCipherStrategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/CipherStrategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/EntLibCryptoStrategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/NativeECDHStrategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/NativeKEMStrategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/NativeSignatureStrategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/SignatureStrategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/StreamCipherStrategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/AESStrategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/ARIAStrategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/AbstractBlockCipher.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/AbstractStreamCipher.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/ChaCha20Poly1305Strategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/ChaCha20Strategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/MLDSAStrategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/MLKEMStrategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/SLHDSAStrategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/X25519Strategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/hybrid/README.md delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/hybrid/X25519MLKEM768Strategy.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/legacy/EntTCP.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/legacy/README.md delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/legacy/certificate/Certificator.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/legacy/certificate/EntSSL.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/legacy/certificate/KeyStoreManager.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/legacy/certificate/SubjectString.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/security/legacy/certificate/TBSData.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/util/io/EntFile.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/util/io/Hash.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/util/io/PemUtil.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/util/security/Password.java delete mode 100644 src/main/java/space/qu4nt/entanglementlib/util/security/SecureCharBuffer.java delete mode 100644 src/test/java/space/qu4nt/entanglementlib/NativeTest.java delete mode 100644 src/test/java/space/qu4nt/entanglementlib/entlibnative/SDCBindingsConcurrencyTest.java delete mode 100644 src/test/java/space/qu4nt/entanglementlib/entlibnative/SDCSemiPerformanceTest.java delete mode 100644 src/test/java/space/qu4nt/entanglementlib/security/communication/SessionTest.java delete mode 100644 src/test/java/space/qu4nt/entanglementlib/security/communication/tls/ServerTest.java delete mode 100644 src/test/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/AESStrategyTest.java delete mode 100644 src/test/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/ChaCha20StrategyTest.java delete mode 100644 src/test/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/MLKEMStrategyTest.java delete mode 100644 src/test/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/hybrid/X25519MLKEM768StrategyTest.java delete mode 100644 src/test/resources/logback.xml diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml deleted file mode 100644 index 366acc3..0000000 --- a/.github/workflows/code_quality.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Qodana -on: - workflow_dispatch: - pull_request: - push: - branches: - - master - - 'feature/*' - -jobs: - qodana: - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - checks: write - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit - fetch-depth: 0 # a full history is required for pull request analysis - - name: 'Qodana Scan' - uses: JetBrains/qodana-action@v2025.3 - with: - pr-mode: false - env: - QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} - QODANA_ENDPOINT: 'https://qodana.cloud' \ No newline at end of file diff --git a/.github/workflows/security-and-static-analysis.yml b/.github/workflows/security-and-static-analysis.yml index e1dcd68..cd91e21 100644 --- a/.github/workflows/security-and-static-analysis.yml +++ b/.github/workflows/security-and-static-analysis.yml @@ -2,8 +2,9 @@ name: Security & Static Analysis on: push: + branches: [ "master", "feature/*" ] pull_request: - branches: [ "master", "develop" ] + branches: [ "master", "feature/*" ] schedule: - cron: '30 1 * * 0' # 매주 일요일 01:30 정기검진 diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 0ead08a..2547228 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -9,6 +9,10 @@ diff --git a/README.md b/README.md index 7b6cfb1..823687f 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,9 @@ ![EntanglementLib](entanglementlib-logo.png) -얽힘 라이브러리(EntanglementLib)는 양자 내성 암호(Post-Quantum Cryptography, PQC) 기술을 기반으로 설계된 고성능 보안 및 유틸리티 라이브러리입니다. 본 라이브러리는 진화하는 디지털 위협 환경에 대응하여 최고 수준의 보안과 시스템 안정성을 제공하는 것을 목표로 합니다. +얽힘 라이브러리(EntanglementLib)는 모든 보안 연산을 안전하며, 빠르게 처리하도록 설계된 군사급 보안 라이브러리입니다. 고전 및 양자-내성 암호화(Post-Quantum Cryptography, PQC) 기술을 제공하며, 이를 사용한 미래지향적 TLS 프로토콜을 제공합니다. + +진화하는 디지털 위협 환경에 대응하여 최고 수준의 보안과 시스템 안정성을 제공하는 것을 목표로 합니다. > [English README](README_EN.md) @@ -14,125 +16,55 @@ 얽힘 라이브러리의 모든 설계는 '군사적 보안', '금융 및 대규모 엔터프라이즈 환경에서의 안전한 인프라 확립'을 위한 보안성(security)을 최우선 원칙으로 합니다. 얽힘 라이브러리는 잠재적 보안 취약점을 원천적으로 방지하고, 데이터 무결성을 보장하도록 구현되었습니다. 두 번째 핵심 가치는 안정성(stability) 으로, 예측 가능하고 일관된 성능을 보장하기 위해 Rust 네이티브 연산을 적용하여 매우 안전하게 메모리 효율성을 극대화하고, 체계적인 오류(또는 예외) 처리 메커니즘을 갖추었습니다. -## 강점 - -얽힘 라이브러리는 다음의 강점을 보유하고 있습니다. - -1. 잔류 데이터 방지 (Anti-Data Remanence) 및 메모리 소거 - - 자바의 메모리 관리 모델인 가비지 컬렉터(Garbage Collection, GC)가 보안에 취약할 수 있다는 점을 정확히 파악하고 이를 기술적으로 극복하기 위한 기술이 탑재되었습니다. - - 이 기술을 위해 [`entlib-native` 네이티브 라이브러리](https://github.com/Quant-Off/entlib-native)를 사용하여 안전하게 소거하도록 설계했습니다. - - 모든 민감 정보를 안전하게 관리하기 위해 [데이터 컨테이너 기능](https://github.com/Quant-Off/entanglementlib/blob/master/src/main/java/space/qu4nt/entanglementlib/entlibnative/SensitiveDataContainer.java)을 제공합니다. -2. 최신 PQC 표준 준수 - - FIPS 203, 204, 205에 따른 ML-KEM, ML-DSA, SLH-DSA 등의 NIST 표준화가 완료된 최신 알고리즘 및 AES, ARIA, ChaCha20과 같은 고전 알고리즘을 네이티브에서 처리하도록 하여 안전한 연산이 가능합니다. -3. 아키텍처 및 디자인 패턴 - - 팩토리 패턴 및 인터페이스를 명확히 분리했습니다. [암호화 알고리즘은 전략적](https://github.com/Quant-Off/entanglementlib/tree/master/src/main/java/space/qu4nt/entanglementlib/security/crypto)으로 호출하여 사용할 수 있습니다. -4. 예외 처리 - - 표준 예외를 그대로 던지지 않도록 했습니다. 얽힘 라이브러리만의 커스텀 예외 클래스로 래핑하여 명확한 문맥(context)을 제공했습니다. - - 보안 측면에서 문제를 발견한 경우, 시스템이 해결 방법을 제시하고, 우려되는 공격(해킹)을 방어하도록 설계했습니다. - -## 주요 기능 - -얽힘 라이브러리는 강력한 암호화 기능과 개발 생산성 향상을 위한 유틸리티를 포함합니다. - -| 모듈 | 기술 명세 | -|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `security.crypto` | 양자 내성 암호(PQC) 및 현대 암호화 알고리즘을 제공합니다.
- **PQC (NIST 표준)**: `ML-DSA` (Digital Signature Algorithm), `ML-KEM` (Key Encapsulation Mechanism), `SLH-DSA`를 지원하여 양자 컴퓨팅 시대에 대응하는 보안 체계를 구축합니다.
- **대칭키 암호**: `ChaCha20-Poly1305` AEAD(Authenticated Encryption with Associated Data)를 지원하여 높은 수준의 기밀성과 무결성을 동시에 제공합니다.
- **고성능 연산**: 주요 알고리즘은 SIMD(Single Instruction, Multiple Data) 명령어 셋을 활용하여 병렬 처리를 극대화하고 연산 속도를 최적화합니다.
- **전략 패턴**: `RegistrableStrategy` 및 `EntLibCryptoRegistry`를 통해 알고리즘을 유연하게 등록하고 사용할 수 있습니다. | -| `security.communication` | 안전한 통신을 위한 세션 및 TLS 관련 기능을 제공합니다.
- **세션 관리**: `Session`, `Participant`, `SessionConfig` 등을 통해 안전한 세션을 생성하고 관리합니다.
- **TLS 지원**: `Server`, `ServerConfig` 등을 통해 TLS 기반의 보안 통신 채널을 구축할 수 있습니다. | -| `entlibnative` | 네이티브 라이브러리와의 연동을 담당합니다.
- **데이터 보호**: `SensitiveDataContainer`를 통해 민감한 데이터를 안전하게 관리하고 소거합니다.
- **네이티브 연동**: `NativeLinkerManager`를 통해 Rust 등으로 작성된 고성능 네이티브 라이브러리를 로드하고 사용합니다. | -| `util` | 보안 및 개발 편의성을 고려한 고성능 유틸리티를 제공합니다.
- **입출력 및 청크 처리**: `EntFile`, `ChunkProcessor` 등을 통해 대용량 파일 및 데이터를 효율적으로 처리합니다.
- **보안 유틸리티**: `Password`, `SecureCharBuffer` 등을 통해 비밀번호 및 민감한 문자열을 안전하게 다룹니다.
- **기타**: `Hex`, `Hash`, `Async` 등 다양한 유틸리티 클래스를 제공합니다. | -| `exception` | 안정적인 오류 처리를 위한 체계적인 예외 계층 구조를 제공합니다.
- **보안 예외**: `EntLibSecureException` 및 하위 클래스들을 통해 보안 관련 오류를 명확하게 처리합니다.
- **암호화 예외**: `EntLibCryptoException` 등을 통해 암호화 과정에서 발생하는 오류를 세분화하여 관리합니다.
- **치명적 오류**: `EntLibError` 및 하위 클래스들을 통해 시스템의 치명적인 오류를 정의합니다. | - -## 기술 명세 - -얽힘 라이브러리가 네이티브 라이브러리와 어떻게 상호 작용하는지, 네이티브에서 보안 연산은 어떻게 수행하는지에 대한 기술 명세를 [이 곳](TECHNICAL.md)에 작성됩니다. - -## 벤치마킹 기록 - -얽힘 라이브러리에서 FFM API를 사용하여 Rust 네이티브 함수를 호출할 떄 발생하는 나노세컨드 지연 등 다양한 연산에 대한 브릿지 벤치마킹을 수행하고 있습니다. 이러한 작업은 성능 및 보안성에 직결되며, 최적의 코드를 창출하기 위해 중요한 역할을 합니다. - -얽힘 라이브러리의 벤치마킹은 [`native-benchmark`](native-benchmark) Rust 프로젝트에서 진행되며, 벤치마킹 기록은 [이 곳](NATIVE_BENCHMARK.md)에 작성됩니다. - -## 시작하기 +## 기술 -### 1. 요구 사항 +얽힘 라이브러리는 모든 보안 연산을 [Rust 기반 네이티브](https://github.com/Quant-Off/entlib-native)를 통해 수행합니다. 네이티브에선 `heap` 메모리 할당에 따른 가비지 컬렉터(Garbage Collection, GC)의 청소 메커니즘에서 발발될 수 있는 모든 보안 약점을 파훼합니다. Java 측의 민감 데이터를 `off-heap`으로 받아 작업을 수행하고, 호출자 또는 피호출자 패턴을 통해 해당 포인터의 데이터를 즉각적으로 안전하게 소거합니다. -얽힘 라이브러리는 다음의 환경에서 개발되었습니다. 다른 버전에 대한 세부적인 테스트는 진행되지 않은 상태입니다. +Java 측에서 네이티브와 상호 작용할 때, 단순히 JNI(Java Native Interface) 기능이 사용되지 않습니다. 핵심적인 기술은 [JEP 389](https://openjdk.org/jeps/389), [JEP 454](https://openjdk.org/jeps/454) 개선안에 의거한 고급적 네이티브 호출 기능인 Linker, FFM API(Foreign Function & Memory API)이며, 네이티브 측에선 캡슐화된 로직을 통해 FFI(Foreign Function Interface)로 연동됩니다. -- Java 25 -- Gradle 9.2.0 +> [!TIP] +> 네이티브에 대한 배경 및 개요가 궁금하시다면 [이 곳](https://qu4nt.space/projects/entlib-native)을 참고하세요. +> +> 또는 얽힘 라이브러리의 배경에 대해 궁금하시다면 [이 곳](https://qu4nt.space/projects/entanglementlib)을 참고하세요. -얽힘 라이브러리는 NIST FIPS 203, 204, 205 표준화에 포함된 알고리즘을 안정적으로 사용하기 위해 Java 25 이상의 버전을 사용합니다. +이 라이브러리 내에서 사용자의 데이터는 바이트 배열(`byte[]`)이나 문자 배열(`char[]`)로 관리되지 않습니다. 이러한 타입은 `heap` 메모리, 나아가 GC에게 주도권을 주는 셈입니다. 이러한 원시적 사용 대신 `SensitiveDataContainer` 객체를 사용할 수 있습니다. 이 객체는 민감 데이터의 소유권을 넘겨받고 네이티브에 안전히 넘겨 작업 처리를 안전하고 효율적으로 할 수 있도록 도와줍니다. 좀 더 구체적으로, 해당 객체는 [Rust의 RAII(Resource Acquisition Is Initialization) 패턴](https://doc.rust-lang.org/rust-by-example/scope/raii.html)과 유사하게 인스턴스화 시점에 자원을 획득하고 `close()` 호출 시점에 자원을 해제합니다. 이러한 개념은 Java에서 꽤 특이할 수도 있습니다. -### 2. 환경 변수 설정 +## 멀티모듈 -`EntanglementLib`의 정상적인 동작을 위해 다음 환경 변수 설정이 필요합니다. +얽힘 라이브러리는 이제 멀티모듈 프로젝트입니다. 각 모듈의 역할은 작업 및 실용적 어노테이션, 보안 그리고 각종 편의성 도구를 포함한 유틸리티로 나눠집니다. 어노테이션 및 코어 모듈은 보안 모듈에서 핵심적으로 사용되지만, 보안 모듈은 다른 모듈에서 절대로 사용되지 않습니다. -| 변수명 | 설명 | 기본값 | 필수 여부 | -|-------------------------|------------------------------------------------|-----|-------| -| `ENTLIB_NATIVE_BIN` | 네이티브 라이브러리의 바이너리가 포함된 디렉터리 경로를 지정합니다. | - | 필수 | -| `ENTANGLEMENT_HOME_DIR` | 얽힘 라이브러리 내부에서 사용되는 리소스가 저장될 디렉터리 경로를 지정합니다. | - | 필수 | +| 모듈 | 기능 | +|---------------|------------------------------------------------------------------| +| `security` | 핵심 보안 모듈입니다. 네이티브와의 상호 작용을 위한 로직과, FFI를 통해 연동된 갖가지 보안 기능을 제공합니다. | +| `core` | 예외, 국제화 및 비동기, 청크 작업, 문자열, 자료구조를 관리하는 유틸리티 기능을 제공합니다. | +| `annotations` | 간편한 코드 설계 및 사용자의 코드 이해 복잡도를 개선하기 위한 어노테이션이 포함되어 있습니다. | -```bash -# 예시: Linux/macOS -export ENTLIB_NATIVE_BIN="/path/to/entlib-native/release" -export ENTANGLEMENT_HOME_DIR="/path/to/entanglementlib" -``` -```bash -# 예시: Windows -setx ENTLIB_NATIVE_BIN "C:\path\to\entlib-native\release" -setx ENTANGLEMENT_HOME_DIR "C:\path\to\entanglementlib" -``` - -### 3. 저장소 클론 또는 Maven 저장소 사용 - -다음의 명령을 통해 저장소를 클론할 수 있습니다. - -```shell -$ git clone https://github.com/Quant-Off/entanglementlib.git -$ cd entanglementlib -``` +## 기술 명세 -의존성으로 등록하려는 경우 간편히 `Maven` 저장소를 이용할 수도 있습니다. +얽힘 라이브러리에 대한 상세한 기술 명세를 작성 중에 있습니다. -#### Maven Project +## 벤치마킹 기록 -```xml - - - space.qu4nt.entanglementlib - entanglementlib - 1.0.0 - - -``` +얽힘 라이브러리에서 FFM API를 사용하여 Rust 네이티브 함수를 호출할 떄 발생하는 나노세컨드 지연 등 다양한 연산에 대한 브릿지 벤치마킹을 수행하고 있습니다. 이러한 작업은 성능 및 보안성에 직결되며, 최적의 코드를 창출하기 위해 중요한 역할을 합니다. -#### Gradle Project +이 알파 버전에서 많은 벤치마킹 작업이 예정되어 있습니다. JMH(Java Microbenchmark Harness)를 통해 이 작업을 진행할 예정이며, 완료되는데로 새로운 문서에 정리하겠습니다. -```kotlin -repositories { - mavenCentral() -} +## 기여 -dependencies { - implementaion("space.qu4nt.entanglementlib:entanglementlib:1.0.0") - // or Groovy - // implementaion 'space.qu4nt.entanglementlib:entanglementlib:1.0.0' -} -``` +이 프로젝트는 현재 `Alpha` 버전이며, 아직 많이 부족합니다. 여러분의 피드백을 적극적으로 받을 준비가 이미 되어 있습니다. 얽힘 라이브러리는 단순히 PQC 알고리즘을 제공하는 것만이 아닌, 체계적으로 사용자 환경의 인프라 보안을 감시하고 사용자에게 해결책을 찾아주는 유능한 도구로써 사용되도록 개발됩니다. 최신 릴리즈부턴 이 신념에 강력한 초점을 맞출 것입니다. ## TODO -이 프로젝트는 아직 많이 부족합니다. 얽힘 라이브러리는 미래에 금융 및 보안 인프라 프로덕션에서 사용할 수 있도록 다음의 TODO를 명확히 하고자 합니다. +얽힘 라이브러리는 미래에 금융 및 보안 인프라 프로덕션에서 사용할 수 있도록 다음의 TODO를 명확히 하고자 합니다. -- [ ] 공급자 유동화 및 팩토리 최적화 - - 사용자의 선택에 따라 Java의 기본 공급자를 사용할 수 있도록 수정해야 합니다. 그리고 `InternalFactory` 클래스를 포함한 대부분의 클래스에서 제공되는 팩토리를 최적화해야 합니다. -- [ ] 전체 디자인 패턴 최적화 - - 현재 코드는 스파게티라고 해도 과언이 아닐 만큼 더러운 부분이 많이 보입니다. 제 방처럼 쾌적한 코드를 작성할 수 있도록 수정해야 합니다. +- [ ] TLS 통신 로직 추가 +- [ ] 복합 검증 작업 준비 및 수행 +- [ ] 커스텀 예외 최적화 - [ ] JPMS 적용 - 안전한 캡슐화와 일관된 호출(또는 사용) 패턴이 완성되면 JPMS를 통해 캡슐화된 패키지를 모듈로서 관리하려고 합니다. -- [ ] `BouncyCastle` 의존성 제거 - - 이제 `1.1.0` 릴리즈부턴 `BC` 의존성을 최소화하며, 끝내 제거하려고 합니다. 현재 AES, ARIA, ChaCha20 같은 고전 알고리즘은 여전히 `BC`에 의존하고 있습니다. 얽힘 라이브러리의 보안 철학을 확실히 하고자 모든 암호학적 연산은 `entlib-native`에서 수행하기로 결정했습니다. +- [ ] 외부 의존성 최소화 + - 이제 `1.1.0` 릴리즈부턴 `BouncyCastle` 의존성을 최소화하며, 끝내 제거하는 데 성공했습니다. 현재 코드 작성에 필요한 몇 가지 유용한 도구를 제공하는 의존성은 여전히 남아 있지만, 이들도 끝내 최소화될 예정입니다. - [ ] `i18n` 업데이트 - 최신 릴리즈 개발을 수행하며 다국어 지원을 많이 누락했습니다. 구성 설정에 따라 각 언어별로 로깅을 지원할 수 있도록 수정해야 합니다. @@ -151,18 +83,6 @@ dependencies { - [X] 보안 기능에 대한 기술 명세 작성 - **해결**: 얽힘 라이브러리의 중요한 보안 관련 기능을 [별도의 문서](TECHNICAL.md)에 작성했습니다. -## 기여 - -초기 버전에서 얽힘 라이브러리는 'BouncyCastle 래퍼', '어중간한 데이터 소거', ... 와 같이 정말 애매모호한 목표를 가지고 있었습니다. 이 부분에 대해 꽤 많은 시간을 투자해 곰곰히 생각했으며, 얽힘 라이브러리만의 고유한 목표를 다시금 정립했습니다. - -이제 얽힘 라이브러리는 단순히 PQC 알고리즘을 제공하는 것만이 아닌, 체계적으로 사용자 환경의 인프라 보안을 감시하고 사용자에게 해결책을 찾아주는 유능한 도구로써 사용되도록 개발됩니다. 최신 릴리즈부턴 이 신념에 강력한 초점을 맞출 것입니다. - -그렇기 때문에... 얽힘 라이브러리의 궁극적 목표를 완성시키기 위해 여러 개발자분들의 힘이 필요합니다. 언제든 코드에 대한 피드백을 남겨주세요. 퀀트 팀에게 아주아주 큰 힘이 됩니다! - -## Alpha - -현재 얽힘 라이브러리는 뭉친 업데이트 내용을 재빨리 반영하기 위해 꽤 성급하게 `1.1.0-Alpha`를 공개한 상태입니다. 이 버전의 안정화 버전은 `1.1.0` 정식 릴리즈로 수정되서 반영될 예정입니다. 아무래도 1인 개발이다 보니 속도가 더뎌지는 문제가 있으나, 모든 수정까지 얼마 남지 않았습니다. - ## 라이선스 본 프로젝트는 `PolyForm Noncommercial License 1.0.0`을 따릅니다. 이 프로젝트 내에서 `entlib-native`를 동시 관리하는 탓에 라이선스가 가끔 `MIT`로 잘못 반영될 때가 있지만, 여전히 `PolyForm` 라이선스를 따른다는 것을 참고해주세요. 이 라이선스에 관해 자세한 내용은 [LICENSE](LICENSE) 파일을 참고하세요. diff --git a/README_EN.md b/README_EN.md index 7ea589e..212c900 100644 --- a/README_EN.md +++ b/README_EN.md @@ -6,169 +6,89 @@ ![EntanglementLib](entanglementlib-logo.png) -EntanglementLib is a high-performance security and utility library designed based on Post-Quantum Cryptography (PQC) technology. This library aims to provide the highest level of security and system stability in response to the evolving digital threat landscape. +EntanglementLib is a military-grade security library designed to process all security operations safely and quickly. It provides classical and Post-Quantum Cryptography (PQC) technologies, and offers a future-oriented TLS protocol using them. + +It aims to provide the highest level of security and system stability in response to the evolving digital threat environment. > [Korean README](README.md) ## Core Philosophy -All designs of EntanglementLib prioritize 'security' for 'military-grade security' and 'establishing secure infrastructure in financial and large-scale enterprise environments' as the foremost principle. EntanglementLib is implemented to fundamentally prevent potential security vulnerabilities and ensure data integrity. The second core value is 'stability'. To ensure predictable and consistent performance, Rust native operations are applied to maximize memory efficiency very safely, and a systematic error (or exception) handling mechanism is equipped. - -## Strengths - -EntanglementLib possesses the following strengths: - -1. **Anti-Data Remanence and Memory Erasure** - - We accurately identified that Java's memory management model, the Garbage Collector (GC), can be vulnerable to security threats, and equipped technology to technically overcome this. - - For this technology, we designed it to safely erase data using the [`entlib-native` native library](https://github.com/Quant-Off/entlib-native). - - We provide a [Data Container feature](https://github.com/Quant-Off/entanglementlib/blob/master/src/main/java/space/qu4nt/entanglementlib/entlibnative/SensitiveDataContainer.java) to safely manage all sensitive information. -2. **Compliance with Latest PQC Standards** - - It supports the latest algorithms standardized by NIST such as ML-KEM, ML-DSA, and SLH-DSA according to FIPS 203, 204, and 205, as well as classical algorithms like AES, ARIA, and ChaCha20, processing them natively to enable secure operations. -3. **Architecture and Design Patterns** - - Factory patterns and interfaces are clearly separated. [Cryptographic algorithms can be called and used strategically](https://github.com/Quant-Off/entanglementlib/tree/master/src/main/java/space/qu4nt/entanglementlib/security/crypto). -4. **Exception Handling** - - We ensured that standard exceptions are not thrown as is. They are wrapped in EntanglementLib's custom exception classes to provide clear context. - - If a problem is discovered from a security perspective, the system is designed to suggest a solution and defend against concerning attacks (hacking). - -## Key Features - -EntanglementLib includes powerful encryption features and utilities for improving development productivity. - -| Module | Technical Specification | -| :--- | :--- | -| `security.crypto` | Provides Post-Quantum Cryptography (PQC) and modern encryption algorithms.
- **PQC (NIST Standards)**: Supports `ML-DSA` (Digital Signature Algorithm), `ML-KEM` (Key Encapsulation Mechanism), and `SLH-DSA` to build a security system responding to the quantum computing era.
- **Symmetric Key Encryption**: Supports `ChaCha20-Poly1305` AEAD (Authenticated Encryption with Associated Data) to provide high levels of confidentiality and integrity simultaneously.
- **High-Performance Computing**: Major algorithms utilize SIMD (Single Instruction, Multiple Data) instruction sets to maximize parallel processing and optimize calculation speed.
- **Strategy Pattern**: Algorithms can be flexibly registered and used through `RegistrableStrategy` and `EntLibCryptoRegistry`. | -| `security.communication` | Provides session and TLS-related functions for secure communication.
- **Session Management**: Creates and manages secure sessions through `Session`, `Participant`, `SessionConfig`, etc.
- **TLS Support**: Builds TLS-based secure communication channels through `Server`, `ServerConfig`, etc. | -| `entlibnative` | Handles linkage with native libraries.
- **Data Protection**: Safely manages and erases sensitive data through `SensitiveDataContainer`.
- **Native Linkage**: Loads and uses high-performance native libraries written in Rust, etc., through `NativeLinkerManager`. | -| `util` | Provides high-performance utilities considering security and development convenience.
- **I/O and Chunk Processing**: Efficiently processes large files and data through `EntFile`, `ChunkProcessor`, etc.
- **Security Utilities**: Safely handles passwords and sensitive strings through `Password`, `SecureCharBuffer`, etc.
- **Others**: Provides various utility classes such as `Hex`, `Hash`, `Async`, etc. | -| `exception` | Provides a systematic exception hierarchy for stable error handling.
- **Security Exceptions**: Clearly handles security-related errors through `EntLibSecureException` and its subclasses.
- **Encryption Exceptions**: Manages errors occurring during the encryption process by subdividing them through `EntLibCryptoException`, etc.
- **Fatal Errors**: Defines fatal system errors through `EntLibError` and its subclasses. | - -## Technical Specifications - -Technical specifications on how EntanglementLib interacts with native libraries and how security operations are performed natively are written [here](TECHNICAL.md). - -## Benchmarking Records - -We are performing bridge benchmarking for various operations, such as nanosecond delays occurring when calling Rust native functions using the FFM API in EntanglementLib. These tasks are directly related to performance and security and play an important role in creating optimal code. - -EntanglementLib's benchmarking is conducted in the [`native-benchmark`](native-benchmark) Rust project, and benchmarking records are written [here](NATIVE_BENCHMARK.md). +All designs of EntanglementLib prioritize security for 'military security' and 'establishing secure infrastructure in financial and large enterprise environments'. EntanglementLib is implemented to fundamentally prevent potential security vulnerabilities and ensure data integrity. The second core value is stability. To ensure predictable and consistent performance, Rust native operations are applied to maximize memory efficiency very safely, and a systematic error (or exception) handling mechanism is equipped. -## Getting Started +## Technology -### 1. Requirements +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. -EntanglementLib was developed in the following environment. Detailed testing for other versions has not been conducted. +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. -- Java 25 -- Gradle 9.2.0 +> [!TIP] +> If you are curious about the background and overview of Native, please refer to [here](https://qu4nt.space/projects/entlib-native). +> +> Or if you are curious about the background of EntanglementLib, please refer to [here](https://qu4nt.space/projects/entanglementlib). -EntanglementLib uses Java 25 or higher to stably use the algorithms included in NIST FIPS 203, 204, and 205 standardization. +Within this library, user data is not managed as byte arrays (`byte[]`) or character arrays (`char[]`). These types give initiative to `heap` memory, and furthermore to GC. Instead of this primitive use, you can use the `SensitiveDataContainer` object. This object takes over the ownership of sensitive data and passes it safely to Native to help process tasks safely and efficiently. More specifically, this object acquires resources at the time of instantiation and releases resources at the time of `close()` call, similar to [Rust's RAII (Resource Acquisition Is Initialization) pattern](https://doc.rust-lang.org/rust-by-example/scope/raii.html). This concept may be quite unique in Java. -### 2. Environment Variable Configuration +## Multi-module -The following environment variable settings are required for the normal operation of `EntanglementLib`. +EntanglementLib is now a multi-module project. The role of each module is divided into utilities including tasks and practical annotations, security, and various convenience tools. Annotation and core modules are essentially used in security modules, but security modules are never used in other modules. -| Variable Name | Description | Default | Required | -| :--- | :--- | :--- | :--- | -| `ENTLIB_NATIVE_BIN` | Specifies the directory path containing the native library binaries. | - | Required | -| `ENTANGLEMENT_HOME_DIR` | Specifies the directory path where resources used inside EntanglementLib will be stored. | - | Required | +| 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. | +| `annotations` | Includes annotations for easy code design and improving user's code understanding complexity. | -```bash -# Example: Linux/macOS -export ENTLIB_NATIVE_BIN="/path/to/entlib-native/release" -export ENTANGLEMENT_HOME_DIR="/path/to/entanglementlib" -``` -```bash -# Example: Windows -setx ENTLIB_NATIVE_BIN "C:\path\to\entlib-native\release" -setx ENTANGLEMENT_HOME_DIR "C:\path\to\entanglementlib" -``` +## Technical Specification -### 3. Clone Repository or Use Maven Repository +Detailed technical specifications for EntanglementLib are being written. -You can clone the repository via the following command: - -```shell -$ git clone https://github.com/Quant-Off/entanglementlib.git -$ cd entanglementlib -``` - -If you want to register it as a dependency, you can simply use the `Maven` repository. - -#### Maven Project +## Benchmarking Records -```xml - - - space.qu4nt.entanglementlib - entanglementlib - 1.0.0 - - -``` +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. -#### Gradle Project +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. -```kotlin -repositories { - mavenCentral() -} +## Contribution -dependencies { - implementaion("space.qu4nt.entanglementlib:entanglementlib:1.0.0") - // or Groovy - // implementaion 'space.qu4nt.entanglementlib:entanglementlib:1.0.0' -} -``` +This project is currently `Alpha` version, and is still lacking a lot. We are already ready to actively receive your feedback. EntanglementLib is developed not only to provide PQC algorithms, but also to be used as a competent tool that systematically monitors infrastructure security in the user environment and finds solutions for users. From the latest release, we will focus strongly on this belief. ## TODO -This project is still lacking in many areas. EntanglementLib aims to clarify the following TODOs so that it can be used in financial and security infrastructure production in the future. +EntanglementLib wants to clarify the following TODOs so that it can be used in financial and security infrastructure production in the future. -- [ ] **Provider Fluidity and Factory Optimization** - - We need to modify it so that Java's default provider can be used according to the user's choice. And we need to optimize the factories provided in most classes, including the `InternalFactory` class. -- [ ] **Overall Design Pattern Optimization** - - The current code has many dirty parts, enough to be called spaghetti code. We need to modify it to write pleasant code like my room. -- [ ] **JPMS Application** - - Once secure encapsulation and consistent call (or usage) patterns are completed, we intend to manage encapsulated packages as modules through JPMS. -- [ ] **Remove `BouncyCastle` Dependency** - - From the `1.1.0` release onwards, we intend to minimize `BC` dependency and eventually remove it. Currently, classical algorithms like AES, ARIA, and ChaCha20 still depend on `BC`. To solidify EntanglementLib's security philosophy, we have decided to perform all cryptographic operations in `entlib-native`. -- [ ] **`i18n` Update** - - We missed a lot of multi-language support while developing the latest release. We need to modify it to support logging for each language according to configuration settings. +- [ ] Add TLS communication logic +- [ ] Prepare and perform complex verification tasks +- [ ] Custom Exception Optimization +- [ ] Apply JPMS + - 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. +- [ ] Update `i18n` + - While performing the latest release development, we missed a lot of multilingual support. We need to modify it to support logging for each language according to configuration settings. ### Resolved -- [X] **Conflict between Java Module System (JPMS) and Reflection** - - `KeyDestroyHelper` uses `Field.setAccessible(true)` to modify `private` fields in `BouncyCastle` internals or the Java standard library. Due to the strong encapsulation policy since Java 17, there is a very high probability that an `InaccessibleObjectException` will occur without the `--add-opens` JVM option at runtime. +- [X] Conflict between Java Module System (JPMS) and Reflection + - In `KeyDestroyHelper`, `Field.setAccessible(true)` is used to modify `private` fields inside `BouncyCastle` or Java standard libraries. Due to the strong encapsulation policy since Java 17, there is a very high probability that `InaccessibleObjectException` will occur without the `--add-opens` JVM option at runtime. - You can bypass JPMS security warnings by adding the JVM option `--add-opens java.base/java.security=ALL-UNNAMED`. - - **Resolution**: We didn't worry too much about solving this problem. Because we decided to use the `BC Lightweight API` from the `1.1.0` release. The first goal was to avoid some constraints of the existing JCA/JCE with low-level access. In other words, access via reflection is still indispensable. It means we might not be able to respond flexibly when generating keys or calling internal encryption engines. To solve these complex problems, we introduced the `entlib-native` native library and intend to focus on minimizing EntanglementLib's `BC` dependency. -- [X] **Performance vs. Security Trade-off** - - We are performing defensive copies (deep copy) for all I/O. In server environments that process large data in gigabytes or require high throughput (Tick Per Second, TPS), frequent memory allocation and Garbage Collector load can cause performance degradation. Even looking at existing algorithm classes, binding data to instances is visible. - - **Resolution**: We added the `entlib-native` native library and designed it so that all memory-related operations are handled on the `Rust` side. Now, on the `Java` side, we just need to erase sensitive data like byte arrays that are simply received. `Rust` will reliably handle memory operations in the background. -- [X] **Random Number and Nonce Management** - - `ChaCha20Poly1305` uses `InternalFactory.getSafeRandom()` to generate the nonce value `Nonce`. If the `Nonce` is reused with the same key, the security of `ChaCha20Poly1305` completely collapses. - - **Resolution**: This problem was also solved with the `entlib-native` native library. Now, a `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 Features** - - **Resolution**: Important security-related features of EntanglementLib have been written in a [separate document](TECHNICAL.md). - -## Contribution - -In the early versions, EntanglementLib had really vague goals like 'BouncyCastle wrapper', 'half-hearted data erasure', etc. We spent quite a lot of time thinking deeply about this part and re-established EntanglementLib's unique goals. - -Now, EntanglementLib is developed not just to provide PQC algorithms, but to be used as a competent tool that systematically monitors infrastructure security in the user's environment and finds solutions for the user. From the latest release, we will focus strongly on this belief. - -Therefore... we need the power of many developers to complete the ultimate goal of EntanglementLib. Please leave feedback on the code at any time. It is a very, very big help to the Quant team! - -## Alpha - -Currently, EntanglementLib has released `1.1.0-Alpha` quite hastily to quickly reflect the accumulated updates. The stable version of this version is scheduled to be modified and reflected as the `1.1.0` official release. Since it is a one-person development, there is a problem that the speed is slow, but not much is left until all modifications. + - **Resolution**: We didn't worry too much to solve this problem. Because we decided to use `BC Lightweight API` from the `1.1.0` release. The first goal was to avoid some restrictions of existing JCA/JCE with low-level access. In other words, access through reflection is still inevitable. It means that we may not be able to respond flexibly when generating keys or calling internal encryption engines. To solve these complex problems, we introduced the `entlib-native` native library, and we intend to focus on minimizing `BC` dependencies of EntanglementLib. +- [X] Performance vs Security Trade-off + - Defensive copy (deep copy) is performed for all inputs and outputs. In server environments that process large data in gigabytes or require high throughput (Tick Per Second, TPS), frequent memory allocation and garbage collector load can cause performance degradation. Just looking at existing algorithm classes, you can see data binding to instances. + - **Resolution**: By adding the `entlib-native` native library, we designed all memory-related operations to be processed on the `Rust` side. Now, on the `Java` side, we simply need to erase sensitive data such as byte arrays received purely. `Rust` will reliably take care of memory operations in the back. +- [X] Random Number and Nonce Management + - 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). ## License -This project follows the `PolyForm Noncommercial License 1.0.0`. Please note that due to the simultaneous management of `entlib-native` within this project, the license is sometimes incorrectly reflected as `MIT`, but it still follows the `PolyForm` license. For more details on this license, please refer to the [LICENSE](LICENSE) file. +This project follows `PolyForm Noncommercial License 1.0.0`. Please note that although the license is sometimes incorrectly reflected as `MIT` due to simultaneous management of `entlib-native` within this project, it still follows the `PolyForm` license. For more details on this license, please refer to the [LICENSE](LICENSE) file. --- -# Changelog +# Changes Changes can be found in the [CHANGE.md](CHANGE.md) document. This document will be added when the `1.1.0` release is published. \ No newline at end of file diff --git a/annotation-processor/build.gradle.kts b/annotation-processor/build.gradle.kts new file mode 100644 index 0000000..48339bb --- /dev/null +++ b/annotation-processor/build.gradle.kts @@ -0,0 +1,3 @@ +dependencies { + implementation(project(":annotations")) +} \ No newline at end of file diff --git a/annotations/build.gradle.kts b/annotations/build.gradle.kts new file mode 100644 index 0000000..0ce6a16 --- /dev/null +++ b/annotations/build.gradle.kts @@ -0,0 +1,3 @@ +dependencies { + +} \ No newline at end of file diff --git a/annotations/src/main/java/space/qu4nt/entanglementlib/annotations/CallerResponsibility.java b/annotations/src/main/java/space/qu4nt/entanglementlib/annotations/CallerResponsibility.java new file mode 100644 index 0000000..3462f0a --- /dev/null +++ b/annotations/src/main/java/space/qu4nt/entanglementlib/annotations/CallerResponsibility.java @@ -0,0 +1,29 @@ +package space.qu4nt.entanglementlib.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/// 보안적 책임은 호출자에게 있음을 알리는 어노테이션입니다. 호출자가 해당 어노테이션이 +/// 사용된 멤버 사용 시, 작업 종료 후 반드시 보안 작업이 필요함을 의미합니다. +/// +/// 예를 들어, 이 어노테이션이 (복사본이 아닌) 원본 데이터를 반환하는 메소드에 사용되었고 +/// 해당 메소드를 사용하고자 하는 경우, 반환받은 데이터를 소거해야 함을 의미합니다. +/// +/// 또는 이 어노테이션이 사용된 멤버는 네이티브 측의 "호출자 할당" 패턴을 사용한 경우로, +/// 작업이 종료됨과 동시에 네이티브에 할당 해제 함수를 호출해야 함을 의미합니다. +/// +/// @author Q. T. Felix +/// @since 1.1.0 +@Documented +@Target(ElementType.TYPE_USE) +public @interface CallerResponsibility { + + /** + * 책임 전가의 사유 또는 설명를 정의합니다. + * + * @return 책임 전가 사유 또는 설명 + */ + String value() default ""; + +} \ No newline at end of file diff --git a/src/main/java/space/qu4nt/entanglementlib/ExternalPattern.java b/annotations/src/main/java/space/qu4nt/entanglementlib/annotations/ExternalPattern.java similarity index 62% rename from src/main/java/space/qu4nt/entanglementlib/ExternalPattern.java rename to annotations/src/main/java/space/qu4nt/entanglementlib/annotations/ExternalPattern.java index 804884f..423d64e 100644 --- a/src/main/java/space/qu4nt/entanglementlib/ExternalPattern.java +++ b/annotations/src/main/java/space/qu4nt/entanglementlib/annotations/ExternalPattern.java @@ -1,16 +1,11 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib; +package space.qu4nt.entanglementlib.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Target; /** * 반드시 외부에서만 사용됨을 알리는 마커 어노테이션입니다. - * {@link InternalFactory} 객체 부트스트랩 시, 내부(internal)에서 사용되는 멤버와 + * 라이브러리 부트스트랩, 또는 기타 상황 속 내부(internal)에서 사용되는 멤버와 * 외부(external)에서 사용되는 멤버는 다르다는 것을 명확히 하기 위해 사용됩니다. *

* 이 어노테이션은 타입 레벨에 사용하지 마세요. 혼동이 생길 수 있습니다. @@ -19,6 +14,4 @@ * @since 1.1.0 */ @Target(ElementType.TYPE_USE) -public @interface ExternalPattern { - -} +public @interface ExternalPattern { } diff --git a/src/main/java/space/qu4nt/entanglementlib/Unsafe.java b/annotations/src/main/java/space/qu4nt/entanglementlib/annotations/Unsafe.java similarity index 79% rename from src/main/java/space/qu4nt/entanglementlib/Unsafe.java rename to annotations/src/main/java/space/qu4nt/entanglementlib/annotations/Unsafe.java index 50548da..1b417c1 100644 --- a/src/main/java/space/qu4nt/entanglementlib/Unsafe.java +++ b/annotations/src/main/java/space/qu4nt/entanglementlib/annotations/Unsafe.java @@ -1,12 +1,4 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib; - -import com.quant.quantregular.annotations.QuantTypeOwner; -import com.quant.quantregular.annotations.Quanters; +package space.qu4nt.entanglementlib.annotations; import java.lang.annotation.Documented; @@ -16,7 +8,7 @@ /// /// 이 어노테이션이 사용된 요소(멤버)가 본질적으로 안전하지 않으며 보안 위험을 초래할 수 있음을 나타내기 위한 /// 어노테이션입니다. -/// +/// /// 구체적으로 이 어노테이션은 사용된 메소드, 필드 또는 타입 등의 멤버가 다음 이유 중 하나로 인해 안전하지 /// 않음을 나타냅니다. /// @@ -26,22 +18,25 @@ /// - 보안 취약점 발견 /// /// 이 API를 사용하려면 기본 구현 및 잠재적인 부작용에 대한 깊은 이해가 필요합니다. -/// +/// /// # Important /// /// 이 요소를 부적절하게 사용하면 `메모리 손상`, `데이터 유출` 또는 **임의 코드 실행을 포함하되 이에 국한되지 않는 /// 심각한 보안 취약점이 발생**할 수 있습니다. 또한 정의되지 않은 동작이나 애플리케이션 불안정을 초래할 수도 있습니다. -/// +/// /// 사용자는 절대적으로 필요한 경우에만 이 요소를 사용해야 하며, 위험을 완화하기 위해 적절한 보안 조치와 /// 검증 로직이 마련되어 있는지 확인해야 합니다. /// /// # Note /// -/// 이 어노테이션이 사용된 멤버는 `# Security` 헤더에 `Unsafe` 근거가 기록되어야 합니다. +/// 이 어노테이션이 사용된 멤버는 `# Unsafe` 헤더에 근거가 기록되어야 합니다. /// /// @author Q. T. Felix /// @since 1.1.0 @Documented -@QuantTypeOwner(Quanters.Q_T_FELIX) public @interface Unsafe { + + /// 명확한 `unsafe` 사유입니다. + /// @return 명확한 `unsafe` 사유 + String value() default ""; } diff --git a/build.gradle.kts b/build.gradle.kts index 6ac1a06..043e846 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,9 +4,8 @@ */ plugins { - id("java") + java id("com.vanniktech.maven.publish") version "0.28.0" - id("me.champeau.jmh") version "0.7.3" } val commonGroupId = project.findProperty("commonGroupId") as? String ?: "space.qu4nt" @@ -14,210 +13,140 @@ val quantPublicDir = project.findProperty("quantPublicDir") as? String ?: layout.buildDirectory.dir("dummy-resources").get().asFile.absolutePath val lombokVersion = "org.projectlombok:lombok:1.18.42" -val bouncyCastleVer = "1.83" val entLibVersion = "1.1.2-Alpha3" -group = commonGroupId -version = entLibVersion - -sourceSets { - main { - java { - srcDirs("src/main/java") - } - resources { - srcDirs("src/main/resources") - - if (quantPublicDir.isNotEmpty()) { - val extraResourceDir = File("${quantPublicDir}/entanglementlib") - if (extraResourceDir.exists()) { - srcDir(extraResourceDir) - } else { - logger.warn("Warning: External resource directory not found: $extraResourceDir. Skipping...") - } - } - } - } +allprojects { + group = "{$commonGroupId}.entanglementlib" + version = entLibVersion +} - test { - java { - srcDirs("src/test/java") - } - resources { - srcDirs("src/test/resources") +subprojects { + apply(plugin = "java") + apply(plugin = "com.vanniktech.maven.publish") - if (quantPublicDir.isNotEmpty()) { - val extraTestResourceDir = File("${quantPublicDir}/entanglementlib-test") - if (extraTestResourceDir.exists()) { - srcDir(extraTestResourceDir) - } - } + java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(25)) } + sourceCompatibility = JavaVersion.VERSION_25 + targetCompatibility = JavaVersion.VERSION_25 } - named("jmh") { - java { - srcDirs("src/benchmark/java") - } - resources { - srcDirs("src/benchmark/resources") - } + repositories { + mavenCentral() } -} -java { - toolchain { - languageVersion.set(JavaLanguageVersion.of(25)) + dependencies { + // JetBrains Annotations + // https://mvnrepository.com/artifact/org.jetbrains/annotations + implementation("org.jetbrains:annotations:26.0.2-1") + + // 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") + + // Lombok + // https://mvnrepository.com/artifact/org.projectlombok/lombok + implementation(lombokVersion) + annotationProcessor(lombokVersion) + + // Tests JUnit 5 + testImplementation(platform("org.junit:junit-bom:5.10.0")) + testImplementation("org.junit.jupiter:junit-jupiter") + testImplementation("org.assertj:assertj-core:3.27.7") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") + testAnnotationProcessor(lombokVersion) } - sourceCompatibility = JavaVersion.VERSION_25 - targetCompatibility = JavaVersion.VERSION_25 -} - -repositories { - mavenCentral() -} -dependencies { - // Quant-regular - implementation("space.qu4nt.quant-regular:annotations:1.0.0") - - // JetBrains Annotations - // https://mvnrepository.com/artifact/org.jetbrains/annotations - implementation("org.jetbrains:annotations:26.0.2-1") - - // 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") - - // Lombok - // https://mvnrepository.com/artifact/org.projectlombok/lombok - implementation(lombokVersion) - annotationProcessor(lombokVersion) - - // https://mvnrepository.com/artifact/org.yaml/snakeyaml - implementation("org.yaml:snakeyaml:2.5") - - // Jackson - // https://mvnrepository.com/artifact/tools.jackson.core/jackson-databind - implementation("tools.jackson.core:jackson-databind:3.0.2") - // https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations - implementation("com.fasterxml.jackson.core:jackson-annotations:3.0-rc5") - - // BouncyCastle - implementation("org.bouncycastle:bcprov-jdk18on:${bouncyCastleVer}") - implementation("org.bouncycastle:bcutil-jdk18on:${bouncyCastleVer}") - implementation("org.bouncycastle:bcpkix-jdk18on:${bouncyCastleVer}") - implementation("org.bouncycastle:bctls-jdk18on:${bouncyCastleVer}") - - // Tests JUnit 5 - testImplementation(platform("org.junit:junit-bom:5.10.0")) - testImplementation("org.junit.jupiter:junit-jupiter") - testImplementation("org.assertj:assertj-core:3.27.7") - testRuntimeOnly("org.junit.platform:junit-platform-launcher") - testAnnotationProcessor(lombokVersion) - - // JMH - jmh("org.openjdk.jmh:jmh-core:1.37") - jmh("org.openjdk.jmh:jmh-generator-annprocess:1.37") - // Source: https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core - testImplementation("org.openjdk.jmh:jmh-core:1.37") - // Source: https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-generator-annprocess - testImplementation("org.openjdk.jmh:jmh-generator-annprocess:1.37") - jmhAnnotationProcessor(lombokVersion) -} - -tasks.test { - useJUnitPlatform() -} + sourceSets { + main { + java { + srcDirs("src/main/java") + } + resources { + srcDirs("src/main/resources") + + if (quantPublicDir.isNotEmpty()) { + val extraResourceDir = File("${quantPublicDir}/entanglementlib") + if (extraResourceDir.exists()) { + srcDir(extraResourceDir) + } else { + logger.warn("Warning: External resource directory not found: $extraResourceDir. Skipping...") + } + } + } + } -tasks.jar { - from("entlib-native/dist") { - into("native") - include("linux/libentlib_native_aarch64.so", "linux/libentlib_native_x86_64.so", - "macos/libentlib_native_aarch64.dylib", "macos/libentlib_native_universal.dylib", "macos/libentlib_native_x86_64.dylib", - "windows/entlib_native_x86_64.dll") + test { + java { + srcDirs("src/test/java") + } + resources { + srcDirs("src/test/resources") + + if (quantPublicDir.isNotEmpty()) { + val extraTestResourceDir = File("${quantPublicDir}/entanglementlib-test") + if (extraTestResourceDir.exists()) { + srcDir(extraTestResourceDir) + } + } + } + } } -} -tasks.withType { - duplicatesStrategy = DuplicatesStrategy.INCLUDE -} + mavenPublishing { + signAllPublications() -mavenPublishing { - signAllPublications() + coordinates("${commonGroupId}.${project.name}", project.name, entLibVersion) - coordinates("${commonGroupId}.${project.name}", project.name, entLibVersion) + pom { + name = project.name + description = "Quant EntanglementLib" + inceptionYear = "2025" + url = "https://github.com/Quant-Off/entanglementlib" - pom { - name = project.name - description = "Quant EntanglementLib" - inceptionYear = "2025" - url = "https://github.com/Quant-Off/entanglementlib" + licenses { + license { + name = "PolyForm Noncommercial License 1.0.0" + url = "https://polyformproject.org/licenses/noncommercial/1.0.0/" + } + } - licenses { - license { - name = "PolyForm Noncommercial License 1.0.0" - url = "https://polyformproject.org/licenses/noncommercial/1.0.0/" + developers { + developer { + id = "qtfelix" + name = "Q. T. Felix" + url = "https://github.com/Quant-TheodoreFelix" + } } - } - developers { - developer { - id = "qtfelix" - name = "Q. T. Felix" - url = "https://github.com/Quant-TheodoreFelix" + scm { + url = "https://github.com/Quant-Off/entanglementlib" + connection = "scm:git:git://github.com/Quant-Off/entanglementlib.git" + developerConnection = "scm:git:ssh://git@github.com/Quant-Off/entanglementlib.git" } } - scm { - url = "https://github.com/Quant-Off/entanglementlib" - connection = "scm:git:git://github.com/Quant-Off/entanglementlib.git" - developerConnection = "scm:git:ssh://git@github.com/Quant-Off/entanglementlib.git" - } + configure( + com.vanniktech.maven.publish.JavaLibrary( + sourcesJar = true, + javadocJar = com.vanniktech.maven.publish.JavadocJar.Javadoc() + ) + ) } - configure( - com.vanniktech.maven.publish.JavaLibrary( - sourcesJar = true, - javadocJar = com.vanniktech.maven.publish.JavadocJar.Javadoc() - ) - ) -} + tasks.test { + useJUnitPlatform() + } -// -// JMH - start -// -jmh { - jmhVersion.set("1.37") - - // 테스트 벤치마킹 클래스 등록 - includeTests.set(true) - includes.set(listOf(".*_JMHBenchmark")) - - // 벤치마크 실행 시 필요한 jvm 인자 중앙 제어 - jvmArgs.set(listOf( - "--enable-native-access=ALL-UNNAMED", - "--enable-preview", - "-Djava.library.path=${projectDir}/native-benchmark/target/debug", - "-Xms2g", "-Xmx2g" // gc 간섭 최소화 - )) - fork.set(1) - - // 결과 출력 포맷 - resultFormat.set("JSON") - - // 벤치마크 수행 옵션 (프로세스 및 반복 횟수) - fork.set(1) - warmupIterations.set(3) - iterations.set(5) -} -// -// JMH - end -// \ No newline at end of file + tasks.withType { + duplicatesStrategy = DuplicatesStrategy.INCLUDE + } +} \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts new file mode 100644 index 0000000..195d444 --- /dev/null +++ b/core/build.gradle.kts @@ -0,0 +1,12 @@ +dependencies { + implementation(project(":annotations")) + + // Jackson + // https://mvnrepository.com/artifact/tools.jackson.core/jackson-databind + implementation("tools.jackson.core:jackson-databind:3.0.2") + // https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations + implementation("com.fasterxml.jackson.core:jackson-annotations:3.0-rc5") + + // https://mvnrepository.com/artifact/org.yaml/snakeyaml + implementation("org.yaml:snakeyaml:2.5") +} \ No newline at end of file diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/EntanglementLibCoreContext.java b/core/src/main/java/space/qu4nt/entanglementlib/core/EntanglementLibCoreContext.java new file mode 100644 index 0000000..ce1c30c --- /dev/null +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/EntanglementLibCoreContext.java @@ -0,0 +1,27 @@ +package space.qu4nt.entanglementlib.core; + +import lombok.Getter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import space.qu4nt.entanglementlib.core.exception.core.ELIBCoreIllegalArgumentException; +import space.qu4nt.entanglementlib.core.i18n.EntanglementLibCoreI18n; + +import java.util.Locale; + +public final class EntanglementLibCoreContext { + + @Getter + private static volatile boolean initialized = false; + + private EntanglementLibCoreContext() { + throw new UnsupportedOperationException("cannot access"); + } + + public static synchronized void initialize(final @NotNull Locale locale, @Nullable String userResourceBasename) + throws ELIBCoreIllegalArgumentException { + if (!initialized) { + EntanglementLibCoreI18n.initialize(locale, userResourceBasename); + initialized = true; + } + } +} diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/exception/ELIBException.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/ELIBException.java new file mode 100644 index 0000000..04fb805 --- /dev/null +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/ELIBException.java @@ -0,0 +1,55 @@ +/* + * Copyright © 2025-2026 Quant. + * Under License "PolyForm Noncommercial License 1.0.0". + */ + +package space.qu4nt.entanglementlib.core.exception; + +import java.io.Serial; + +/// 얽힘 라이브러리 전반에서 사용되는 기본 예외 클래스입니다. +/// +/// 이 클래스는 [Exception]을 상속받으며, 기본적으로 `Checked Exception`입니다. +/// 확장하여 다양한 상황에 대해 예외 처리가 가능합니다. +/// +/// @author Q. T. Felix +/// @since 1.1.0 +public class ELIBException extends Exception { + + @Serial + private static final long serialVersionUID = 2378593800480779310L; + + /** + * 새로운 {@link ELIBException} 인스턴스를 생성하는 기본 생성자 메소드입니다. + */ + public ELIBException() { + super(); + } + + /** + * 다국어 처리가 필요하지 않은 경우 이 인스턴스를 사용할 수 있습니다. + * + * @param message 메시지 + */ + public ELIBException(String message) { + super(message); + } + + /** + * 발생한 예외만 넘기기 위해 이 인스턴스를 사용할 수 있습니다. + * + * @param cause 발생 예외 + */ + public ELIBException(Throwable cause) { + super(cause); + } + + /// 다국어 처리가 필요하지 않으며, 발생한 예외를 넘기기 위해 이 + /// 인스턴스를 사용할 수 있습니다. + /// + /// @param message 메시지 + /// @param cause 발생 예외 + public ELIBException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/exception/ELIBUncheckedException.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/ELIBUncheckedException.java new file mode 100644 index 0000000..374eb99 --- /dev/null +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/ELIBUncheckedException.java @@ -0,0 +1,44 @@ +/* + * Copyright © 2025-2026 Quant. + * Under License "PolyForm Noncommercial License 1.0.0". + */ + +package space.qu4nt.entanglementlib.core.exception; + +import java.io.Serial; + +/// 얽힘 라이브러리 전반에서 사용되는 기본 예외 클래스입니다. +/// +/// 이 클래스는 [Exception]을 상속받으며, `Unchecked Exception`입니다. +/// +/// @author Q. T. Felix +/// @since 1.1.0 +public class ELIBUncheckedException extends RuntimeException { + + @Serial + private static final long serialVersionUID = 2378593800480779310L; + + /** + * 새로운 {@link ELIBUncheckedException} 인스턴스를 생성하는 기본 생성자 메소드입니다. + */ + public ELIBUncheckedException() { + } + + /** + * 다국어 처리가 필요하지 않은 경우 이 인스턴스를 사용할 수 있습니다. + * + * @param message 메시지 + */ + public ELIBUncheckedException(String message) { + super(message); + } + + /** + * 발생한 예외만 넘기기 위해 이 인스턴스를 사용할 수 있습니다. + * + * @param cause 발생 예외 + */ + public ELIBUncheckedException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/ExceptionLogger.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/ExceptionLogger.java similarity index 96% rename from src/main/java/space/qu4nt/entanglementlib/exception/ExceptionLogger.java rename to core/src/main/java/space/qu4nt/entanglementlib/core/exception/ExceptionLogger.java index 033b9e0..b0e4f89 100644 --- a/src/main/java/space/qu4nt/entanglementlib/exception/ExceptionLogger.java +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/ExceptionLogger.java @@ -3,7 +3,7 @@ * Under License "PolyForm Noncommercial License 1.0.0". */ -package space.qu4nt.entanglementlib.exception; +package space.qu4nt.entanglementlib.core.exception; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/exception/core/ELIBCoreException.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/core/ELIBCoreException.java new file mode 100644 index 0000000..3e64b0d --- /dev/null +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/core/ELIBCoreException.java @@ -0,0 +1,28 @@ +package space.qu4nt.entanglementlib.core.exception.core; + +import java.io.Serial; + +public class ELIBCoreException extends Exception { + + @Serial + private static final long serialVersionUID = -8478889423877054012L; + + public ELIBCoreException() { + } + + public ELIBCoreException(String message) { + super(message); + } + + public ELIBCoreException(String message, Throwable cause) { + super(message, cause); + } + + public ELIBCoreException(Throwable cause) { + super(cause); + } + + public ELIBCoreException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/exception/core/ELIBCoreIllegalArgumentException.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/core/ELIBCoreIllegalArgumentException.java new file mode 100644 index 0000000..5fcddd0 --- /dev/null +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/core/ELIBCoreIllegalArgumentException.java @@ -0,0 +1,28 @@ +package space.qu4nt.entanglementlib.core.exception.core; + +import java.io.Serial; + +public class ELIBCoreIllegalArgumentException extends ELIBCoreException { + + @Serial + private static final long serialVersionUID = 8726329909869726228L; + + public ELIBCoreIllegalArgumentException() { + } + + public ELIBCoreIllegalArgumentException(String message) { + super(message); + } + + public ELIBCoreIllegalArgumentException(String message, Throwable cause) { + super(message, cause); + } + + public ELIBCoreIllegalArgumentException(Throwable cause) { + super(cause); + } + + public ELIBCoreIllegalArgumentException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/exception/core/ELIBCoreUtilityException.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/core/ELIBCoreUtilityException.java new file mode 100644 index 0000000..f1ff035 --- /dev/null +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/core/ELIBCoreUtilityException.java @@ -0,0 +1,28 @@ +package space.qu4nt.entanglementlib.core.exception.core; + +import java.io.Serial; + +public class ELIBCoreUtilityException extends RuntimeException { + + @Serial + private static final long serialVersionUID = -3902892852757530511L; + + public ELIBCoreUtilityException() { + } + + public ELIBCoreUtilityException(String message) { + super(message); + } + + public ELIBCoreUtilityException(String message, Throwable cause) { + super(message, cause); + } + + public ELIBCoreUtilityException(Throwable cause) { + super(cause); + } + + public ELIBCoreUtilityException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityException.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityException.java new file mode 100644 index 0000000..044f33d --- /dev/null +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityException.java @@ -0,0 +1,28 @@ +package space.qu4nt.entanglementlib.core.exception.security.checked; + +import java.io.Serial; + +public class ELIBSecurityException extends Exception { + + @Serial + private static final long serialVersionUID = 7009027800432974319L; + + public ELIBSecurityException() { + } + + public ELIBSecurityException(String message) { + super(message); + } + + public ELIBSecurityException(String message, Throwable cause) { + super(message, cause); + } + + public ELIBSecurityException(Throwable cause) { + super(cause); + } + + public ELIBSecurityException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityIOException.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityIOException.java new file mode 100644 index 0000000..9010216 --- /dev/null +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityIOException.java @@ -0,0 +1,28 @@ +package space.qu4nt.entanglementlib.core.exception.security.checked; + +import java.io.Serial; + +public class ELIBSecurityIOException extends ELIBSecurityException { + + @Serial + private static final long serialVersionUID = -5611460691131894020L; + + public ELIBSecurityIOException() { + } + + public ELIBSecurityIOException(String message) { + super(message); + } + + public ELIBSecurityIOException(String message, Throwable cause) { + super(message, cause); + } + + public ELIBSecurityIOException(Throwable cause) { + super(cause); + } + + public ELIBSecurityIOException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityIllegalStateException.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityIllegalStateException.java new file mode 100644 index 0000000..8d6fc35 --- /dev/null +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityIllegalStateException.java @@ -0,0 +1,28 @@ +package space.qu4nt.entanglementlib.core.exception.security.checked; + +import java.io.Serial; + +public class ELIBSecurityIllegalStateException extends ELIBSecurityException { + + @Serial + private static final long serialVersionUID = -2918719188402645982L; + + public ELIBSecurityIllegalStateException() { + } + + public ELIBSecurityIllegalStateException(String message) { + super(message); + } + + public ELIBSecurityIllegalStateException(String message, Throwable cause) { + super(message, cause); + } + + public ELIBSecurityIllegalStateException(Throwable cause) { + super(cause); + } + + public ELIBSecurityIllegalStateException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityProcessException.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityProcessException.java new file mode 100644 index 0000000..638704c --- /dev/null +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/checked/ELIBSecurityProcessException.java @@ -0,0 +1,28 @@ +package space.qu4nt.entanglementlib.core.exception.security.checked; + +import java.io.Serial; + +public class ELIBSecurityProcessException extends ELIBSecurityException { + + @Serial + private static final long serialVersionUID = -4547118937218025817L; + + public ELIBSecurityProcessException() { + } + + public ELIBSecurityProcessException(String message) { + super(message); + } + + public ELIBSecurityProcessException(String message, Throwable cause) { + super(message, cause); + } + + public ELIBSecurityProcessException(Throwable cause) { + super(cause); + } + + public ELIBSecurityProcessException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/critical/ELIBSecurityCritical.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/critical/ELIBSecurityCritical.java new file mode 100644 index 0000000..4140a89 --- /dev/null +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/critical/ELIBSecurityCritical.java @@ -0,0 +1,28 @@ +package space.qu4nt.entanglementlib.core.exception.security.critical; + +import java.io.Serial; + +public class ELIBSecurityCritical extends Error { + + @Serial + private static final long serialVersionUID = -6905370663633314089L; + + public ELIBSecurityCritical() { + } + + public ELIBSecurityCritical(String message) { + super(message); + } + + public ELIBSecurityCritical(String message, Throwable cause) { + super(message, cause); + } + + public ELIBSecurityCritical(Throwable cause) { + super(cause); + } + + public ELIBSecurityCritical(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/critical/ELIBSecurityNativeCritical.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/critical/ELIBSecurityNativeCritical.java new file mode 100644 index 0000000..9f62306 --- /dev/null +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/critical/ELIBSecurityNativeCritical.java @@ -0,0 +1,28 @@ +package space.qu4nt.entanglementlib.core.exception.security.critical; + +import java.io.Serial; + +public class ELIBSecurityNativeCritical extends ELIBSecurityCritical { + + @Serial + private static final long serialVersionUID = -7557337159377165891L; + + public ELIBSecurityNativeCritical() { + } + + public ELIBSecurityNativeCritical(String message) { + super(message); + } + + public ELIBSecurityNativeCritical(String message, Throwable cause) { + super(message, cause); + } + + public ELIBSecurityNativeCritical(Throwable cause) { + super(cause); + } + + public ELIBSecurityNativeCritical(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/unchecked/ELIBSecurityIllegalArgumentException.java b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/unchecked/ELIBSecurityIllegalArgumentException.java new file mode 100644 index 0000000..1dd7663 --- /dev/null +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/exception/security/unchecked/ELIBSecurityIllegalArgumentException.java @@ -0,0 +1,28 @@ +package space.qu4nt.entanglementlib.core.exception.security.unchecked; + +import java.io.Serial; + +public class ELIBSecurityIllegalArgumentException extends RuntimeException { + + @Serial + private static final long serialVersionUID = 3401738922121497475L; + + public ELIBSecurityIllegalArgumentException() { + } + + public ELIBSecurityIllegalArgumentException(String message) { + super(message); + } + + public ELIBSecurityIllegalArgumentException(String message, Throwable cause) { + super(message, cause); + } + + public ELIBSecurityIllegalArgumentException(Throwable cause) { + super(cause); + } + + public ELIBSecurityIllegalArgumentException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/core/src/main/java/space/qu4nt/entanglementlib/core/i18n/EntanglementLibCoreI18n.java b/core/src/main/java/space/qu4nt/entanglementlib/core/i18n/EntanglementLibCoreI18n.java new file mode 100644 index 0000000..36f16f2 --- /dev/null +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/i18n/EntanglementLibCoreI18n.java @@ -0,0 +1,42 @@ +package space.qu4nt.entanglementlib.core.i18n; + +import lombok.Getter; +import org.jetbrains.annotations.Nullable; +import space.qu4nt.entanglementlib.core.exception.core.ELIBCoreIllegalArgumentException; + +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.Objects; +import java.util.ResourceBundle; + +public class EntanglementLibCoreI18n { + + private static final String SYSTEM_DEFAULT_BASENAME = "entanglementlib-messages"; + + @Getter + private static Locale locale; + @Getter + private static ResourceBundle coreDefaultResourceBundle; + @Getter + private static @Nullable ResourceBundle userResourceBundle; + + public static synchronized void initialize(final Locale locale, @Nullable String userResourceBasename) throws ELIBCoreIllegalArgumentException { + EntanglementLibCoreI18n.locale = Objects.requireNonNull(locale, "locale is null"); + // getLanguage() => ISO 639 alpha2 + try { + coreDefaultResourceBundle = ResourceBundle.getBundle(SYSTEM_DEFAULT_BASENAME, locale); + } catch (MissingResourceException e) { // 시스템 기본 국제화 파일을 찾을 수 없음 + throw new ELIBCoreIllegalArgumentException("Could not find 'system default' i18n(" + locale.getLanguage() + ") file"); + } + + // 사용자 지정된 베이스네임 있으면 사용 + if (userResourceBasename != null && !userResourceBasename.isBlank()) { + try { + userResourceBundle = ResourceBundle.getBundle(userResourceBasename, locale); + } catch (MissingResourceException e) { // 사용자 지정 국제화 파일을 찾을 수 없음 + throw new ELIBCoreIllegalArgumentException("Could not find custom i18n(" + locale.getLanguage() + ") file '" + userResourceBasename + "'"); + } + } + } + +} diff --git a/src/main/java/space/qu4nt/entanglementlib/util/Async.java b/core/src/main/java/space/qu4nt/entanglementlib/core/util/Async.java similarity index 94% rename from src/main/java/space/qu4nt/entanglementlib/util/Async.java rename to core/src/main/java/space/qu4nt/entanglementlib/core/util/Async.java index 4a3ed2f..deb2a7e 100644 --- a/src/main/java/space/qu4nt/entanglementlib/util/Async.java +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/util/Async.java @@ -3,9 +3,9 @@ * Under License "PolyForm Noncommercial License 1.0.0". */ -package space.qu4nt.entanglementlib.util; +package space.qu4nt.entanglementlib.core.util; -import space.qu4nt.entanglementlib.exception.util.EntLibUtilityException; +import space.qu4nt.entanglementlib.core.exception.core.ELIBCoreUtilityException; import java.util.List; import java.util.concurrent.*; @@ -67,7 +67,7 @@ public static CompletableFuture> runAsyncTaskList(List> return future.join(); } catch (CompletionException e) { // 개별 작업에서 발생한 예외를 감싸서 재처리 - throw new EntLibUtilityException(Async.class, "future-join-in-exc", e); + throw new ELIBCoreUtilityException("비동기 작업 중 예외가 발생했습니다!"); } }).collect(Collectors.toList()) ); diff --git a/src/main/java/space/qu4nt/entanglementlib/util/Nill.java b/core/src/main/java/space/qu4nt/entanglementlib/core/util/Nill.java similarity index 94% rename from src/main/java/space/qu4nt/entanglementlib/util/Nill.java rename to core/src/main/java/space/qu4nt/entanglementlib/core/util/Nill.java index 5937ebb..29d70ee 100644 --- a/src/main/java/space/qu4nt/entanglementlib/util/Nill.java +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/util/Nill.java @@ -3,7 +3,9 @@ * Under License "PolyForm Noncommercial License 1.0.0". */ -package space.qu4nt.entanglementlib.util; +package space.qu4nt.entanglementlib.core.util; + +import org.jetbrains.annotations.NotNull; import java.util.function.Supplier; @@ -86,7 +88,7 @@ public static void ifSo(final T obj, final Runnable runnable) { * @param 타입 파라미터 * @return 결과 */ - public static T nullDef(final T obj, final Supplier supplier) { + public static @NotNull T nullDef(final T obj, final @NotNull Supplier supplier) { if (isNull(obj)) return supplier.get(); return obj; diff --git a/src/main/java/space/qu4nt/entanglementlib/util/StringUtil.java b/core/src/main/java/space/qu4nt/entanglementlib/core/util/StringUtil.java similarity index 97% rename from src/main/java/space/qu4nt/entanglementlib/util/StringUtil.java rename to core/src/main/java/space/qu4nt/entanglementlib/core/util/StringUtil.java index 93e9e6d..739cd2c 100644 --- a/src/main/java/space/qu4nt/entanglementlib/util/StringUtil.java +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/util/StringUtil.java @@ -3,9 +3,10 @@ * Under License "PolyForm Noncommercial License 1.0.0". */ -package space.qu4nt.entanglementlib.util; +package space.qu4nt.entanglementlib.core.util; -import space.qu4nt.entanglementlib.exception.util.EntLibUtilityIllegalArgumentException; +import space.qu4nt.entanglementlib.core.exception.core.ELIBCoreIllegalArgumentException; +import space.qu4nt.entanglementlib.core.exception.core.ELIBCoreUtilityException; import java.util.Objects; import java.util.UUID; @@ -61,7 +62,7 @@ public static String replace(String source, String... rep) { if (source == null) return null; if (rep.length % 2 != 0) - throw new EntLibUtilityIllegalArgumentException(StringUtil.class, "replace-arg-not-even-exc"); + throw new ELIBCoreUtilityException("replace-arg-not-even-exc"); for (int i = 0; i < rep.length; i += 2) source = replace(source, rep[i], rep[i + 1]); return source; @@ -242,7 +243,7 @@ public static String truncateMiddle(String input, int maxLength, int prefixLengt return ""; } if (maxLength <= 0 || prefixLength < 0 || suffixLength < 0) - throw new EntLibUtilityIllegalArgumentException(StringUtil.class, "truncate-length-exc"); + throw new ELIBCoreUtilityException("truncate-length-exc"); int inputLength = input.length(); if (inputLength <= maxLength) { diff --git a/src/main/java/space/qu4nt/entanglementlib/util/chunk/ByteArrayChunkProcessor.java b/core/src/main/java/space/qu4nt/entanglementlib/core/util/chunk/ByteArrayChunkProcessor.java similarity index 98% rename from src/main/java/space/qu4nt/entanglementlib/util/chunk/ByteArrayChunkProcessor.java rename to core/src/main/java/space/qu4nt/entanglementlib/core/util/chunk/ByteArrayChunkProcessor.java index 7185232..9c6f7d7 100644 --- a/src/main/java/space/qu4nt/entanglementlib/util/chunk/ByteArrayChunkProcessor.java +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/util/chunk/ByteArrayChunkProcessor.java @@ -3,7 +3,7 @@ * Under License "PolyForm Noncommercial License 1.0.0". */ -package space.qu4nt.entanglementlib.util.chunk; +package space.qu4nt.entanglementlib.core.util.chunk; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/space/qu4nt/entanglementlib/util/chunk/ChunkProcessor.java b/core/src/main/java/space/qu4nt/entanglementlib/core/util/chunk/ChunkProcessor.java similarity index 93% rename from src/main/java/space/qu4nt/entanglementlib/util/chunk/ChunkProcessor.java rename to core/src/main/java/space/qu4nt/entanglementlib/core/util/chunk/ChunkProcessor.java index 6db0c3d..5209acf 100644 --- a/src/main/java/space/qu4nt/entanglementlib/util/chunk/ChunkProcessor.java +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/util/chunk/ChunkProcessor.java @@ -3,7 +3,7 @@ * Under License "PolyForm Noncommercial License 1.0.0". */ -package space.qu4nt.entanglementlib.util.chunk; +package space.qu4nt.entanglementlib.core.util.chunk; /** * 대용량 데이터를 청크 단위로 처리할 때 사용되는 함수형 인터페이스입니다. diff --git a/src/main/java/space/qu4nt/entanglementlib/util/chunk/FileChunkProcessor.java b/core/src/main/java/space/qu4nt/entanglementlib/core/util/chunk/FileChunkProcessor.java similarity index 99% rename from src/main/java/space/qu4nt/entanglementlib/util/chunk/FileChunkProcessor.java rename to core/src/main/java/space/qu4nt/entanglementlib/core/util/chunk/FileChunkProcessor.java index d434337..e666ab5 100644 --- a/src/main/java/space/qu4nt/entanglementlib/util/chunk/FileChunkProcessor.java +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/util/chunk/FileChunkProcessor.java @@ -3,7 +3,7 @@ * Under License "PolyForm Noncommercial License 1.0.0". */ -package space.qu4nt.entanglementlib.util.chunk; +package space.qu4nt.entanglementlib.core.util.chunk; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/space/qu4nt/entanglementlib/util/chunk/StringChunkProcessor.java b/core/src/main/java/space/qu4nt/entanglementlib/core/util/chunk/StringChunkProcessor.java similarity index 98% rename from src/main/java/space/qu4nt/entanglementlib/util/chunk/StringChunkProcessor.java rename to core/src/main/java/space/qu4nt/entanglementlib/core/util/chunk/StringChunkProcessor.java index afdaf1e..9ea1c64 100644 --- a/src/main/java/space/qu4nt/entanglementlib/util/chunk/StringChunkProcessor.java +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/util/chunk/StringChunkProcessor.java @@ -3,7 +3,7 @@ * Under License "PolyForm Noncommercial License 1.0.0". */ -package space.qu4nt.entanglementlib.util.chunk; +package space.qu4nt.entanglementlib.core.util.chunk; import java.util.Objects; import java.util.stream.IntStream; diff --git a/src/main/java/space/qu4nt/entanglementlib/util/collection/SafeList.java b/core/src/main/java/space/qu4nt/entanglementlib/core/util/collection/SafeList.java similarity index 85% rename from src/main/java/space/qu4nt/entanglementlib/util/collection/SafeList.java rename to core/src/main/java/space/qu4nt/entanglementlib/core/util/collection/SafeList.java index d7d7672..9698eaa 100644 --- a/src/main/java/space/qu4nt/entanglementlib/util/collection/SafeList.java +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/util/collection/SafeList.java @@ -3,10 +3,9 @@ * Under License "PolyForm Noncommercial License 1.0.0". */ -package space.qu4nt.entanglementlib.util.collection; +package space.qu4nt.entanglementlib.core.util.collection; import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.resource.language.LanguageInstanceBased; import java.util.ArrayList; import java.util.Iterator; @@ -24,9 +23,6 @@ */ public class SafeList implements Iterable { - @SuppressWarnings({"rawtypes"}) - private static final LanguageInstanceBased lang = LanguageInstanceBased.create(SafeList.class); - private final ArrayList list = new ArrayList<>(); /** @@ -56,7 +52,7 @@ public synchronized void add(int index, T item) { public synchronized T remove(int index) { if (index < 0 || index >= list.size()) - throw new IndexOutOfBoundsException(lang.argsNonTopKey("index-out-of-bounds-exc", index, list.size())); + throw new IndexOutOfBoundsException("index-out-of-bounds-exc"); return list.remove(index); } @@ -66,13 +62,13 @@ public synchronized boolean remove(T item) { public synchronized T get(int index) { if (index < 0 || index >= list.size()) - throw new IndexOutOfBoundsException(lang.argsNonTopKey("index-out-of-bounds-exc", index, list.size())); + throw new IndexOutOfBoundsException("index-out-of-bounds-exc"); return list.get(index); } public synchronized T set(int index, T item) { if (index < 0 || index >= list.size()) - throw new IndexOutOfBoundsException(lang.argsNonTopKey("index-out-of-bounds-exc", index, list.size())); + throw new IndexOutOfBoundsException("index-out-of-bounds-exc"); return list.set(index, item); } diff --git a/src/main/java/space/qu4nt/entanglementlib/util/wrapper/Arrays.java b/core/src/main/java/space/qu4nt/entanglementlib/core/util/wrapper/Arrays.java similarity index 98% rename from src/main/java/space/qu4nt/entanglementlib/util/wrapper/Arrays.java rename to core/src/main/java/space/qu4nt/entanglementlib/core/util/wrapper/Arrays.java index 8589f12..f7ff7f3 100644 --- a/src/main/java/space/qu4nt/entanglementlib/util/wrapper/Arrays.java +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/util/wrapper/Arrays.java @@ -3,7 +3,7 @@ * Under License "PolyForm Noncommercial License 1.0.0". */ -package space.qu4nt.entanglementlib.util.wrapper; +package space.qu4nt.entanglementlib.core.util.wrapper; import java.math.BigInteger; diff --git a/src/main/java/space/qu4nt/entanglementlib/util/wrapper/Hex.java b/core/src/main/java/space/qu4nt/entanglementlib/core/util/wrapper/Hex.java similarity index 84% rename from src/main/java/space/qu4nt/entanglementlib/util/wrapper/Hex.java rename to core/src/main/java/space/qu4nt/entanglementlib/core/util/wrapper/Hex.java index fd1dde6..c106848 100644 --- a/src/main/java/space/qu4nt/entanglementlib/util/wrapper/Hex.java +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/util/wrapper/Hex.java @@ -3,7 +3,7 @@ * Under License "PolyForm Noncommercial License 1.0.0". */ -package space.qu4nt.entanglementlib.util.wrapper; +package space.qu4nt.entanglementlib.core.util.wrapper; /** * {@code BouncyCastle} 라이브러리의 {@code Hex} 클래스의 몇 가지 @@ -22,7 +22,7 @@ public final class Hex { * @return 바이트 배열의 Hex 문자열 표현 */ public static String toHexString(byte[] bytes) { - return org.bouncycastle.util.encoders.Hex.toHexString(bytes); + return null; // Q. T. Felix TODO: native } } diff --git a/src/main/java/space/qu4nt/entanglementlib/util/wrapper/Pair.java b/core/src/main/java/space/qu4nt/entanglementlib/core/util/wrapper/Pair.java similarity index 91% rename from src/main/java/space/qu4nt/entanglementlib/util/wrapper/Pair.java rename to core/src/main/java/space/qu4nt/entanglementlib/core/util/wrapper/Pair.java index cbd8c89..9286511 100644 --- a/src/main/java/space/qu4nt/entanglementlib/util/wrapper/Pair.java +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/util/wrapper/Pair.java @@ -3,7 +3,7 @@ * Under License "PolyForm Noncommercial License 1.0.0". */ -package space.qu4nt.entanglementlib.util.wrapper; +package space.qu4nt.entanglementlib.core.util.wrapper; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/space/qu4nt/entanglementlib/util/wrapper/Tuple.java b/core/src/main/java/space/qu4nt/entanglementlib/core/util/wrapper/Tuple.java similarity index 92% rename from src/main/java/space/qu4nt/entanglementlib/util/wrapper/Tuple.java rename to core/src/main/java/space/qu4nt/entanglementlib/core/util/wrapper/Tuple.java index 7bb71a0..e1288b3 100644 --- a/src/main/java/space/qu4nt/entanglementlib/util/wrapper/Tuple.java +++ b/core/src/main/java/space/qu4nt/entanglementlib/core/util/wrapper/Tuple.java @@ -3,7 +3,7 @@ * Under License "PolyForm Noncommercial License 1.0.0". */ -package space.qu4nt.entanglementlib.util.wrapper; +package space.qu4nt.entanglementlib.core.util.wrapper; import lombok.AllArgsConstructor; import lombok.Getter; 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/qodana.yaml b/qodana.yaml deleted file mode 100644 index 8bc8ec1..0000000 --- a/qodana.yaml +++ /dev/null @@ -1,10 +0,0 @@ -#################################################################################################################### -# WARNING: Do not store sensitive information in this file, as its contents will be included in the Qodana report. # -#################################################################################################################### - -version: "1.0" -linter: jetbrains/qodana-jvm-community:2025.3 -profile: - name: qodana.recommended -include: - - name: CheckDependencyLicenses \ No newline at end of file diff --git a/security/build.gradle.kts b/security/build.gradle.kts new file mode 100644 index 0000000..2208733 --- /dev/null +++ b/security/build.gradle.kts @@ -0,0 +1,73 @@ +plugins { + id("me.champeau.jmh") version "0.7.3" +} + +dependencies { + implementation(project(":core")) + implementation(project(":annotations")) + + // JMH + jmh("org.openjdk.jmh:jmh-core:1.37") + jmh("org.openjdk.jmh:jmh-generator-annprocess:1.37") + // Source: https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-core + testImplementation("org.openjdk.jmh:jmh-core:1.37") + // Source: https://mvnrepository.com/artifact/org.openjdk.jmh/jmh-generator-annprocess + testImplementation("org.openjdk.jmh:jmh-generator-annprocess:1.37") + jmhAnnotationProcessor("org.projectlombok:lombok:1.18.42") +} + +sourceSets { + named("jmh") { + java { + srcDirs("src/benchmark/java") + } + resources { + srcDirs("src/benchmark/resources") + } + } +} + +tasks.jar { + from("../entlib-native/dist") { + into("native") + include( + "linux/libentlib_native_aarch64.so", + "linux/libentlib_native_x86_64.so", + "macos/libentlib_native_aarch64.dylib", + "macos/libentlib_native_universal.dylib", + "macos/libentlib_native_x86_64.dylib", + "windows/entlib_native_x86_64.dll" + ) + } +} + +// +// JMH - start +// +jmh { + jmhVersion.set("1.37") + + // 테스트 벤치마킹 클래스 등록 + includeTests.set(true) + includes.set(listOf(".*_JMHBenchmark")) + + // 벤치마크 실행 시 필요한 jvm 인자 중앙 제어 + jvmArgs.set(listOf( + "--enable-native-access=ALL-UNNAMED", + "--enable-preview", + "-Djava.library.path=${rootDir}/native-benchmark/target/debug", + "-Xms2g", "-Xmx2g" // gc 간섭 최소화 + )) + fork.set(1) + + // 결과 출력 포맷 + resultFormat.set("JSON") + + // 벤치마크 수행 옵션 (프로세스 및 반복 횟수) + fork.set(1) + warmupIterations.set(3) + iterations.set(5) +} +// +// JMH - end +// \ No newline at end of file diff --git a/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeAVXCall_JMHBenchmark.java b/security/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeAVXCall_JMHBenchmark.java similarity index 98% rename from src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeAVXCall_JMHBenchmark.java rename to security/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeAVXCall_JMHBenchmark.java index 6a0bab6..13d93a0 100644 --- a/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeAVXCall_JMHBenchmark.java +++ b/security/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeAVXCall_JMHBenchmark.java @@ -1,7 +1,8 @@ -package space.qu4nt.entanglementlib.benchmark;/* +/* * Copyright © 2025-2026 Quant. * Under License "PolyForm Noncommercial License 1.0.0". */ +package space.qu4nt.entanglementlib.benchmark; import org.openjdk.jmh.annotations.*; diff --git a/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeCall_JMHBenchmark.java b/security/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeCall_JMHBenchmark.java similarity index 99% rename from src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeCall_JMHBenchmark.java rename to security/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeCall_JMHBenchmark.java index 82f5979..8e83dd0 100644 --- a/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeCall_JMHBenchmark.java +++ b/security/src/benchmark/java/space/qu4nt/entanglementlib/benchmark/NativeCall_JMHBenchmark.java @@ -1,7 +1,8 @@ -package space.qu4nt.entanglementlib.benchmark;/* +/* * Copyright © 2025-2026 Quant. * Under License "PolyForm Noncommercial License 1.0.0". */ +package space.qu4nt.entanglementlib.benchmark; import org.openjdk.jmh.annotations.*; @@ -59,7 +60,6 @@ public class NativeCall_JMHBenchmark { ); - // 보안 벡터 처리 함수 FFM_SECURE_VECTOR_HANDLE = LINKER.downcallHandle( lookup.find("process_secure_vector").orElseThrow(), @@ -74,7 +74,6 @@ public class NativeCall_JMHBenchmark { ); - // 다항식 모듈러 가산 함수 FFM_POLY_MODULAR_ADD = LINKER.downcallHandle( lookup.find("poly_modular_add").orElseThrow(), diff --git a/src/benchmark/resources/logback.xml b/security/src/benchmark/resources/logback.xml similarity index 100% rename from src/benchmark/resources/logback.xml rename to security/src/benchmark/resources/logback.xml diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/EntanglementLibSecurityConfig.java b/security/src/main/java/space/qu4nt/entanglementlib/security/EntanglementLibSecurityConfig.java new file mode 100644 index 0000000..1b9db53 --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/EntanglementLibSecurityConfig.java @@ -0,0 +1,32 @@ +package space.qu4nt.entanglementlib.security; + +import lombok.Getter; +import lombok.Setter; +import org.jetbrains.annotations.Nullable; +import space.qu4nt.entanglementlib.core.util.Nill; +import space.qu4nt.entanglementlib.security.data.HeuristicArenaFactory; +import space.qu4nt.entanglementlib.security.entlibnative.NativeSpecContext; + +@Getter +@Setter +public class EntanglementLibSecurityConfig { + + private NativeSpecContext nativeContext; + private HeuristicArenaFactory.ArenaMode arenaMode; + + private EntanglementLibSecurityConfig(final NativeSpecContext nativeContext, + final @Nullable HeuristicArenaFactory.ArenaMode arenaMode) { + this.nativeContext = nativeContext; + this.arenaMode = arenaMode; + } + + public static EntanglementLibSecurityConfig create( + final NativeSpecContext nativeContext, + final @Nullable HeuristicArenaFactory.ArenaMode arenaMode + ) { + return new EntanglementLibSecurityConfig( + Nill.nullDef(nativeContext, NativeSpecContext::defaults), + Nill.nullDef(arenaMode, () -> HeuristicArenaFactory.ArenaMode.AUTO) + ); + } +} diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/EntanglementLibSecurityFacade.java b/security/src/main/java/space/qu4nt/entanglementlib/security/EntanglementLibSecurityFacade.java new file mode 100644 index 0000000..66eb4ab --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/EntanglementLibSecurityFacade.java @@ -0,0 +1,17 @@ +package space.qu4nt.entanglementlib.security; + +import org.jetbrains.annotations.NotNull; +import space.qu4nt.entanglementlib.security.data.HeuristicArenaFactory; +import space.qu4nt.entanglementlib.security.entlibnative.NativeLoader; + +public final class EntanglementLibSecurityFacade { + + private EntanglementLibSecurityFacade() { + throw new UnsupportedOperationException("cannot access"); + } + + public static void initialize(@NotNull EntanglementLibSecurityConfig config) { + NativeLoader.loadNativeLibrary(config); + HeuristicArenaFactory.setGlobalArenaMode(config.getArenaMode()); + } +} 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 new file mode 100644 index 0000000..4882862 --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/ChaCha20.java @@ -0,0 +1,109 @@ +package space.qu4nt.entanglementlib.security.crypto; + +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityProcessException; +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 java.lang.foreign.MemorySegment; + +@Slf4j +public final class ChaCha20 { + + private ChaCha20() { + throw new UnsupportedOperationException("cannot access"); + } + + /// ChaCha20-Poly1305 암호화를 수행합니다. + /// + /// @param context 메모리 소거 생명주기를 관리하는 컨텍스트 + /// @param key 32바이트 대칭키 (호출 즉시 네이티브로 이전 후 소거됨) + /// @param nonce 12바이트 Nonce (호출 즉시 네이티브로 이전 후 소거됨) + /// @param aad 추가 인증 데이터 (선택 사항) + /// @param plaintext 암호화할 평문 데이터 (호출 즉시 네이티브로 이전 후 소거됨) + /// @return 암호화된 사이퍼텍스트(MAC 태그 포함)를 담은 SDC 객체 + public static @NotNull SensitiveDataContainer encrypt( + final @NotNull SDCScopeContext context, + final @NotNull SensitiveDataContainer key, + final @NotNull SensitiveDataContainer nonce, + final @Nullable SensitiveDataContainer aad, + final @NotNull SensitiveDataContainer plaintext) throws ELIBSecurityProcessException { + + try { + // 입력 데이터를 SDC에 할당하여 네이티브 메모리로 이전 (forceWipe = true로 힙 메모리 즉시 소거) + MemorySegment aadSegment = MemorySegment.NULL; + long aadLen = 0; + if (aad != null) { + aadSegment = InternalNativeBridge.unwrapMemorySegment(aad); + aadLen = InternalNativeBridge.unwrapMemorySegment(aad).byteSize(); + } + + // Rust FFI 호출 (Callee-allocated Opaque Pointer 반환) + MemorySegment rustBufferPtr = (MemorySegment) EntLibNativeManager + .call(Function.ChaCha20_Poly1305_Encrypt) + .invokeExact( + InternalNativeBridge.unwrapMemorySegment(key), (long) InternalNativeBridge.unwrapMemorySegment(key).byteSize(), + InternalNativeBridge.unwrapMemorySegment(nonce), (long) InternalNativeBridge.unwrapMemorySegment(nonce).byteSize(), + aadSegment, aadLen, + InternalNativeBridge.unwrapMemorySegment(plaintext), (long) InternalNativeBridge.unwrapMemorySegment(plaintext).byteSize() + ); + + if (rustBufferPtr.equals(MemorySegment.NULL)) { + throw new ELIBSecurityProcessException("ChaCha20 암호화 실패: 유효하지 않은 입력 길이"); + } + + return EntLibNativeManager.transferNativeBufferBindToContext( + context, rustBufferPtr + ); + } catch (Throwable t) { + log.error("ChaCha20 암호화 중 치명적 보안 예외 발생", t); + throw new ELIBSecurityProcessException("암호화 프로세스 실패", t); + } + } + + /// ChaCha20-Poly1305 복호화를 수행합니다. + /// + /// @return 복호화된 평문 데이터를 담은 SDC 객체 + public static @NotNull SensitiveDataContainer decrypt( + final @NotNull SDCScopeContext context, + final @NotNull SensitiveDataContainer key, + final @NotNull SensitiveDataContainer nonce, + final @Nullable SensitiveDataContainer aad, + final @NotNull SensitiveDataContainer ciphertext) throws ELIBSecurityProcessException { + + try { + MemorySegment aadSegment = MemorySegment.NULL; + long aadLen = 0; + if (aad != null) { + aadSegment = InternalNativeBridge.unwrapMemorySegment(aad); + aadLen = InternalNativeBridge.unwrapMemorySegment(aad).byteSize(); + } + + MemorySegment rustBufferPtr = (MemorySegment) EntLibNativeManager + .call(Function.ChaCha20_Poly1305_Decrypt) + .invokeExact( + InternalNativeBridge.unwrapMemorySegment(key), (long) InternalNativeBridge.unwrapMemorySegment(key).byteSize(), + InternalNativeBridge.unwrapMemorySegment(nonce), (long) InternalNativeBridge.unwrapMemorySegment(nonce).byteSize(), + aadSegment, aadLen, + InternalNativeBridge.unwrapMemorySegment(ciphertext), (long) InternalNativeBridge.unwrapMemorySegment(ciphertext).byteSize() + ); + + // Authentication Failed 또는 입력 오류 + if (rustBufferPtr.equals(MemorySegment.NULL)) { + throw new ELIBSecurityProcessException("ChaCha20 복호화 실패: 무결성 검증(MAC) 실패 또는 유효하지 않은 입력"); + } + + return EntLibNativeManager.transferNativeBufferBindToContext( + context, rustBufferPtr + ); + } catch (Throwable t) { + log.error("ChaCha20 복호화 중 치명적 보안 예외 발생", t); + throw new ELIBSecurityProcessException("복호화 프로세스 실패", 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 new file mode 100644 index 0000000..c1052cb --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/encode/Base64.java @@ -0,0 +1,93 @@ +package space.qu4nt.entanglementlib.security.crypto.encode; + +import space.qu4nt.entanglementlib.security.data.InternalNativeBridge; +import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer; +import space.qu4nt.entanglementlib.security.entlibnative.EntLibNativeManager; +import space.qu4nt.entanglementlib.security.entlibnative.Function; +import sun.misc.Unsafe; + +/** + * 네이티브 메모리(native memory) 기반의 안전한 Base64 인코딩 및 디코딩 유틸리티입니다. + * 모든 입출력은 {@link SensitiveDataContainer}를 통해 소유권(ownership)이 통제되며, + * 가비지 컬렉터(garbage collector)가 관리하는 자바 힙(java heap) 메모리에 민감 데이터를 노출하지 않습니다. + */ +public final class Base64 { + + private Base64() { + throw new AssertionError("유틸리티 클래스는 인스턴스화할 수 없습니다."); + } + + /** + * 제공된 보안 컨테이너의 데이터를 Base64로 인코딩합니다. + * 반환된 새 컨테이너는 호출자(caller)가 소유권을 가지며, 세션 종료 시 명시적으로 소거해야 합니다. + * + * @param input 원본 데이터가 담긴 보안 컨테이너 + * @return Base64 인코딩 결과가 담긴 새로운 보안 컨테이너 + */ + public static SensitiveDataContainer encode(final SensitiveDataContainer input) { + if (input == null || !InternalNativeBridge.unwrapArena(input).scope().isAlive()) { + throw new IllegalArgumentException("유효하지 않거나 이미 소거된 입력 컨테이너입니다."); + } + + 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; + } catch (Throwable t) { + output.close(); + throw new RuntimeException("FFM API 인코딩 호출 실패", t); + } + } + + /** + * 제공된 보안 컨테이너의 Base64 데이터를 디코딩합니다. + * 반환된 새 컨테이너는 호출자(caller)가 소유권을 가지며, 세션 종료 시 명시적으로 소거해야 합니다. + * + * @param input Base64 인코딩 데이터가 담긴 보안 컨테이너 + * @return 디코딩된 원본 데이터가 담긴 새로운 보안 컨테이너 + */ + public static SensitiveDataContainer decode(final SensitiveDataContainer input) { + if (input == null || !InternalNativeBridge.unwrapArena(input).scope().isAlive()) { + throw new IllegalArgumentException("유효하지 않거나 이미 소거된 입력 컨테이너입니다."); + } + + final long inputLen = InternalNativeBridge.unwrapMemorySegment(input).byteSize(); + final int maxRequired = (int) ((inputLen / 4 + 1) * 3); + + SensitiveDataContainer output = new SensitiveDataContainer(maxRequired); + + try { + long result = (long) EntLibNativeManager.call(Function.Base64_decode).invokeExact( + InternalNativeBridge.unwrapMemorySegment(input), + inputLen, + InternalNativeBridge.unwrapMemorySegment(output), + (long) maxRequired + ); + + if (result < 0) { + output.close(); + throw new RuntimeException("디코딩 중 네이티브 오류 발생 (error code): " + result); + } + + return output; + } catch (Throwable t) { + output.close(); + throw new RuntimeException("FFM API 디코딩 호출 실패", t); + } + } +} \ 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 new file mode 100644 index 0000000..46f024a --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/hash/Hash.java @@ -0,0 +1,218 @@ +package space.qu4nt.entanglementlib.security.crypto.hash; + +import org.jetbrains.annotations.NotNull; +import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityProcessException; +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 java.lang.foreign.MemorySegment; +import java.lang.invoke.MethodHandle; +import java.util.stream.IntStream; + +public final class Hash { + + public static SensitiveDataContainer sha2( + final int length, + @NotNull SDCScopeContext scope, + @NotNull SensitiveDataContainer input + ) throws Throwable { + final int[] ableLens = {224, 256, 384, 512}; + if (IntStream.of(ableLens).noneMatch(l -> l == length)) + return null; + + // 길이에 맞는 함수 등록 + Function 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; + } + case 256 -> { + newContextFunc = Function.SHA2_256_New; + updateFunc = Function.SHA2_256_Update; + finalizeFunc = Function.SHA2_256_Finalize; + freeFunc = Function.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; + } + case 512 -> { + newContextFunc = Function.SHA2_512_New; + updateFunc = Function.SHA2_512_Update; + finalizeFunc = Function.SHA2_512_Finalize; + freeFunc = Function.SHA2_512_Free; + } + default -> throw new ELIBSecurityProcessException("불가능한 길이"); + } + + // 컨텍스트 생성 + MemorySegment ctx = (MemorySegment) EntLibNativeManager.call(newContextFunc).invokeExact(); + boolean isCtxConsumed = false; // double-free 방지를 위한 상태 플래그 + + try { + // 데이터 업데이트 + MethodHandle updateMH = EntLibNativeManager.call(updateFunc); + MemorySegment dataSeg = InternalNativeBridge.unwrapMemorySegment(input); + int status = (int) updateMH.invokeExact(ctx, dataSeg, dataSeg.byteSize()); + if (status != 0) { + throw new ELIBSecurityProcessException("업데이트 실패, 상태 코드: " + status); + } + + // 연산 수행 -> SecureBuffer* 반환 (ctx 소유권 소비됨) + MemorySegment secureBufferPtr = (MemorySegment) EntLibNativeManager.call(finalizeFunc) + .invokeExact(ctx); + isCtxConsumed = true; // 성공적으로 finalize 되었으므로 플래그 전환 + + if (secureBufferPtr.equals(MemorySegment.NULL)) + throw new ELIBSecurityProcessException("해시 연산 결과가 null입니다!"); + + return EntLibNativeManager.transferNativeBufferBindToContext( + scope, secureBufferPtr + ); // 이 작업 내에서 버퍼 소거가 진행됨 + } finally { + // 예외 발생 등으로 인해 finalize가 호출되지 않은 경우에만 early free + if (!isCtxConsumed && ctx != null && !ctx.equals(MemorySegment.NULL)) { + EntLibNativeManager.call(freeFunc).invokeExact(ctx); + } + } + } + + public static SensitiveDataContainer sha3( + final int length, + @NotNull SDCScopeContext scope, + @NotNull SensitiveDataContainer input + ) throws Throwable { + final int[] ableLens = {224, 256, 384, 512}; + if (IntStream.of(ableLens).noneMatch(l -> l == length)) + return null; + + // 길이에 맞는 함수 등록 + Function 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; + } + case 256 -> { + newContextFunc = Function.SHA3_256_New; + updateFunc = Function.SHA3_256_Update; + finalizeFunc = Function.SHA3_256_Finalize; + freeFunc = Function.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; + } + case 512 -> { + newContextFunc = Function.SHA3_512_New; + updateFunc = Function.SHA3_512_Update; + finalizeFunc = Function.SHA3_512_Finalize; + freeFunc = Function.SHA3_512_Free; + } + default -> throw new ELIBSecurityProcessException("불가능한 길이"); + } + + // 컨텍스트 생성 + MemorySegment ctx = (MemorySegment) EntLibNativeManager.call(newContextFunc).invokeExact(); + boolean isCtxConsumed = false; // double-free 방지를 위한 상태 플래그 + + try { + // 데이터 업데이트 + MethodHandle updateMH = EntLibNativeManager.call(updateFunc); + MemorySegment dataSeg = InternalNativeBridge.unwrapMemorySegment(input); + int status = (int) updateMH.invokeExact(ctx, dataSeg, dataSeg.byteSize()); + if (status != 0) { + throw new ELIBSecurityProcessException("업데이트 실패, 상태 코드: " + status); + } + + // 연산 수행 -> SecureBuffer* 반환 (ctx 소유권 소비됨) + MemorySegment secureBufferPtr = (MemorySegment) EntLibNativeManager.call(finalizeFunc) + .invokeExact(ctx); + isCtxConsumed = true; // 성공적으로 finalize 되었으므로 플래그 전환 + + if (secureBufferPtr.equals(MemorySegment.NULL)) + throw new ELIBSecurityProcessException("해시 연산 결과가 null입니다!"); + + return EntLibNativeManager.transferNativeBufferBindToContext( + scope, secureBufferPtr + ); // 이 작업 내에서 버퍼 소거가 진행됨 + } finally { + // 예외 발생 등으로 인해 finalize가 호출되지 않은 경우에만 early free + if (!isCtxConsumed && ctx != null && !ctx.equals(MemorySegment.NULL)) { + EntLibNativeManager.call(freeFunc).invokeExact(ctx); + } + } + } + + public static SensitiveDataContainer sha3Shake( + final int length, + final long byteOutLen, + @NotNull SDCScopeContext scope, + @NotNull SensitiveDataContainer input + ) throws Throwable { + final int[] ableLens = {128, 256}; + if (IntStream.of(ableLens).noneMatch(l -> l == length)) + return null; + + // 길이에 맞는 함수 등록 + Function 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; + } + case 256 -> { + newContextFunc = Function.SHA3_SHAKE256_New; + updateFunc = Function.SHA3_SHAKE256_Update; + finalizeFunc = Function.SHA3_SHAKE256_Finalize; + freeFunc = Function.SHA3_SHAKE256_Free; + } + default -> throw new ELIBSecurityProcessException("불가능한 길이"); + } + + // 컨텍스트 생성 + MemorySegment ctx = (MemorySegment) EntLibNativeManager.call(newContextFunc).invokeExact(); + boolean isCtxConsumed = false; // double-free 방지를 위한 상태 플래그 + + try { + // 데이터 업데이트 + MethodHandle updateMH = EntLibNativeManager.call(updateFunc); + MemorySegment dataSeg = InternalNativeBridge.unwrapMemorySegment(input); + int status = (int) updateMH.invokeExact(ctx, dataSeg, dataSeg.byteSize()); + if (status != 0) { + throw new ELIBSecurityProcessException("업데이트 실패, 상태 코드: " + status); + } + + // 연산 수행 -> SecureBuffer* 반환 (ctx 소유권 소비됨) + MemorySegment secureBufferPtr = (MemorySegment) EntLibNativeManager.call(finalizeFunc) + .invokeExact(ctx, byteOutLen); + isCtxConsumed = true; // 성공적으로 finalize 되었으므로 플래그 전환 + + if (secureBufferPtr.equals(MemorySegment.NULL)) + throw new ELIBSecurityProcessException("해시 연산 결과가 null입니다."); + + return EntLibNativeManager.transferNativeBufferBindToContext( + scope, secureBufferPtr + ); // 이 작업 내에서 버퍼 소거가 진행됨 + } finally { + // 예외 발생 등으로 인해 finalize가 호출되지 않은 경우에만 early free + if (!isCtxConsumed && ctx != null && !ctx.equals(MemorySegment.NULL)) { + EntLibNativeManager.call(freeFunc).invokeExact(ctx); + } + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..8b102f4 --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/crypto/rng/RNG.java @@ -0,0 +1,90 @@ +package space.qu4nt.entanglementlib.security.crypto.rng; + +import org.jetbrains.annotations.NotNull; +import space.qu4nt.entanglementlib.core.exception.security.critical.ELIBSecurityNativeCritical; +import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityProcessException; +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 java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; + +/// 하드웨어 진난수 및 양자 난수(quantum random number) 생성 인터페이스입니다. +/// 모든 반환 데이터는 네이티브 스코프 내에서 완벽한 데이터 소거(zeroize)가 보장됩니다. +/// +/// @author Q. T. Felix +public final class RNG { + + public static final byte LOCAL_HARDWARE = (byte) 0; + public static final byte QUANTUM_NETWORK = (byte) 1; + + /// 지정된 엔트로피 전략(entropy strategy)에 따라 난수를 생성하고 컨테이너에 바인딩합니다. + /// + /// @param entropyStrategy 난수 생성 전략 (로컬 하드웨어 또는 양자 네트워크) + /// @param scope 보안 데이터 생명주기를 관리하는 컨텍스트(context) + /// @param length 생성할 난수의 바이트 길이 + /// @return `heap` 오염 없이 난수 데이터를 소유하는 민감 데이터 컨테이너 + /// @throws ELIBSecurityProcessException 난수 생성 또는 복사 중 에러 발생 시 + public static SensitiveDataContainer generateRNG(final byte entropyStrategy, + final @NotNull SDCScopeContext scope, + final long length) throws ELIBSecurityProcessException { + + // FFI 호출에 필요한 파라미터 및 포인터를 임시 관리하기 위한 로컬 아레나(arena) + try (Arena localArena = Arena.ofConfined()) { + MemorySegment errFlag = localArena.allocate(ValueLayout.JAVA_BYTE); + + // 혼합 난수 생성기(mixed rng) 인스턴스 초기화 + MemorySegment rngPtr = (MemorySegment) EntLibNativeManager.call(Function.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) + .invokeExact(rngPtr, length, errFlag); + checkError(errFlag.get(ValueLayout.JAVA_BYTE, 0)); + + // 러스트 영역의 보안 버퍼(secure buffer)에서 실제 데이터 포인터 추출 + MemorySegment dataPtr = (MemorySegment) EntLibNativeManager.call(Function.Callee_Secure_Buffer_Data) + .invokeExact(secureBufPtr); + MemorySegment nativeDataSegment = dataPtr.reinterpret(length); + + // 컨텍스트를 통한 SDC 할당 + SensitiveDataContainer sdc = scope.allocate((int) length); + + // heap 배열을 거치지 않고 네이티브 대 네이티브로 직접 메모리 복사 수행 + MemorySegment.copy(nativeDataSegment, 0, InternalNativeBridge.unwrapMemorySegment(sdc), 0, length); + + return sdc; + } finally { + // 러스트 힙에 할당된 포인터의 강제 해제 및 소거 유도 + if (secureBufPtr != null && !secureBufPtr.equals(MemorySegment.NULL)) { + EntLibNativeManager.call(Function.Callee_Secure_Buffer_Free).invokeExact(secureBufPtr); + } + if (rngPtr != null && !rngPtr.equals(MemorySegment.NULL)) { + EntLibNativeManager.call(Function.RNG_MIXED_Free).invokeExact(rngPtr); + } + } + } catch (Throwable e) { + throw new ELIBSecurityProcessException("네이티브 브릿지를 통한 난수 생성에 실패했습니다.", e); + } + } + + private static void checkError(byte errorCode) { + if (errorCode == 0) return; + throw switch (errorCode) { + case 1 -> new ELIBSecurityNativeCritical("지원하지 않는 하드웨어(hardware) 환경입니다!"); + case 2 -> new ELIBSecurityNativeCritical("엔트로피(entropy)가 고갈되었습니다!"); + case 3 -> new ELIBSecurityNativeCritical("잘못된 메모리 포인터(pointer) 참조입니다!"); + case 4 -> new ELIBSecurityNativeCritical("양자 난수 생성기 네트워크(network) 통신에 실패했습니다!"); + case 5 -> new ELIBSecurityNativeCritical("양자 난수 데이터 파싱(parsing) 에러가 발생했습니다!"); + case 6 -> new ELIBSecurityNativeCritical("잘못된 파라미터(parameter)가 전달되었습니다!"); + default -> new ELIBSecurityNativeCritical("알 수 없는 네이티브(native) 에러 코드: " + errorCode); + }; + } +} \ No newline at end of file diff --git a/src/main/java/space/qu4nt/entanglementlib/HeuristicArenaFactory.java b/security/src/main/java/space/qu4nt/entanglementlib/security/data/HeuristicArenaFactory.java similarity index 98% rename from src/main/java/space/qu4nt/entanglementlib/HeuristicArenaFactory.java rename to security/src/main/java/space/qu4nt/entanglementlib/security/data/HeuristicArenaFactory.java index 1dc2662..37bd509 100644 --- a/src/main/java/space/qu4nt/entanglementlib/HeuristicArenaFactory.java +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/data/HeuristicArenaFactory.java @@ -3,7 +3,7 @@ * Under License "PolyForm Noncommercial License 1.0.0". */ -package space.qu4nt.entanglementlib; +package space.qu4nt.entanglementlib.security.data; import lombok.extern.slf4j.Slf4j; diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/data/InternalNativeBridge.java b/security/src/main/java/space/qu4nt/entanglementlib/security/data/InternalNativeBridge.java new file mode 100644 index 0000000..0285cd4 --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/data/InternalNativeBridge.java @@ -0,0 +1,24 @@ +package space.qu4nt.entanglementlib.security.data; + +import org.jetbrains.annotations.NotNull; +import space.qu4nt.entanglementlib.annotations.Unsafe; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.util.Objects; + +public final class InternalNativeBridge { + + private InternalNativeBridge() { + } + + @Unsafe("사용자가 직접 Arena를 조작하는 것은 권장되지 않음") + public static @NotNull Arena unwrapArena(final @NotNull SensitiveDataContainer container) { + return Objects.requireNonNull(container, "container").getArena(); + } + + @Unsafe("MemorySegment는 내부적으로 heap에 데이터가 운반될 수 있는 기능을 포함") + public static @NotNull MemorySegment unwrapMemorySegment(final @NotNull SensitiveDataContainer container) { + return Objects.requireNonNull(container, "container").getMemorySegment(); + } +} diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/data/SDCConsumer.java b/security/src/main/java/space/qu4nt/entanglementlib/security/data/SDCConsumer.java new file mode 100644 index 0000000..27b03ee --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/data/SDCConsumer.java @@ -0,0 +1,9 @@ +package space.qu4nt.entanglementlib.security.data; + +import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityProcessException; + +@FunctionalInterface +public interface SDCConsumer { + + void accept(SensitiveDataContainer container) throws ELIBSecurityProcessException; +} diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/data/SDCFunction.java b/security/src/main/java/space/qu4nt/entanglementlib/security/data/SDCFunction.java new file mode 100644 index 0000000..3149e0a --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/data/SDCFunction.java @@ -0,0 +1,9 @@ +package space.qu4nt.entanglementlib.security.data; + +import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityProcessException; + +@FunctionalInterface +public interface SDCFunction { + + R apply(SensitiveDataContainer container) throws ELIBSecurityProcessException; +} 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 new file mode 100644 index 0000000..c08aa8d --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/data/SDCScopeContext.java @@ -0,0 +1,61 @@ +package space.qu4nt.entanglementlib.security.data; + +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/// 보안 작업 흐름 내에서 생성되는 모든 민감 데이터 컨테이너를 추적하고, +/// 스코프 종료 시 일괄 소거(zeroize)를 보장하는 컨텍스트 클래스입니다. +/// +/// @author Q. T. Felix +/// @since 1.1.1 +@Slf4j +public final class SDCScopeContext implements AutoCloseable { + + private final List<@NotNull SensitiveDataContainer> trackedContainers = Collections.synchronizedList(new ArrayList<>()); + private volatile boolean isAlive = true; + + /// 스코프 내에서 새로운 [SensitiveDataContainer]를 할당하는 메소드입니다. + /// 생성된 컨테이너는 자동으로 현재 스코프에 바인딩됩니다. + public SensitiveDataContainer allocate(int size) { + checkAlive(); + SensitiveDataContainer container = new SensitiveDataContainer(size); + trackedContainers.add(container); + return container; + } + + /// 기존 바이트 배열로부터 데이터 소유권을 이전받는 컨테이너를 생성하는 메소드입니다. + public SensitiveDataContainer allocate(byte[] from, boolean forceWipe) { + checkAlive(); + SensitiveDataContainer container = new SensitiveDataContainer(from, forceWipe); + trackedContainers.add(container); + return container; + } + + private void checkAlive() { + if (!isAlive) + throw new IllegalStateException("이미 소거 완료된 보안 스코프입니다!"); + } + + @Override + public void close() { + if (!isAlive) return; + isAlive = false; + + // 스냅샷 소거 + synchronized (trackedContainers) { + for (int i = trackedContainers.size() - 1; i >= 0; i--) { + SensitiveDataContainer sdc = trackedContainers.get(i); + try { + sdc.close(); + } catch (Exception e) { + log.error("컨텍스트 내부 컨테이너 소거 중 오류 발생", e); + } + } + trackedContainers.clear(); + } + } +} 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 new file mode 100644 index 0000000..2cbe3a7 --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/data/SensitiveDataContainer.java @@ -0,0 +1,224 @@ +/* + * Copyright © 2025-2026 Quant. + * Under License "PolyForm Noncommercial License 1.0.0". + */ + +package space.qu4nt.entanglementlib.security.data; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +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 java.io.IOException; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.nio.ByteBuffer; +import java.nio.channels.WritableByteChannel; +import java.util.*; + +/// `Rust`의 소유권(ownership) 개념처럼 이 클래스도 전달받은 민감 정보에 대한 소유권을 가집니다. +/// 이 클래스에 저장된 데이터는 진행중인 세션에 종속되고, 세션이 종료됨에 따라 모든 데이터가 완벽하게 +/// 소거됩니다. 데이터 소거는 `entlib-native`에서 진행됩니다. +/// +/// 이 객체를 통해 네이티브 함수에 필요한 데이터를 전달하거나, 함수 실행 결과를 받을 수 있습니다. +/// 이 경우 **"호출자 패턴"이 사용되며, 해당 데이터의 소거는 보장되지만 할당 해제 권한이 이 객체에 +/// 부여**되어 반드시 지정된 데이터에 대한 할당 해제 함수를 호출해야 합니다. +/// +/// 하지만 이 객체는 [AutoCloseable] 인터페이스를 구현함으로서 [#close()] 수행 시 호출자 측 +/// 메모리 해제 함수를 자동으로 호출해줍니다. +/// +/// --- +/// +/// [#SensitiveDataContainer(byte\[\], boolean)] 생성자를 사용하여 전달받은 바이트 배열 +/// 데이터에 대한 `heap` 소거 진행 여부를 지정할 수 있습니다. 논리 값이 `true`일 경우 데이터는 +/// Java `heap` 메모리에서 즉시 소거됩니다. +/// +/// # Usage +/// 이전엔 일일이 컨테이너를 연결해주어야 했습니다. 이 과정을 축약하기 위해 [SDCScopeContext] +/// 개념을 도입했습니다. 이 객체를 통해 스코프를 생성하고 내부의 전체 컨테이너에 대해 작업 +/// 완료 시 안전하게 소거 및 할당 해제 작업을 자동으로 수행하도록 할 수 있습니다. +/// ```java +/// // 보안 세션 시작 +/// try (SecureScopeContext scope = new SecureScopeContext()) { +/// +/// // allocate raw key data +/// SensitiveDataContainer rawKey = scope.allocate(32); +/// +/// // generate PQC key pair +/// SensitiveDataContainer pqcCiphertext = PQC.encapsulate(scope, rawKey); +/// +/// // encode to Base64 for transmission +/// SensitiveDataContainer b64Data = Base64.encode(scope, pqcCiphertext); +/// +/// // ...network transmission 또는 별도의 로직... +/// +/// } // 스코프가 닫히는 순간 rawKey, pqcCiphertext, b64Data가 역순으로 완벽하게 소거 +/// // 네이티브에 FFI로 넘어온 할당 해제 함수를 자동으로 호출하여 지정된 객체에 대해 모두 수행 +/// ``` +/// 위와 같은 사용은 통신 체계에서 매우 유용하게 사용될 수 있습니다. +/// +/// 작업량이 스코프 작업에 어울리지 않을 수도 있습니다. 예를 들어, 보안 난수 생성 및 세션 내에서 +/// 구조화된 경량 통신 체계를 구축할 때는 다음과 같이 사용될 수 있습니다. +/// ```java +/// SensitiveDataContainer.runScope(256, container -> { +/// // ...Secure stuff... +/// }); // 소비 패턴 +/// +/// int resultSize = SensitiveDataContainer.callScope(1024, container -> { +/// // ...Secure stuff... +/// return container.getMemorySegment().byteSize(); +/// }); // 반환 패턴 +/// ``` +/// 각 메소드의 모든 작업은 `try-with-resources` 블럭 내에서 사용됩니다. +/// +/// # Safety +/// 이 객체의 인스턴스를 직접 생성해야 하는 경우 다음과 같이 네이티브에 메모리 할당 해제 함수를 +/// 호출하여 사용해야 합니다. +/// ```java +/// SensitiveDataContainer sdc = ...; +/// final MemorySegment ms = sdc.getMemorySegment(); +/// // ...Secure stuff... +/// EntLibNativeManager.call(Function.Caller_Secure_Buffer_Wipe) +/// .invokeExact(ms, ms.byteSize()); +/// ``` +/// 이 객체를 상속받아 사용되는 경우도 일관된 사용법을 따를 수 있습니다. +/// +/// @author Q. T. Felix +/// @see HeuristicArenaFactory Arena 자동 할당을 수행하는 클래스 +/// @see SDCScopeContext +/// @since 1.1.0 +@Slf4j +public class SensitiveDataContainer implements AutoCloseable { + + @Getter(AccessLevel.PACKAGE) + private final Arena arena; + @Getter(AccessLevel.PACKAGE) + private final MemorySegment memorySegment; + + /// 네이티브 메모리에 전달받은 정수 값(바이트 크기) 만큼의 메모리 세그먼트를 + /// 생성하여 이 인스턴스를 생성합니다. + /// + /// # Safety + /// 이 생성자를 통해 인스턴스를 생성하면 호출자가 부담하는 보안 책임이 발생합니다. + /// 특별한 경우가 아닌 이상 이 방식을 통한 생성은 권장하지 않습니다. + /// + /// 또한, 이 생성자는 제거 예정은 없으나 권장되는 사용이 아닙니다. [SDCScopeContext]를 + /// 통해 세션식 보안 작업을 수행하세요. + /// + /// @param allocateSIze 바이트 크기 + /// @see #runScope(int, SDCConsumer) 스코프 작업 종료 시 자원 소거를 보장하는 정적 메소드 + /// @see #callScope(int, SDCFunction) 스코프 작업 종료 후 자원을 소거하고 가공된 결과를 반환하는 정적 메소드 + @CallerResponsibility("try-with-resource 사용 또는 close 메소드 직접 호출 필수") + public SensitiveDataContainer(final int allocateSIze) { + this.arena = HeuristicArenaFactory.intelligenceCreateArena(); + this.memorySegment = Objects.requireNonNull(arena.allocate(allocateSIze)); + } + + /// 원본 바이트 배열을 전달받고 네이티브 메모리에 바인딩하여 이 인스턴스를 생성합니다. + /// + /// 생성 시점에 전달받은 원본 바이트 배열 데이터에 대한 소유권을 이 인스턴스에 넘길지 결정할 수 + /// 있습니다. 논리 값이 `true`일 경우 데이터는 즉시 소거되고, 이는 이 인스턴스가 소유권을 + /// 가질 필요가 없다는 것을 의미합니다. + /// + /// # Safety + /// 이 생성자를 통해 인스턴스를 생성하면 호출자가 부담하는 보안 책임이 발생합니다. + /// 특별한 경우가 아닌 이상 이 방식을 통한 생성은 권장하지 않습니다. 또한, 결국 + /// `heap` 메모리에 데이터를 노출하는 것은 위험합니다. + /// + /// 또한, 이 생성자는 제거 예정은 없으나 권장되는 사용이 아닙니다. [SDCScopeContext]를 + /// 통해 세션식 보안 작업을 수행하세요. + /// + /// @param from 네이티브 메모리에 바인딩할 원본 바이트 배열 + /// @param forceWipe 인스턴스에 소유권 이전 여부 + /// @see #runScope(int, SDCConsumer) 스코프 작업 종료 시 자원 소거를 보장하는 정적 메소드 + /// @see #callScope(int, SDCFunction) 스코프 작업 종료 후 자원을 소거하고 가공된 결과를 반환하는 정적 메소드 + @CallerResponsibility("try-with-resource 사용 또는 close 메소드 직접 호출 필수") + public SensitiveDataContainer(final byte @NotNull [] from, boolean forceWipe) { + this.arena = HeuristicArenaFactory.intelligenceCreateArena(); + this.memorySegment = Objects.requireNonNull(arena.allocateFrom(ValueLayout.JAVA_BYTE, from)); + if (forceWipe) + Arrays.fill(from, (byte) 0); + } + + /** + * 보안 컨테이너의 생명주기를 자동으로 관리하는 실행 메소드입니다. + * Execute-Around-Pattern이 적용되어 작업 완료 즉시 메모리가 소거됨을 보장합니다. + * + * @param allocateSize 할당할 버퍼 크기 + * @param action 컨테이너를 사용하여 수행할 보안 로직 + */ + public static void runScope(int allocateSize, SDCConsumer action) throws ELIBSecurityProcessException { + try (SensitiveDataContainer sdc = new SensitiveDataContainer(allocateSize)) { + action.accept(sdc); + } + } + + /// 보안 컨테이너를 사용하여 값을 계산하고 반환하는 실행 메소드입니다. + /// 반환값은 민감한 데이터(`byte[]`)가 아닌, 가공된 결과물(암호화 성공 여부, 상태 코드 등)이어야 합니다. + /// + /// @param allocateSize 할당할 버퍼 크기 + /// @param action 컨테이너를 사용하여 수행할 계산 로직 + /// @return 계산 결과 + public static R callScope(int allocateSize, SDCFunction action) throws ELIBSecurityProcessException { + try (SensitiveDataContainer sdc = new SensitiveDataContainer(allocateSize)) { + return action.apply(sdc); + } + } + + public static void transmitZeroCopy(SensitiveDataContainer sdc, WritableByteChannel channel) throws ELIBSecurityProcessException { + if (!sdc.getArena().scope().isAlive()) { + throw new IllegalStateException("이미 소거 완료되었거나 유효하지 않은 컨테이너입니다!"); + } + + // 메소드 내부에서만 생존하는 임시 Direct ByteBuffer 뷰 생성 + // 이 객체는 절대 외부로 반환되거나 다른 스레드로 전달되어서는 안 됨 + ByteBuffer transientView = sdc.getMemorySegment().asByteBuffer(); + + // WritableByteChannel (SocketChannel 등)에 기록 + // 채널이 Direct ByteBuffer를 인식하면 Java Heap으로 데이터를 복사하지 않고 + // JNI를 통해 OS의 write() 또는 send() 시스템 콜로 메모리 주소를 직접 넘김 + while (transientView.hasRemaining()) { + try { + channel.write(transientView); + } catch (IOException e) { + throw new ELIBSecurityProcessException(e); + } + } + + // gc hint + transientView = null; + } + + @Override + public void close() { + // 더 이상 하위 바인딩(bindings)을 관리하지 않아 로직 수정 // + // 스레드 안전성과 다중 호출 시의 멱등성을 보장하기 위해 인스턴스 락 사용 + synchronized (this) { + // 이미 닫힌 경우 early-return + if (this.arena == null || !this.arena.scope().isAlive()) { + log.debug("이미 소거 완료되었거나 유효하지 않은 컨테이너입니다."); + return; + } + + try { + // 호출자 측 네이티브 메모리 완벽 소거 & 할당 해제 + EntLibNativeManager + .call(Function.Caller_Secure_Buffer_Wipe) + .invokeExact(this.memorySegment, this.memorySegment.byteSize()); + } catch (Throwable e) { + // 네이티브 소거 실패는 치명적 보안 이벤트이기 떄문에 에러 출력 + log.error("치명적 보안 예외가 발생했습니다!", e); + } finally { + // 예외 발생 여부와 상관없이 접근 채널은 반드시 닫음 + this.arena.close(); + } + } + } +} 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 new file mode 100644 index 0000000..a224d86 --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/EntLibNativeManager.java @@ -0,0 +1,122 @@ +package space.qu4nt.entanglementlib.security.entlibnative; + +import org.jetbrains.annotations.NotNull; +import space.qu4nt.entanglementlib.annotations.CallerResponsibility; +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.*; +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/Function.java b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/Function.java new file mode 100644 index 0000000..430dfa5 --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/Function.java @@ -0,0 +1,266 @@ +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/NativeLoader.java b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeLoader.java new file mode 100644 index 0000000..dff14f6 --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeLoader.java @@ -0,0 +1,117 @@ +package space.qu4nt.entanglementlib.security.entlibnative; + +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityIOException; +import space.qu4nt.entanglementlib.core.exception.security.critical.ELIBSecurityNativeCritical; +import space.qu4nt.entanglementlib.security.EntanglementLibSecurityConfig; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.Set; + +// todo 이 클래스 호출 시점에 국제화 파일 로드돼잇을거임 +@Slf4j +public final class NativeLoader { + + private static volatile boolean loaded = false; + + private NativeLoader() { + throw new UnsupportedOperationException("cannot access"); + } + + private static synchronized void loadSuccess() { + loaded = true; + EntLibNativeManager.setup(); + } + + public static synchronized void loadNativeLibrary(final @NotNull EntanglementLibSecurityConfig config) { + if (loaded) return; + + try { + NativePlatform os = NativePlatform.detectOs(); + if (os == NativePlatform.UNKNOWN) + throw new ELIBSecurityNativeCritical("지원하지 않는 운영체제입니다!"); + + String fileName = os.buildFileName(config.getNativeContext().getNativeFilename()); + + // 사용자 할당 시 외부 절대 경로 탐색 + final String dirName = config.getNativeContext().getNativeDirName(); + Path externalPath = Path.of(dirName); + Path exactExternalFile = externalPath.resolve(fileName); + + log.info(exactExternalFile.toString()); + + // 사용자가 파일명까지 포함한 절대 경로를 입력했거나, 디렉터리 경로를 입력한 경우 모두 처리 + if (Files.exists(externalPath) && !Files.isDirectory(externalPath)) { + System.load(externalPath.toAbsolutePath().toString()); + loadSuccess(); + return; + } else if (Files.exists(exactExternalFile)) { + System.load(exactExternalFile.toAbsolutePath().toString()); + loadSuccess(); + return; + } + + // jar 내부 자원 로드 + String architecture = NativePlatform.detectArchitecture(); + String resourcePath = dirName + "/" + os.name().toLowerCase() + "/" + architecture + "/" + fileName; + + extractAndLoadFromJar(resourcePath, fileName); + loadSuccess(); + } catch (UnsatisfiedLinkError e) { + throw new ELIBSecurityNativeCritical( + "파일명이 절대 경로명이 아니거나, " + + "네이티브 라이브러리가 VM과 정적으로 연결되지 않거나, " + + "호스트 시스템에서 라이브러리를 네이티브 라이브러리 이미지에 매핑할 수 없습니다!"); + } catch (IllegalCallerException e) { + throw new ELIBSecurityNativeCritical("현재 모듈은 네이티브 액세스 권한이 없습니다!"); + } catch (ELIBSecurityIOException e) { + throw new RuntimeException(e); + } + } + + private static void extractAndLoadFromJar(String resourcePath, String fileName) throws ELIBSecurityIOException { + try (InputStream is = NativeLoader.class.getResourceAsStream(resourcePath)) { + if (is == null) + throw new ELIBSecurityNativeCritical("jar 내부 '" + resourcePath + "' 경로에서 네이티브 라이브러리를 찾을 수 없습니다!"); + + Path tempFile = createSecureTempFile(fileName); + tempFile.toFile().deleteOnExit(); + + Files.copy(is, tempFile, StandardCopyOption.REPLACE_EXISTING); + System.load(tempFile.toAbsolutePath().toString()); + + } catch (IOException e) { + throw new ELIBSecurityIOException("임시 파일 생성 및 복사(덮어쓰기)에 실패했습니다!", e); + } + } + + @SuppressWarnings("ResultOfMethodCallIgnored") + private static Path createSecureTempFile(String fileName) throws IOException { + int dotIndex = fileName.lastIndexOf('.'); + String prefix = fileName.substring(0, dotIndex) + "-"; + String suffix = fileName.substring(dotIndex); + + boolean isPosix = FileSystems.getDefault().supportedFileAttributeViews().contains("posix"); + + if (isPosix) { + // POSIX 호환 시스템 (linux, macos): 파일 권한 엄격 제한 + Set perms = PosixFilePermissions.fromString("rwx------"); + return Files.createTempFile(prefix, suffix, PosixFilePermissions.asFileAttribute(perms)); + } else { + // 윈도우 등 non POSIX 시스템: 기본 임시 파일 생성 후 읽기/쓰기/실행 권한 제어 + Path tempFile = Files.createTempFile(prefix, suffix); + tempFile.toFile().setReadable(true, true); + tempFile.toFile().setWritable(true, true); + tempFile.toFile().setExecutable(true, true); + return tempFile; + } + } +} diff --git a/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativePlatform.java b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativePlatform.java new file mode 100644 index 0000000..acd33e5 --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativePlatform.java @@ -0,0 +1,38 @@ +package space.qu4nt.entanglementlib.security.entlibnative; + +import java.util.Locale; + +public enum NativePlatform { + + WINDOWS("", ".dll"), + LINUX("lib", ".so"), + MACOS("lib", ".dylib"), + UNKNOWN("", ""); + + private final String prefix; + private final String extension; + + NativePlatform(String prefix, String extension) { + this.prefix = prefix; + this.extension = extension; + } + + public static NativePlatform detectOs() { + String osName = System.getProperty("os.name").toLowerCase(Locale.ENGLISH); + if (osName.contains("win")) return WINDOWS; + if (osName.contains("mac") || osName.contains("darwin")) return MACOS; + if (osName.contains("nix") || osName.contains("nux") || osName.contains("aix")) return LINUX; + return UNKNOWN; + } + + public static String detectArchitecture() { + String arch = System.getProperty("os.arch").toLowerCase(Locale.ENGLISH); + if (arch.contains("amd64") || arch.contains("x86_64")) return "x86_64"; + if (arch.contains("aarch64") || arch.contains("arm64")) return "aarch64"; + return arch; // 기타 아키텍처 (필요시 확장) + } + + public String buildFileName(String baseName) { + return this.prefix + baseName + this.extension; + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..8765f1a --- /dev/null +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/NativeSpecContext.java @@ -0,0 +1,96 @@ +package space.qu4nt.entanglementlib.security.entlibnative; + +import lombok.Getter; +import lombok.Setter; + +import java.util.Set; + +import static space.qu4nt.entanglementlib.security.entlibnative.Function.*; + +@Getter +@Setter +public class NativeSpecContext { + + private String nativeDirName; + private String nativeFilename; + private Set functions; + + public NativeSpecContext(String nativeDirName, String nativeFilename, Set functions) { + this.nativeDirName = nativeDirName; + this.nativeFilename = nativeFilename; + this.functions = functions; + } + + public NativeSpecContext(String nativeDirName, String nativeFilename, Function... functions) { + this.nativeDirName = nativeDirName; + this.nativeFilename = nativeFilename; + this.functions = Set.of(functions); + } + + 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 + )); + } +} diff --git a/src/main/resources/logback.xml b/security/src/main/resources/logback.xml similarity index 100% rename from src/main/resources/logback.xml rename to security/src/main/resources/logback.xml 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 new file mode 100644 index 0000000..6fc7d79 --- /dev/null +++ b/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/Base64Test.java @@ -0,0 +1,38 @@ +package space.qu4nt.entanglementlib.security.crypto; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import space.qu4nt.entanglementlib.security.EntanglementLibSecurityConfig; +import space.qu4nt.entanglementlib.security.EntanglementLibSecurityFacade; +import space.qu4nt.entanglementlib.security.data.HeuristicArenaFactory; +import space.qu4nt.entanglementlib.security.entlibnative.Function; +import space.qu4nt.entanglementlib.security.entlibnative.NativeSpecContext; + +import java.nio.charset.StandardCharsets; + +@Slf4j +class Base64Test { + + @Test + @DisplayName("Base64 En/Decode") + void test() { + EntanglementLibSecurityFacade.initialize( + EntanglementLibSecurityConfig.create( + new NativeSpecContext("/entlib-native/target/debug", "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), + HeuristicArenaFactory.ArenaMode.CONFINED) + ); + + final byte[] input = "Hello, World!".getBytes(StandardCharsets.UTF_8); +// String result = Base64.encode(input); +// +// log.info("Encode: {}", result); +// log.info("Decode: {}", new String(Base64.decode(result))); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..40cd93b --- /dev/null +++ b/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/ChaCha20Test.java @@ -0,0 +1,122 @@ +package space.qu4nt.entanglementlib.security.crypto; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import space.qu4nt.entanglementlib.core.util.wrapper.Hex; +import space.qu4nt.entanglementlib.security.EntanglementLibSecurityConfig; +import space.qu4nt.entanglementlib.security.EntanglementLibSecurityFacade; +import space.qu4nt.entanglementlib.security.data.HeuristicArenaFactory; +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.NativeSpecContext; + +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.*; + +class ChaCha20Test { + + @BeforeAll + static void setUp() { + // 테스트 클래스 로드 시 1회만 네이티브 라이브러리를 초기화하여 성능 최적화 + EntanglementLibSecurityFacade.initialize( + EntanglementLibSecurityConfig.create( + new NativeSpecContext("/Library/Quant/Repository/projects/entanglementlib/entlib-native/target/debug", "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), + HeuristicArenaFactory.ArenaMode.CONFINED) + ); + } + + @Test + @DisplayName("ChaCha20 known answer test & 메모리 안전성 검증") + void chacha20Test() throws Throwable { + // 테스트 벡터 + final String inputText = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."; + // 기댓값 + final byte[] expected = new byte[]{ + (byte) 0xd3, 0x1a, (byte) 0x8d, 0x34, 0x64, (byte) 0x8e, 0x60, (byte) 0xdb, 0x7b, (byte) 0x86, + (byte) 0xaf, (byte) 0xbc, 0x53, (byte) 0xef, 0x7e, (byte) 0xc2, (byte) 0xa4, (byte) 0xad, + (byte) 0xed, 0x51, 0x29, 0x6e, 0x08, (byte) 0xfe, (byte) 0xa9, (byte) 0xe2, (byte) 0xb5, + (byte) 0xa7, 0x36, (byte) 0xee, 0x62, (byte) 0xd6, 0x3d, (byte) 0xbe, (byte) 0xa4, 0x5e, + (byte) 0x8c, (byte) 0xa9, 0x67, 0x12, (byte) 0x82, (byte) 0xfa, (byte) 0xfb, 0x69, (byte) 0xda, + (byte) 0x92, 0x72, (byte) 0x8b, 0x1a, 0x71, (byte) 0xde, 0x0a, (byte) 0x9e, 0x06, 0x0b, 0x29, + 0x05, (byte) 0xd6, (byte) 0xa5, (byte) 0xb6, 0x7e, (byte) 0xcd, 0x3b, 0x36, (byte) 0x92, + (byte) 0xdd, (byte) 0xbd, 0x7f, 0x2d, 0x77, (byte) 0x8b, (byte) 0x8c, (byte) 0x98, 0x03, + (byte) 0xae, (byte) 0xe3, 0x28, 0x09, 0x1b, 0x58, (byte) 0xfa, (byte) 0xb3, 0x24, (byte) 0xe4, + (byte) 0xfa, (byte) 0xd6, 0x75, (byte) 0x94, 0x55, (byte) 0x85, (byte) 0x80, (byte) 0x8b, 0x48, + 0x31, (byte) 0xd7, (byte) 0xbc, 0x3f, (byte) 0xf4, (byte) 0xde, (byte) 0xf0, (byte) 0x8e, 0x4b, + 0x7a, (byte) 0x9d, (byte) 0xe5, 0x76, (byte) 0xd2, 0x65, (byte) 0x86, (byte) 0xce, (byte) 0xc6, + 0x4b, 0x61, 0x16 + }; + final byte[] expectedTag = new byte[]{ + 0x1a, (byte) 0xe1, 0x0b, 0x59, 0x4f, 0x09, (byte) 0xe2, 0x6a, 0x7e, (byte) 0x90, 0x2e, + (byte) 0xcb, (byte) 0xd0, 0x60, 0x06, (byte) 0x91 + }; + + byte[] inputBytes = inputText.getBytes(StandardCharsets.UTF_8); + + MemorySegment resultSegmentAlias; + + // 보안 스코프 내에서 암호화 연산 수행 + try (SDCScopeContext context = new SDCScopeContext()) { + SensitiveDataContainer inputData = context.allocate(inputBytes, true); + SensitiveDataContainer key = context.allocate(new byte[]{ + (byte) 0x80, (byte) 0x81, (byte) 0x82, (byte) 0x83, (byte) 0x84, (byte) 0x85, (byte) 0x86, (byte) 0x87, + (byte) 0x88, (byte) 0x89, (byte) 0x8a, (byte) 0x8b, (byte) 0x8c, (byte) 0x8d, (byte) 0x8e, (byte) 0x8f, + (byte) 0x90, (byte) 0x91, (byte) 0x92, (byte) 0x93, (byte) 0x94, (byte) 0x95, (byte) 0x96, (byte) 0x97, + (byte) 0x98, (byte) 0x99, (byte) 0x9a, (byte) 0x9b, (byte) 0x9c, (byte) 0x9d, (byte) 0x9e, (byte) 0x9f + }, true); + SensitiveDataContainer nonce = context.allocate(new byte[]{ + 0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47 + }, true); + SensitiveDataContainer aad = context.allocate(new byte[]{ + 0x50, 0x51, 0x52, 0x53, (byte) 0xc0, (byte) 0xc1, (byte) 0xc2, (byte) 0xc3, (byte) 0xc4, (byte) 0xc5, (byte) 0xc6, (byte) 0xc7 + }, true); + SensitiveDataContainer result = ChaCha20.encrypt(context, key, nonce, aad, inputData); + + assertNotNull(result, "연산 결과는 null일 수 없습니다."); + resultSegmentAlias = result.getMemorySegment(); + + // [검증 A] 길이 검증 + assertEquals(expected.length + 16, resultSegmentAlias.byteSize(), + "암호문 길이가 불일치합니다."); + + // [검증 B] 무결성 검증 (correctness check) + byte[] actualCipherBytes = resultSegmentAlias.toArray(ValueLayout.JAVA_BYTE); // 프로덕션에서 사용 권장 X + assertEquals(actualCipherBytes.length, expected.length + 16, "암호문 길이가 불일치합니다."); + byte[] newACBytes = new byte[actualCipherBytes.length - 16]; + System.arraycopy(actualCipherBytes, 0, newACBytes, 0, actualCipherBytes.length - 16); + assertEquals(Hex.toHexString(expected), Hex.toHexString(newACBytes), + "암호문이 불일치합니다."); + newACBytes = new byte[16]; + System.arraycopy(actualCipherBytes, actualCipherBytes.length - 16, newACBytes, 0, 16); + assertEquals(Hex.toHexString(expectedTag), Hex.toHexString(newACBytes), + "MAC 태그가 불일치합니다."); + + // + // 복호화 + // + SensitiveDataContainer decryptResult = ChaCha20.decrypt(context, key, nonce, aad, result); + MemorySegment decResultOpt = decryptResult.getMemorySegment(); + assertNotEquals(MemorySegment.NULL, decResultOpt, "네이티브 측 복호화 결과가 null입니다."); + assertNotEquals(0, decResultOpt.address(), "네이티브 측 복호화 결과가 유효하지 않습니다."); + byte[] actualDecBytes = decResultOpt.toArray(ValueLayout.JAVA_BYTE); // 프로덕션에서 사용 권장 X + byte[] newADCBytes = new byte[actualDecBytes.length]; + System.arraycopy(actualDecBytes, 0, newADCBytes, 0, actualDecBytes.length); + assertEquals(Hex.toHexString(inputBytes), Hex.toHexString(newADCBytes), "복호화 결과가 불일치합니다."); + } // 스코프 벗어남 -> 컨텍스트 내부 모든 데이터 소거 요청 -> Rust에서 소거 + + assertThrows(IllegalStateException.class, () -> + resultSegmentAlias.get(ValueLayout.JAVA_BYTE, 0), + "스코프가 종료된 이후 메모리 세그먼트에 접근할 경우 반드시 예외가 발생하여 잔류 데이터 유출을 막아야 합니다."); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..3a5c1ae --- /dev/null +++ b/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/RNGTest.java @@ -0,0 +1,96 @@ +package space.qu4nt.entanglementlib.security.crypto; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityProcessException; +import space.qu4nt.entanglementlib.core.exception.security.critical.ELIBSecurityNativeCritical; +import space.qu4nt.entanglementlib.security.EntanglementLibSecurityConfig; +import space.qu4nt.entanglementlib.security.EntanglementLibSecurityFacade; +import space.qu4nt.entanglementlib.security.crypto.rng.RNG; +import space.qu4nt.entanglementlib.security.data.HeuristicArenaFactory; +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.NativeSpecContext; + +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; + +import static org.junit.jupiter.api.Assertions.*; + +class RNGTest { + + @BeforeAll + static void setUp() { + // 테스트 클래스 로드 시 1회만 네이티브 라이브러리를 초기화하여 성능 최적화 + EntanglementLibSecurityFacade.initialize( + EntanglementLibSecurityConfig.create( + new NativeSpecContext("/Library/Quant/Repository/projects/entanglementlib/entlib-native/target/debug", "entlib_native_ffi", + Function.chain( + Function.withCalleeSecureBuffer(), + Function.withCallerSecureBuffer(), + Function.withRNG())), + HeuristicArenaFactory.ArenaMode.CONFINED) + ); + } + + @Test + @DisplayName("로컬 하드웨어(local hardware) 기반 혼합 난수 생성 및 소거(zeroize) 검증") + void hardwareRngLifecycleAndZeroizeTest() throws ELIBSecurityProcessException { + final long PQC_KEY_LENGTH = 32; // 양자-내성 암호화(post-quantum cryptography) 키 길이를 가정한 32바이트 + MemorySegment capturedSegment; + + // 보안 스코프(secure scope) 컨텍스트 생성 및 난수 할당 + try (SDCScopeContext scope = new SDCScopeContext()) { + SensitiveDataContainer sdc = RNG.generateRNG(RNG.LOCAL_HARDWARE, scope, PQC_KEY_LENGTH); + + assertNotNull(sdc, "생성된 민감 데이터 컨테이너(sensitive data container)는 null이 아니어야 합니다."); + capturedSegment = sdc.getMemorySegment(); + + // arena 및 메모리 세그먼트 유효성 검증 + assertTrue(sdc.getArena().scope().isAlive(), "스코프(scope) 내부에서는 Arena가 활성화 상태여야 합니다."); + assertEquals(PQC_KEY_LENGTH, capturedSegment.byteSize(), "생성된 난수의 크기가 요청한 길이와 일치해야 합니다."); + + // 엔트로피 데이터 존재 여부 검증 (모두 0인지 확인) + boolean hasNonZero = false; + for (long i = 0; i < PQC_KEY_LENGTH; i++) { + if (capturedSegment.get(ValueLayout.JAVA_BYTE, i) != 0) { + hasNonZero = true; + break; + } + } + assertTrue(hasNonZero, "초기화된 난수 버퍼가 모두 0일 수는 없습니다. (엔트로피 부족 의심)"); + } // try-with-resources 종료 시 scope.close() 호출 -> SDC 소거(zeroize) 자동 수행 + + // 스코프 종료 후 자원 파기 검증 + // SDCScopeContext가 닫히면서 소속된 모든 컨테이너의 아레나가 닫히고 네이티브 포인터가 무효화되어야 함 + assertFalse(capturedSegment.scope().isAlive(), "컨텍스트 종료 후에는 Arena가 반드시 닫혀야 합니다."); + + // 메모리 영역 강제 접근 시 예외 발생 검증 + assertThrows(IllegalStateException.class, () -> { + capturedSegment.get(ValueLayout.JAVA_BYTE, 0); + }, "해제 및 소거(zeroize)된 네이티브 메모리에 접근 시도 시 예외가 발생해야 합니다."); + } + + @Test + @DisplayName("양자 네트워크(quantum network) 기반 혼합 난수 생성 에러 핸들링 검증") + void quantumNetworkRngErrorHandlingTest() throws ELIBSecurityProcessException { + // 양자 네트워크 특성상 외부 통신 환경에 따라 실패할 수 있어서 생성 성공 여부 또는 + // 네트워크 타임아웃/파싱 에러 시 ELIBSecurityNativeCritical 예외가 올바르게 전파되는지 검증해야됌 + try (SDCScopeContext scope = new SDCScopeContext()) { + try { + SensitiveDataContainer sdc = RNG.generateRNG(RNG.QUANTUM_NETWORK, scope, 64); + assertNotNull(sdc); + assertTrue(sdc.getArena().scope().isAlive()); + } catch (ELIBSecurityNativeCritical e) { + // 통신 실패 시 던져지는 예외 메시지가 우리가 정의한 규칙에 부합하는지 확인 + String msg = e.getMessage(); + assertTrue( + msg.contains("네트워크(network)") || msg.contains("파싱(parsing)") || msg.contains("알 수 없는"), + "예상치 못한 네이티브 에러가 발생했습니다: " + msg + ); + } + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..379453d --- /dev/null +++ b/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/SHA2HashTest.java @@ -0,0 +1,189 @@ +package space.qu4nt.entanglementlib.security.crypto; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import space.qu4nt.entanglementlib.security.EntanglementLibSecurityConfig; +import space.qu4nt.entanglementlib.security.EntanglementLibSecurityFacade; +import space.qu4nt.entanglementlib.security.crypto.hash.Hash; +import space.qu4nt.entanglementlib.security.data.HeuristicArenaFactory; +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.NativeSpecContext; + +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.nio.charset.StandardCharsets; +import java.util.HexFormat; + +import static org.junit.jupiter.api.Assertions.*; + +class SHA2HashTest { + + @BeforeAll + static void setUp() { + // 테스트 클래스 로드 시 1회만 네이티브 라이브러리를 초기화하여 성능 최적화 + EntanglementLibSecurityFacade.initialize( + EntanglementLibSecurityConfig.create( + new NativeSpecContext("/Library/Quant/Repository/projects/entanglementlib/entlib-native/target/debug", "entlib_native_ffi", + Function.chain( + Function.withCalleeSecureBuffer(), + Function.withCallerSecureBuffer(), + Function.withHash(true))), + HeuristicArenaFactory.ArenaMode.CONFINED) + ); + } + + @Test + @DisplayName("SHA-224 known answer test & 메모리 안전성 검증") + void sha224StrictTest() throws Throwable { + // 테스트 벡터 + final String inputText = "Hello, World!"; + // SHA-224 기댓값 + final String expectedHex = "72a23dfa411ba6fde01dbfabf3b00a709c93ebf273dc29e2d8b261ff"; + final byte[] inputBytes = inputText.getBytes(StandardCharsets.UTF_8); + + MemorySegment resultSegmentAlias; + + // 보안 스코프 내에서 암호화 연산 수행 + try (SDCScopeContext context = new SDCScopeContext()) { + SensitiveDataContainer inputData = context.allocate(inputBytes, true); + SensitiveDataContainer result = Hash.sha2(224, context, inputData); + + assertNotNull(result, "해시 연산 결과는 null일 수 없습니다."); + resultSegmentAlias = result.getMemorySegment(); + + // [검증 A] 길이 검증 + assertEquals(28, resultSegmentAlias.byteSize(), + "SHA-224 해시 결과는 정확히 28바이트(224비트)여야 합니다."); + + // [검증 B] 무결성 검증 (correctness check) + byte[] actualHashBytes = resultSegmentAlias.toArray(ValueLayout.JAVA_BYTE); + String actualHex = HexFormat.of().formatHex(actualHashBytes); + assertEquals(expectedHex, actualHex, + "계산된 해시값이 예상된 SHA-224 KAT(known answer test) 값과 정확히 일치해야 합니다."); + } + + // 스코프 종료 후 메모리 보호 메커니즘 검증 + // 컨텍스트(context)가 close()된 이후, 해당 arena에 종속된 세그먼트에 접근을 시도하면 + // 자바 런타임이 IllegalStateException을 발생시켜야 안전한 소거 및 접근 차단이 입증됨 + assertThrows(IllegalStateException.class, () -> + resultSegmentAlias.get(ValueLayout.JAVA_BYTE, 0), + "스코프가 종료된 이후 메모리 세그먼트에 접근할 경우 반드시 예외가 발생하여 잔류 데이터 유출을 막아야 합니다."); + } + + @Test + @DisplayName("SHA-256 known answer test & 메모리 안전성 검증") + void sha256StrictTest() throws Throwable { + // 테스트 벡터 + final String inputText = "Hello, World!"; + // SHA-256 기댓값 + final String expectedHex = "dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f"; + final byte[] inputBytes = inputText.getBytes(StandardCharsets.UTF_8); + + MemorySegment resultSegmentAlias; + + // 보안 스코프 내에서 암호화 연산 수행 + try (SDCScopeContext context = new SDCScopeContext()) { + SensitiveDataContainer inputData = context.allocate(inputBytes, true); + SensitiveDataContainer result = Hash.sha2(256, context, inputData); + + assertNotNull(result, "해시 연산 결과는 null일 수 없습니다."); + resultSegmentAlias = result.getMemorySegment(); + + // [검증 A] 길이 검증 + assertEquals(32, resultSegmentAlias.byteSize(), + "SHA-256 해시 결과는 정확히 32바이트(256비트)여야 합니다."); + + // [검증 B] 무결성 검증 (correctness check) + byte[] actualHashBytes = resultSegmentAlias.toArray(ValueLayout.JAVA_BYTE); + String actualHex = HexFormat.of().formatHex(actualHashBytes); + assertEquals(expectedHex, actualHex, + "계산된 해시값이 예상된 SHA-256 KAT(known answer test) 값과 정확히 일치해야 합니다."); + } + + // 스코프 종료 후 메모리 보호 메커니즘 검증 + // 컨텍스트(context)가 close()된 이후, 해당 arena에 종속된 세그먼트에 접근을 시도하면 + // 자바 런타임이 IllegalStateException을 발생시켜야 안전한 소거 및 접근 차단이 입증됨 + assertThrows(IllegalStateException.class, () -> + resultSegmentAlias.get(ValueLayout.JAVA_BYTE, 0), + "스코프가 종료된 이후 메모리 세그먼트에 접근할 경우 반드시 예외가 발생하여 잔류 데이터 유출을 막아야 합니다."); + } + + @Test + @DisplayName("SHA-384 known answer test & 메모리 안전성 검증") + void sha384StrictTest() throws Throwable { + // 테스트 벡터 + final String inputText = "Hello, World!"; + // SHA-384 기댓값 + final String expectedHex = "5485cc9b3365b4305dfb4e8337e0a598a574f8242bf17289e0dd6c20a3cd44a089de16ab4ab308f63e44b1170eb5f515"; + final byte[] inputBytes = inputText.getBytes(StandardCharsets.UTF_8); + + MemorySegment resultSegmentAlias; + + // 보안 스코프 내에서 암호화 연산 수행 + try (SDCScopeContext context = new SDCScopeContext()) { + SensitiveDataContainer inputData = context.allocate(inputBytes, true); + SensitiveDataContainer result = Hash.sha2(384, context, inputData); + + assertNotNull(result, "해시 연산 결과는 null일 수 없습니다."); + resultSegmentAlias = result.getMemorySegment(); + + // [검증 A] 길이 검증 + assertEquals(48, resultSegmentAlias.byteSize(), + "SHA-384 해시 결과는 정확히 48바이트(384비트)여야 합니다."); + + // [검증 B] 무결성 검증 (correctness check) + byte[] actualHashBytes = resultSegmentAlias.toArray(ValueLayout.JAVA_BYTE); + String actualHex = HexFormat.of().formatHex(actualHashBytes); + assertEquals(expectedHex, actualHex, + "계산된 해시값이 예상된 SHA-384 KAT(known answer test) 값과 정확히 일치해야 합니다."); + } + + // 스코프 종료 후 메모리 보호 메커니즘 검증 + // 컨텍스트(context)가 close()된 이후, 해당 arena에 종속된 세그먼트에 접근을 시도하면 + // 자바 런타임이 IllegalStateException을 발생시켜야 안전한 소거 및 접근 차단이 입증됨 + assertThrows(IllegalStateException.class, () -> + resultSegmentAlias.get(ValueLayout.JAVA_BYTE, 0), + "스코프가 종료된 이후 메모리 세그먼트에 접근할 경우 반드시 예외가 발생하여 잔류 데이터 유출을 막아야 합니다."); + } + + @Test + @DisplayName("SHA-512 known answer test & 메모리 안전성 검증") + void sha512StrictTest() throws Throwable { + // 테스트 벡터 + final String inputText = "Hello, World!"; + // SHA-512 기댓값 + final String expectedHex = "374d794a95cdcfd8b35993185fef9ba368f160d8daf432d08ba9f1ed1e5abe6cc69291e0fa2fe0006a52570ef18c19def4e617c33ce52ef0a6e5fbe318cb0387"; + final byte[] inputBytes = inputText.getBytes(StandardCharsets.UTF_8); + + MemorySegment resultSegmentAlias; + + // 보안 스코프 내에서 암호화 연산 수행 + try (SDCScopeContext context = new SDCScopeContext()) { + SensitiveDataContainer inputData = context.allocate(inputBytes, true); + SensitiveDataContainer result = Hash.sha2(512, context, inputData); + + assertNotNull(result, "해시 연산 결과는 null일 수 없습니다."); + resultSegmentAlias = result.getMemorySegment(); + + // [검증 A] 길이 검증 + assertEquals(64, resultSegmentAlias.byteSize(), + "SHA-512 해시 결과는 정확히 64바이트(512비트)여야 합니다."); + + // [검증 B] 무결성 검증 (correctness check) + byte[] actualHashBytes = resultSegmentAlias.toArray(ValueLayout.JAVA_BYTE); + String actualHex = HexFormat.of().formatHex(actualHashBytes); + assertEquals(expectedHex, actualHex, + "계산된 해시값이 예상된 SHA-512 KAT(known answer test) 값과 정확히 일치해야 합니다."); + } + + // 스코프 종료 후 메모리 보호 메커니즘 검증 + // 컨텍스트(context)가 close()된 이후, 해당 arena에 종속된 세그먼트에 접근을 시도하면 + // 자바 런타임이 IllegalStateException을 발생시켜야 안전한 소거 및 접근 차단이 입증됨 + assertThrows(IllegalStateException.class, () -> { + resultSegmentAlias.get(ValueLayout.JAVA_BYTE, 0); + }, "스코프가 종료된 이후 메모리 세그먼트에 접근할 경우 반드시 예외가 발생하여 잔류 데이터 유출을 막아야 합니다."); + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..d1458bf --- /dev/null +++ b/security/src/test/java/space/qu4nt/entanglementlib/security/crypto/SHA3HashTest.java @@ -0,0 +1,266 @@ +package space.qu4nt.entanglementlib.security.crypto; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import space.qu4nt.entanglementlib.security.EntanglementLibSecurityConfig; +import space.qu4nt.entanglementlib.security.EntanglementLibSecurityFacade; +import space.qu4nt.entanglementlib.security.data.HeuristicArenaFactory; +import space.qu4nt.entanglementlib.security.crypto.hash.Hash; +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.NativeSpecContext; + +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.nio.charset.StandardCharsets; +import java.util.HexFormat; + +import static org.junit.jupiter.api.Assertions.*; + +class SHA3HashTest { + + @BeforeAll + static void setUp() { + // 테스트 클래스 로드 시 1회만 네이티브 라이브러리를 초기화하여 성능 최적화 + EntanglementLibSecurityFacade.initialize( + EntanglementLibSecurityConfig.create( + new NativeSpecContext("/Library/Quant/Repository/projects/entanglementlib/entlib-native/target/debug", "entlib_native_ffi", + Function.chain( + Function.withCalleeSecureBuffer(), + Function.withCallerSecureBuffer(), + Function.withHash(false)) + ), + HeuristicArenaFactory.ArenaMode.CONFINED) + ); + } + + @Test + @DisplayName("SHA3-224 known answer test & 메모리 안전성 검증") + void sha3_224StrictTest() throws Throwable { + // 테스트 벡터 + final String inputText = "Hello, World!"; + // SHA3-224 기댓값 + final String expectedHex = "853048fb8b11462b6100385633c0cc8dcdc6e2b8e376c28102bc84f2"; + final byte[] inputBytes = inputText.getBytes(StandardCharsets.UTF_8); + + MemorySegment resultSegmentAlias; + + // 보안 스코프 내에서 암호화 연산 수행 + try (SDCScopeContext context = new SDCScopeContext()) { + SensitiveDataContainer inputData = context.allocate(inputBytes, true); + SensitiveDataContainer result = Hash.sha3(224, context, inputData); + + assertNotNull(result, "해시 연산 결과는 null일 수 없습니다."); + resultSegmentAlias = result.getMemorySegment(); + + // [검증 A] 길이 검증 + assertEquals(28, resultSegmentAlias.byteSize(), + "SHA3-224 해시 결과는 정확히 28바이트(224비트)여야 합니다."); + + // [검증 B] 무결성 검증 (correctness check) + byte[] actualHashBytes = resultSegmentAlias.toArray(ValueLayout.JAVA_BYTE); + String actualHex = HexFormat.of().formatHex(actualHashBytes); + assertEquals(expectedHex, actualHex, + "계산된 해시값이 예상된 SH3A-224 KAT(known answer test) 값과 정확히 일치해야 합니다."); + } + + // 스코프 종료 후 메모리 보호 메커니즘 검증 + // 컨텍스트(context)가 close()된 이후, 해당 arena에 종속된 세그먼트에 접근을 시도하면 + // 자바 런타임이 IllegalStateException을 발생시켜야 안전한 소거 및 접근 차단이 입증됨 + assertThrows(IllegalStateException.class, () -> + resultSegmentAlias.get(ValueLayout.JAVA_BYTE, 0), + "스코프가 종료된 이후 메모리 세그먼트에 접근할 경우 반드시 예외가 발생하여 잔류 데이터 유출을 막아야 합니다."); + } + + @Test + @DisplayName("SHA3-256 known answer test & 메모리 안전성 검증") + void sha3_256StrictTest() throws Throwable { + // 테스트 벡터 + final String inputText = "Hello, World!"; + // SHA3-256 기댓값 + final String expectedHex = "1af17a664e3fa8e419b8ba05c2a173169df76162a5a286e0c405b460d478f7ef"; + final byte[] inputBytes = inputText.getBytes(StandardCharsets.UTF_8); + + MemorySegment resultSegmentAlias; + + // 보안 스코프 내에서 암호화 연산 수행 + try (SDCScopeContext context = new SDCScopeContext()) { + SensitiveDataContainer inputData = context.allocate(inputBytes, true); + SensitiveDataContainer result = Hash.sha3(256, context, inputData); + + assertNotNull(result, "해시 연산 결과는 null일 수 없습니다."); + resultSegmentAlias = result.getMemorySegment(); + + // [검증 A] 길이 검증 + assertEquals(32, resultSegmentAlias.byteSize(), + "SHA3-256 해시 결과는 정확히 32바이트(256비트)여야 합니다."); + + // [검증 B] 무결성 검증 (correctness check) + byte[] actualHashBytes = resultSegmentAlias.toArray(ValueLayout.JAVA_BYTE); + String actualHex = HexFormat.of().formatHex(actualHashBytes); + assertEquals(expectedHex, actualHex, + "계산된 해시값이 예상된 SHA3-256 KAT(known answer test) 값과 정확히 일치해야 합니다."); + } + + // 스코프 종료 후 메모리 보호 메커니즘 검증 + // 컨텍스트(context)가 close()된 이후, 해당 arena에 종속된 세그먼트에 접근을 시도하면 + // 자바 런타임이 IllegalStateException을 발생시켜야 안전한 소거 및 접근 차단이 입증됨 + assertThrows(IllegalStateException.class, () -> + resultSegmentAlias.get(ValueLayout.JAVA_BYTE, 0), + "스코프가 종료된 이후 메모리 세그먼트에 접근할 경우 반드시 예외가 발생하여 잔류 데이터 유출을 막아야 합니다."); + } + + @Test + @DisplayName("SHA3-384 known answer test & 메모리 안전성 검증") + void sha3_384StrictTest() throws Throwable { + // 테스트 벡터 + final String inputText = "Hello, World!"; + // SHA3-384 기댓값 + final String expectedHex = "aa9ad8a49f31d2ddcabbb7010a1566417cff803fef50eba239558826f872e468c5743e7f026b0a8e5b2d7a1cc465cdbe"; + final byte[] inputBytes = inputText.getBytes(StandardCharsets.UTF_8); + + MemorySegment resultSegmentAlias; + + // 보안 스코프 내에서 암호화 연산 수행 + try (SDCScopeContext context = new SDCScopeContext()) { + SensitiveDataContainer inputData = context.allocate(inputBytes, true); + SensitiveDataContainer result = Hash.sha3(384, context, inputData); + + assertNotNull(result, "해시 연산 결과는 null일 수 없습니다."); + resultSegmentAlias = result.getMemorySegment(); + + // [검증 A] 길이 검증 + assertEquals(48, resultSegmentAlias.byteSize(), + "SHA3-384 해시 결과는 정확히 32바이트(384비트)여야 합니다."); + + // [검증 B] 무결성 검증 (correctness check) + byte[] actualHashBytes = resultSegmentAlias.toArray(ValueLayout.JAVA_BYTE); + String actualHex = HexFormat.of().formatHex(actualHashBytes); + assertEquals(expectedHex, actualHex, + "계산된 해시값이 예상된 SHA3-384 KAT(known answer test) 값과 정확히 일치해야 합니다."); + } + + // 스코프 종료 후 메모리 보호 메커니즘 검증 + // 컨텍스트(context)가 close()된 이후, 해당 arena에 종속된 세그먼트에 접근을 시도하면 + // 자바 런타임이 IllegalStateException을 발생시켜야 안전한 소거 및 접근 차단이 입증됨 + assertThrows(IllegalStateException.class, () -> + resultSegmentAlias.get(ValueLayout.JAVA_BYTE, 0), + "스코프가 종료된 이후 메모리 세그먼트에 접근할 경우 반드시 예외가 발생하여 잔류 데이터 유출을 막아야 합니다."); + } + + @Test + @DisplayName("SHA3-512 known answer test & 메모리 안전성 검증") + void sha3_512StrictTest() throws Throwable { + // 테스트 벡터 + final String inputText = "Hello, World!"; + // SHA3-512 기댓값 + final String expectedHex = "38e05c33d7b067127f217d8c856e554fcff09c9320b8a5979ce2ff5d95dd27ba35d1fba50c562dfd1d6cc48bc9c5baa4390894418cc942d968f97bcb659419ed"; + final byte[] inputBytes = inputText.getBytes(StandardCharsets.UTF_8); + + MemorySegment resultSegmentAlias; + + // 보안 스코프 내에서 암호화 연산 수행 + try (SDCScopeContext context = new SDCScopeContext()) { + SensitiveDataContainer inputData = context.allocate(inputBytes, true); + SensitiveDataContainer result = Hash.sha3(512, context, inputData); + + assertNotNull(result, "해시 연산 결과는 null일 수 없습니다."); + resultSegmentAlias = result.getMemorySegment(); + + // [검증 A] 길이 검증 + assertEquals(64, resultSegmentAlias.byteSize(), + "SHA3-512 해시 결과는 정확히 32바이트(512비트)여야 합니다."); + + // [검증 B] 무결성 검증 (correctness check) + byte[] actualHashBytes = resultSegmentAlias.toArray(ValueLayout.JAVA_BYTE); + String actualHex = HexFormat.of().formatHex(actualHashBytes); + assertEquals(expectedHex, actualHex, + "계산된 해시값이 예상된 SHA3-512 KAT(known answer test) 값과 정확히 일치해야 합니다."); + } + + // 스코프 종료 후 메모리 보호 메커니즘 검증 + // 컨텍스트(context)가 close()된 이후, 해당 arena에 종속된 세그먼트에 접근을 시도하면 + // 자바 런타임이 IllegalStateException을 발생시켜야 안전한 소거 및 접근 차단이 입증됨 + assertThrows(IllegalStateException.class, () -> + resultSegmentAlias.get(ValueLayout.JAVA_BYTE, 0), + "스코프가 종료된 이후 메모리 세그먼트에 접근할 경우 반드시 예외가 발생하여 잔류 데이터 유출을 막아야 합니다."); + } + + @Test + @DisplayName("SHA3-SHAKE128 known answer test & 메모리 안전성 검증") + void sha3_shake128StrictTest() throws Throwable { + // 테스트 벡터 + final String inputText = "Hello, World!"; + // SHA3-SHAKE128 (256비트) 기댓값 + final String expectedHex = "2bf5e6dee6079fad604f573194ba8426bd4d30eb13e8ba2edae70e529b570cbd"; + final byte[] inputBytes = inputText.getBytes(StandardCharsets.UTF_8); + + MemorySegment resultSegmentAlias; + + // 보안 스코프 내에서 암호화 연산 수행 + try (SDCScopeContext context = new SDCScopeContext()) { + SensitiveDataContainer inputData = context.allocate(inputBytes, true); + SensitiveDataContainer result = Hash.sha3Shake(128, 32, context, inputData); + + assertNotNull(result, "해시 연산 결과는 null일 수 없습니다."); + resultSegmentAlias = result.getMemorySegment(); + + // [검증 A] 길이 검증 + assertEquals(32, resultSegmentAlias.byteSize(), + "이 테스트에서 SHA3-SHAKE128 해시 결과는 정확히 256비트여야 합니다."); + + // [검증 B] 무결성 검증 (correctness check) + byte[] actualHashBytes = resultSegmentAlias.toArray(ValueLayout.JAVA_BYTE); + String actualHex = HexFormat.of().formatHex(actualHashBytes); + assertEquals(expectedHex, actualHex, + "계산된 해시값이 예상된 SHA3-SHAKE128 KAT(known answer test) 값과 정확히 일치해야 합니다."); + } + + // 스코프 종료 후 메모리 보호 메커니즘 검증 + // 컨텍스트(context)가 close()된 이후, 해당 arena에 종속된 세그먼트에 접근을 시도하면 + // 자바 런타임이 IllegalStateException을 발생시켜야 안전한 소거 및 접근 차단이 입증됨 + assertThrows(IllegalStateException.class, () -> + resultSegmentAlias.get(ValueLayout.JAVA_BYTE, 0), + "스코프가 종료된 이후 메모리 세그먼트에 접근할 경우 반드시 예외가 발생하여 잔류 데이터 유출을 막아야 합니다."); + } + + @Test + @DisplayName("SHA3-SHAKE256 known answer test & 메모리 안전성 검증") + void sha3_shake256StrictTest() throws Throwable { + // 테스트 벡터 + final String inputText = "Hello, World!"; + // SHA3-SHAKE256 (512비트) 기댓값 + final String expectedHex = "b3be97bfd978833a65588ceae8a34cf59e95585af62063e6b89d0789f372424e8b0d1be4f21b40ce5a83a438473271e0661854f02d431db74e6904d6c347d757"; + final byte[] inputBytes = inputText.getBytes(StandardCharsets.UTF_8); + + MemorySegment resultSegmentAlias; + + // 보안 스코프 내에서 암호화 연산 수행 + try (SDCScopeContext context = new SDCScopeContext()) { + SensitiveDataContainer inputData = context.allocate(inputBytes, true); + SensitiveDataContainer result = Hash.sha3Shake(256, 64, context, inputData); + + assertNotNull(result, "해시 연산 결과는 null일 수 없습니다."); + resultSegmentAlias = result.getMemorySegment(); + + // [검증 A] 길이 검증 + assertEquals(64, resultSegmentAlias.byteSize(), + "이 테스트에서 SHA3-SHAKE256 해시 결과는 정확히 128자리여야 합니다."); + + // [검증 B] 무결성 검증 (correctness check) + byte[] actualHashBytes = resultSegmentAlias.toArray(ValueLayout.JAVA_BYTE); + String actualHex = HexFormat.of().formatHex(actualHashBytes); + assertEquals(expectedHex, actualHex, + "계산된 해시값이 예상된 SHA3-SHAKE256 KAT(known answer test) 값과 정확히 일치해야 합니다."); + } + + // 스코프 종료 후 메모리 보호 메커니즘 검증 + // 컨텍스트(context)가 close()된 이후, 해당 arena에 종속된 세그먼트에 접근을 시도하면 + // 자바 런타임이 IllegalStateException을 발생시켜야 안전한 소거 및 접근 차단이 입증됨 + assertThrows(IllegalStateException.class, () -> + resultSegmentAlias.get(ValueLayout.JAVA_BYTE, 0), + "스코프가 종료된 이후 메모리 세그먼트에 접근할 경우 반드시 예외가 발생하여 잔류 데이터 유출을 막아야 합니다."); + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index b1fc284..d219926 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1 +1,5 @@ -rootProject.name = "entanglementlib" \ No newline at end of file +rootProject.name = "entanglementlib" +include("core") +include("security") +include("annotations") +include("annotation-processor") \ No newline at end of file diff --git a/src/main/java/space/qu4nt/entanglementlib/CallerResponsibility.java b/src/main/java/space/qu4nt/entanglementlib/CallerResponsibility.java deleted file mode 100644 index e4f6d12..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/CallerResponsibility.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; - -/** - * 보안적 책임은 호출자에게 있음을 알리는 어노테이션입니다. - * 호출자가 해당 어노테이션이 사용된 멤버 사용 시, 작업 종료 후 - * 반드시 보안 작업이 필요함을 의미합니다. - *

- * 예를 들어, 이 어노테이션이 (복사본이 아닌) 원본 데이터를 반환하는 메소드에 사용되었고 - * 해당 메소드를 사용하고자 하는 경우 반환받은 데이터를 소거해야 합니다. - * - * @author Q. T. Felix - * @since 1.1.0 - */ -@Documented -@Target(ElementType.TYPE_USE) -public @interface CallerResponsibility { - - /** - * 책임 전가의 사유 또는 설명를 정의합니다. - * - * @return 책임 전가 사유 또는 설명 - */ - String value() default ""; - -} diff --git a/src/main/java/space/qu4nt/entanglementlib/EntanglementLibBootstrap.java b/src/main/java/space/qu4nt/entanglementlib/EntanglementLibBootstrap.java deleted file mode 100644 index f151508..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/EntanglementLibBootstrap.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; - -import java.io.IOException; -import java.lang.reflect.Field; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.security.Provider; -import java.security.SecureRandom; -import java.security.Security; -import java.util.*; - -/** - * 얽힘 라이브러리의 몇 가지 기능을 외부에서 즉시 호출할 수도 있지만 - * 이 경우 정적 블록에 대한 메모리 할당 및 그에 상응하는 작업의 시간 복잡도가 - * 증가합니다. 이를 해결하기 위해 만들어진 내부 로딩 부트스트랩 클래스입니다. - *

- * 이 클래스가 내부에서 사용되는 경우는 전달받은 외부 프로젝트(호출자)의 이름을 - * 사용하는 때 이외엔 없으며, 호출되어서도 안 됩니다. - * - * @author Q. T. Felix - * @since 1.1.0 - */ -@Slf4j -@Getter -@Setter -@AllArgsConstructor(access = AccessLevel.PRIVATE) -public final class EntanglementLibBootstrap { - - static { - log.debug("얽힘 라이브러리(EntanglementLib) 등록"); - } - - @ApiStatus.Internal - private @NotNull String projectName; - - @ExternalPattern - public static EntanglementLibBootstrap registerEntanglementLib(@NotNull String projectName, boolean setBCProviders) { - if (setBCProviders) - InternalFactory.registerInternalEntanglementLib(); - return new EntanglementLibBootstrap(projectName); - } - - @ExternalPattern - public @NotNull SecureRandom getSafeRandom() { - return InternalFactory.getSafeRandom(); - } - - @ApiStatus.Internal - public static void providerInformation() throws IOException { - Map>> providerInfo = new LinkedHashMap<>(); - - for (Provider provider : Security.getProviders()) { - if (provider == null || provider.getName() == null) continue; - - String providerName = provider.getName(); - Map> services = new TreeMap<>(); - - for (Provider.Service service : provider.getServices()) { - String type = service.getType(); - String algorithm = service.getAlgorithm(); - - StringBuilder algoDetails = new StringBuilder(algorithm); - - try { - Field attrField = service.getClass().getDeclaredField("attributes"); - attrField.setAccessible(true); - @SuppressWarnings("unchecked") - Map map = (Map) attrField.get(service); - if (map != null) { - map.forEach((k, v) -> { - if (!"Software".equals(v)) { -// algoDetails.append("\n - ").append(k).append(": ").append(v); - } - }); - } - } catch (Exception _) { - } - - services.computeIfAbsent(type, k -> new ArrayList<>()).add(algoDetails.toString()); - } - providerInfo.put(providerName, services); - } - - StringBuilder stringTower = new StringBuilder(); - providerInfo.forEach((pName, services) -> { - stringTower.append("공급자: ").append(pName).append("\n"); - services.forEach((type, algos) -> { - stringTower.append("- ").append(type).append("\n"); - for (String algo : algos) { - stringTower.append(" - ").append(algo).append("\n"); - } - }); - stringTower.append("\n"); - }); - - Files.writeString(Paths.get(InternalFactory.envEntanglementPublicDir()).resolve("security-providers.txt"), stringTower.toString(), StandardCharsets.UTF_8); - } - - @ApiStatus.Internal - static void main() throws IOException { - InternalFactory.setupSecurityProviders(); - providerInformation(); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/EntanglementLibEnvs.java b/src/main/java/space/qu4nt/entanglementlib/EntanglementLibEnvs.java deleted file mode 100644 index 6b303f3..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/EntanglementLibEnvs.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib; - -import com.quant.quantregular.annotations.QuantTypeOwner; -import com.quant.quantregular.annotations.Quanters; - -/** - * {@code EntanglementLib}에서 공통으로 사용되는 환경 변수 값을 정의한 클래스입니다. - *

- * 이 클래스는 {@link InternalFactory}에서만 구현되며, 외부에서 호출할 수 없습니다. - * - * @author Q. T. Felix - * @since 1.0.0 - */ -@QuantTypeOwner(Quanters.Q_T_FELIX) -sealed class EntanglementLibEnvs extends WrapEnv permits InternalFactory { - - private static final EntanglementLibEnvs entanglementPublicDir; - - private static final EntanglementLibEnvs entanglementHomeDir; - - private static final EntanglementLibEnvs entLibNativeDir; - - static { - entanglementPublicDir = new EntanglementLibEnvs("ENTANGLEMENT_PUBLIC_DIR" , true); - entanglementHomeDir = new EntanglementLibEnvs("ENTANGLEMENT_HOME_DIR" , true); - entLibNativeDir = new EntanglementLibEnvs("ENTLIB_NATIVE_BIN" , false, "native"); - } - - EntanglementLibEnvs() { - throw new UnsupportedOperationException("do not empty instantiate this class"); - } - - EntanglementLibEnvs(String env, boolean req, String def) { - super(env, req, def); - } - - EntanglementLibEnvs(String env, boolean req) { - this(env, req, null); - } - - public static String envEntanglementPublicDir() { - return entanglementPublicDir.getEnv(); - } - - public static String envEntanglementHomeDir() { - return entanglementHomeDir.getEnv(); - } - - public static String envEntLibNativeDir() { - return entLibNativeDir.getEnv(); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/InternalFactory.java b/src/main/java/space/qu4nt/entanglementlib/InternalFactory.java deleted file mode 100644 index 822ef17..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/InternalFactory.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib; - -import lombok.extern.slf4j.Slf4j; -import org.bouncycastle.crypto.CryptoServicesRegistrar; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; -import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import space.qu4nt.entanglementlib.entlibnative.NativeLinkerManager; -import space.qu4nt.entanglementlib.exception.critical.EntLibNativeError; -import space.qu4nt.entanglementlib.resource.config.PublicConfiguration; -import space.qu4nt.entanglementlib.resource.language.LanguageInstanceBased; -import space.qu4nt.entanglementlib.security.crypto.EntLibCryptoRegistry; -import space.qu4nt.entanglementlib.util.chunk.ByteArrayChunkProcessor; -import tools.jackson.databind.ObjectMapper; - -import java.lang.foreign.ValueLayout; -import java.security.*; -import java.util.Objects; - -/// 환경 변수 할당 및 암호화 연산을 중앙에서 처리하기 위한 내부 클래스입니다. -/// -/// `BouncyCastle` 라이브러리 공급자를 `JCA`에 등록하거나 서명, KEM 등의 알고리즘 연산을 간편화할 때 사용됩니다. -/// 필요한 경우 [#_bcNormalProvider] 또는 [#_bcPQCProvider] 상수를 통해 라이브러리 공급자 이름을 사용하거나 -/// `JCA`에 공급자를 등록할 수 있습니다. -/// -/// @author Q. T. Felix -/// @since 1.0.0 -@ApiStatus.Internal -@Slf4j -public final class InternalFactory extends EntanglementLibEnvs { - - /** - * 얽힘 라이브러리의 공개 구성입니다. 라이브러리 로드 시 가장 먼저 초기화되어야 합니다. - */ - private static final PublicConfiguration config; - /** - * {@link InternalFactory} 클래스에 대한 다국어 지원 인스턴스입니다. - */ - private static final LanguageInstanceBased lang; - - /** - * BouncyCastle 일반 및 NIST 표준화된 암호화 공급자 이름입니다. - */ - private static String _bcNormalProvider; - /** - * BouncyCastle 양자-내성 암호화 공급자 이름입니다. - */ - private static String _bcPQCProvider; - /** - * BouncyCastle JSSE (Java Secure Socket Extension) 공급자 이름입니다. - */ - private static String _bcJSSEProvider; - - /** - * 최초 선언 후 변경되지 않는 보안 난수 생성기입니다. - *

- * 보안 난수를 생성하기 위해 해당 상수만을 사용해야 합니다. - *

- * {@code BouncyCastle} 라이브러리가 자동으로 최적의 소스를 선택합니다. - */ - private static final SecureRandom SAFE_RANDOM; - - // TODO: 동적 리소스 할당 로직과 그에 따른 약간의 패턴 변형 - static { - config = new PublicConfiguration(new ObjectMapper()); - lang = LanguageInstanceBased.create(InternalFactory.class); - log.debug(lang.argsNonTopKey("loaded-configuration", config)); - - SAFE_RANDOM = CryptoServicesRegistrar.getSecureRandom(); - log.debug(lang.msg("init-saferandom")); - } - - // - // EntLib-Native - start - // - - private static final NativeLinkerManager NATIVE; - - static { - log.debug("네이티브 로드"); - NATIVE = new NativeLinkerManager("entlib_native") - .addVoidMethodHandle("entanglement_secure_wipe", ValueLayout.ADDRESS, ValueLayout.JAVA_LONG); - } - - @NotNull - public static NativeLinkerManager callNativeLib() { - if (NATIVE == null) - throw new EntLibNativeError("네이티브 라이브러리가 등록되지 않았습니다!"); - return NATIVE; - } - - // - // EntLib-Native - end todo; 배포 용이하게 수정 - // - - static void registerInternalEntanglementLib() { - setupSecurityProviders(); - System.setProperty("jdk.tls.maxHandshakeMessageSize", String.valueOf(config.getTlsMaxHandshakeMessageSize())); - log.debug("얽힘 라이브러리 레지스트리에 {}개의 알고리즘 등록됨", EntLibCryptoRegistry.registeredCount()); - } - - /** - * 유틸리티 클래스의 인스턴스화를 방지하기 위한 생성자입니다. - */ - private InternalFactory() { - throw new UnsupportedOperationException("InternalFactory"); - } - - /** - * 등록된 {@link BouncyCastleProvider} 공급자명을 가져오는 메소드입니다. - * - * @return {@link BouncyCastleProvider} 공급자명 - */ - public static String getBCNormalProvider() { - return _bcNormalProvider; - } - - /** - * 등록된 {@link BouncyCastlePQCProvider} 공급자명을 가져오는 메소드입니다. - *

- * 구성에 {@code enabledExperimental} 옵션이 비활성화 되어있으면 해당 공급자를 - * 사용할 수 없습니다. - * - * @return {@link BouncyCastlePQCProvider} 공급자명, 구성 비활성화 시 {@code null} - */ - public static @Nullable String getBCPQCProvider() { - return _bcPQCProvider; - } - - /** - * 등록된 {@link BouncyCastleJsseProvider} 공급자명을 가져오는 메소드입니다. - * - * @return {@link BouncyCastleJsseProvider} 공급자명 - */ - public static String getBCJSSEProvider() { - return _bcJSSEProvider; - } - - /** - * 최초 선언 후 변경되지 않는 보안 난수 생성기입니다. - *

- * 보안 난수를 생성하기 위해 해당 상수만을 사용해야 합니다. - *

- * {@code BouncyCastle} 라이브러리가 자동으로 최적의 소스(/dev/urandom, Fortuna/PRNG, Auto-seeding 등)를 선택합니다. - */ - public static SecureRandom getSafeRandom() { - return SAFE_RANDOM; - } - - /** - * 라이브러리의 공개 구성을 가져오는 메소드입니다. - * - * @return {@link PublicConfiguration} 인스턴스 - */ - public static PublicConfiguration getPublicConfig() { - return Objects.requireNonNull(config, "public config"); - } - - /** - * 지정된 이름의 보안 공급자가 등록되어 있지 않은지 확인하는 메소드입니다. - * - * @param provider 확인할 공급자 이름 - * @return 등록되어 있지 않으면 {@code true}, 그렇지 않으면 {@code false} - */ - static boolean isNotBindingSecurityProvider(String provider) { - return getSpecificProvider(provider) == null; - } - - /** - * 지정된 이름의 보안 공급자를 가져오는 메소드입니다. - * - * @param provider 가져올 공급자 이름 - * @return {@link Provider} 인스턴스, 없으면 {@code null} - */ - static Provider getSpecificProvider(String provider) { - return Security.getProvider(provider); - } - - /** - * BouncyCastle 보안 공급자를 JCA에 등록하는 메소드입니다. - *

- * 일반, PQC, JSSE 공급자를 순서대로 등록하며, 실험적 기능 사용 여부에 따라 PQC 공급자 등록이 결정됩니다. - * 멀티 스레드 환경에서의 안전한 등록을 위해 동기화 처리되었습니다. - */ - static synchronized void setupSecurityProviders() { - if (isNotBindingSecurityProvider(_bcNormalProvider)) { - Security.addProvider(new BouncyCastleProvider()); - _bcNormalProvider = BouncyCastleProvider.PROVIDER_NAME; - log.debug(lang.argsNonTopKey("setting-bc-provider", _bcNormalProvider)); - } - - // NOTE: 실험적 기능이 포함된 공급자는 테스트 시에만 사용 - if (getPublicConfig().isEnabledExperimental()) { - if (isNotBindingSecurityProvider(_bcPQCProvider)) { - Security.addProvider(new BouncyCastlePQCProvider()); - _bcPQCProvider = BouncyCastlePQCProvider.PROVIDER_NAME; - log.debug(lang.argsNonTopKey("setting-bc-provider", _bcPQCProvider)); - } - } - - if (isNotBindingSecurityProvider(_bcJSSEProvider)) { - Security.addProvider(new BouncyCastleJsseProvider()); - _bcJSSEProvider = BouncyCastleJsseProvider.PROVIDER_NAME; - log.debug(lang.argsNonTopKey("setting-bc-provider", _bcJSSEProvider)); - } - } - - /** - * {@link Signature} 객체에 평문 데이터를 업데이트하는 공통 메소드입니다. - *

- * 대용량 데이터의 경우 청크 단위로 처리하며, 작은 데이터는 일반 업데이트를 수행합니다. - * 이 메소드는 서명 생성 및 검증 과정에서 공통으로 사용됩니다. - * - * @param signature 업데이트할 Signature 객체 - * @param plain 업데이트할 평문 데이터 - * @param chunkSize 청크 크기 (0 이하인 경우 일반 처리) - * @throws SignatureException 서명 업데이트 중 예외가 발생한 경우 - */ - private static void updateSignature(@NotNull Signature signature, byte @NotNull [] plain, int chunkSize) throws SignatureException { - if (plain.length > 1023 && chunkSize > 0) { - ByteArrayChunkProcessor.processInChunks(plain, chunkSize, signature::update); - } else { - signature.update(plain, 0, plain.length); - } - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/WrapEnv.java b/src/main/java/space/qu4nt/entanglementlib/WrapEnv.java deleted file mode 100644 index 0368033..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/WrapEnv.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib; - -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import space.qu4nt.entanglementlib.exception.critical.EntLibEnvError; -import space.qu4nt.entanglementlib.util.StringUtil; - -import java.util.Optional; - -/** - * {@code EntanglementLib}에서 사용되는 환경 변수를 래핑하는 추상 클래스입니다. - *

- * 이 라이브러리의 환경 변수는 이 클래스의 인스턴스를 통해 호출이 이루어져야 합니다. - * - * @author Q. T. Felix - * @since 1.0.0 - */ -@Getter(AccessLevel.PACKAGE) -@Setter(AccessLevel.PACKAGE) -@NoArgsConstructor -sealed abstract class WrapEnv permits EntanglementLibEnvs { - - String env; - boolean req; - - /** - * 해당 환경변수 값의 {@code req} 필드가 {@code true}인 경우 - * 그 값은 {@code null}이면 안 됩니다. 이 메소드는 이러한 경우를 체크합니다. - *

- * 해당 환경 변수의 기본값이 할당되어 있으며 {@code req} 필드가 {@code false}인 경우, - * 할당된 기본값을 반환합니다. - * - * @param env 타겟 환경 변수 - * @param req 필수 여부 - * @param def 필수가 아니며 타겟 환경 변수가 {@code null}인 경우 반환값 - */ - WrapEnv(@NotNull String env, boolean req, @Nullable String def) { - env = StringUtil.toUpperCase(env); - Optional opt = Optional.ofNullable(System.getenv(env)); - if (opt.isPresent()) { - this.env = opt.get(); - } else { - if (req) error(env); - if (def == null) error(env); - this.env = def; - } - this.req = req; - } - - /** - * 런타임 예외를 발생시키는 메소드입니다. - * - * @param env 예외에 보여질 누락된 환경 변수 이름 - */ - static void error(String env) { - throw new EntLibEnvError(env); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/entlibnative/NativeLinkerManager.java b/src/main/java/space/qu4nt/entanglementlib/entlibnative/NativeLinkerManager.java deleted file mode 100644 index 2f21ad1..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/entlibnative/NativeLinkerManager.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.entlibnative; - -import lombok.Getter; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.InternalFactory; -import space.qu4nt.entanglementlib.exception.critical.EntLibNativeError; - -import java.lang.foreign.*; -import java.lang.invoke.MethodHandle; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; - -/// 얽힘 라이브러리 네이티브 라이브러리 브릿지를 위한 매니지먼트 클래스입니다. -/// 해당 클래스는 [InternalFactory]를 통해 런타임에서 생성 후 변경되지 -/// 않습니다. 따라서 호출은 [#NativeLinkerManager(String)]가 아닌 -/// [InternalFactory#callNativeLib()] 메소드를 사용해야 합니다. -/// -/// 이 클래스를 외부에서 사용한다면 사용자 설정된 네이티브를 호출할 수도 -/// 있지만, 이 클래스는 기본적으로 얽힘 라이브러리에서 `entlib-native` -/// 라이브러리만이 호출됨을 예상합니다. -/// -/// @author Q. T. Felix -/// @since 1.1.0 -@Slf4j -@Getter -@Setter -public class NativeLinkerManager { - - private static final String OS_NAME = System.getProperty("os.name").toLowerCase(); - private static final String OS_ARCH = System.getProperty("os.arch").toLowerCase(); - - private final SymbolLookup lookup; - private final Linker linker; - - private String libName; - private Map handles; - - public NativeLinkerManager(final @NotNull String libName) { - Objects.requireNonNull(libName); - Path nativeDir = Path.of(InternalFactory.envEntLibNativeDir()).toAbsolutePath(); - Path lib = resolveNativeLibrary(nativeDir, libName); - if (lib == null) - throw new EntLibNativeError("네이티브 라이브러리 '" + libName + "'을(를) 찾을 수 없습니다! " + - "(검색 경로: " + nativeDir + ", OS: " + OS_NAME + ", Arch: " + OS_ARCH + ")"); - this.libName = lib.getFileName().toString(); - log.debug("네이티브 라이브러리 로드: {}", lib); - this.lookup = SymbolLookup.libraryLookup(lib, Arena.global()); - this.linker = Linker.nativeLinker(); - this.handles = new ConcurrentHashMap<>(); - } - - /// 현재 플랫폼과 아키텍처에 맞는 네이티브 라이브러리 파일을 찾는 메소드입니다. - /// 검색 우선순위는 다음과 같습니다. - /// - /// 1. 정확한 이름 (예: libentlib_native.dylib) - /// 2. 아키텍처 특정 이름 (예: libentlib_native_aarch64.dylib) - /// 3. macOS의 경우 universal 바이너리 (예: libentlib_native_universal.dylib) - /// - /// @param nativeDir 바이너리 파일이 위치한 네이티브 디렉토리 - /// @param libName 네이티브 라이브러리명 - /// @return 특정된 바이너리 파일 - private static Path resolveNativeLibrary(@NotNull Path nativeDir, @NotNull String libName) { - List candidates = generateCandidateNames(libName); - for (String candidate : candidates) { - Path path = nativeDir.resolve(candidate); - if (Files.exists(path)) { - return path; - } - } - return null; - } - - /// 플랫폼과 아키텍처에 따라 후보 파일명 목록을 생성하는 메소드입니다. - private static List generateCandidateNames(@NotNull String libName) { - List candidates = new ArrayList<>(); - String archSuffix = getArchitectureSuffix(); - String ext = getLibraryExtension(); - String prefix = getLibraryPrefix(); - - // 1. 정확한 이름 (System.mapLibraryName과 동일) - candidates.add(prefix + libName + ext); - - // 2. 아키텍처 특정 이름 - candidates.add(prefix + libName + "_" + archSuffix + ext); - - // 3. macOS의 경우 universal 바이너리 - if (isMacOS()) { - candidates.add(prefix + libName + "_universal" + ext); - } - - return candidates; - } - - private static String getArchitectureSuffix() { - if (OS_ARCH.contains("aarch64") || OS_ARCH.contains("arm64")) { - return "aarch64"; - } else if (OS_ARCH.contains("amd64") || OS_ARCH.contains("x86_64")) { - return "x86_64"; - } else if (OS_ARCH.contains("x86") || OS_ARCH.contains("i386") || OS_ARCH.contains("i686")) { - return "i686"; - } - return OS_ARCH; - } - - private static String getLibraryExtension() { - if (isMacOS()) return ".dylib"; - if (isWindows()) return ".dll"; - return ".so"; - } - - private static String getLibraryPrefix() { - if (isWindows()) return ""; - return "lib"; - } - - private static boolean isMacOS() { - return OS_NAME.contains("mac") || OS_NAME.contains("darwin"); - } - - private static boolean isWindows() { - return OS_NAME.contains("win"); - } - - @NotNull - public FunctionDescriptor descriptor(@NotNull MemoryLayout resLayout, MemoryLayout... argLayouts) { - return FunctionDescriptor.of(resLayout, argLayouts); - } - - @NotNull - public FunctionDescriptor descriptorVoid(MemoryLayout... argLayouts) { - return FunctionDescriptor.ofVoid(argLayouts); - } - - @NotNull - MethodHandle getLookup(@NotNull String name, FunctionDescriptor function) { - return lookup.find(name) - .map(symbol -> linker.downcallHandle(symbol, function)) - .orElseThrow(() -> new EntLibNativeError("네이티브 함수 '" + name + "'을(를) 찾을 수 없습니다!")); - } - - /// static 블럭에서 사용해야함 - public NativeLinkerManager addVoidMethodHandle(@NotNull String name, MemoryLayout... argLayouts) { - handles.put(name, getLookup(name, descriptorVoid(argLayouts))); - log.debug("\t네이티브 함수 '{}'을(를) 연결했습니다.", name); - return this; - } - - /// static 블럭에서 사용해야함 - public NativeLinkerManager addReturnableMethodHandle(@NotNull String name, @NotNull MemoryLayout resLayout, MemoryLayout... argLayouts) { - handles.put(name, getLookup(name, descriptor(resLayout, argLayouts))); - log.debug("\t반환값을 가진 네이티브 함수 '{}'을(를) 연결했습니다.", name); - return this; - } - - @NotNull - public MethodHandle getHandle(final @NotNull String name) { - MethodHandle handle = handles.getOrDefault(name, null); - if (handle == null) - throw new EntLibNativeError("네이티브 함수 '" + name + "'을(를) 찾을 수 없습니다!"); - return handle; - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/entlibnative/ProgressResult.java b/src/main/java/space/qu4nt/entanglementlib/entlibnative/ProgressResult.java deleted file mode 100644 index 6def668..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/entlibnative/ProgressResult.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.entlibnative; - -import lombok.Getter; - -import java.util.Arrays; - -@Getter -public enum ProgressResult { - - SUCCESS(0), - CALC_FAILURE(-1), - JUST_FAILURE(-2); - - private final int code; - - ProgressResult(int code) { - this.code = code; - } - - public boolean isFail() { - return this.code != 0; - } - - public static ProgressResult fromCode(final int code) { - return Arrays.stream(ProgressResult.values()) - .filter(i -> i.getCode() == code) - .findFirst() - .orElseThrow(); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/entlibnative/SensitiveDataContainer.java b/src/main/java/space/qu4nt/entanglementlib/entlibnative/SensitiveDataContainer.java deleted file mode 100644 index 8e6cbe4..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/entlibnative/SensitiveDataContainer.java +++ /dev/null @@ -1,335 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.entlibnative; - -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.bouncycastle.util.encoders.Base64; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.Range; -import space.qu4nt.entanglementlib.HeuristicArenaFactory; -import space.qu4nt.entanglementlib.InternalFactory; -import space.qu4nt.entanglementlib.Unsafe; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalStateException; -import space.qu4nt.entanglementlib.security.KeyDestroyHelper; - -import java.lang.foreign.Arena; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.ValueLayout; -import java.nio.ByteBuffer; -import java.security.SecureRandom; -import java.util.*; - -/// `Rust`의 소유권(ownership) 개념처럼 이 클래스도 전달받은 민감 정보에 대한 소유권을 가집니다. -/// 이 클래스에 저장된 데이터는 진행중인 세션에 종속되고, 세션이 종료됨에 따라 모든 데이터가 완벽하게 -/// 소거됩니다. 데이터 소거는 `entlib-native`에서 진행됩니다. -/// -/// [#SensitiveDataContainer(byte\[\], boolean)] 생성자를 사용하여 전달받은 바이트 배열 -/// 데이터에 대한 소유권을 이 클래스에 넘길지 결정할 수 있습니다. 논리 값이 `true`일 경우 데이터는 -/// 즉시 소거됩니다. -/// -/// 이 컨테이너 클래스는 연쇄적으로 사용할 수 있습니다. [#bindings] 리스트는 하위(또는 동등)에 -/// 또 다른 컨테이너를 보관할 수 있도록 만들어졌습니다. 이 기능은 통신 구조에서, 세션에 연결된 각 -/// 통신 상대방에게 다양한 데이터를 함께 전송해야 할 때 용이합니다. -/// -/// @author Q. T. Felix -/// @see HeuristicArenaFactory -/// @since 1.1.0 -@Slf4j -public class SensitiveDataContainer implements AutoCloseable { - - @Getter - private final Arena arena; - @Getter - private final MemorySegment memorySegment; - - /// 사용자가 인스턴스 생성에 전달한 바이트 배열 데이터 - private byte @Nullable [] fromData; - - /// 이 인스턴스가 통신 구조에서 사용되는 경우 통신 상대방에게 - /// 네이티브 메모리 세그먼트에 저장된 데이터를 직렬화하여 - /// 전달하기 위해 사용되는 바이트 배열 값입니다. - /// - /// 선언 시에는 `null` 상태를 가지며, 할당 후 이 데이터에 - /// 대한 소유권은 절대적으로 이 인스턴스가 가지게 됩니다. - private byte @Nullable [] segmentData; - - /// 민감 데이터 컨테이너에 여러 데이터 컨테이너 바인딩 - /// 동시성 이슈 해결을 위해 동기적 리스트 선언 - /// - /// # Solved Problems - /// - /// *`20250126` - race conditions* - private final List bindings = Collections.synchronizedList(new ArrayList<>()); - - /// 네이티브 메모리에 전달받은 정수 값(바이트 크기) 만큼의 메모리 세그먼트를 - /// 생성하여 이 인스턴스를 생성합니다. - /// - /// @param allocateSIze 바이트 크기 - public SensitiveDataContainer(final int allocateSIze) { - this.arena = HeuristicArenaFactory.intelligenceCreateArena(); - this.memorySegment = Objects.requireNonNull(arena.allocate(allocateSIze)); - this.fromData = null; - } - - /// 원본 바이트 배열을 전달받고 네이티브 메모리에 바인딩하여 이 인스턴스를 생성합니다. - /// - /// 생성 시점에 전달받은 원본 바이트 배열 데이터에 대한 소유권을 이 인스턴스에 넘길지 결정할 수 - /// 있습니다. 논리 값이 `true`일 경우 데이터는 즉시 소거되고, 이는 이 인스턴스가 소유권을 - /// 가질 필요가 없다는 것을 의미합니다. - /// - /// @param from 네이티브 메모리에 바인딩할 원본 바이트 배열 - /// @param forceWipe 인스턴스에 소유권 이전 여부 - public SensitiveDataContainer(final byte @NotNull [] from, boolean forceWipe) { - this.arena = HeuristicArenaFactory.intelligenceCreateArena(); - this.memorySegment = Objects.requireNonNull(arena.allocateFrom(ValueLayout.JAVA_BYTE, from)); - if (forceWipe) - KeyDestroyHelper.zeroing(from); - else this.fromData = from; - } - - /// 외부에서 생성된 컨테이너를 이 인스턴스의 하위 바인딩으로 추가하는 메소드입니다 - /// 동시성 안전성을 보장하기 위해 [#bindings] 리스트에 대한 락을 획득한 후 수행됩니다. - /// - /// @param container 하위로 종속시킬 컨테이너 - /// @return 추가된 컨테이너 - /// @throws EntLibSecureIllegalStateException 부모 컨테이너가 이미 소거된 경우 - public SensitiveDataContainer addContainerData(SensitiveDataContainer container) - throws EntLibSecureIllegalStateException { - synchronized (this.bindings) { - if (!this.arena.scope().isAlive()) { - // 이미 부모가 죽은 상태라면 추가하려는 자식도 고아 상태가 되지 않도록 예외 던짐 - throw new EntLibSecureIllegalStateException("이미 소거된 컨테이너에는 하위 데이터를 추가할 수 없습니다!"); - } - this.bindings.add(container); - return container; - } - } - - /// 새로운 크기만큼의 컨테이너를 생성하고 하위 바인딩으로 추가하는 메소드입니다 - /// - /// @param allocateSIze 할당할 바이트 크기 - /// @return 생성 및 바인딩된 새 컨테이너 - public SensitiveDataContainer addContainerData(final int allocateSIze) - throws EntLibSecureIllegalStateException { - synchronized (this.bindings) { - if (!this.arena.scope().isAlive()) { - throw new EntLibSecureIllegalStateException("이미 소거된 컨테이너입니다!"); - } - // lock 내부에서 생성 및 추가를 수행하여 원자성 보장 - SensitiveDataContainer s = new SensitiveDataContainer(allocateSIze); - this.bindings.add(s); - return s; - } - } - - /// 바이트 배열을 기반으로 컨테이너를 생성하고 하위 바인딩으로 추가하는 메소드입니다 - /// - /// @param from 원본 바이트 배열 - /// @param forceWipe 원본 배열 소거 여부 - /// @return 생성 및 바인딩된 새 컨테이너 - public SensitiveDataContainer addContainerData(final byte @NotNull [] from, boolean forceWipe) - throws EntLibSecureIllegalStateException { - synchronized (this.bindings) { - if (!this.arena.scope().isAlive()) { - KeyDestroyHelper.zeroing(from); - throw new EntLibSecureIllegalStateException("이미 소거된 컨테이너입니다! 이 예외가 발생했지만, 전달받은 바이트 배열은 소거되었습니다."); - } - SensitiveDataContainer s = new SensitiveDataContainer(from, forceWipe); - this.bindings.add(s); - return s; - } - } - - public Optional get(final int index) { - try { - return Optional.of(this.bindings.get(index)); - } catch (ArrayIndexOutOfBoundsException e) { - log.error("인덱스 '{}'에 바인딩된 컨테이너가 없습니다!", index); - } - return Optional.empty(); - } - - /// 네이티브 메모리 세그먼트의 내용을 `heap` 메모리로 복사하는 - /// 메소드입니다. - /// - /// 복사된 데이터에 대한 소유권은 여전히 이 인스턴스가 - /// 가집니다. 인스턴스가 `try-with-resource` 블럭 내에서 - /// 사용될 경우 해당 값은 자동으로 소거됩니다. - /// - /// 이 메소드를 수행하면 [#segmentData] 변수를 통해 - /// 복사된 바이트 배열 데이터를 호출할 수 있습니다. - /// - /// # Unsafe - /// - /// 얽힘 라이브러리는 극한의 보안 환경을 중요시합니다. 이런 관점에서 - /// 해당 메소드는 다음의 딜레마에 빠지게 됩니다. - /// - /// > *어째서 안전한 `Off-Heap` 데이터를 다시 불안정한 `Java Heap`으로 복사하는가?* - /// - /// 이 메소드는 분명 편의성을 위한 기능이지만, Java Heap에 올라간 데이터는 GC가 - /// 동작하면서 메모리 위치를 옮길(Relocation) 수 있고, 이 과정에서 지워지지 - /// 않는 고아 복사본이 메모리 어딘가에 남을 수 있습니다. - /// - /// 따라서 얽힘 라이브러리의 보안 철학에 따라 이 메소드는 [`Unsafe`][Unsafe] - /// 처리되며, 다음 릴리즈 공개 전에 제거하기로 결정했습니다. - /// - /// @see #getSegmentData() 복사 반환 메소드 - /// @see #getSegmentDataBase64() Base64 복사 반환 메소드 - /// @see #getSegmentDataToByteBuffer() ByteBuffer 복사 반환 메소드 - @Unsafe - @Deprecated(forRemoval = true) - public void exportData() - throws EntLibSecureIllegalStateException { - synchronized (this.bindings) { - // 락 획득 후 생존 여부 확인 (check-then-act 보호) - if (!arena.scope().isAlive()) { - throw new EntLibSecureIllegalStateException("이미 소거된 컨테이너입니다!"); - } - // 안전하게 데이터 복사 - this.segmentData = memorySegment.toArray(ValueLayout.JAVA_BYTE); - } - } - - /// `heap` 메모리에 복사된 데이터를 외부에서도 소거할 수 있도록 하는 - /// 메소드입니다. - public void zeroingExportedData() { - if (segmentData != null) - KeyDestroyHelper.zeroing(segmentData); - } - - /// `heap` 메모리에 복사된 네이티브 메모리 세그먼트 데이터의 복사본을 - /// 반환하는 메소드입니다. - /// - /// @return 네이티브 메모리 세그먼트 데이터의 바이트 배열 복사본 - public byte @Nullable [] getSegmentData() { - return segmentData != null ? Arrays.copyOf(segmentData, segmentData.length) : null; - } - - /// `heap` 메모리에 복사된 네이티브 메모리 세그먼트 데이터의 복사본을 - /// `Base64` 인코딩하여 반환하는 메소드입니다. 이 메소드는 - /// 직렬화에 용이합니다. - /// - /// 단순히 인코딩된 이 값을 직렬화할 수도 있지만, 보안상 추가적인 - /// 작업이 권장됩니다. 예를 들어, 이 값에 대칭키 암호화 연산을 수행할 - /// 수 있습니다. - /// - /// @return 직렬화된 네이티브 메모리 세그먼트 데이터의 바이트 배열 복사본 - public @Nullable String getSegmentDataBase64() { - return getSegmentData() != null ? Base64.toBase64String(getSegmentData()) : null; - } - - public ByteBuffer getSegmentDataToByteBuffer() { - return memorySegment.asByteBuffer(); - } - - /// 인스턴스가 소유권을 가진 상태인 경우 해당 메소드를 사용하여 데이터의 복사본을 호출할 수 - /// 있습니다. 내부적으로 [Arrays#copyOf(byte\[\], int)]를 사용하여 사본을 반환합니다. - /// - /// @return 안전한 데이터 복사본 - public byte @Nullable [] getFromData() { - return fromData != null ? Arrays.copyOf(fromData, fromData.length) : null; - } - - /** - * 암호학적으로 안전한 바이트 배열을 생성하는 메소드입니다. - * - * @param length 0 이상의 바이트 배열 사이즈 - * @return {@code Base64} 인코딩된 문자열 - */ - public static byte @NotNull [] generateSafeRandomBytes(@Range(from = 0, to = Integer.MAX_VALUE) int length) { - final SecureRandom random = InternalFactory.getSafeRandom(); - final byte[] bytes = new byte[length]; - random.nextBytes(bytes); - return bytes; - } - - /** - * 암호학적으로 안전한 {@code Base64} 인코딩된 문자열을 반환하는 메소드입니다. - * - * @param length 0 이상의 바이트 배열 사이즈 - * @return {@code Base64} 인코딩된 문자열 - */ - public static @NotNull String generateBase64String(@Range(from = 0, to = Integer.MAX_VALUE) int length) { - return Base64.toBase64String(generateSafeRandomBytes(length)); - } - - @Override - public void close() { - // SECURE UPDATE: 20250126 - qtfelix - // [Phase 1] 스냅샷 생성 및 연결 해제 (Critical Section 최소화) - List snapshot; - synchronized (bindings) { - // 이미 닫힌 경우 빠른 종료 (Idempotency) - if (!arena.scope().isAlive()) { - log.warn("해당 스레드에서 이미 닫힌 컨테이너입니다."); - return; - } - - if (bindings.isEmpty()) { - snapshot = Collections.emptyList(); - } else { - // 방어적 복사: 리스트를 복제하고 원본은 즉시 비움 - snapshot = new ArrayList<>(bindings); - bindings.clear(); - } - } // 1차 락 해제: 이제 다른 스레드가 bindings에 접근해도 데드락이 발생하지 않음 - - // [Phase 2] 하위 컨테이너 리소스 해제 (Open Call) - // 락 바깥에서 수행하므로 자식 컨테이너가 부모를 다시 호출해도 안전함 - if (!snapshot.isEmpty()) { - for (int i = snapshot.size() - 1; i >= 0; i--) { - SensitiveDataContainer child = snapshot.get(i); - try { - child.close(); - } catch (Exception e) { - log.error("하위 컨테이너 리소스 해제 중 예외가 발생했습니다!", e); - } - } - } - - // [Phase 3] 네이티브 메모리 소거 및 최종 종료 - // 다시 락을 획득하여 [Phase 2] 도중에 추가된 데이터가 없는지 확인하고 소거 - synchronized (bindings) { - // [Edge Case 방어] Phase 2 진행 중에 addContainerData가 호출되어 - // bindings에 새 데이터가 들어왔을 수 있음. 이를 확인하고 소거해야 함. - if (!bindings.isEmpty()) { - for (SensitiveDataContainer straggler : bindings) { - try { - straggler.close(); - } catch (Exception e) { - log.error("지연 추가된 하위 컨테이너 해제 중 오류 발생", e); - } - } - bindings.clear(); - } - - // Arena가 여전히 살아있는지 확인 후 소거 (Double Check) - if (arena.scope().isAlive()) { - try { - // Rust Native Wipe 호출 - InternalFactory.callNativeLib() - .getHandle("entanglement_secure_wipe") - .invokeExact(memorySegment, memorySegment.byteSize()); - } catch (Throwable e) { - log.error("치명적 보안 예외가 발생했습니다! (Native Wipe Failed)", e); - } - - // Java Heap 데이터 소거 - if (fromData != null) { - KeyDestroyHelper.zeroing(fromData); - fromData = null; // GC Hint - } - zeroingExportedData(); - segmentData = null; // GC Hint - - // Arena 종료 - arena.close(); - } - } - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/EntLibException.java b/src/main/java/space/qu4nt/entanglementlib/exception/EntLibException.java deleted file mode 100644 index be3697b..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/exception/EntLibException.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.exception; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.resource.language.LanguageInstanceBased; - -import java.io.Serial; - -/// 얽힘 라이브러리 전반에서 사용되는 기본 예외 클래스입니다. -/// -/// 이 클래스는 [Exception]을 상속받으며, [LanguageInstanceBased]를 사용하여 -/// 다국어 메시지 처리를 지원합니다. -/// -/// 이 클래스는 기본적으로 `Checked Exception`입니다. 확장하여 다양한 상황에 대해 -/// 예외 처리가 가능합니다. -/// -/// @author Q. T. Felix -/// @since 1.1.0 -public class EntLibException extends Exception { - - @Serial - private static final long serialVersionUID = 2378593800480779310L; - - /** - * 새로운 {@link EntLibException} 인스턴스를 생성하는 기본 생성자 메소드입니다. - */ - public EntLibException() { - super(); - } - - /** - * 다국어 처리가 필요하지 않은 경우 이 인스턴스를 사용할 수 있습니다. - * - * @param message 메시지 - */ - public EntLibException(String message) { - super(message); - } - - /** - * 발생한 예외만 넘기기 위해 이 인스턴스를 사용할 수 있습니다. - * - * @param cause 발생 예외 - */ - public EntLibException(Throwable cause) { - super(cause); - } - - /// 다국어 처리가 필요하지 않으며, 발생한 예외를 넘기기 위해 이 - /// 인스턴스를 사용할 수 있습니다. - /// - /// @param message 메시지 - /// @param cause 발생 예외 - public EntLibException(String message, Throwable cause) { - super(message, cause); - } - - /** - * 지정된 클래스와 메시지 키를 사용하여 새로운 {@link EntLibException} 인스턴스를 생성하는 메소드입니다. - *

- * {@link LanguageInstanceBased}를 통해 해당 클래스에 맞는 다국어 메시지를 조회합니다. - * - * @param i18nTargetClass 다국어 메시지 리소스를 조회할 대상 클래스 - * @param key 메시지 키 ({@code null}이 아니어야 함) - * @param 대상 클래스의 타입 - */ - public EntLibException(Class i18nTargetClass, @NotNull String key) { - super(LanguageInstanceBased.create(i18nTargetClass).msg(key)); - } - - /** - * 지정된 클래스, 메시지 키, 그리고 원인 예외를 사용하여 새로운 {@link EntLibException} 인스턴스를 생성하는 메소드입니다. - * - * @param i18nTargetClass 다국어 메시지 리소스를 조회할 대상 클래스 - * @param key 메시지 키 ({@code null}이 아니어야 함) - * @param cause 이 예외가 발생하게 된 원인 예외 - * @param 대상 클래스의 타입 - */ - public EntLibException(Class i18nTargetClass, @NotNull String key, Throwable cause) { - super(LanguageInstanceBased.create(i18nTargetClass).thr(key, cause)); - } - - /** - * 지정된 클래스, 메시지 키, 원인 예외, 그리고 포맷팅 인자들을 사용하여 새로운 {@link EntLibException} 인스턴스를 생성하는 메소드입니다. - * - * @param i18nTargetClass 다국어 메시지 리소스를 조회할 대상 클래스 - * @param key 메시지 키 ({@code null}이 아니어야 함) - * @param cause 이 예외가 발생하게 된 원인 예외 - * @param args 메시지 포맷팅에 사용될 인자들 - * @param 대상 클래스의 타입 - */ - public EntLibException(Class i18nTargetClass, @NotNull String key, Throwable cause, Object... args) { - super(LanguageInstanceBased.create(i18nTargetClass).thr(key, cause, args)); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/EntLibUncheckedException.java b/src/main/java/space/qu4nt/entanglementlib/exception/EntLibUncheckedException.java deleted file mode 100644 index 5fdd0e8..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/exception/EntLibUncheckedException.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.exception; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.resource.language.LanguageInstanceBased; - -import java.io.Serial; - -/// 얽힘 라이브러리 전반에서 사용되는 기본 예외 클래스입니다. -/// -/// 이 클래스는 [Exception]을 상속받으며, [LanguageInstanceBased]를 사용하여 -/// 다국어 메시지 처리를 지원합니다. -/// -/// 이 클래스는 `Unchecked Exception`입니다. -/// -/// @author Q. T. Felix -/// @since 1.1.0 -public class EntLibUncheckedException extends RuntimeException { - - @Serial - private static final long serialVersionUID = 2378593800480779310L; - - /** - * 새로운 {@link EntLibUncheckedException} 인스턴스를 생성하는 기본 생성자 메소드입니다. - */ - public EntLibUncheckedException() { - } - - /** - * 다국어 처리가 필요하지 않은 경우 이 인스턴스를 사용할 수 있습니다. - * - * @param message 메시지 - */ - public EntLibUncheckedException(String message) { - super(message); - } - - /** - * 발생한 예외만 넘기기 위해 이 인스턴스를 사용할 수 있습니다. - * - * @param cause 발생 예외 - */ - public EntLibUncheckedException(Throwable cause) { - super(cause); - } - - /** - * 지정된 클래스와 메시지 키를 사용하여 새로운 {@link EntLibUncheckedException} 인스턴스를 생성하는 메소드입니다. - *

- * {@link LanguageInstanceBased}를 통해 해당 클래스에 맞는 다국어 메시지를 조회합니다. - * - * @param i18nTargetClass 다국어 메시지 리소스를 조회할 대상 클래스 - * @param key 메시지 키 ({@code null}이 아니어야 함) - * @param 대상 클래스의 타입 - */ - public EntLibUncheckedException(Class i18nTargetClass, @NotNull String key) { - super(LanguageInstanceBased.create(i18nTargetClass).msg(key)); - } - - /** - * 지정된 클래스, 메시지 키, 그리고 원인 예외를 사용하여 새로운 {@link EntLibUncheckedException} 인스턴스를 생성하는 메소드입니다. - * - * @param i18nTargetClass 다국어 메시지 리소스를 조회할 대상 클래스 - * @param key 메시지 키 ({@code null}이 아니어야 함) - * @param cause 이 예외가 발생하게 된 원인 예외 - * @param 대상 클래스의 타입 - */ - public EntLibUncheckedException(Class i18nTargetClass, @NotNull String key, Throwable cause) { - super(LanguageInstanceBased.create(i18nTargetClass).thr(key, cause)); - } - - /** - * 지정된 클래스, 메시지 키, 원인 예외, 그리고 포맷팅 인자들을 사용하여 새로운 {@link EntLibUncheckedException} 인스턴스를 생성하는 메소드입니다. - * - * @param i18nTargetClass 다국어 메시지 리소스를 조회할 대상 클래스 - * @param key 메시지 키 ({@code null}이 아니어야 함) - * @param cause 이 예외가 발생하게 된 원인 예외 - * @param args 메시지 포맷팅에 사용될 인자들 - * @param 대상 클래스의 타입 - */ - public EntLibUncheckedException(Class i18nTargetClass, @NotNull String key, Throwable cause, Object... args) { - super(LanguageInstanceBased.create(i18nTargetClass).thr(key, cause, args)); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/critical/EntLibEnvError.java b/src/main/java/space/qu4nt/entanglementlib/exception/critical/EntLibEnvError.java deleted file mode 100644 index 5bf9e85..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/exception/critical/EntLibEnvError.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.exception.critical; - -import java.io.Serial; - -public class EntLibEnvError extends EntLibError { - - @Serial - private static final long serialVersionUID = -4680763736515724771L; - - public EntLibEnvError(String envName) { - super(envName + " env variables are missing"); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/critical/EntLibError.java b/src/main/java/space/qu4nt/entanglementlib/exception/critical/EntLibError.java deleted file mode 100644 index e9a77fb..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/exception/critical/EntLibError.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.exception.critical; - -/// 얽힘 라이브러리에서 발생하는 치명적인 오류를 나타내는 클래스입니다. -/// -/// 이 클래스는 [Error]를 상속받으며, 복구할 수 없는 심각한 문제가 발생했을 때 사용됩니다. -/// -/// @author Q. T. Felix -/// @since 1.1.0 -public class EntLibError extends Error { - - /** - * 새로운 {@link EntLibError} 인스턴스를 생성하는 기본 생성자 메소드입니다. - */ - public EntLibError() { - } - - /** - * 지정된 상세 메시지를 사용하여 새로운 {@link EntLibError} 인스턴스를 생성하는 메소드입니다. - * - * @param message 상세 오류 메시지 - */ - public EntLibError(String message) { - super(message); - } - - /** - * 지정된 상세 메시지와 원인 예외를 사용하여 새로운 {@link EntLibError} 인스턴스를 생성하는 메소드입니다. - * - * @param message 상세 오류 메시지 - * @param cause 이 오류가 발생하게 된 원인 ({@code null}이 허용됨) - */ - public EntLibError(String message, Throwable cause) { - super(message, cause); - } - - /** - * 지정된 원인 예외를 사용하여 새로운 {@link EntLibError} 인스턴스를 생성하는 메소드입니다. - *

- * 상세 메시지는 {@code cause}가 {@code null}이 아닌 경우 {@code cause.toString()}의 결과로 설정됩니다. - *

- * - * @param cause 이 오류가 발생하게 된 원인 ({@code null}이 허용됨) - */ - public EntLibError(Throwable cause) { - super(cause); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/critical/EntLibNativeError.java b/src/main/java/space/qu4nt/entanglementlib/exception/critical/EntLibNativeError.java deleted file mode 100644 index cd1507f..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/exception/critical/EntLibNativeError.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.exception.critical; - -import java.io.Serial; - -public class EntLibNativeError extends EntLibError { - - @Serial - private static final long serialVersionUID = -1665435182140213888L; - - public EntLibNativeError(String message) { - super(message); - } - - public EntLibNativeError(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/critical/EntLibSecurityError.java b/src/main/java/space/qu4nt/entanglementlib/exception/critical/EntLibSecurityError.java deleted file mode 100644 index 52795d9..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/exception/critical/EntLibSecurityError.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.exception.critical; - -import java.io.Serial; - -/// 이 에러는 보안 측면에서 큰 문제가 발생했거나, 시스템이 그렇게 예상할 때 -/// 사용됩니다. -/// -/// # Safety -/// -/// 이 에러가 발생한 경우 단순히 넘어가면 절대 안 됩니다. 얽힘 라이브러리의 -/// 시스템은 체계적이며, 치명적 에러를 던졌다 함은 단순히 내부적인 오류이기 -/// 보단 제3자의 의한 악의적인 수행일 수 있음을 의미합니다. -/// -/// @author Q. T. Felix -/// @since 1.1.0 -public class EntLibSecurityError extends EntLibError { - - @Serial - private static final long serialVersionUID = -8998597327015285852L; - - private int[] codes; - - public EntLibSecurityError(String message) { - // secure level 상승 - // JCA/JCE 공급자 전환 - super(message); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/secure/EntLibSecureCertProcessException.java b/src/main/java/space/qu4nt/entanglementlib/exception/secure/EntLibSecureCertProcessException.java deleted file mode 100644 index 2838681..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/exception/secure/EntLibSecureCertProcessException.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.exception.secure; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.exception.EntLibException; - -import java.io.Serial; - -public class EntLibSecureCertProcessException extends EntLibException { - - @Serial - private static final long serialVersionUID = 3497403903418334407L; - - public EntLibSecureCertProcessException(Throwable cause) { - super(cause); - } - - public EntLibSecureCertProcessException(Class i18nTargetClass, @NotNull String key) { - super(i18nTargetClass, key); - } - - public EntLibSecureCertProcessException(Class i18nTargetClass, @NotNull String key, Throwable cause) { - super(i18nTargetClass, key, cause); - } - - public EntLibSecureCertProcessException(Class i18nTargetClass, @NotNull String key, Throwable cause, Object... args) { - super(i18nTargetClass, key, cause, args); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/secure/EntLibSecureException.java b/src/main/java/space/qu4nt/entanglementlib/exception/secure/EntLibSecureException.java deleted file mode 100644 index 19b9565..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/exception/secure/EntLibSecureException.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.exception.secure; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.exception.EntLibException; - -import java.io.Serial; - -public class EntLibSecureException extends EntLibException { - - @Serial - private static final long serialVersionUID = -2452853243888606864L; - - public EntLibSecureException() { - super(); - } - - public EntLibSecureException(String message) { - super(message); - } - - public EntLibSecureException(Throwable cause) { - super(cause); - } - - public EntLibSecureException(String message, Throwable cause) { - super(message, cause); - } - - public EntLibSecureException(Class i18nTargetClass, @NotNull String key) { - super(i18nTargetClass, key); - } - - public EntLibSecureException(Class i18nTargetClass, @NotNull String key, Throwable cause) { - super(i18nTargetClass, key, cause); - } - - public EntLibSecureException(Class i18nTargetClass, @NotNull String key, Throwable cause, Object... args) { - super(i18nTargetClass, key, cause, args); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/secure/EntLibSecureIllegalArgumentException.java b/src/main/java/space/qu4nt/entanglementlib/exception/secure/EntLibSecureIllegalArgumentException.java deleted file mode 100644 index 1128dba..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/exception/secure/EntLibSecureIllegalArgumentException.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.exception.secure; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.exception.EntLibException; - -import java.io.Serial; - -public class EntLibSecureIllegalArgumentException extends EntLibSecureException { - - @Serial - private static final long serialVersionUID = -2452853243888606864L; - - public EntLibSecureIllegalArgumentException() { - super(); - } - - public EntLibSecureIllegalArgumentException(String message) { - super(message); - } - - public EntLibSecureIllegalArgumentException(String message, Throwable cause) { - super(message, cause); - } - - public EntLibSecureIllegalArgumentException(Class i18nTargetClass, @NotNull String key) { - super(i18nTargetClass, key); - } - - public EntLibSecureIllegalArgumentException(Class i18nTargetClass, @NotNull String key, Throwable cause) { - super(i18nTargetClass, key, cause); - } - - public EntLibSecureIllegalArgumentException(Class i18nTargetClass, @NotNull String key, Throwable cause, Object... args) { - super(i18nTargetClass, key, cause, args); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/secure/EntLibSecureIllegalStateException.java b/src/main/java/space/qu4nt/entanglementlib/exception/secure/EntLibSecureIllegalStateException.java deleted file mode 100644 index bfb6b8d..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/exception/secure/EntLibSecureIllegalStateException.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.exception.secure; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.exception.EntLibException; - -import java.io.Serial; - -public class EntLibSecureIllegalStateException extends EntLibException { - - @Serial - private static final long serialVersionUID = -5903209133861840394L; - - public EntLibSecureIllegalStateException() { - super(); - } - - public EntLibSecureIllegalStateException(String message) { - super(message); - } - - public EntLibSecureIllegalStateException(Class i18nTargetClass, @NotNull String key) { - super(i18nTargetClass, key); - } - - public EntLibSecureIllegalStateException(Class i18nTargetClass, @NotNull String key, Throwable cause) { - super(i18nTargetClass, key, cause); - } - - public EntLibSecureIllegalStateException(Class i18nTargetClass, @NotNull String key, Throwable cause, Object... args) { - super(i18nTargetClass, key, cause, args); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/secure/EntLibSecureJCAJCEStoreProcessException.java b/src/main/java/space/qu4nt/entanglementlib/exception/secure/EntLibSecureJCAJCEStoreProcessException.java deleted file mode 100644 index 56459ec..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/exception/secure/EntLibSecureJCAJCEStoreProcessException.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.exception.secure; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.exception.EntLibException; - -import java.io.Serial; - -public class EntLibSecureJCAJCEStoreProcessException extends EntLibException { - - @Serial - private static final long serialVersionUID = -1891654898835144441L; - - public EntLibSecureJCAJCEStoreProcessException(Throwable cause) { - super(cause); - } - - public EntLibSecureJCAJCEStoreProcessException(Class i18nTargetClass, @NotNull String key) { - super(i18nTargetClass, key); - } - - public EntLibSecureJCAJCEStoreProcessException(Class i18nTargetClass, @NotNull String key, Throwable cause) { - super(i18nTargetClass, key, cause); - } - - public EntLibSecureJCAJCEStoreProcessException(Class i18nTargetClass, @NotNull String key, Throwable cause, Object... args) { - super(i18nTargetClass, key, cause, args); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/secure/EntLibSensitiveDataException.java b/src/main/java/space/qu4nt/entanglementlib/exception/secure/EntLibSensitiveDataException.java deleted file mode 100644 index 320aa3d..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/exception/secure/EntLibSensitiveDataException.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.exception.secure; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.exception.EntLibException; - -import java.io.Serial; - -public class EntLibSensitiveDataException extends EntLibException { - - @Serial - private static final long serialVersionUID = 6683621189466570305L; - - public EntLibSensitiveDataException() { - super(); - } - - public EntLibSensitiveDataException(Class i18nTargetClass, @NotNull String key) { - super(i18nTargetClass, key); - } - - public EntLibSensitiveDataException(Class i18nTargetClass, @NotNull String key, Throwable cause) { - super(i18nTargetClass, key, cause); - } - - public EntLibSensitiveDataException(Class i18nTargetClass, @NotNull String key, Throwable cause, Object... args) { - super(i18nTargetClass, key, cause, args); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/secure/crypto/EntLibCryptoCipherIllegalIVStateException.java b/src/main/java/space/qu4nt/entanglementlib/exception/secure/crypto/EntLibCryptoCipherIllegalIVStateException.java deleted file mode 100644 index e7c5130..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/exception/secure/crypto/EntLibCryptoCipherIllegalIVStateException.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.exception.secure.crypto; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.exception.EntLibException; - -import java.io.Serial; - -public class EntLibCryptoCipherIllegalIVStateException extends EntLibCryptoCipherProcessException { - - @Serial - private static final long serialVersionUID = -1818319588140211117L; - - public EntLibCryptoCipherIllegalIVStateException(String message) { - super(message); - } - - public EntLibCryptoCipherIllegalIVStateException(Class i18nTargetClass, @NotNull String key) { - super(i18nTargetClass, key); - } - - public EntLibCryptoCipherIllegalIVStateException(Class i18nTargetClass, @NotNull String key, Throwable cause) { - super(i18nTargetClass, key, cause); - } - - public EntLibCryptoCipherIllegalIVStateException(Class i18nTargetClass, @NotNull String key, Throwable cause, Object... args) { - super(i18nTargetClass, key, cause, args); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/secure/crypto/EntLibCryptoCipherProcessException.java b/src/main/java/space/qu4nt/entanglementlib/exception/secure/crypto/EntLibCryptoCipherProcessException.java deleted file mode 100644 index 923bd19..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/exception/secure/crypto/EntLibCryptoCipherProcessException.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.exception.secure.crypto; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.exception.EntLibException; - -import java.io.Serial; - -public class EntLibCryptoCipherProcessException extends EntLibException { - - @Serial - private static final long serialVersionUID = 2560472028225459178L; - - public EntLibCryptoCipherProcessException(Throwable cause) { - super(cause); - } - - public EntLibCryptoCipherProcessException(String message) { - super(message); - } - - public EntLibCryptoCipherProcessException(String message, Throwable cause) { - super(message, cause); - } - - public EntLibCryptoCipherProcessException(Class i18nTargetClass, @NotNull String key) { - super(i18nTargetClass, key); - } - - public EntLibCryptoCipherProcessException(Class i18nTargetClass, @NotNull String key, Throwable cause) { - super(i18nTargetClass, key, cause); - } - - public EntLibCryptoCipherProcessException(Class i18nTargetClass, @NotNull String key, Throwable cause, Object... args) { - super(i18nTargetClass, key, cause, args); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/secure/crypto/EntLibCryptoException.java b/src/main/java/space/qu4nt/entanglementlib/exception/secure/crypto/EntLibCryptoException.java deleted file mode 100644 index 9d485e0..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/exception/secure/crypto/EntLibCryptoException.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.exception.secure.crypto; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.exception.EntLibException; - -import java.io.Serial; - -public class EntLibCryptoException extends EntLibException { - - @Serial - private static final long serialVersionUID = 7785037773312632659L; - - public EntLibCryptoException(Throwable cause) { - super(cause); - } - - public EntLibCryptoException(Class i18nTargetClass, @NotNull String key) { - super(i18nTargetClass, key); - } - - public EntLibCryptoException(Class i18nTargetClass, @NotNull String key, Throwable cause) { - super(i18nTargetClass, key, cause); - } - - public EntLibCryptoException(Class i18nTargetClass, @NotNull String key, Throwable cause, Object... args) { - super(i18nTargetClass, key, cause, args); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/secure/crypto/EntLibCryptoKEMProcessingException.java b/src/main/java/space/qu4nt/entanglementlib/exception/secure/crypto/EntLibCryptoKEMProcessingException.java deleted file mode 100644 index 712e9b5..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/exception/secure/crypto/EntLibCryptoKEMProcessingException.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.exception.secure.crypto; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.exception.EntLibException; - -import java.io.Serial; - -public class EntLibCryptoKEMProcessingException extends EntLibException { - - @Serial - private static final long serialVersionUID = 6227897663782430821L; - - public EntLibCryptoKEMProcessingException(String message) { - super(message); - } - - public EntLibCryptoKEMProcessingException(String message, Throwable cause) { - super(message, cause); - } - - public EntLibCryptoKEMProcessingException(Class i18nTargetClass, @NotNull String key) { - super(i18nTargetClass, key); - } - - public EntLibCryptoKEMProcessingException(Class i18nTargetClass, @NotNull String key, Throwable cause) { - super(i18nTargetClass, key, cause); - } - - public EntLibCryptoKEMProcessingException(Class i18nTargetClass, @NotNull String key, Throwable cause, Object... args) { - super(i18nTargetClass, key, cause, args); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/secure/crypto/EntLibCryptoSignatureProcessingException.java b/src/main/java/space/qu4nt/entanglementlib/exception/secure/crypto/EntLibCryptoSignatureProcessingException.java deleted file mode 100644 index 25cca03..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/exception/secure/crypto/EntLibCryptoSignatureProcessingException.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.exception.secure.crypto; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.exception.EntLibException; - -import java.io.Serial; - -public class EntLibCryptoSignatureProcessingException extends EntLibException { - - @Serial - private static final long serialVersionUID = 951548478208181292L; - - public EntLibCryptoSignatureProcessingException(String message) { - super(message); - } - - public EntLibCryptoSignatureProcessingException(String message, Throwable cause) { - super(message, cause); - } - - public EntLibCryptoSignatureProcessingException(Class i18nTargetClass, @NotNull String key) { - super(i18nTargetClass, key); - } - - public EntLibCryptoSignatureProcessingException(Class i18nTargetClass, @NotNull String key, Throwable cause) { - super(i18nTargetClass, key, cause); - } - - public EntLibCryptoSignatureProcessingException(Class i18nTargetClass, @NotNull String key, Throwable cause, Object... args) { - super(i18nTargetClass, key, cause, args); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/secure/crypto/EntLibCryptoStreamCipherProcessException.java b/src/main/java/space/qu4nt/entanglementlib/exception/secure/crypto/EntLibCryptoStreamCipherProcessException.java deleted file mode 100644 index 7a77f48..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/exception/secure/crypto/EntLibCryptoStreamCipherProcessException.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.exception.secure.crypto; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.exception.EntLibException; - -import java.io.Serial; - -public class EntLibCryptoStreamCipherProcessException extends EntLibException { - - @Serial - private static final long serialVersionUID = 8751428890964444674L; - - public EntLibCryptoStreamCipherProcessException(Throwable cause) { - super(cause); - } - - public EntLibCryptoStreamCipherProcessException(String message, Throwable cause) { - super(message, cause); - } - - public EntLibCryptoStreamCipherProcessException(Class i18nTargetClass, @NotNull String key) { - super(i18nTargetClass, key); - } - - public EntLibCryptoStreamCipherProcessException(Class i18nTargetClass, @NotNull String key, Throwable cause) { - super(i18nTargetClass, key, cause); - } - - public EntLibCryptoStreamCipherProcessException(Class i18nTargetClass, @NotNull String key, Throwable cause, Object... args) { - super(i18nTargetClass, key, cause, args); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/server/EntLibServerException.java b/src/main/java/space/qu4nt/entanglementlib/exception/server/EntLibServerException.java deleted file mode 100644 index b948faf..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/exception/server/EntLibServerException.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.exception.server; - -import space.qu4nt.entanglementlib.exception.EntLibException; -import space.qu4nt.entanglementlib.security.communication.session.Session; - -import java.io.Serial; - -/// 서버 관련 예외를 나타내는 클래스입니다. -/// -/// @author Q. T. Felix -/// @see Session -/// @since 1.1.0 -public class EntLibServerException extends EntLibException { - - @Serial - private static final long serialVersionUID = 9158866932313942233L; - - public EntLibServerException(String message) { - super(message); - } - - public EntLibServerException(Throwable cause) { - super(cause); - } - - public EntLibServerException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/server/EntLibServerIllegalStateException.java b/src/main/java/space/qu4nt/entanglementlib/exception/server/EntLibServerIllegalStateException.java deleted file mode 100644 index d83c307..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/exception/server/EntLibServerIllegalStateException.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.exception.server; - -import space.qu4nt.entanglementlib.exception.EntLibException; -import space.qu4nt.entanglementlib.security.communication.session.Session; - -import java.io.Serial; - -/// @author Q. T. Felix -/// @see Session -/// @since 1.1.0 -public class EntLibServerIllegalStateException extends EntLibException { - - @Serial - private static final long serialVersionUID = 709411898221020869L; - - public EntLibServerIllegalStateException(String message) { - super(message); - } - - public EntLibServerIllegalStateException(Throwable cause) { - super(cause); - } - - public EntLibServerIllegalStateException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/server/EntLibServerSecurityWarningException.java b/src/main/java/space/qu4nt/entanglementlib/exception/server/EntLibServerSecurityWarningException.java deleted file mode 100644 index 2170be4..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/exception/server/EntLibServerSecurityWarningException.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.exception.server; - -import space.qu4nt.entanglementlib.exception.EntLibException; - -import java.io.Serial; - -/// 이 예외는 서버의 보안 측면에서 큰 문제가 발생했거나, 시스템이 그렇게 예상할 때 -/// 사용됩니다. -/// -/// # Safety -/// -/// 이 예외가 발생한 경우, 발생한 문제가 얽힘 라이브러리에 의해 1차적으로 막혔다는 -/// 것을 의미합니다. 문제에 대해 방어되었지만, 서버의 보안 점검잉 긴급한 상황일 수 -/// 있습니다. -/// -/// 이 예외가 발생한다고 해서 서버가 종료되지는 않습니다. -/// -/// @author Q. T. Felix -/// @since 1.1.0 -public class EntLibServerSecurityWarningException extends EntLibServerException { - - @Serial - private static final long serialVersionUID = -9043595617089057163L; - - public EntLibServerSecurityWarningException(String type, String message) { - super("[SERVER-SECURITY-WARNING] '" + type + "' 감지, 메시지: " + message); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/session/EntLibSessionException.java b/src/main/java/space/qu4nt/entanglementlib/exception/session/EntLibSessionException.java deleted file mode 100644 index 4dc4a4b..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/exception/session/EntLibSessionException.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.exception.session; - -import space.qu4nt.entanglementlib.exception.EntLibException; -import space.qu4nt.entanglementlib.security.communication.session.Session; - -import java.io.Serial; - -/// 세션 관련 예외를 나타내는 클래스입니다. -/// -/// 세션 생성, 참여자 관리, 상태 전환 등에서 발생할 수 있는 -/// 예외 상황을 처리합니다. -/// -/// @author Q. T. Felix -/// @see Session -/// @since 1.1.0 -public class EntLibSessionException extends EntLibException { - - @Serial - private static final long serialVersionUID = -4075244968782759826L; - - public EntLibSessionException(String message) { - super(message); - } - - public EntLibSessionException(Throwable cause) { - super(cause); - } - - public EntLibSessionException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/session/EntLibSessionIllegalStateException.java b/src/main/java/space/qu4nt/entanglementlib/exception/session/EntLibSessionIllegalStateException.java deleted file mode 100644 index 6a805ab..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/exception/session/EntLibSessionIllegalStateException.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.exception.session; - -import space.qu4nt.entanglementlib.exception.EntLibException; -import space.qu4nt.entanglementlib.security.communication.session.Session; - -import java.io.Serial; - -/// 세션 관련 예외를 나타내는 클래스입니다. -/// -/// 세션 생성, 참여자 관리, 상태 전환 등에서 발생할 수 있는 -/// 예외 상황을 처리합니다. -/// -/// @author Q. T. Felix -/// @see Session -/// @since 1.1.0 -public class EntLibSessionIllegalStateException extends EntLibException { - - @Serial - private static final long serialVersionUID = 4840174130643612125L; - - public EntLibSessionIllegalStateException(String message) { - super(message); - } - - public EntLibSessionIllegalStateException(Throwable cause) { - super(cause); - } - - public EntLibSessionIllegalStateException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/util/EntLibUtilityException.java b/src/main/java/space/qu4nt/entanglementlib/exception/util/EntLibUtilityException.java deleted file mode 100644 index 3f9821c..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/exception/util/EntLibUtilityException.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.exception.util; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.exception.EntLibUncheckedException; - -import java.io.Serial; - -public class EntLibUtilityException extends EntLibUncheckedException { - - @Serial - private static final long serialVersionUID = -162263763510190731L; - - public EntLibUtilityException(Class i18nTargetClass, @NotNull String key) { - super(i18nTargetClass, key); - } - - public EntLibUtilityException(Class i18nTargetClass, @NotNull String key, Throwable cause) { - super(i18nTargetClass, key, cause); - } - - public EntLibUtilityException(Class i18nTargetClass, @NotNull String key, Throwable cause, Object... args) { - super(i18nTargetClass, key, cause, args); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/exception/util/EntLibUtilityIllegalArgumentException.java b/src/main/java/space/qu4nt/entanglementlib/exception/util/EntLibUtilityIllegalArgumentException.java deleted file mode 100644 index 005e582..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/exception/util/EntLibUtilityIllegalArgumentException.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.exception.util; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.exception.EntLibUncheckedException; - -import java.io.Serial; - -public class EntLibUtilityIllegalArgumentException extends EntLibUncheckedException { - - @Serial - private static final long serialVersionUID = -162263763510190731L; - - public EntLibUtilityIllegalArgumentException(Class i18nTargetClass, @NotNull String key) { - super(i18nTargetClass, key); - } - - public EntLibUtilityIllegalArgumentException(Class i18nTargetClass, @NotNull String key, Throwable cause) { - super(i18nTargetClass, key, cause); - } - - public EntLibUtilityIllegalArgumentException(Class i18nTargetClass, @NotNull String key, Throwable cause, Object... args) { - super(i18nTargetClass, key, cause, args); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/experimental/package-info.java b/src/main/java/space/qu4nt/entanglementlib/experimental/package-info.java deleted file mode 100644 index 37a278a..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/experimental/package-info.java +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -/// 해당 패키지는 얽힘 라이브러리 내에서 연구중이지만 사용 가능한 타입을 -/// 모아둔 패키지입니다. 해당 패키지 하위의 타입은 기본적으로 사용을 -/// 권장하지 않습니다. 사용 후 피드백을 적극적으로 받고, 픽스하기 위한 -/// 목적으로 사용되고 만들어집니다. -/// -/// @author Q. T. Felix -/// @since 1.0.0 -package space.qu4nt.entanglementlib.experimental; \ No newline at end of file diff --git a/src/main/java/space/qu4nt/entanglementlib/resource/ResourceCaller.java b/src/main/java/space/qu4nt/entanglementlib/resource/ResourceCaller.java deleted file mode 100644 index 58ed30e..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/resource/ResourceCaller.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.resource; - -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.InternalFactory; -import space.qu4nt.entanglementlib.resource.control.PublicJSONFileSystemResourceBundleControl; -import space.qu4nt.entanglementlib.resource.control.PublicYamlFileSystemResourceBundleControl; -import space.qu4nt.entanglementlib.util.io.EntFile; -import tools.jackson.core.JsonParser; -import tools.jackson.databind.ObjectMapper; -import tools.jackson.databind.ObjectReader; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Locale; -import java.util.MissingResourceException; -import java.util.ResourceBundle; - -/** - * 리소스 번들의 소스를 관리하는 클래스입니다. - * - * @author Q. T. Felix - * @since 1.0.0 - */ -@Slf4j -public final class ResourceCaller { - - /** - * 프로젝트가 사용하는 리소스 번들의 경로를 사용자 지정하고자 하는 경우에 - * 사용되는 메소드입니다. - * - * @param format 리소스 파일 포멧(확장자) - * @param customDirStringPath 리소스 디렉토리 - * @param baseName 리소스 디렉토리에 포함된 리소스 (확장자 없는) 파일 이름 - * @param streamCharset 리소스 로드에 사용할 {@link Charset} - * @return 지정된 경로의 파일로 정의된 {@link ResourceBundle} - */ - public static ResourceBundle getCustomResourceBundle(final SupportedFormat format, final String customDirStringPath, final String baseName, Charset streamCharset) { - try { - Path path = Paths.get(customDirStringPath); - Files.createDirectories(path); - } catch (IOException e) { - throw new RuntimeException(e); - } - ResourceBundle.Control ctrl = - switch (format) { - case YAML -> new PublicYamlFileSystemResourceBundleControl(customDirStringPath, streamCharset); - case JSON -> new PublicJSONFileSystemResourceBundleControl(customDirStringPath, streamCharset); - }; - ResourceBundle bundle = ResourceBundle.getBundle(baseName, Locale.getDefault(), ctrl); - // NOTE: 어째서 파일은 있고 엔트리가 없어도 MissingResourceException 예외가 발생하는 것인가? null 체크가 의미 없는 것인가...? - if (bundle == null) - throw new MissingResourceException(baseName, customDirStringPath, null); - if (bundle.getKeys().nextElement() == null) - log.warn("No keys found in {}", baseName); - return bundle; - } - - /** - * 프로젝트가 사용하는 리소스 번들의 경로를 사용자 지정된 디렉토리로 결정하고, - * 리소스 파일을 사용자 지정하고자 하는 경우에 사용되는 메소드입니다. - *

- * 디렉토리를 설정하기 위해 {@code ENTANGLEMENT_PUBLIC_DIR} 환경 변수에 사용자 지정된 - * 디렉토리 위치를 정의해야 합니다. - * - * @param baseName 리소스 디렉토리에 포함된 리소스 (확장자 없는) 파일 이름 - * @param streamCharset 리소스 로드에 사용할 {@link Charset} - * @return 지정된 경로의 파일로 정의된 {@link ResourceBundle} - */ - public static ResourceBundle getCustomResourceInPublic(final SupportedFormat format, final String baseName, Charset streamCharset) { - return getCustomResourceBundle(format, InternalFactory.envEntanglementPublicDir(), baseName, streamCharset); - } - - /** - * 프로젝트가 사용하는 리소스 번들의 경로를 사용자 지정된 디렉토리 하위에서 - * 사용자 지정하고자 하는 경우에 사용되는 메소드입니다. - *

- * 디렉토리를 설정하기 위해 {@code ENTANGLEMENT_PUBLIC_DIR} 환경 변수에 사용자 지정된 - * 디렉토리 위치를 정의해야 합니다. - * - * @param inPublicDirName 공개 디렉토리 내의 하위 디렉토리 이름 - * @param baseName 디렉토리 하위에 포함된 리소스 (확장자 없는) 파일 이름 - * @param streamCharset 리소스 로드에 사용할 {@link Charset} - * @return 지정된 경로의 파일로 정의된 {@link ResourceBundle} - */ - public static ResourceBundle getCustomResourceInPublicInnerDir(final SupportedFormat format, final String inPublicDirName, final String baseName, Charset streamCharset) { - String path = Paths.get(InternalFactory.envEntanglementPublicDir()).resolve(inPublicDirName).toString(); - return getCustomResourceBundle(format, path, baseName, streamCharset); - } - - /** - * 지정된 공개 디렉토리 하위 파일을 Jackson 라이브러리를 사용하여 역직렬화합니다. - * 이 메소드는 파일의 복합 보안 검증(트래버셜, 무결성)을 수행하지 않습니다. - * - * @param mapper 사용할 {@link ObjectMapper} 인스턴스 - * @param filename 디렉토리 하위에 포함된 역직렬화할 파일 이름 - * @param ref 역직렬화될 객체의 {@link Class} 타입 - * @param 역직렬화될 객체의 타입 - * @return 역직렬화된 객체 - * @throws IOException 읽기 작업 중 문제가 발생한 경우 - */ - public static T jacksonDeserializeInPublic(@NotNull ObjectMapper mapper, final @NotNull String filename, @NotNull Class ref) - throws IOException { - Path filePath = Paths.get(InternalFactory.envEntanglementPublicDir()).resolve(filename); - try (InputStream is = EntFile.Unchecked.openStream(filePath)) { - return deserializeWithStream(mapper, is, ref); - } - } - - /** - * 지정된 공개 디렉토리 하위 디렉토리의 파일을 Jackson 라이브러리를 사용하여 역직렬화합니다. - * 이 메소드는 파일의 복합 보안 검증(트래버셜, 무결성)을 수행하지 않습니다. - * - * @param mapper 사용할 {@link ObjectMapper} 인스턴스 - * @param inPublicDirName 공개 디렉토리 내의 하위 디렉토리 이름 - * @param filename 디렉토리 하위에 포함된 역직렬화할 파일 이름 - * @param ref 역직렬화될 객체의 {@link Class} 타입 - * @param 역직렬화될 객체의 타입 - * @return 역직렬화된 객체 - * @throws IOException 읽기 작업 중 문제가 발생한 경우 - */ - public static T jacksonDeserializeInPublicInnerDir(@NotNull ObjectMapper mapper, @NotNull String inPublicDirName, @NotNull String filename, @NotNull Class ref) - throws IOException { - Path filePath = Paths.get(InternalFactory.envEntanglementPublicDir()).resolve(inPublicDirName).resolve(filename); - try (InputStream is = EntFile.Unchecked.openStream(filePath)) { - return deserializeWithStream(mapper, is, ref); - } - } - - /** - * {@link InputStream}에서 데이터를 읽어와 Jackson 라이브러리를 사용하여 역직렬화합니다. - * 이 메소드는 스트림을 효율적으로 처리하며, 전체 내용을 메모리에 로드하지 않습니다. - * - * @param mapper 사용할 {@link ObjectMapper} 인스턴스 - * @param inputStream 역직렬화할 데이터의 입력 스트림 - * @param ref 역직렬화될 객체의 {@link Class} 타입 - * @param 역직렬화될 객체의 타입 - * @return 역직렬화된 객체 - */ - private static T deserializeWithStream(ObjectMapper mapper, InputStream inputStream, Class ref) { - ObjectReader reader = mapper.readerFor(ref); - // JsonParser가 InputStream을 소비함 (전체 로드 X) - try (JsonParser parser = mapper.createParser(inputStream)) { - return reader.readValue(parser); - } - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/resource/ResourceHandler.java b/src/main/java/space/qu4nt/entanglementlib/resource/ResourceHandler.java deleted file mode 100644 index d18c061..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/resource/ResourceHandler.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.resource; - -import lombok.Getter; -import lombok.Setter; -import org.jetbrains.annotations.NotNull; - -import java.nio.charset.Charset; -import java.util.*; - -public abstract class ResourceHandler extends ResourceBundle.Control { - - @Setter - @Getter - protected Charset streamCharset; - - private final List formats; - - public ResourceHandler(Charset streamCharset, final List formats) { - this.streamCharset = streamCharset; - this.formats = formats; - } - - @Override - public List getFormats(String baseName) { - return formats; - } - - /** - * 중첩된 Map을 평평한 키-값 구조로 변환하는 헬퍼 메소드입니다. - *

- * {@code YAML} 형식의 파일인 경우, 계층 구조를 받아 {@code .}으로 - * 구분하기 위해 사용됩니다. - * - * @param map 타겟 Map - * @param prefix 접두사 - * @return 평평한 키-값 구조 Map - */ - public Map flattenMap(Map map, String prefix) { - Map flat = new HashMap<>(); - for (Map.Entry entry : map.entrySet()) { - String key = prefix.isEmpty() ? entry.getKey() : prefix + "." + entry.getKey(); - Object value = entry.getValue(); - if (value instanceof Map) { - @SuppressWarnings("unchecked") - Map nestedMap = (Map) value; - flat.putAll(flattenMap(nestedMap, key)); - } else { - flat.put(key, value); - } - } - return flat; - } - - /** - * YAML 데이터를 위한 커스텀 {@link ResourceBundle} 구현체 클래스입니다. - * - * @author Q. T. Felix - * @since 1.0.0 - */ - public static class YamlResourceBundle extends ResourceBundle { - private final Map map; - - public YamlResourceBundle(Map map) { - this.map = map; - } - - @Override - protected Object handleGetObject(@NotNull String key) { - return map.get(key); - } - - @Override - public @NotNull Enumeration getKeys() { - return Collections.enumeration(map.keySet()); - } - } - -} diff --git a/src/main/java/space/qu4nt/entanglementlib/resource/SupportedFormat.java b/src/main/java/space/qu4nt/entanglementlib/resource/SupportedFormat.java deleted file mode 100644 index 9b36828..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/resource/SupportedFormat.java +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.resource; - -public enum SupportedFormat { - YAML, - JSON -} diff --git a/src/main/java/space/qu4nt/entanglementlib/resource/config/Configer.java b/src/main/java/space/qu4nt/entanglementlib/resource/config/Configer.java deleted file mode 100644 index de80fa0..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/resource/config/Configer.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.resource.config; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import tools.jackson.databind.JavaType; -import tools.jackson.databind.ObjectMapper; - -import java.util.Collections; -import java.util.List; -import java.util.ResourceBundle; - -/** - * 구성 파일에서 값을 읽어오는 유틸리티 클래스입니다. - * - * @author Q. T. Felix - * @since 1.0.0 - */ -public final class Configer { - - private static final ObjectMapper MAPPER = new ObjectMapper(); - - /** - * 구성에서 정수 값을 불러오는 메소드입니다. - * - * @param bundle 기반 리소스 번들 - * @param key 불러오고자 하는 값의 경로 - * @param def 키를 찾을 수 없는 경우의 반환값 - * @return 경로에 위치한 정수 값, 키를 찾을 수 없으면 전달된 기본 값 - */ - public static int getInt(final @NotNull ResourceBundle bundle, final String key, int def) { - return bundle.containsKey(key) ? (int) bundle.getObject(key) : def; - } - - /** - * 구성에서 정수 값을 불러오는 메소드입니다. - * - * @param bundle 기반 리소스 번들 - * @param key 불러오고자 하는 값의 경로 - * @return 경로에 위치한 정수 값, 키를 찾을 수 없으면 {@code -1} - */ - public static int getInt(final @NotNull ResourceBundle bundle, final String key) { - return getInt(bundle, key, -1); - } - - /** - * 구성에서 {@code double} 실수 값을 불러오는 메소드입니다. - * - * @param bundle 기반 리소스 번들 - * @param key 불러오고자 하는 값의 경로 - * @param def 키를 찾을 수 없는 경우의 반환값 - * @return 경로에 위치한 {@code double} 실수 값, 키를 찾을 수 없으면 전달된 기본 값 - */ - public static double getDouble(final @NotNull ResourceBundle bundle, final String key, double def) { - return bundle.containsKey(key) ? (double) bundle.getObject(key) : def; - } - - /** - * 구성에서 {@code double} 실수 값을 불러오는 메소드입니다. - * - * @param bundle 기반 리소스 번들 - * @param key 불러오고자 하는 값의 경로 - * @return 경로에 위치한 {@code double} 실수 값, 키를 찾을 수 없으면 {@code -1} - */ - public static double getDouble(final @NotNull ResourceBundle bundle, final String key) { - return getDouble(bundle, key, -1); - } - - /** - * 구성에서 {@code float} 실수 값을 불러오는 메소드입니다. - * - * @param bundle 기반 리소스 번들 - * @param key 불러오고자 하는 값의 경로 - * @param def 키를 찾을 수 없는 경우의 반환값 - * @return 경로에 위치한 {@code float} 실수 값, 키를 찾을 수 없으면 전달된 기본 값 - */ - public static float getFloat(final @NotNull ResourceBundle bundle, final String key, float def) { - return bundle.containsKey(key) ? (float) bundle.getObject(key) : def; - } - - /** - * 구성에서 {@code float} 실수 값을 불러오는 메소드입니다. - * - * @param bundle 기반 리소스 번들 - * @param key 불러오고자 하는 값의 경로 - * @return 경로에 위치한 {@code float} 실수 값, 키를 찾을 수 없으면 {@code -1} - */ - public static float getFloat(final @NotNull ResourceBundle bundle, final String key) { - return getFloat(bundle, key, -1); - } - - /** - * 구성에서 논리 값을 불러오는 메소드입니다. - * - * @param bundle 기반 리소스 번들 - * @param key 불러오고자 하는 값의 경로 - * @return 경로에 위치한 논리 값, 키를 찾을 수 없으면 false - */ - public static boolean getBoolean(final @NotNull ResourceBundle bundle, final String key) { - return bundle.containsKey(key) && (boolean) bundle.getObject(key); - } - - /** - * 구성에서 문자열 값을 불러오는 메소드입니다. - * - * @param bundle 기반 리소스 번들 - * @param key 불러오고자 하는 값의 경로 - * @param def 키를 찾을 수 없는 경우의 반환값 - * @return 경로에 위치한 문자열 값, 키를 찾을 수 없으면 전달된 기본 값 - */ - @Nullable - public static String getString(final @NotNull ResourceBundle bundle, final String key, String def) { - return bundle.containsKey(key) ? bundle.getString(key) : def; - } - - /** - * 구성에서 문자열 값을 불러오는 메소드입니다. - * - * @param bundle 기반 리소스 번들 - * @param key 불러오고자 하는 값의 경로 - * @return 경로에 위치한 문자열 값, 키를 찾을 수 없으면 {@code null} - */ - @Nullable - public static String getString(final @NotNull ResourceBundle bundle, final String key) { - return getString(bundle, key, null); - } - - /** - * 구성에서 오브젝트 배열을 불러오는 메소드입니다. - * 사용되는 타입은 직접 지정해야 합니다. - * - * @param bundle 기반 리소스 번들 - * @param key 불러오고자 하는 값의 경로 - * @param elementClass 타입 지정 - * @param 사용자 정의 타입 - * @return 경로에 위치한 사용자 지정 타입 배열 - */ - public static List getObjectList(final @NotNull ResourceBundle bundle, final String key, @NotNull Class elementClass) { - String json = bundle.getString(key); - if (json.trim().isEmpty()) - return Collections.emptyList(); - JavaType type = MAPPER.getTypeFactory().constructCollectionType(List.class, elementClass); - return MAPPER.readValue(json, type); - } - - /** - * 구성에서 {@code Enum} 타입 값을 불러오는 메소드입니다. - * - * @param bundle 기반 리소스 번들 - * @param key 불러오고자 하는 값의 경로 - * @param elementClass Enum 클래스 타입 - * @param def 키를 찾을 수 없거나 값이 유효하지 않은 경우의 반환값 - * @param Enum 타입 - * @return 경로에 위치한 Enum 값, 키를 찾을 수 없거나 유효하지 않으면 전달된 기본 값 - * @throws IllegalArgumentException 기본 값이 {@code null}이고 유효하지 않은 Enum 값일 경우 발생 - */ - @Nullable - public static > T getEnumType(final @NotNull ResourceBundle bundle, - final String key, - @NotNull Class elementClass, - final @Nullable T def) { - if (bundle.containsKey(key)) { - String enumName = bundle.getString(key); - try { - return Enum.valueOf(elementClass, enumName); - } catch (IllegalArgumentException e) { - if (def == null) - throw new IllegalArgumentException("Invalid enum value for key: " + key + ", value: " + enumName, e); - return def; - } - } - return def; - } - - /** - * 구성에서 {@code Enum} 타입 값을 불러오는 메소드입니다. - * - * @param bundle 기반 리소스 번들 - * @param key 불러오고자 하는 값의 경로 - * @param elementClass Enum 클래스 타입 - * @param Enum 타입 - * @return 경로에 위치한 Enum 값, 키를 찾을 수 없거나 유효하지 않으면 {@code null} - */ - @Nullable - public static > T getEnumType(final @NotNull ResourceBundle bundle, - final String key, - @NotNull Class elementClass) { - return getEnumType(bundle, key, elementClass, null); - } - -} diff --git a/src/main/java/space/qu4nt/entanglementlib/resource/config/ConfigerInstanceBased.java b/src/main/java/space/qu4nt/entanglementlib/resource/config/ConfigerInstanceBased.java deleted file mode 100644 index f06e3fb..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/resource/config/ConfigerInstanceBased.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.resource.config; - -import lombok.Getter; -import lombok.Setter; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; -import java.util.ResourceBundle; - -/** - * 인스턴스 기반으로 구성 파일에서 값을 읽어오는 클래스입니다. - *

- * 이 클래스를 상속하여 구성 리소스 번들을 등록하고 자유롭게 호출하여 - * 사용할 수도 있습니다 - * - * @author Q. T. Felix - * @since 1.0.0 - */ -@Getter -@Setter -public class ConfigerInstanceBased { - - private ResourceBundle bundle; - - /** - * {@link ConfigerInstanceBased} 객체를 생성합니다. - * - * @param bundle 리소스 번들 - */ - protected ConfigerInstanceBased(@NotNull ResourceBundle bundle) { - this.bundle = bundle; - } - - /** - * 주어진 리소스 번들로 {@link ConfigerInstanceBased} 인스턴스를 생성합니다. - * - * @param bundle 리소스 번들 - * @return ConfigerInstanceBased 인스턴스 - */ - public static ConfigerInstanceBased of(@NotNull ResourceBundle bundle) { - return new ConfigerInstanceBased(bundle); - } - - /** - * 구성에서 정수 값을 불러오는 메소드입니다. - * - * @param key 불러오고자 하는 값의 경로 - * @return 경로에 위치한 정수 값 - */ - public int getInt(String key) { - return Configer.getInt(bundle, key); - } - - /** - * 구성에서 정수 값을 불러오는 메소드입니다. - * - * @param key 불러오고자 하는 값의 경로 - * @param def 키를 찾을 수 없는 경우의 반환값 - * @return 경로에 위치한 정수 값, 키를 찾을 수 없으면 전달된 기본 값 - */ - public int getInt(String key, int def) { - return Configer.getInt(bundle, key, def); - } - - /** - * 구성에서 {@code double} 실수 값을 불러오는 메소드입니다. - * - * @param key 불러오고자 하는 값의 경로 - * @return 경로에 위치한 {@code double} 실수 값 - */ - public double getDouble(String key) { - return Configer.getDouble(bundle, key); - } - - /** - * 구성에서 {@code double} 실수 값을 불러오는 메소드입니다. - * - * @param key 불러오고자 하는 값의 경로 - * @param def 키를 찾을 수 없는 경우의 반환값 - * @return 경로에 위치한 {@code double} 실수 값, 키를 찾을 수 없으면 전달된 기본 값 - */ - public double getDouble(String key, double def) { - return Configer.getDouble(bundle, key, def); - } - - /** - * 구성에서 {@code float} 실수 값을 불러오는 메소드입니다. - * - * @param key 불러오고자 하는 값의 경로 - * @return 경로에 위치한 {@code float} 실수 값 - */ - public float getFloat(String key) { - return Configer.getFloat(bundle, key); - } - - /** - * 구성에서 {@code float} 실수 값을 불러오는 메소드입니다. - * - * @param key 불러오고자 하는 값의 경로 - * @param def 키를 찾을 수 없는 경우의 반환값 - * @return 경로에 위치한 {@code float} 실수 값, 키를 찾을 수 없으면 전달된 기본 값 - */ - public float getFloat(String key, float def) { - return Configer.getFloat(bundle, key, def); - } - - /** - * 구성에서 논리 값을 불러오는 메소드입니다. - * - * @param key 불러오고자 하는 값의 경로 - * @return 경로에 위치한 논리 값 - */ - public boolean getBoolean(String key) { - return Configer.getBoolean(bundle, key); - } - - /** - * 구성에서 문자열 값을 불러오는 메소드입니다. - * - * @param key 불러오고자 하는 값의 경로 - * @param def 키를 찾을 수 없는 경우의 반환값 - * @return 경로에 위치한 문자열 값, 키를 찾을 수 없으면 전달된 기본 값 - */ - public String getString(String key, String def) { - return Configer.getString(bundle, key, def); - } - - /** - * 구성에서 문자열 값을 불러오는 메소드입니다. - * - * @param key 불러오고자 하는 값의 경로 - * @return 경로에 위치한 문자열 값 - */ - public String getString(String key) { - return Configer.getString(bundle, key); - } - - /** - * 구성에서 문자열 리스트를 불러오는 메소드입니다. - * - * @param key 불러오고자 하는 값의 경로 - * @return 경로에 위치한 문자열 리스트 - */ - public List getStringList(String key) { - return Configer.getObjectList(bundle, key, String.class); - } - - /** - * 구성에서 오브젝트 리스트를 불러오는 메소드입니다. - * - * @param key 불러오고자 하는 값의 경로 - * @param elementClass 요소의 클래스 타입 - * @param 요소의 타입 - * @return 경로에 위치한 오브젝트 리스트 - */ - public List getObjectList(String key, Class elementClass) { - return Configer.getObjectList(bundle, key, elementClass); - } - - /** - * 구성에서 {@code Enum} 타입 값을 불러오는 메소드입니다. - * - * @param key 불러오고자 하는 값의 경로 - * @param elementClass Enum 클래스 타입 - * @param def 키를 찾을 수 없거나 값이 유효하지 않은 경우의 반환값 - * @param Enum 타입 - * @return 경로에 위치한 Enum 값, 키를 찾을 수 없거나 유효하지 않으면 전달된 기본 값 - */ - public > T getEnumType(String key, Class elementClass, final @Nullable T def) { - return Configer.getEnumType(bundle, key, elementClass, def); - } - - /** - * 구성에서 Enum 타입 값을 불러오는 메소드입니다. - * - * @param key 불러오고자 하는 값의 경로 - * @param elementClass Enum 클래스 타입 - * @param Enum 타입 - * @return 경로에 위치한 Enum 값 - */ - public > T getEnumType(String key, Class elementClass) { - return Configer.getEnumType(bundle, key, elementClass); - } - -} diff --git a/src/main/java/space/qu4nt/entanglementlib/resource/config/PublicConfigTree.java b/src/main/java/space/qu4nt/entanglementlib/resource/config/PublicConfigTree.java deleted file mode 100644 index 3715999..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/resource/config/PublicConfigTree.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.resource.config; - -import com.fasterxml.jackson.annotation.JsonInclude; -import lombok.*; - -/** - * {@code public} 디렉토리에 정의된 구성 파일 {@code configuration.json}을 - * 역/직렬화 하기 위해 사용되는 클래스입니다. - * - * @author Q. T. Felix - * @since 1.0.0 - */ -@Getter -@Setter -@ToString -@NoArgsConstructor -@AllArgsConstructor -@JsonInclude(JsonInclude.Include.NON_NULL) -public final class PublicConfigTree { - - private String language; - private Secure secure; - - @Getter - @Setter - @ToString - @NoArgsConstructor - @AllArgsConstructor - @JsonInclude(JsonInclude.Include.NON_NULL) - public static final class Secure { - private boolean enabledExperimental; - private TLS tls; - - @Getter - @Setter - @ToString - @NoArgsConstructor - @AllArgsConstructor - @JsonInclude(JsonInclude.Include.NON_NULL) - public static final class TLS { - private String protocol; - private Cert cert; - private int maxHandshakeMessageSize; - - @Getter - @Setter - @ToString - @NoArgsConstructor - @AllArgsConstructor - @JsonInclude(JsonInclude.Include.NON_NULL) - public static final class Cert { - private boolean enabledPQC; - private String root; - private String server; - } - } - } - -} diff --git a/src/main/java/space/qu4nt/entanglementlib/resource/config/PublicConfiguration.java b/src/main/java/space/qu4nt/entanglementlib/resource/config/PublicConfiguration.java deleted file mode 100644 index c0ac515..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/resource/config/PublicConfiguration.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.resource.config; - -import lombok.Getter; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import space.qu4nt.entanglementlib.resource.ResourceCaller; -import space.qu4nt.entanglementlib.resource.language.SupportedLanguage; -import space.qu4nt.entanglementlib.security.EntLibParameterSpec; -import tools.jackson.databind.ObjectMapper; - -import java.io.IOException; -import java.util.Objects; - -/** - * {@code public} 디렉토리에 정의된 구성 파일 {@code configuration.json}을 - * 호출하는 클래스입니다. - *

- * 이 클래스는 {@link space.qu4nt.entanglementlib.InternalFactory}에서 - * 정적 선언되고 할당값을 변경하지 않습니다. - * - * @author Q. T. Felix - * @since 1.0.0 - */ -@Slf4j -@Getter -@Setter -public final class PublicConfiguration { - - private PublicConfigTree publicConfigTree; - - private SupportedLanguage language; - private boolean isEnabledExperimental; - private String tlsProtocol; - private boolean enabledPQC; - private EntLibParameterSpec tlsRootCACertAlgorithm; - private EntLibParameterSpec tlsServerCertAlgorithm; - private int tlsMaxHandshakeMessageSize; - - /** - * Jackson 라이브러리를 사용하여 얽힘 라이브러리의 구성 파일을 역직렬화한 후 - * 전역 변수에 할당하는 생성자입니다. - * - * @param mapper 역직렬화에 사용될 매퍼 - */ - public PublicConfiguration(final @NotNull ObjectMapper mapper) { - try { - this.publicConfigTree = ResourceCaller.jacksonDeserializeInPublic( - mapper, - "configuration.json", - PublicConfigTree.class - ); - } catch (IOException e) { - throw new RuntimeException("역직렬화 예외", e); - } - - validation(); - } - - private void validation() { - Objects.requireNonNull(publicConfigTree, "public config"); - - // 언어 기본: ko_KR - try { - this.language = SupportedLanguage.valueOf(this.publicConfigTree.getLanguage()); - } catch (IllegalArgumentException e) { - this.language = SupportedLanguage.ko_KR; - } - - // 데모 로직 활성화 여부 - this.isEnabledExperimental = this.publicConfigTree.getSecure().isEnabledExperimental(); - - // tls protocol 기본: TLSv1.3 - @Nullable String tlsProtocol = this.publicConfigTree.getSecure().getTls().getProtocol(); - if (isNullOrEmpty(tlsProtocol)) { - this.tlsProtocol = "TLSv1.3"; - } else if (tlsProtocol.trim().equalsIgnoreCase("TLSv1.1")) { - log.warn("The TLSv1.1 protocol is not available in the EntanglementLib. Therefore, I set the protocol to the default value of 'TLSv1.3'."); - this.tlsProtocol = "TLSv1.3"; - } else { - this.tlsProtocol = tlsProtocol.trim(); - } - - // 루트 및 서버 인증서 생성 알고리즘 검증 - this.enabledPQC = this.publicConfigTree.getSecure().getTls().getCert().isEnabledPQC(); - // PQC = true인 경우 사용 가능한 알고리즘은 mldsa, slhdsa밖에 없음. 이 중에 찾으면 됌 - @Nullable String rootAlg = this.publicConfigTree.getSecure().getTls().getCert().getRoot(); - if (isNullOrEmpty(rootAlg)) { -// this.tlsRootCACertAlgorithm = SLHDSAType.SLH_DSA_SHA2_256s; - } else if (enabledPQC && (rootAlg.contains("slh-dsa"))) { -// SLHDSAType.fromName(StringUtil.toLowerCase(rootAlg)) -// .ifPresent(slhdsaType -> this.tlsRootCACertAlgorithm = slhdsaType); - } else if (enabledPQC && (rootAlg.contains("ml-dsa"))) { -// MLDSAType.fromName(StringUtil.toLowerCase(rootAlg)) -// .ifPresent(mldsaType -> this.tlsRootCACertAlgorithm = mldsaType); - } else { - // TODO: 고전 알고리즘 로직 - } - - @Nullable String servAlg = this.publicConfigTree.getSecure().getTls().getCert().getServer(); - if (isNullOrEmpty(servAlg)) { -// this.tlsServerCertAlgorithm = MLDSAType.ML_DSA_87; - } else if (enabledPQC && (servAlg.contains("slh-dsa"))) { -// SLHDSAType.fromName(StringUtil.toLowerCase(servAlg)) -// .ifPresent(slhdsaType -> this.tlsServerCertAlgorithm = slhdsaType); - } else if (enabledPQC && (servAlg.contains("ml-dsa"))) { -// MLDSAType.fromName(StringUtil.toLowerCase(servAlg)) -// .ifPresent(mldsaType -> this.tlsServerCertAlgorithm = mldsaType); - } else { - // TODO: 고전 알고리즘 로직 - } - - // 핸드셰이크 메시지 사이즈 기본: 131072 - int size = this.publicConfigTree.getSecure().getTls().getMaxHandshakeMessageSize(); - if (size < 1 || size > Integer.MAX_VALUE) { - this.tlsMaxHandshakeMessageSize = 131072; - } else { - this.tlsMaxHandshakeMessageSize = size; - } - } - - @Override - public String toString() { - return """ - PublicConfiguration: { - "language": "%s", - "secure": { - "enabledExperimental": %b - } - }""".formatted( - language.name(), - isEnabledExperimental); - } - - private boolean isNullOrEmpty(final String value) { - return value == null || value.isEmpty(); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/resource/control/PublicJSONFileSystemResourceBundleControl.java b/src/main/java/space/qu4nt/entanglementlib/resource/control/PublicJSONFileSystemResourceBundleControl.java deleted file mode 100644 index 94ea15d..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/resource/control/PublicJSONFileSystemResourceBundleControl.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.resource.control; - -import lombok.Getter; -import lombok.Setter; -import space.qu4nt.entanglementlib.InternalFactory; -import space.qu4nt.entanglementlib.resource.ResourceHandler; -import tools.jackson.core.type.TypeReference; -import tools.jackson.databind.ObjectMapper; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.Charset; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.ResourceBundle; - -@Getter -@Setter -public class PublicJSONFileSystemResourceBundleControl extends ResourceHandler { - - private String customDirStringPath; - - public PublicJSONFileSystemResourceBundleControl(String customDirStringPath, Charset streamCharset) { - super(streamCharset, List.of("json")); - this.customDirStringPath = customDirStringPath == null ? InternalFactory.envEntanglementPublicDir() : customDirStringPath; - } - - public PublicJSONFileSystemResourceBundleControl(Charset streamCharset) { - this(null, streamCharset); - } - - @Override - public ResourceBundle newBundle(String baseName, - Locale locale, - String format, - ClassLoader loader, - boolean reload) - throws IllegalAccessException, InstantiationException, IOException { - if (!format.equals("json")) - return super.newBundle(baseName, locale, format, loader, reload); - - String resourceName = baseName + ".json"; - final Path filePath = Paths.get(customDirStringPath + "/" + resourceName); - try (InputStream stream = Files.newInputStream(filePath); - InputStreamReader reader = new InputStreamReader(stream, streamCharset)) { - ObjectMapper objectMapper = new ObjectMapper(); - Map map = objectMapper.readValue(reader, new TypeReference<>() { - }); - Map flatMap = flattenMap(map, ""); - return new YamlResourceBundle(flatMap); - } catch (IOException e) { - return null; - } - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/resource/control/PublicYamlFileSystemResourceBundleControl.java b/src/main/java/space/qu4nt/entanglementlib/resource/control/PublicYamlFileSystemResourceBundleControl.java deleted file mode 100644 index 775ca1a..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/resource/control/PublicYamlFileSystemResourceBundleControl.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.resource.control; - -import lombok.Getter; -import lombok.Setter; -import org.yaml.snakeyaml.Yaml; -import space.qu4nt.entanglementlib.InternalFactory; -import space.qu4nt.entanglementlib.resource.ResourceHandler; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.Charset; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.ResourceBundle; - -@Getter -@Setter -public class PublicYamlFileSystemResourceBundleControl extends ResourceHandler { - - private String customDirStringPath; - - public PublicYamlFileSystemResourceBundleControl(String customDirStringPath, Charset streamCharset) { - super(streamCharset, List.of("yaml")); - this.customDirStringPath = customDirStringPath == null ? InternalFactory.envEntanglementPublicDir() : customDirStringPath; - } - - public PublicYamlFileSystemResourceBundleControl(Charset streamCharset) { - this(null, streamCharset); - } - - @Override - public ResourceBundle newBundle(String baseName, - Locale locale, - String format, - ClassLoader loader, - boolean reload) - throws IllegalAccessException, InstantiationException, IOException { - if (!format.equals("yaml")) - return super.newBundle(baseName, locale, format, loader, reload); - - String resourceName = baseName + ".yml"; - final Path filePath = Paths.get(customDirStringPath + "/" + resourceName); - try (InputStream stream = Files.newInputStream(filePath); - InputStreamReader reader = new InputStreamReader(stream, streamCharset)) { - Yaml yaml = new Yaml(); - Map map = yaml.load(reader); - Map flatMap = flattenMap(map, ""); - return new YamlResourceBundle(flatMap); - } catch (IOException e) { - return null; - } - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/resource/language/Language.java b/src/main/java/space/qu4nt/entanglementlib/resource/language/Language.java deleted file mode 100644 index 2179a37..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/resource/language/Language.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.resource.language; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.InternalFactory; -import space.qu4nt.entanglementlib.resource.ResourceCaller; -import space.qu4nt.entanglementlib.resource.SupportedFormat; - -import java.nio.charset.StandardCharsets; -import java.util.ResourceBundle; - -import static space.qu4nt.entanglementlib.util.StringUtil.placeholderFormat; - -/** - * 다양한 국적의 언어를 간편하게 사용하기 위한 클래스입니다. - *

- * 언어 파일은 멀티 모듈 프로젝트가 아닌 경우 다음의 형식으로 저장됩니다. - *

- * class-SomeClass:
- *   msg-field: "Hello, World!"
- *   ...
- * 
- * 멀티 모듈 프로젝트의 경우 다음의 형식으로 저장됩니다. - *
- * module-a:
- *   class-SomeClass:
- *     msg-field: "Hello, Multi-World!"
- * 
- * 이 경우 키 호출 양식을 주의하세요. - *

- * 언어 파일은 {@code .yml} 또는 {@code .yaml} 형식으로 저장되며, - * {@code messages_} 이름을 유지하세요. locale 플레이스 홀더는 - * ISO 3166-1 에 따른 국가 코드, - * ISO 639-1 - * 에 따른 언어 코드 양식을 준수하세요. - * - * @author Q. T. Felix - * @since 1.0.0 - */ -public final class Language { - - // MSG - - /** - * 공용 리소스에서 입력받은 언어의 메시지 파일 속 메시지 키에 할당된 메시지를 - * 반환하는 메소드입니다. - * - * @param language 사용 가능한 언어 중 가져오고자 하는 언어 {@link SupportedLanguage} - * @param key 메시지 파일 속 참조할 메시지의 키 - * @param def 메시지 파일 속 참조할 메시지의 키가 존재하지 않은 경우 반환할 메시지 - * @return 할당된 메시지 - */ - public static String msg(final SupportedLanguage language, String key, @NotNull String def) { - final ResourceBundle bundle = ResourceCaller - .getCustomResourceInPublicInnerDir(SupportedFormat.YAML, "lang", language.getFilename(), StandardCharsets.UTF_8); - return msg(bundle, key, def); - } - - /** - * 공용 리소스에서 입력받은 언어의 메시지 파일 속 메시지 키에 할당된 메시지를 - * 반환하는 메소드입니다. 키를 찾을 수 없는 경우, 키 경로가 표시됩니다. - * - * @param language 사용 가능한 언어 중 가져오고자 하는 언어 {@link SupportedLanguage} - * @param key 메시지 파일 속 참조할 메시지의 키 - * @return 할당된 메시지, 찾을 수 없는 경우 키 경로 문자열 - */ - public static String msg(final SupportedLanguage language, String key) { - return msg(language, key, key); - } - - /** - * 공용 리소스에서 설정된 기본 언어의 메시지 파일 속 메시지 키에 할당된 메시지를 - * 반환하는 메소드입니다. 만약 기본 언어를 세팅하지 않은 경우 {@code ENTANGLEMENT_DEFAULT_LANG} - * 환경 변수에 할당된 언어를 불러옵니다. - * - * @param key 메시지 파일 속 참조할 메시지의 키 - * @param def 메시지 파일 속 참조할 메시지의 키가 존재하지 않은 경우 반환할 메시지 - * @return 할당된 메시지 - */ - public static String msg(@NotNull String key, @NotNull String def) { - return msg(InternalFactory.getPublicConfig().getLanguage(), - key, def); - } - - /** - * 공용 리소스에서 설정된 기본 언어의 메시지 파일 속 메시지 키에 할당된 메시지를 - * 반환하는 메소드입니다. 만약 기본 언어를 세팅하지 않은 경우 {@code ENTANGLEMENT_DEFAULT_LANG} - * 환경 변수에 할당된 언어를 불러옵니다. 키를 찾을 수 없는 경우, 키 경로가 표시됩니다. - * - * @param key 메시지 파일 속 참조할 메시지의 키 - * @return 할당된 메시지 - */ - public static String msg(@NotNull String key) { - return msg(InternalFactory.getPublicConfig().getLanguage(), - key, key); - } - - /** - * 입력받은 리소스 번들에서 메시지 키에 할당된 메시지를 반환하는 메소드입니다. - * - * @param bundle 리소스 번들 - * @param key 메시지 파일 속 참조할 메시지의 키 - * @param def 메시지 파일 속 참조할 메시지의 키가 존재하지 않은 경우 반환할 메시지 - * @return 할당된 메시지 - */ - public static String msg(@NotNull ResourceBundle bundle, String key, @NotNull String def) { - if (bundle.containsKey(key)) - return bundle.getString(key); - return def; - } - - /** - * 입력받은 리소스 번들에서 메시지 키에 할당된 메시지를 반환하는 메소드입니다. - * 키를 찾을 수 없는 경우, 키 경로가 표시됩니다. - * - * @param bundle 리소스 번들 - * @param key 메시지 파일 속 참조할 메시지의 키 - * @return 할당된 메시지, 찾을 수 없는 경우 키 경로 문자열 - */ - public static String msg(@NotNull ResourceBundle bundle, String key) { - return msg(bundle, key, key); - } - - // ARGS - - /** - * 공용 리소스에서 입력받은 언어의 메시지 파일 속 메시지 키에 할당된 메시지(raw)를 - * 호출하여 플레이스홀더를 변환하여 반환하는 메소드입니다. - * - * @param language 사용 가능한 언어 중 가져오고자 하는 언어 {@link SupportedLanguage} - * @param key 메시지 파일 속 참조할 메시지의 키 - * @param def 메시지 파일 속 참조할 메시지의 키가 존재하지 않은 경우 반환할 메시지 - * @param args 플레이스홀더 변경 인자 - * @return 할당 후 변환된 메시지 - */ - public static String args(final SupportedLanguage language, String key, @NotNull String def, Object... args) { - // 기존 메소드를 호출하여 raw 메시지 가져오기 - String message = msg(language, key, def); - if (args == null || args.length == 0) - return message; // 인자가 없으면 그대로 반환 - return placeholderFormat(message, args); - } - - /** - * 공용 리소스에서 입력받은 언어의 메시지 파일 속 메시지 키에 할당된 메시지(raw)를 - * 호출하여 플레이스홀더를 변환하여 반환하는 메소드입니다. 키를 찾을 수 없는 경우, - * 키 경로가 표시됩니다. - * - * @param language 사용 가능한 언어 중 가져오고자 하는 언어 {@link SupportedLanguage} - * @param key 메시지 파일 속 참조할 메시지의 키 - * @param args 플레이스홀더 변경 인자 - * @return 할당 후 변환된 메시지, 키를 찾을 수 없는 경우 키 경로 문자열 - */ - public static String args(final SupportedLanguage language, String key, Object... args) { - return args(language, key, key, args); - } - - /** - * 공용 리소스에서 입력받은 언어의 메시지 파일 속 메시지 키에 할당된 메시지(raw)를 호출하여 플레이스홀더를 - * 변환하여 반환하는 메소드입니다. 만약 기본 언어를 세팅하지 않은 경우 {@code ENTANGLEMENT_DEFAULT_LANG} - * 환경 변수에 할당된 언어를 불러옵니다. - * - * @param key 메시지 파일 속 참조할 메시지의 키 - * @param def 메시지 파일 속 참조할 메시지의 키가 존재하지 않은 경우 반환할 메시지 - * @param args 플레이스홀더 변경 인자 - * @return 할당된 메시지 - */ - public static String args(@NotNull String key, @NotNull String def, Object... args) { - return args(InternalFactory.getPublicConfig().getLanguage(), - key, def, args); - } - - /** - * 공용 리소스에서 입력받은 언어의 메시지 파일 속 메시지 키에 할당된 메시지(raw)를 호출하여 플레이스홀더를 - * 변환하여 반환하는 메소드입니다. 만약 기본 언어를 세팅하지 않은 경우 {@code ENTANGLEMENT_DEFAULT_LANG} - * 환경 변수에 할당된 언어를 불러옵니다. 키를 찾을 수 없는 경우, 키 경로가 표시됩니다. - * - * @param key 메시지 파일 속 참조할 메시지의 키 - * @param args 플레이스홀더 변경 인자 - * @return 할당된 메시지 - */ - public static String args(@NotNull String key, Object... args) { - return args(InternalFactory.getPublicConfig().getLanguage(), - key, args); - } - - /** - * 입력받은 리소스 번들에서 메시지 키에 할당된 메시지(raw)를 호출하여 플레이스홀더를 - * 변환하여 반환하는 메소드입니다. - * - * @param bundle 리소스 번들 - * @param key 메시지 파일 속 참조할 메시지의 키 - * @param def 메시지 파일 속 참조할 메시지의 키가 존재하지 않은 경우 반환할 메시지 - * @param args 플레이스홀더 변경 인자 - * @return 할당 후 변환된 메시지 - */ - public static String args(@NotNull ResourceBundle bundle, String key, @NotNull String def, Object... args) { - String message = msg(bundle, key, def); - if (args == null || args.length == 0) - return message; - return placeholderFormat(message, args); - } - - /** - * 입력받은 리소스 번들에서 메시지 키에 할당된 메시지(raw)를 호출하여 플레이스홀더를 - * 변환하여 반환하는 메소드입니다. 키를 찾을 수 없는 경우, 키 경로가 표시됩니다. - * - * @param bundle 리소스 번들 - * @param key 메시지 파일 속 참조할 메시지의 키 - * @param args 플레이스홀더 변경 인자 - * @return 할당 후 변환된 메시지, 키를 찾을 수 없는 경우 키 경로 문자열 - */ - public static String args(@NotNull ResourceBundle bundle, String key, Object... args) { - return args(bundle, key, key, args); - } - - // THROW - - /** - * 공용 리소스에서 입력받은 언어의 메시지 파일 속 메시지 키에 할당된 메시지(raw)를 호출하여 예외와 - * 플레이스 홀더를 변환하여 반환하는 메소드입니다. - * - * @param language 사용 가능한 언어 중 가져오고자 하는 언어 {@link SupportedLanguage} - * @param key 메시지 파일 속 참조할 메시지의 키 - * @param def 메시지 파일 속 참조할 메시지의 키가 존재하지 않은 경우 반환할 메시지 - * @param args 플레이스홀더 변경 인자 - * @return 할당 후 변환된 메시지 - */ - public static String thr(final SupportedLanguage language, String key, @NotNull String def, @NotNull Throwable cause, Object... args) { - String message = args(language, key, def, args); - // 예외 메시지 추가 - if (cause != null) { - String causeMessage = cause.getMessage(); - if (causeMessage == null) { - causeMessage = "unknown error"; - } - message += " : " + causeMessage; - } - return message; - } - - /** - * 공용 리소스에서 입력받은 언어의 메시지 파일 속 메시지 키에 할당된 메시지(raw)를 호출하여 예외와 - * 플레이스 홀더를 변환하여 반환하는 메소드입니다. 키를 찾을 수 없는 경우, 키 경로가 표시됩니다. - * - * @param language 사용 가능한 언어 중 가져오고자 하는 언어 {@link SupportedLanguage} - * @param key 메시지 파일 속 참조할 메시지의 키 - * @param args 플레이스홀더 변경 인자 - * @return 할당 후 변환된 메시지, 키를 찾을 수 없는 경우 키 경로 문자열 - */ - public static String thr(final SupportedLanguage language, String key, @NotNull Throwable cause, Object... args) { - return thr(language, key, key, cause, args); - } - - /** - * 공용 리소스에서 입력받은 언어의 메시지 파일 속 메시지 키에 할당된 메시지(raw)를 호출하여 예외와 - * 플레이스 홀더를 변환하여 반환하는 메소드입니다. 만약 기본 언어를 세팅하지 않은 경우 {@code ENTANGLEMENT_DEFAULT_LANG} - * 환경 변수에 할당된 언어를 불러옵니다. - * - * @param key 메시지 파일 속 참조할 메시지의 키 - * @param def 메시지 파일 속 참조할 메시지의 키가 존재하지 않은 경우 반환할 메시지 - * @param args 플레이스홀더 변경 인자 - * @return 할당된 메시지 - */ - public static String thr(@NotNull String key, @NotNull String def, @NotNull Throwable cause, Object... args) { - return thr(InternalFactory.getPublicConfig().getLanguage(), - key, def, cause, args); - } - - /** - * 공용 리소스에서 입력받은 언어의 메시지 파일 속 메시지 키에 할당된 메시지(raw)를 호출하여 예외와 - * 플레이스 홀더를 변환하여 반환하는 메소드입니다. 만약 기본 언어를 세팅하지 않은 경우 {@code ENTANGLEMENT_DEFAULT_LANG} - * 환경 변수에 할당된 언어를 불러옵니다. 키를 찾을 수 없는 경우, 키 경로가 표시됩니다. - * - * @param key 메시지 파일 속 참조할 메시지의 키 - * @param args 플레이스홀더 변경 인자 - * @return 할당된 메시지 - */ - public static String thr(@NotNull String key, @NotNull Throwable cause, Object... args) { - return thr(InternalFactory.getPublicConfig().getLanguage(), - key, cause, args); - } - - /** - * 입력받은 리소스 번들에서 메시지 키에 할당된 메시지(raw)를 호출하여 예외와 - * 플레이스 홀더를 변환하여 반환하는 메소드입니다. - * - * @param bundle 리소스 번들 - * @param key 메시지 파일 속 참조할 메시지의 키 - * @param def 메시지 파일 속 참조할 메시지의 키가 존재하지 않은 경우 반환할 메시지 - * @param cause 예외 - * @param args 플레이스홀더 변경 인자 - * @return 할당 후 변환된 메시지 - */ - public static String thr(@NotNull ResourceBundle bundle, String key, @NotNull String def, @NotNull Throwable cause, Object... args) { - String message = args(bundle, key, def, args); - // 예외 메시지 추가 - if (cause != null) { - String causeMessage = cause.getMessage(); - if (causeMessage == null) { - causeMessage = "unknown error"; - } - message += " : " + causeMessage; - } - return message; - } - - /** - * 입력받은 리소스 번들에서 메시지 키에 할당된 메시지(raw)를 호출하여 예외와 - * 플레이스 홀더를 변환하여 반환하는 메소드입니다. 키를 찾을 수 없는 경우, 키 경로가 표시됩니다. - * - * @param bundle 리소스 번들 - * @param key 메시지 파일 속 참조할 메시지의 키 - * @param cause 예외 - * @param args 플레이스홀더 변경 인자 - * @return 할당 후 변환된 메시지, 키를 찾을 수 없는 경우 키 경로 문자열 - */ - public static String thr(@NotNull ResourceBundle bundle, String key, @NotNull Throwable cause, Object... args) { - return thr(bundle, key, key, cause, args); - } - -} diff --git a/src/main/java/space/qu4nt/entanglementlib/resource/language/LanguageInstanceBased.java b/src/main/java/space/qu4nt/entanglementlib/resource/language/LanguageInstanceBased.java deleted file mode 100644 index 5bbe1c4..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/resource/language/LanguageInstanceBased.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.resource.language; - -import lombok.Getter; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Objects; -import java.util.ResourceBundle; - -/** - * 언어를 좀 더 간편하게 사용할 수 있도록 상수로써 선언할 수 있도록 해주는 클래스입니다. - * 특정 클래스에서 {@link Language} 클래스를 통한 언어 호출이 잦은 경우 유용합니다. - *

- * 이 클래스에선 {@code ENTANGLEMENT_DEFAULT_LANG} 환경 변수를 기반으로 동작합니다. - * - * @param 바인딩 타겟 타입 파라미터 - * @author Q. T. Felix - * @see Language 기반 언어 클래스 - * @since 1.0.0 - */ -@Getter -public class LanguageInstanceBased { - - private Class clazz; - /** - * 이 변수는 {@code null}일 수 있지만, - * null이 아닌 경우 우선적으로 사용됩니다. - */ - @Nullable - private final ResourceBundle bundle; - - private LanguageInstanceBased(Class clazz) { - this.clazz = clazz; - this.bundle = null; - } - - private LanguageInstanceBased(@NotNull ResourceBundle bundle, Class clazz) { - this.clazz = clazz; - this.bundle = bundle; - } - - public static LanguageInstanceBased create(Class clazz) { - return new LanguageInstanceBased<>(clazz); - } - - /** - * 전달받은 리소스 번들에서 언어 파일을 로드하기 위한 메소드입니다. - *

- * 얽힘 라이브러리를 외부에서 사용하는 경우 이 라이브러리에 포함된 메시지 키를 사용하지 않고 - * 호출자 프로젝트에서 언어 파일을 새롭게 로드하고자 하는 경우 사용됩니다. - * - * @param bundle 리소스 번들 - * @param clazz 언어 파일에서 바인딩할 타입 - * @param 바인딩 타겟 타입 파라미터 - * @return 언어 인스턴스 {@link LanguageInstanceBased} - */ - public static LanguageInstanceBased create(@NotNull ResourceBundle bundle, Class clazz) { - return new LanguageInstanceBased<>(bundle, clazz); - } - - public LanguageInstanceBased setClass(Class clazz) { - this.clazz = clazz; - return this; - } - - public String msg(@Nullable String topKey, String lowKey) { - Objects.requireNonNull(lowKey); - String key = fixTopKey(topKey) + lowKey; - if (bundle != null) { - return Language.msg(bundle, key); - } - return Language.msg(key); - } - - public String msg(String lowKey) { - Objects.requireNonNull(lowKey); - return msg(null, clazz.getSimpleName() + "." + lowKey); - } - - public String args(@Nullable String topKey, String lowKey, Object... args) { - Objects.requireNonNull(lowKey); - String key = fixTopKey(topKey) + lowKey; - if (bundle != null) { - return Language.args(bundle, key, args); - } - return Language.args(key, args); - } - - public String argsNonTopKey(String lowKey, Object... args) { - Objects.requireNonNull(lowKey); - return args(null, clazz.getSimpleName() + "." + lowKey, args); - } - - public String thr(@Nullable String topKey, String lowKey, @NotNull Throwable cause, Object... args) { - Objects.requireNonNull(lowKey); - String key = fixTopKey(topKey) + lowKey; - if (bundle != null) { - return Language.thr(bundle, key, cause, args); - } - return Language.thr(key, cause, args); - } - - public String thr(String lowKey, @NotNull Throwable cause, Object... args) { - Objects.requireNonNull(lowKey); - return thr(null, clazz.getSimpleName() + "." + lowKey, cause, args); - } - - private String fixTopKey(String topKey) { - return topKey == null ? "class-" : (topKey + ".class-"); - } - -} diff --git a/src/main/java/space/qu4nt/entanglementlib/resource/language/SupportedLanguage.java b/src/main/java/space/qu4nt/entanglementlib/resource/language/SupportedLanguage.java deleted file mode 100644 index a3b55a3..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/resource/language/SupportedLanguage.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.resource.language; - -import lombok.Getter; - -/** - * 현재 프로젝트가 지원하는 언어입니다. 새로운 언어를 추가하고자 하는 경우, {@code i18n}에 따라 - * 국적에 맞는 열거 상수를 추가한 뒤, 공용 리소스 디렉토리에 {@code messages_en_US.yml}와 같이 - * 언어 파일을 생성하세요. - *

- * ISO 3166-1 에 따른 국가 코드, - * ISO 639-1 - * 에 따른 언어 코드 양식을 준수하세요. - * - * @author Q. T. Felix - * @since 1.0.0 - */ -@Getter -public enum SupportedLanguage { - - /** - * 한국어 - */ - ko_KR, - /** - * US English - */ - en_US; - - private final String filename = "messages_" + name(); - -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/EntLibParameterSpec.java b/src/main/java/space/qu4nt/entanglementlib/security/EntLibParameterSpec.java deleted file mode 100644 index 3746670..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/EntLibParameterSpec.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security; - -import org.jetbrains.annotations.NotNull; - -import java.util.Objects; - -public interface EntLibParameterSpec { - - String getAlgorithmName(); - - default boolean startsWith(@NotNull String prefix) { - return Objects.requireNonNull(getAlgorithmName(), "normal algorithm name").startsWith(prefix); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/KeyDestroyHelper.java b/src/main/java/space/qu4nt/entanglementlib/security/KeyDestroyHelper.java deleted file mode 100644 index 9e546c9..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/KeyDestroyHelper.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security; - -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.resource.language.LanguageInstanceBased; -import space.qu4nt.entanglementlib.util.wrapper.Hex; - -import javax.crypto.spec.SecretKeySpec; -import javax.security.auth.DestroyFailedException; -import java.lang.foreign.MemorySegment; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.math.BigInteger; -import java.util.Arrays; -import java.util.Collections; -import java.util.IdentityHashMap; -import java.util.Set; - -/** - * 키 데이터를 영소거(zeroing)하기 위한 몇 가지 도구를 제공하는 클래스입니다. - * 소거된 키는 공격자가 배열 정보를 알 수 없도록 리플렉션을 통해 완전히 제거하고, - * 소거 후 배열 사이즈를 {@code 0}으로 초기화합니다. - * - * @author Q. T. Felix - * @since 1.0.0 - */ -@Slf4j -public final class KeyDestroyHelper { - - private static final LanguageInstanceBased lang = - LanguageInstanceBased.create(KeyDestroyHelper.class); - - /** - * 리플렉션을 통해 전달받은 객체 타입의 필드를 영소거하는 메소드입니다. - * 내부 인스턴스의 모든 필드를 검사하여 민감한 데이터({@code byte[]}, - * {@code char[]}, {@code SecretKey})가 해당됩니다. - *

- * 단, {@link javax.crypto.SecretKey} 객체를 구현하는 - * {@link javax.crypto.spec.SecretKeySpec} 객체의 경우, - * JPMS 보안 시스템이 강화되어 리플렉션으로도 접근할 수 없습니다. - * - * @param targetObject 소거할 내부 키 파라미터 객체 - * @deprecated 1.1.0 이상 버전부턴 모든 메모리 관련 기능이 {@code entlib-native}를 통해 구현됩니다. - */ - @Deprecated - public static void destroy(Object targetObject) { - if (targetObject == null) return; - - // 순환 참조 방지를 위해 방문한 객체의 주소값을 저장 (IdentityHashMap 사용) - Set visited = Collections.newSetFromMap(new IdentityHashMap<>()); - recursiveZeroing(targetObject, visited, false); - } - - /** - * 리플렉션을 통해 전달받은 객체 타입의 필드를 영소거하는 메소드입니다. - * 얕은 소거(shallow)를 활성화하면 해당 객체의 필드만 소거하고 재귀적으로 탐색하지 않습니다. - * - * @param targetObject 소거할 내부 키 파라미터 객체 - * @param shallow 얕은 소거 여부, 재귀 탐색을 원치 않으면 true, 그렇지 않으면 false - * @deprecated 1.1.0 이상 버전부턴 모든 메모리 관련 기능이 {@code entlib-native}를 통해 구현됩니다. - */ - @Deprecated - public static void destroy(Object targetObject, boolean shallow) { - if (targetObject == null) return; - - Set visited = Collections.newSetFromMap(new IdentityHashMap<>()); - recursiveZeroing(targetObject, visited, shallow); - } - - /** - * @deprecated 1.1.0 이상 버전부턴 모든 메모리 관련 기능이 {@code entlib-native}를 통해 구현됩니다. - */ - @Deprecated - private static void recursiveZeroing(Object targetObject, Set visited, boolean shallow) { - if (targetObject == null) return; - - // JPMS 보안 강화로 인해 리플렉션 불가능 - // TODO: 대칭 키 객체 소거 방안 찾기 - if (targetObject instanceof SecretKeySpec s) { - try { - s.destroy(); - } catch (DestroyFailedException e) { - zeroing(s.getEncoded()); // 대안이 생기기 전 까진 복사본이라도 소거 - } - return; - } - - // 이미 방문한 객체인지 확인 (무한 루프 방지) - if (visited.contains(targetObject)) return; - visited.add(targetObject); - - Class currentClass = targetObject.getClass(); - - // 탐색 제외 조건 (Java 내부 클래스나 원시 타입 래퍼 등은 내부를 탐색할 필요 없음) - // 단, byte[]는 배열이므로 여기서 걸러지지 않도록 주의 (isArray 체크는 아래에서 함) - if (isSkippingType(currentClass)) { - return; - } - - // 현재 클래스와 모든 부모 클래스의 필드를 탐색 - while (currentClass != null && currentClass != Object.class) { - Field[] fields = currentClass.getDeclaredFields(); - - for (Field field : fields) { - try { - // static 필드는 제외 - if (Modifier.isStatic(field.getModifiers())) continue; - - field.setAccessible(true); - Object value = field.get(targetObject); - - if (value == null) continue; - - // 타겟 발견 소거 수행 - if (field.getType() == byte[].class) { - zeroing((byte[]) value); - field.set(targetObject, new byte[0]); - log.debug(lang.argsNonTopKey("debug-byte-arr-field-zeroing-result", field.getName(), Hex.toHexString((byte[]) field.get(targetObject)))); - } else if (field.getType() == char[].class) { - zeroing((char[]) value); - field.set(targetObject, new char[0]); - log.debug(lang.argsNonTopKey("debug-char-arr-field-zeroing-result", field.getName(), Arrays.toString((char[]) field.get(targetObject)))); - } else if (field.getType() == BigInteger.class) { - field.set(targetObject, new BigInteger(0, new byte[0])); - log.debug(lang.argsNonTopKey("debug-number-field-zeroing-result", field.getName(), field.get(targetObject))); - } else if (field.getType() == Number.class) { - field.set(targetObject, 0); - log.debug(lang.argsNonTopKey("debug-number-field-zeroing-result", field.getName(), field.get(targetObject))); - } - // 배열만 재귀 탐색 - else if (!shallow && !field.getType().isPrimitive() && !field.getType().isArray()) { - recursiveZeroing(value, visited, false); - } - - } catch (IllegalAccessException e) { - log.error(lang.msg("cannot-access-exc"), e); - } - } - // 부모 클래스로 이동하여 필드 탐색 계속 - currentClass = currentClass.getSuperclass(); - } - } - - // 탐색을 멈출 타입 정의 (String, Number 등 불변 객체거나 내부 필드가 의미 없는 경우) - @Deprecated - private static boolean isSkippingType(Class clazz) { - if (clazz == null) return true; - String name = clazz.getName(); - return clazz.isPrimitive() || - name.startsWith("java.lang.") || - name.startsWith("java.util.") || - name.startsWith("java.math."); - } - - /** - * 메모리에서 바이트 배열을 안전하게 영소거하는 메소드입니다. - * - * @param bytes 영소거할 바이트 배열 - */ - public static void zeroing(byte @NotNull [] bytes) { - Arrays.fill(bytes, (byte) 0); - } - - /** - * 메모리에서 2차원 바이트 배열을 안전하게 영소거하는 메소드입니다. - * - * @param bytes2d 영소거할 2차원 바이트 배열 - */ - public static void zeroing(byte[][] bytes2d) { - for (byte[] bytes : bytes2d) - zeroing(bytes); - } - - /** - * 메모리에서 문자 배열을 안전하게 영소거하는 메소드입니다. - * - * @param chars 영소거할 문자 배열 - */ - public static void zeroing(char @NotNull [] chars) { - Arrays.fill(chars, '\0'); - } - - /** - * 메모리에서 2차원 바이트 배열을 안전하게 영소거하는 메소드입니다. - * - * @param chars2d 영소거할 2차원 바이트 배열 - */ - public static void zeroing(char[][] chars2d) { - for (char[] chars : chars2d) - zeroing(chars); - } - - /// 네이티브 메모리에 할당된 데이터를 영소거하는 메소드입니다. - /// - /// @param memorySegment 영소거할 메모리 타겟 - public static void zeroing(final MemorySegment memorySegment) { - memorySegment.fill((byte) 0); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/PostQuantumParameterSpec.java b/src/main/java/space/qu4nt/entanglementlib/security/PostQuantumParameterSpec.java deleted file mode 100644 index 6cab221..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/PostQuantumParameterSpec.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security; - -import org.jetbrains.annotations.NotNull; - -import java.util.Objects; - -public interface PostQuantumParameterSpec extends EntLibParameterSpec { - - @Override - default boolean startsWith(@NotNull String prefix) { - return Objects.requireNonNull(getAlgorithmName(), "pqc algorithm name").startsWith(prefix); - } - -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/auth/TOTP.java b/src/main/java/space/qu4nt/entanglementlib/security/auth/TOTP.java deleted file mode 100644 index 9cffe54..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/auth/TOTP.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.auth; - -import org.jetbrains.annotations.Range; -import space.qu4nt.entanglementlib.security.crypto.Digest; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.time.Instant; -import java.util.Base64; - -/** - * 상태를 가지지 않는 TOTP(Time-based One-Time Password) 2FA를 제공하는 클래스입니다. - * - * @author Q. T. Felix - * @since 1.1.0 - */ -public final class TOTP { - - /** - * 현재 시간을 기준으로 TOTP 코드를 생성하는 메소드입니다. - * - * @param digest 해시 알고리즘 - * @param digit TOTP 코드의 자릿수 - * @param timeStepSeconds 시간 간격(초) - * @param secretKeyBase64 {@code Base64}로 인코딩된 비밀키 - * @return 생성된 TOTP 코드 - */ - public static String generateCurrentTotp(Digest digest, int digit, final long timeStepSeconds, String secretKeyBase64) { - long currentTimeSeconds = Instant.now().getEpochSecond(); - long timeStep = currentTimeSeconds / timeStepSeconds; - - return computeTotp(digest, digit, secretKeyBase64, timeStep); - } - - /** - * 입력된 TOTP 코드를 검증하는 메소드입니다. - * 네트워크 지연 등을 고려하여 현재 스텝과 이전 스텝까지 유효성을 확인합니다. - * - * @param digest 해시 알고리즘 - * @param digit TOTP 코드의 자릿수 - * @param timeStepSeconds 시간 간격(초) - * @param secretKeyBase64 {@code Base64}로 인코딩된 비밀키 - * @param inputCode 검증할 TOTP 코드 - * @return 검증 성공 여부 - */ - public static boolean verifyTotp(Digest digest, int digit, final long timeStepSeconds, String secretKeyBase64, String inputCode) { - long currentTimeSeconds = Instant.now().getEpochSecond(); - long currentTimeStep = currentTimeSeconds / timeStepSeconds; - - // 네트워크 지연 등을 고려해 현재 스텝과 바로 이전 스텝까지 유효하다고 판단 - for (int i = -1; i <= 0; i++) { - String generated = computeTotp(digest, digit, secretKeyBase64, currentTimeStep + i); - if (generated.equals(inputCode)) { - return true; - } - } - return false; - } - - /** - * 주어진 시간 스텝과 비밀키를 사용하여 TOTP 코드를 계산하는 내부 메소드입니다. - * - * @param digest 해시 알고리즘 - * @param digit TOTP 코드의 자릿수 - * @param secretKeyBase64 {@code Base64}로 인코딩된 비밀키 - * @param timeStep 시간 스텝 - * @return 계산된 TOTP 코드 - */ - private static String computeTotp(Digest digest, - @Range(from = 6, to = Integer.MAX_VALUE) int digit, - String secretKeyBase64, - long timeStep) { - try { - byte[] secretKey = Base64.getDecoder().decode(secretKeyBase64); - - // TimeStep을 8바이트 배열로 변환 - byte[] data = new byte[8]; - for (int i = 8; i-- > 0; timeStep >>>= 8) { - data[i] = (byte) timeStep; - } - - final String alg = "Hmac" + digest.getName(); - Mac mac = Mac.getInstance(alg); - mac.init(new SecretKeySpec(secretKey, alg)); - byte[] hash = mac.doFinal(data); - - // 동적 Truncation - int offset = hash[hash.length - 1] & 0xF; - long binary = - ((hash[offset] & 0x7f) << 24) | - ((hash[offset + 1] & 0xff) << 16) | - ((hash[offset + 2] & 0xff) << 8) | - (hash[offset + 3] & 0xff); - - long otp = binary % (long) Math.pow(10, digit); - - // 0 패딩 - return String.format("%0" + digit + "d", otp); - - } catch (NoSuchAlgorithmException | InvalidKeyException e) { - throw new RuntimeException("TOTP Generation Error", e); - } - } - -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/auth/package-info.java b/src/main/java/space/qu4nt/entanglementlib/security/auth/package-info.java deleted file mode 100644 index b0ceff2..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/auth/package-info.java +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -/** - * 인증 수단을 관리하기 위한 도구를 제공하는 패키지입니다. - * - * @author Q. T. Felix - * @since 1.1.0 - */ -package space.qu4nt.entanglementlib.security.auth; \ No newline at end of file diff --git a/src/main/java/space/qu4nt/entanglementlib/security/communication/README.md b/src/main/java/space/qu4nt/entanglementlib/security/communication/README.md deleted file mode 100644 index 87b6708..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/communication/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# 얽힘 라이브러리: 통신 - -얽힘 라이브러리는 아주 다양한 통신 프로토콜을 통해 세션을 생성하고 개별 세션에 대해 전략적인 암호화 대응이 가능하도록 강력한 아키텍처를 제공합니다. \ No newline at end of file diff --git a/src/main/java/space/qu4nt/entanglementlib/security/communication/session/ConnectionState.java b/src/main/java/space/qu4nt/entanglementlib/security/communication/session/ConnectionState.java deleted file mode 100644 index a84fcd3..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/communication/session/ConnectionState.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.communication.session; - -public enum ConnectionState { - - /// 참여자가 TCP 연결되었지만, 핸드셰이킹을 시작하진 않았습니다. - CONNECTING, - - /// 참여자가 핸드셰이킹합니다. 이 과정에 키 교환을 수행할 수 - /// 있습니다. - HANDSHAKING, - - /// 참여자의 보안 터널이 활성화되었습니다. - ESTABLISHED, - - /// 참여자가 연결을 중단하고 있습니다. - CLOSING, - - /// 참여자의 연결이 완전히 종료되었습니다. - CLOSED -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/communication/session/Participant.java b/src/main/java/space/qu4nt/entanglementlib/security/communication/session/Participant.java deleted file mode 100644 index d44cc19..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/communication/session/Participant.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.communication.session; - -import lombok.Getter; - -import java.io.IOException; -import java.net.SocketAddress; -import java.nio.ByteBuffer; -import java.nio.channels.SocketChannel; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.atomic.AtomicReference; - -/// 세션 참여자는 반드시 다음 세 가지의 핵심 요소를 포함해야 합니다. -/// -/// 1. 식별자(identity) - 누구인가? -/// 2. 연결 방법(transport) - 어떻게 왔는가? -/// 3. 보안 방법(security) - 어떻게 암호화되나? -/// -/// 이 클래스는 이러한 정보를 안고 연결될 '참여자'의 기본 스키마를 -/// 제공하며, 논블로킹 I/O를 위한 인바운드/아웃바운드 버퍼링을 지원합니다. -/// -/// @author Q. T. Felix -/// @since 1.1.0 -@Getter -public class Participant { - - // 식별자 정보 - private final String id; - private final ParticipantRole role; - private final long connectedAt; - - // 네트워크 컨텍스트 - private final SocketChannel channel; - private final SocketAddress remoteAddress; - - // TCP 분할(Fragmentation) 및 부분 쓰기(Partial Write) 처리를 위한 버퍼 - // 대형 PQC 키 교환 시 필수적입니다. - private final ByteBuffer inboundBuffer; - - // 스레드 안전성을 위해 ConcurrentLinkedQueue 사용 (App스레드 -> IO스레드) - private final Queue outboundQueue; - - // 보안 컨텍스트 - private final ParticipantSecurityContext participantSecurityContext; - - // 참여자 연결 상태 매니지먼트 - private final AtomicReference state; - - public Participant(String id, ParticipantRole role, SocketChannel channel, int bufferSize, ParticipantSecurityContext participantSecurityContext) { - this.id = id; - this.role = role; - this.channel = channel; - this.remoteAddress = channel.socket().getRemoteSocketAddress(); - this.connectedAt = System.currentTimeMillis(); - - // zero-copy 성능을 위한 직접 버퍼 할당 - this.inboundBuffer = ByteBuffer.allocateDirect(bufferSize); - - // ArrayDeque는 스레드 안전하지 않으므로 ConcurrentLinkedQueue로 변경 - this.outboundQueue = new ConcurrentLinkedQueue<>(); - - this.participantSecurityContext = participantSecurityContext; - this.state = new AtomicReference<>(ConnectionState.CONNECTING); - } - - public boolean isSecure() { - return state.get() == ConnectionState.ESTABLISHED; - } - - public void transitionState(ConnectionState newState) { - // TODO: 상태 전이 검증 로직 (예: CLOSED 상태에서는 변경 불가 등) - this.state.set(newState); - } - - /// 전송할 데이터를 아웃바운드 큐에 등록합니다. - /// 실제 전송은 Selector의 OP_WRITE 이벤트 루프에서 처리됩니다. - /// - /// @param data 전송할 데이터가 담긴 ByteBuffer - public void enqueueMessage(ByteBuffer data) { - if (data != null && data.hasRemaining()) { - outboundQueue.offer(data); - } - } - - /// 아웃바운드 큐에 쌓인 데이터를 채널로 전송을 시도합니다. - /// 논블로킹 모드이므로 데이터가 전부 전송되지 않을 수 있습니다. - /// - /// @return true면 아직 보낼 데이터가 남음(OP_WRITE 유지 필요), - /// false면 큐가 비었음(OP_WRITE 해제 가능) - /// @throws IOException 채널 쓰기 중 오류 발생 시 - public boolean flushOutbound() throws IOException { - ByteBuffer buffer; - - // 큐의 헤드(가장 먼저 들어온 데이터)를 확인 - while ((buffer = outboundQueue.peek()) != null) { - - // 채널에 쓰기 시도 - channel.write(buffer); - - // 1. Partial Write 발생: 채널 버퍼가 꽉 차서 데이터를 다 못 씀 - if (buffer.hasRemaining()) { - // 루프 종료. 다음 OP_WRITE 이벤트 때 남은 부분부터 이어서 보냄 - return true; - } - - // 2. Full Write 완료: 현재 버퍼를 다 썼으므로 큐에서 제거 - outboundQueue.poll(); - } - - // 큐가 비었음 - return false; - } - - /// 아웃바운드 큐에 데이터가 있는지 확인합니다. - public boolean hasOutboundData() { - return !outboundQueue.isEmpty(); - } - - /// 리소스 정리 시 호출하여 메모리 누수를 방지합니다. - public void cleanup() { - outboundQueue.clear(); - // DirectBuffer는 GC가 처리하지만, 필요한 경우 명시적 해제 로직 추가 가능 - } -} \ No newline at end of file diff --git a/src/main/java/space/qu4nt/entanglementlib/security/communication/session/ParticipantRole.java b/src/main/java/space/qu4nt/entanglementlib/security/communication/session/ParticipantRole.java deleted file mode 100644 index a39d67b..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/communication/session/ParticipantRole.java +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.communication.session; - -public enum ParticipantRole { - - INITIATOR, - RESPONDER, - OBSERVER, - CUSTOM -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/communication/session/ParticipantSecurityContext.java b/src/main/java/space/qu4nt/entanglementlib/security/communication/session/ParticipantSecurityContext.java deleted file mode 100644 index 598bf2b..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/communication/session/ParticipantSecurityContext.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.communication.session; - -import lombok.Getter; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; - -import java.util.concurrent.atomic.AtomicLong; - -/// 세션 참여자의 암호화 컨텍스트를 관리하는 클래스입니다. -/// -/// 얽힘 라이브러리 TLS 체계는 참여자가 세션에 접속할 때 이 컨텍스트를 가지고 있다고 -/// 예상합니다. -/// -/// @author Q. T. Felix -/// @see Session -/// @since 1.1.0 -public class ParticipantSecurityContext { - - // 합의된 세션 키 - @Getter - private volatile SensitiveDataContainer sessionKey; - - // AEAD의 논스 및 카운터 - // 수신 카운터 - private final AtomicLong readSequence = new AtomicLong(0); - // 송신 카운터 - private final AtomicLong writeSequence = new AtomicLong(0); - - // 연결에 사용되는 특정 스트레티지(세션 기본값과 다를 경우) - private String negotiatedStrategy; - - // 핸드셰이크 프레그먼트에 임시 저장 - private SensitiveDataContainer peerPublicKey; - - public ParticipantSecurityContext(SensitiveDataContainer sessionKey, String negotiatedStrategy, SensitiveDataContainer peerPublicKey) { - this.sessionKey = sessionKey; - this.negotiatedStrategy = negotiatedStrategy; - this.peerPublicKey = peerPublicKey; - } - - public long getNextWriteSequence() { - return writeSequence.getAndIncrement(); - } - - public long getNextReadSequence() { - return readSequence.getAndIncrement(); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/communication/session/Session.java b/src/main/java/space/qu4nt/entanglementlib/security/communication/session/Session.java deleted file mode 100644 index 03c813f..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/communication/session/Session.java +++ /dev/null @@ -1,514 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.communication.session; - -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import space.qu4nt.entanglementlib.exception.session.EntLibSessionException; - -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.atomic.AtomicReference; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.function.Consumer; -import java.util.function.Predicate; - -/// 보안 통신 세션을 관리하는 클래스입니다. -/// -/// 세션은 여러 참여자들이 보안 통신을 수행할 수 있는 논리적 컨텍스트를 제공합니다. -/// 각 세션은 고유한 식별자, 세션 수준의 보안 컨텍스트, 그리고 참여자 목록을 관리합니다. -/// -/// ## 주요 기능 -/// - 다수의 참여자 관리 (추가, 제거, 조회) -/// - 세션 수준 보안 컨텍스트 관리 -/// - 세션 생명주기 관리 (생성, 활성, 종료) -/// - 유연한 세션 설정 (최대 참여자 수, 타임아웃 등) -/// - 스레드 안전 연산 -/// -/// ## 사용 예시 -/// ```java -/// SessionConfig config = SessionConfig.builder() -/// .maxParticipants(10) -/// .sessionTimeout(Duration.ofMinutes(30)) -/// .build(); -/// -/// Session session = Session.create(config); -/// session.addParticipant(participant); -/// ``` -/// -/// @author Q. T. Felix -/// @see Participant -/// @see ParticipantSecurityContext -/// @see SessionConfig -/// @since 1.1.0 -@Slf4j -@Getter -public class Session { - - /// 세션 고유 식별자 - private final String sessionId; - - /// 세션 생성 시각 (Unix timestamp milliseconds) - private final long createdAt; - - /// 세션 설정 - private final SessionConfig config; - - /// 세션 수준 보안 컨텍스트 (그룹 키, 공통 암호화 설정 등) - private volatile SessionSecurityContext sessionSecurityContext; - - /// 세션 상태 - private final AtomicReference state; - - /// 참여자 저장소 (ID -> Participant) - private final ConcurrentHashMap participants; - - /// 역할별 참여자 인덱스 (빠른 역할 기반 조회용) - private final ConcurrentHashMap> participantsByRole; - - /// 참여자 목록 수정을 위한 읽기-쓰기 락 - private final ReentrantReadWriteLock participantsLock; - - /// 세션 이벤트 리스너 - private final List eventListeners; - - /// 마지막 활동 시각 - private volatile long lastActivityAt; - - // - // factory - start - // - - private Session(String sessionId, SessionConfig config) { - this.sessionId = sessionId; - this.createdAt = System.currentTimeMillis(); - this.lastActivityAt = this.createdAt; - this.config = config; - - this.state = new AtomicReference<>(SessionState.CREATED); - this.participants = new ConcurrentHashMap<>(); - this.participantsByRole = new ConcurrentHashMap<>(); - this.participantsLock = new ReentrantReadWriteLock(); - // 순회 시 락이 필요 없는 CopyOnWrite 병렬 컬렉션 사용 변경 - this.eventListeners = new CopyOnWriteArrayList<>(); - - log.debug("세션 생성됨: {}", sessionId); - } - - /// 기본 설정으로 새 세션을 생성합니다. - /// - /// @return 새로 생성된 세션 - public static Session create() { - return create(SessionConfig.defaults()); - } - - /// 지정된 설정으로 새 세션을 생성합니다. - /// - /// @param config 세션 설정 - /// @return 새로 생성된 세션 - public static Session create(@NotNull SessionConfig config) { - String sessionId = generateSessionId(); - return new Session(sessionId, config); - } - - /// 지정된 ID와 설정으로 새 세션을 생성합니다. - /// - /// @param sessionId 세션 ID - /// @param config 세션 설정 - /// @return 새로 생성된 세션 - public static Session create(@NotNull String sessionId, @NotNull SessionConfig config) { - return new Session(sessionId, config); - } - - private static String generateSessionId() { - return UUID.randomUUID().toString(); - } - - // - // factory - end - // - - // - // Participants - start - // - - /// 세션에 참여자를 추가합니다. - /// - /// @param participant 추가할 참여자 - /// @return 추가 성공 여부 - /// @throws EntLibSessionException 세션이 활성 상태가 아니거나 최대 참여자 수 초과 시 - public boolean addParticipant(@NotNull Participant participant) throws EntLibSessionException { - validateSessionActive(); - - participantsLock.writeLock().lock(); - try { - // 최대 참여자 수 확인 - if (config.getMaxParticipants() > 0 && - participants.size() >= config.getMaxParticipants()) { - throw new EntLibSessionException("최대 참여자 수를 초과했습니다: " + config.getMaxParticipants()); - } - - // 중복 확인 - if (participants.containsKey(participant.getId())) { - log.warn("이미 존재하는 참여자 ID: {}", participant.getId()); - return false; - } - - // 참여자 추가 - participants.put(participant.getId(), participant); - - // 역할별 인덱스 업데이트 - participantsByRole - .computeIfAbsent(participant.getRole(), k -> ConcurrentHashMap.newKeySet()) - .add(participant.getId()); - - updateActivity(); - notifyListeners(listener -> listener.onParticipantJoined(this, participant)); - - log.debug("참여자 추가됨: {} (역할: {})", participant.getId(), participant.getRole()); - return true; - } finally { - participantsLock.writeLock().unlock(); - } - } - - /// 세션에서 참여자를 제거합니다. - /// - /// @param participantId 제거할 참여자 ID - /// @return 제거된 참여자 (없으면 null) - @Nullable - public Participant removeParticipant(@NotNull String participantId) { - participantsLock.writeLock().lock(); - try { - Participant removed = participants.remove(participantId); - if (removed != null) { - // 역할별 인덱스에서도 제거 - Set roleSet = participantsByRole.get(removed.getRole()); - if (roleSet != null) { - roleSet.remove(participantId); - } - - updateActivity(); - notifyListeners(listener -> listener.onParticipantLeft(this, removed)); - - log.debug("참여자 제거됨: {}", participantId); - } - return removed; - } finally { - participantsLock.writeLock().unlock(); - } - } - - /// ID로 참여자를 조회합니다. - /// - /// @param participantId 참여자 ID - /// @return 참여자 (없으면 null) - @Nullable - public Participant getParticipant(@NotNull String participantId) { - return participants.get(participantId); - } - - /// 특정 역할의 모든 참여자를 조회합니다. - /// - /// @param role 참여자 역할 - /// @return 해당 역할의 참여자 목록 (불변) - @NotNull - public List getParticipantsByRole(@NotNull ParticipantRole role) { - participantsLock.readLock().lock(); - try { - Set ids = participantsByRole.get(role); - if (ids == null || ids.isEmpty()) { - return Collections.emptyList(); - } - return ids.stream() - .map(participants::get) - .filter(Objects::nonNull) - .toList(); - } finally { - participantsLock.readLock().unlock(); - } - } - - /// 조건에 맞는 참여자들을 조회합니다. - /// - /// @param predicate 필터 조건 - /// @return 조건에 맞는 참여자 목록 - @NotNull - public List findParticipants(@NotNull Predicate predicate) { - participantsLock.readLock().lock(); - try { - return participants.values().stream() - .filter(predicate) - .toList(); - } finally { - participantsLock.readLock().unlock(); - } - } - - /// 모든 참여자 목록을 반환합니다. - /// - /// @return 모든 참여자 목록 (불변) - @NotNull - public Collection getAllParticipants() { - return Collections.unmodifiableCollection(participants.values()); - } - - /// 현재 참여자 수를 반환합니다. - /// - /// @return 참여자 수 - public int getParticipantCount() { - return participants.size(); - } - - /// 세션에 참여자가 있는지 확인합니다. - /// - /// @param participantId 참여자 ID - /// @return 존재 여부 - public boolean hasParticipant(@NotNull String participantId) { - return participants.containsKey(participantId); - } - - // - // Participants - end - // - - // - // Session Status - start - // - - /// 세션을 활성화합니다. - /// - /// @throws EntLibSessionException 세션이 이미 종료된 경우 - public void activate() throws EntLibSessionException { - SessionState current = state.get(); - if (current == SessionState.CLOSED || current == SessionState.TERMINATED) { - throw new EntLibSessionException("종료된 세션은 활성화할 수 없습니다."); - } - - if (state.compareAndSet(SessionState.CREATED, SessionState.ACTIVE) || - state.compareAndSet(SessionState.SUSPENDED, SessionState.ACTIVE)) { - updateActivity(); - notifyListeners(listener -> listener.onSessionStateChanged(this, current, SessionState.ACTIVE)); - log.info("세션 활성화됨: {}", sessionId); - } - } - - /// 세션을 일시 중단합니다. - public void suspend() { - SessionState current = state.get(); - if (current == SessionState.ACTIVE) { - if (state.compareAndSet(SessionState.ACTIVE, SessionState.SUSPENDED)) { - notifyListeners(listener -> listener.onSessionStateChanged(this, current, SessionState.SUSPENDED)); - log.info("세션 일시 중단됨: {}", sessionId); - } - } - } - - /// 세션을 정상 종료합니다. - /// 모든 참여자에게 종료를 알리고 리소스를 정리합니다. - public void close() { - SessionState current = state.get(); - if (current == SessionState.CLOSED || current == SessionState.TERMINATED) { - return; - } - - if (state.compareAndSet(current, SessionState.CLOSING)) { - try { - notifyListeners(listener -> listener.onSessionClosing(this)); - - // 방어적 복사 로컬 리스트 - List participantsToClose; - - // lock scope 최소화 -> 내부 컬렉션 상태만 조작 - participantsLock.writeLock().lock(); - try { - // 스냅샷 생성 - participantsToClose = new ArrayList<>(participants.values()); - participants.clear(); - participantsByRole.clear(); - } finally { - participantsLock.writeLock().unlock(); - } - - // 락이 해제된 안전한 상태에서 외부 메소드(alien method) 호출 - participantsToClose.forEach(p -> - p.transitionState(ConnectionState.CLOSING) - ); - - if (sessionSecurityContext != null) { - sessionSecurityContext.clear(); - } - - state.set(SessionState.CLOSED); - notifyListeners(listener -> listener.onSessionClosed(this)); - log.info("세션 종료됨: {}", sessionId); - } catch (Exception e) { - state.set(SessionState.TERMINATED); - log.error("세션 종료 중 오류 발생: {}", sessionId, e); - } - } - } - - /// 세션을 강제 종료합니다. - public void terminate() { - SessionState current = state.get(); - if (current != SessionState.TERMINATED) { - state.set(SessionState.TERMINATED); - - participantsLock.writeLock().lock(); - try { - participants.clear(); - participantsByRole.clear(); - } finally { - participantsLock.writeLock().unlock(); - } - - if (sessionSecurityContext != null) { - sessionSecurityContext.clear(); - } - - notifyListeners(listener -> listener.onSessionTerminated(this)); - log.warn("세션 강제 종료됨: {}", sessionId); - } - } - - /// 세션이 활성 상태인지 확인합니다. - /// - /// @return 활성 상태 여부 - public boolean isActive() { - return state.get() == SessionState.ACTIVE; - } - - /// 세션이 종료되었는지 확인합니다. - /// - /// @return 종료 여부 - public boolean isClosed() { - SessionState current = state.get(); - return current == SessionState.CLOSED || current == SessionState.TERMINATED; - } - - // - // Session Status - end - // - - // - // SecurityContext - start - // - - /// 세션 수준 보안 컨텍스트를 설정합니다. - /// - /// @param securityContext 설정할 보안 컨텍스트 - public void setSessionSecurityContext(@NotNull SessionSecurityContext securityContext) { - this.sessionSecurityContext = securityContext; - updateActivity(); - log.debug("세션 보안 컨텍스트 설정됨: {}", sessionId); - } - - /// 세션의 모든 참여자가 보안 연결 상태인지 확인합니다. - /// - /// @return 모든 참여자가 보안 연결 상태면 true - public boolean isFullySecure() { - if (participants.isEmpty()) { - return false; - } - return participants.values().stream().allMatch(Participant::isSecure); - } - - // - // SecurityContext - end - // - - // - // EventListener - start - // - - /// 세션 이벤트 리스너를 등록합니다. - /// - /// @param listener 등록할 리스너 - public void addEventListener(@NotNull SessionEventListener listener) { - eventListeners.add(listener); - } - - /// 세션 이벤트 리스너를 제거합니다. - /// - /// @param listener 제거할 리스너 - public void removeEventListener(@NotNull SessionEventListener listener) { - eventListeners.remove(listener); - } - - private void notifyListeners(Consumer action) { - for (SessionEventListener listener : eventListeners) { - try { - action.accept(listener); - } catch (Exception e) { - log.error("이벤트 리스너 실행 중 오류", e); - } - } - } - - // - // EventListener - end - // - - // - // Utility - start - // - - private void validateSessionActive() throws EntLibSessionException { - SessionState current = state.get(); - if (current != SessionState.ACTIVE && current != SessionState.CREATED) { - throw new EntLibSessionException("세션이 활성 상태가 아닙니다: " + current); - } - } - - private void updateActivity() { - this.lastActivityAt = System.currentTimeMillis(); - } - - /// 세션 유휴 시간을 반환합니다. - /// - /// @return 마지막 활동 이후 경과 시간 (밀리초) - public long getIdleTime() { - return System.currentTimeMillis() - lastActivityAt; - } - - /// 세션 지속 시간을 반환합니다. - /// - /// @return 세션 생성 이후 경과 시간 (밀리초) - public long getDuration() { - return System.currentTimeMillis() - createdAt; - } - - // - // Utility - end - // - - @Override - public String toString() { - return "Session{" + - "sessionId='" + sessionId + '\'' + - ", state=" + state.get() + - ", participants=" + participants.size() + - ", createdAt=" + createdAt + - '}'; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Session session = (Session) o; - return Objects.equals(sessionId, session.sessionId); - } - - @Override - public int hashCode() { - return Objects.hash(sessionId); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/communication/session/SessionConfig.java b/src/main/java/space/qu4nt/entanglementlib/security/communication/session/SessionConfig.java deleted file mode 100644 index 72080fd..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/communication/session/SessionConfig.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.communication.session; - -import lombok.Builder; -import lombok.Getter; -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.security.crypto.KEMType; -import space.qu4nt.entanglementlib.security.crypto.SignatureType; - -import java.time.Duration; - -/// 세션 설정을 담는 불변 클래스입니다. -/// -/// 빌더 패턴을 사용하여 세션의 다양한 설정을 구성할 수 있습니다. -/// -/// ## 사용 예시 -/// ```java -/// SessionConfig config = SessionConfig.builder() -/// .maxParticipants(10) -/// .sessionTimeout(Duration.ofHours(1)) -/// .idleTimeout(Duration.ofMinutes(15)) -/// .kemType(KEMType.ML_KEM_768) -/// .signatureType(SignatureType.ML_DSA_65) -/// .build(); -/// ``` -/// -/// @author Q. T. Felix -/// @see Session -/// @since 1.1.0 -@Getter -@Builder -public class SessionConfig { - - /// 기본 최대 참여자 수 (0 = 무제한) - public static final int DEFAULT_MAX_PARTICIPANTS = 0; - - /// 기본 세션 타임아웃 (24시간) - public static final Duration DEFAULT_SESSION_TIMEOUT = Duration.ofHours(24); - - /// 기본 유휴 타임아웃 (30분) - public static final Duration DEFAULT_IDLE_TIMEOUT = Duration.ofMinutes(30); - - /// 기본 인바운드 버퍼 크기 (64KB) - public static final int DEFAULT_BUFFER_SIZE = 64 * 1024; - - /// 최대 참여자 수 (0 = 무제한) - @Builder.Default - private final int maxParticipants = DEFAULT_MAX_PARTICIPANTS; - - /// 세션 전체 타임아웃 (세션 생성 후 최대 유지 시간) - @Builder.Default - private final Duration sessionTimeout = DEFAULT_SESSION_TIMEOUT; - - /// 유휴 타임아웃 (마지막 활동 후 세션 종료까지 시간) - @Builder.Default - private final Duration idleTimeout = DEFAULT_IDLE_TIMEOUT; - - /// 참여자별 인바운드 버퍼 크기 - @Builder.Default - private final int bufferSize = DEFAULT_BUFFER_SIZE; - - /// 세션에서 사용할 기본 KEM 알고리즘 - @Builder.Default - private final KEMType defaultKemType = KEMType.ML_KEM_768; - - /// 세션에서 사용할 기본 서명 알고리즘 - @Builder.Default - private final SignatureType defaultSignatureType = SignatureType.ML_DSA_65; - - /// 클래식 ECDH(X25519) 사용 여부 (하이브리드 모드용) - @Builder.Default - private final boolean useClassicEcdh = true; - - /// 자동 재연결 허용 여부 - @Builder.Default - private final boolean allowReconnection = true; - - /// 재연결 허용 시간 (연결 끊김 후 재연결 가능 시간) - @Builder.Default - private final Duration reconnectionWindow = Duration.ofMinutes(5); - - /// 핸드셰이크 타임아웃 - @Builder.Default - private final Duration handshakeTimeout = Duration.ofSeconds(30); - - /// 세션 이름 (선택적, 디버깅/로깅용) - private final String sessionName; - - /// 세션 메타데이터 (사용자 정의 데이터) - private final Object metadata; - - /// 기본 설정으로 SessionConfig를 생성합니다. - /// - /// @return 기본 설정의 SessionConfig - @NotNull - public static SessionConfig defaults() { - return SessionConfig.builder().build(); - } - - /// 고보안 설정으로 SessionConfig를 생성합니다. - /// [KEMType#X25519MLKEM768]과 [SignatureType#ML_DSA_87]을 사용하며, - /// 짧은 타임아웃을 적용합니다. - /// - /// @return 고보안 설정의 SessionConfig - @NotNull - public static SessionConfig highSecurity() { - return SessionConfig.builder() - .defaultKemType(KEMType.X25519MLKEM768) - .defaultSignatureType(SignatureType.ML_DSA_87) - .sessionTimeout(Duration.ofHours(4)) - .idleTimeout(Duration.ofMinutes(10)) - .handshakeTimeout(Duration.ofSeconds(15)) - .allowReconnection(false) - .build(); - } - - /// 경량 설정으로 SessionConfig를 생성합니다. - /// ML-KEM-512와 ML-DSA-44를 사용하며, 긴 타임아웃을 적용합니다. - /// - /// @return 경량 설정의 SessionConfig - @NotNull - public static SessionConfig lightweight() { - return SessionConfig.builder() - .defaultKemType(KEMType.ML_KEM_512) - .defaultSignatureType(SignatureType.ML_DSA_44) - .sessionTimeout(Duration.ofHours(48)) - .idleTimeout(Duration.ofHours(2)) - .handshakeTimeout(Duration.ofSeconds(60)) - .build(); - } - - /// 세션 타임아웃을 밀리초 단위로 반환합니다. - /// - /// @return 세션 타임아웃 (밀리초) - public long getSessionTimeoutMillis() { - return sessionTimeout.toMillis(); - } - - /// 유휴 타임아웃을 밀리초 단위로 반환합니다. - /// - /// @return 유휴 타임아웃 (밀리초) - public long getIdleTimeoutMillis() { - return idleTimeout.toMillis(); - } - - /// 핸드셰이크 타임아웃을 밀리초 단위로 반환합니다. - /// - /// @return 핸드셰이크 타임아웃 (밀리초) - public long getHandshakeTimeoutMillis() { - return handshakeTimeout.toMillis(); - } - - /// 재연결 허용 시간을 밀리초 단위로 반환합니다. - /// - /// @return 재연결 허용 시간 (밀리초) - public long getReconnectionWindowMillis() { - return reconnectionWindow.toMillis(); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/communication/session/SessionEventListener.java b/src/main/java/space/qu4nt/entanglementlib/security/communication/session/SessionEventListener.java deleted file mode 100644 index 03bc23c..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/communication/session/SessionEventListener.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.communication.session; - -/// 세션 이벤트를 수신하는 리스너 인터페이스입니다. -/// -/// 세션의 생명주기와 참여자 변경 이벤트를 처리할 수 있습니다. -/// 모든 메소드는 기본 구현(no-op)을 제공하므로 필요한 이벤트만 -/// 선택적으로 오버라이드할 수 있습니다. -/// -/// ## 사용 예시 -/// ```java -/// session.addEventListener(new SessionEventListener() { -/// @Override -/// public void onParticipantJoined(Session session, Participant participant) { -/// System.out.println("새 참여자: " + participant.getId()); -/// } -/// }); -/// ``` -/// -/// @author Q. T. Felix -/// @see Session -/// @since 1.1.0 -public interface SessionEventListener { - - /// 참여자가 세션에 참여했을 때 호출됩니다. - /// - /// @param session 세션 - /// @param participant 참여한 참여자 - default void onParticipantJoined(Session session, Participant participant) { - } - - /// 참여자가 세션에서 나갔을 때 호출됩니다. - /// - /// @param session 세션 - /// @param participant 나간 참여자 - default void onParticipantLeft(Session session, Participant participant) { - } - - /// 세션 상태가 변경되었을 때 호출됩니다. - /// - /// @param session 세션 - /// @param oldState 이전 상태 - /// @param newState 새 상태 - default void onSessionStateChanged(Session session, SessionState oldState, SessionState newState) { - } - - /// 세션이 종료 중일 때 호출됩니다. - /// 정리 작업이 시작되기 전에 호출됩니다. - /// - /// @param session 세션 - default void onSessionClosing(Session session) { - } - - /// 세션이 정상 종료되었을 때 호출됩니다. - /// - /// @param session 세션 - default void onSessionClosed(Session session) { - } - - /// 세션이 강제 종료되었을 때 호출됩니다. - /// - /// @param session 세션 - default void onSessionTerminated(Session session) { - } - - /// 세션 보안 컨텍스트가 변경되었을 때 호출됩니다. - /// - /// @param session 세션 - default void onSecurityContextChanged(Session session) { - } - - /// 세션에서 오류가 발생했을 때 호출됩니다. - /// - /// @param session 세션 - /// @param throwable 발생한 오류 - default void onSessionError(Session session, Throwable throwable) { - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/communication/session/SessionSecurityContext.java b/src/main/java/space/qu4nt/entanglementlib/security/communication/session/SessionSecurityContext.java deleted file mode 100644 index 9935c32..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/communication/session/SessionSecurityContext.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.communication.session; - -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.exception.session.EntLibSessionIllegalStateException; -import space.qu4nt.entanglementlib.security.crypto.KEMType; -import space.qu4nt.entanglementlib.security.crypto.SignatureType; - -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; - -/// 세션 수준의 보안 컨텍스트를 관리하는 클래스입니다. -/// -/// 이 클래스는 세션 전체에 적용되는 암호화 설정과 키 자료를 관리합니다. -/// 개별 참여자의 [ParticipantSecurityContext]와 구분되며, 그룹 통신 시 공통으로 -/// 사용되는 보안 파라미터를 포함합니다. -/// -/// ## 주요 구성 요소 -/// - 세션 마스터 키 (그룹 암호화용) -/// - 협상된 암호화 알고리즘 -/// - 세션 수준 시퀀스 카운터 -/// -/// @author Q. T. Felix -/// @see Session -/// @see ParticipantSecurityContext -/// @since 1.1.0 -@Slf4j -@Getter -public class SessionSecurityContext { - - /// 세션 마스터 키 (그룹 브로드캐스트 암호화용) - private volatile SensitiveDataContainer sessionMasterKey; - - /// 세션 마스터 키 파생용 솔트 - private volatile SensitiveDataContainer masterKeySalt; - - /// 협상된 KEM 알고리즘 - private volatile KEMType negotiatedKemType; - - /// 협상된 서명 알고리즘 - private volatile SignatureType negotiatedSignatureType; - - /// 클래식 ECDH 사용 여부 (하이브리드 모드) - private volatile boolean classicEcdhEnabled; - - /// 세션 수준 메시지 카운터 (리플레이 공격 방지) - private final AtomicLong sessionMessageCounter; - - /// 마지막 키 갱신 시각 - private volatile long lastKeyRotationAt; - - /// 키 갱신 주기 (밀리초, 0이면 자동 갱신 안함) - private volatile long keyRotationIntervalMillis; - - /// 컨텍스트 초기화 완료 여부 - private final AtomicBoolean initialized; - - /// 컨텍스트 정리 완료 여부 - private final AtomicBoolean cleared; - - public SessionSecurityContext() { - this.sessionMessageCounter = new AtomicLong(0); - this.initialized = new AtomicBoolean(false); - this.cleared = new AtomicBoolean(false); - this.lastKeyRotationAt = System.currentTimeMillis(); - this.keyRotationIntervalMillis = 0; - } - - /// 세션 보안 컨텍스트를 초기화합니다. - /// - /// @param masterKey 세션 마스터 키 - /// @param salt 마스터 키 솔트 - /// @param kemType KEM 알고리즘 - /// @param signatureType 서명 알고리즘 - /// @param useClassicEcdh 클래식 ECDH 사용 여부 - public void initialize( - @NotNull SensitiveDataContainer masterKey, - @Nullable SensitiveDataContainer salt, - @NotNull KEMType kemType, - @NotNull SignatureType signatureType, - boolean useClassicEcdh - ) throws EntLibSessionIllegalStateException { - if (cleared.get()) { - throw new EntLibSessionIllegalStateException("정리된 보안 컨텍스트는 초기화할 수 없습니다."); - } - - this.sessionMasterKey = masterKey; - this.masterKeySalt = salt; - this.negotiatedKemType = kemType; - this.negotiatedSignatureType = signatureType; - this.classicEcdhEnabled = useClassicEcdh; - this.lastKeyRotationAt = System.currentTimeMillis(); - - initialized.set(true); - log.debug("세션 보안 컨텍스트 초기화됨: KEM={}, Sig={}, ClassicECDH={}", - kemType, signatureType, useClassicEcdh); - } - - /// 다음 세션 메시지 카운터 값을 반환하고 증가시킵니다. - /// - /// @return 현재 메시지 카운터 값 - public long getNextMessageCounter() { - return sessionMessageCounter.getAndIncrement(); - } - - /// 현재 세션 메시지 카운터 값을 반환합니다. - /// - /// @return 현재 메시지 카운터 값 - public long getCurrentMessageCounter() { - return sessionMessageCounter.get(); - } - - /// 키 갱신이 필요한지 확인합니다. - /// - /// @return 키 갱신 필요 여부 - public boolean needsKeyRotation() { - if (keyRotationIntervalMillis <= 0) { - return false; - } - return System.currentTimeMillis() - lastKeyRotationAt >= keyRotationIntervalMillis; - } - - /// 세션 마스터 키를 갱신합니다. - /// - /// @param newMasterKey 새 마스터 키 - /// @param newSalt 새 솔트 (선택적) - public void rotateKey(@NotNull SensitiveDataContainer newMasterKey, - @Nullable SensitiveDataContainer newSalt) throws EntLibSessionIllegalStateException { - if (!initialized.get()) - throw new EntLibSessionIllegalStateException("초기화되지 않은 보안 컨텍스트입니다."); - if (cleared.get()) - throw new EntLibSessionIllegalStateException("정리된 보안 컨텍스트입니다."); - - // 기존 키 안전하게 정리 - SensitiveDataContainer oldKey = this.sessionMasterKey; - SensitiveDataContainer oldSalt = this.masterKeySalt; - - this.sessionMasterKey = newMasterKey; - this.masterKeySalt = newSalt; - this.lastKeyRotationAt = System.currentTimeMillis(); - - // 기존 키 안전하게 삭제 - if (oldKey != null) - oldKey.close(); - if (oldSalt != null) - oldSalt.close(); - - log.debug("세션 마스터 키 갱신됨"); - } - - /// 키 갱신 주기를 설정합니다. - /// - /// @param intervalMillis 갱신 주기 (밀리초, 0이면 자동 갱신 안함) - public void setKeyRotationInterval(long intervalMillis) { - this.keyRotationIntervalMillis = intervalMillis; - } - - /// 보안 컨텍스트가 초기화되었는지 확인합니다. - /// - /// @return 초기화 완료 여부 - public boolean isInitialized() { - return initialized.get(); - } - - /// 보안 컨텍스트가 정리되었는지 확인합니다. - /// - /// @return 정리 완료 여부 - public boolean isCleared() { - return cleared.get(); - } - - /// 보안 컨텍스트의 모든 민감한 데이터를 안전하게 정리합니다. - /// - /// 이 메소드 호출 후에는 컨텍스트를 더 이상 사용할 수 없습니다. - public void clear() { - if (cleared.compareAndSet(false, true)) { - if (sessionMasterKey != null) { - sessionMasterKey.close(); - sessionMasterKey = null; - } - if (masterKeySalt != null) { - masterKeySalt.close(); - masterKeySalt = null; - } - - sessionMessageCounter.set(0); - initialized.set(false); - - log.debug("세션 보안 컨텍스트 정리됨"); - } - } - - /// 보안 컨텍스트의 요약 정보를 반환합니다. - /// - /// @return 요약 문자열 (민감한 정보 제외) - @Override - public String toString() { - return "SessionSecurityContext{" + - "initialized=" + initialized.get() + - ", cleared=" + cleared.get() + - ", kemType=" + negotiatedKemType + - ", signatureType=" + negotiatedSignatureType + - ", classicEcdh=" + classicEcdhEnabled + - ", messageCounter=" + sessionMessageCounter.get() + - '}'; - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/communication/session/SessionState.java b/src/main/java/space/qu4nt/entanglementlib/security/communication/session/SessionState.java deleted file mode 100644 index 4597753..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/communication/session/SessionState.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.communication.session; - -/// 세션의 생명주기 상태를 나타내는 열거형입니다. -/// -/// @author Q. T. Felix -/// @see Session -/// @since 1.1.0 -public enum SessionState { - - /// 세션이 생성되었지만 아직 활성화되지 않았습니다. - CREATED, - - /// 세션이 활성 상태입니다. 참여자가 참여하고 통신할 수 있습니다. - ACTIVE, - - /// 세션이 일시 중단되었습니다. 새 참여자 참여가 차단됩니다. - SUSPENDED, - - /// 세션이 종료 중입니다. 정리 작업이 진행 중입니다. - CLOSING, - - /// 세션이 정상적으로 종료되었습니다. - CLOSED, - - /// 세션이 오류로 인해 강제 종료되었습니다. - TERMINATED -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/communication/tls/HandshakeMessage.java b/src/main/java/space/qu4nt/entanglementlib/security/communication/tls/HandshakeMessage.java deleted file mode 100644 index e1a44e5..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/communication/tls/HandshakeMessage.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.communication.tls; - -/// TLS 핸드셰이크 메시지 타입을 정의하는 클래스입니다. -/// -/// 포스트-퀀텀 TLS 핸드셰이크 프로토콜: -/// 1. ClientHello: 클라이언트가 공개키와 지원 알고리즘 목록 전송 -/// 2. ServerHello: 서버가 자신의 공개키와 캡슐화된 공유 비밀 전송 -/// 3. Finished: 양측이 핸드셰이크 완료 확인 -/// -/// @author Q. T. Felix -/// @see Server -/// @since 1.1.0 -public final class HandshakeMessage { - - private HandshakeMessage() { - } - - // - // Message Types - // - - /// 클라이언트 Hello 메시지 - /// 클라이언트가 연결 시작 시 전송하며, 공개키와 지원 알고리즘 포함 - public static final byte CLIENT_HELLO = 0x01; - - /// 서버 Hello 메시지 - /// 서버가 ClientHello에 응답하여 전송하며, 공개키와 캡슐화 결과 포함 - public static final byte SERVER_HELLO = 0x02; - - /// 인증서 메시지 (선택적) - /// 서버 또는 클라이언트의 인증서 체인 - public static final byte CERTIFICATE = 0x03; - - /// 인증서 검증 메시지 (선택적) - /// 인증서의 개인키 소유 증명 - public static final byte CERTIFICATE_VERIFY = 0x04; - - /// 핸드셰이크 완료 메시지 - /// 핸드셰이크 과정의 무결성 확인 - public static final byte FINISHED = 0x05; - - /// 키 업데이트 메시지 - /// 세션 중 키 갱신 요청 - public static final byte KEY_UPDATE = 0x06; - - /// 경고 메시지 - /// 오류 또는 경고 상황 알림 - public static final byte ALERT = 0x07; - - // - // Alert Types - // - - /// 경고 수준: 경고 (연결 유지) - public static final byte ALERT_LEVEL_WARNING = 0x01; - - /// 경고 수준: 치명적 (연결 종료) - public static final byte ALERT_LEVEL_FATAL = 0x02; - - // - // Alert Descriptions - // - - /// 정상 종료 - public static final byte ALERT_CLOSE_NOTIFY = 0x00; - - /// 예상치 못한 메시지 - public static final byte ALERT_UNEXPECTED_MESSAGE = 0x0A; - - /// 잘못된 MAC - public static final byte ALERT_BAD_RECORD_MAC = 0x14; - - /// 핸드셰이크 실패 - public static final byte ALERT_HANDSHAKE_FAILURE = 0x28; - - /// 잘못된 인증서 - public static final byte ALERT_BAD_CERTIFICATE = 0x2A; - - /// 인증서 만료 - public static final byte ALERT_CERTIFICATE_EXPIRED = 0x2D; - - /// 알 수 없는 CA - public static final byte ALERT_UNKNOWN_CA = 0x30; - - /// 프로토콜 버전 불일치 - public static final byte ALERT_PROTOCOL_VERSION = 0x46; - - /// 내부 오류 - public static final byte ALERT_INTERNAL_ERROR = 0x50; - - // - // Protocol Version - // - - /// 프로토콜 버전 1.0 - public static final byte VERSION_1_0 = 0x01; - - // - // Key Exchange Algorithms - // - - /// ML-KEM-512 - public static final byte KE_ML_KEM_512 = 0x01; - - /// ML-KEM-768 - public static final byte KE_ML_KEM_768 = 0x02; - - /// ML-KEM-1024 - public static final byte KE_ML_KEM_1024 = 0x03; - - /// X25519 - public static final byte KE_X25519 = 0x10; - - /// X25519 + ML-KEM-768 (하이브리드) - public static final byte KE_X25519_ML_KEM_768 = 0x20; - - // - // Utility Methods - // - - /// 메시지 타입의 이름을 반환합니다. - /// - /// @param type 메시지 타입 - /// @return 메시지 타입 이름 - public static String getTypeName(byte type) { - return switch (type) { - case CLIENT_HELLO -> "ClientHello"; - case SERVER_HELLO -> "ServerHello"; - case CERTIFICATE -> "Certificate"; - case CERTIFICATE_VERIFY -> "CertificateVerify"; - case FINISHED -> "Finished"; - case KEY_UPDATE -> "KeyUpdate"; - case ALERT -> "Alert"; - default -> "Unknown(" + type + ")"; - }; - } - - /// 키 교환 알고리즘의 이름을 반환합니다. - /// - /// @param algorithm 알고리즘 식별자 - /// @return 알고리즘 이름 - public static String getKeyExchangeName(byte algorithm) { - return switch (algorithm) { - case KE_ML_KEM_512 -> "ML-KEM-512"; - case KE_ML_KEM_768 -> "ML-KEM-768"; - case KE_ML_KEM_1024 -> "ML-KEM-1024"; - case KE_X25519 -> "X25519"; - case KE_X25519_ML_KEM_768 -> "X25519+ML-KEM-768"; - default -> "Unknown(" + algorithm + ")"; - }; - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/communication/tls/Server.java b/src/main/java/space/qu4nt/entanglementlib/security/communication/tls/Server.java deleted file mode 100644 index edbdfda..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/communication/tls/Server.java +++ /dev/null @@ -1,859 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.communication.tls; - -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalArgumentException; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalStateException; -import space.qu4nt.entanglementlib.exception.secure.crypto.EntLibCryptoCipherProcessException; -import space.qu4nt.entanglementlib.exception.server.EntLibServerIllegalStateException; -import space.qu4nt.entanglementlib.exception.server.EntLibServerSecurityWarningException; -import space.qu4nt.entanglementlib.exception.session.EntLibSessionException; -import space.qu4nt.entanglementlib.security.communication.session.*; -import space.qu4nt.entanglementlib.security.crypto.*; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.EntLibAsymmetricKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.detail.MLKEMKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.detail.X25519KeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.detail.X25519MLKEM768KeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.BlockCipherStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.CipherStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.NativeECDHStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.NativeKEMStrategy; -import space.qu4nt.entanglementlib.util.wrapper.Pair; - -import java.io.Closeable; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.nio.ByteBuffer; -import java.nio.channels.*; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; - -/// PQC를 지원하는 TLS 서버 구현체입니다. -/// -/// 이 서버는 NIO(Non-blocking I/O)를 사용하여 다수의 클라이언트 연결을 -/// 효율적으로 처리합니다. ML-KEM, X25519 등의 키 캡슐화 메커니즘을 통해 -/// 양자 내성 보안 통신을 제공합니다. -/// -/// ## 주요 기능 -/// - NIO 기반 비동기 네트워크 처리 -/// - 세션 기반 연결 관리 -/// - 양자-내성 키 교환 (ML-KEM-512/768/1024) -/// - 하이브리드 모드 (X25519 + ML-KEM) -/// - 자동 핸드셰이크 타임아웃 처리 -/// -/// ## 사용 예시 -/// ```java -/// ServerConfig config = ServerConfig.builder() -/// .port(8443) -/// .sessionConfig(SessionConfig.highSecurity()) -/// .build(); -/// -/// Server server = new Server(config); -/// server.start(); -/// ``` -/// -/// @author Q. T. Felix -/// @see Session -/// @see NativeKEMStrategy -/// @see NativeECDHStrategy -/// @since 1.1.0 -@Slf4j -public class Server implements Closeable { - - // - // Constants - // - - private static final int SELECT_TIMEOUT_MS = 100; - private static final int HANDSHAKE_CHECK_INTERVAL_MS = 1000; - private static final int MAX_HANDSHAKE_MSG_SIZE = 16 * 1024; - - // - // Configuration - // - - @Getter - private final ServerConfig config; - - // - // Network Components - // - - private ServerSocketChannel serverChannel; - private Selector selector; - - // - // Server State - // - - private final AtomicReference state; - private final AtomicBoolean running; - - // - // Session Management - // - - /// 활성 세션 저장소 (세션 ID -> 세션) - private final ConcurrentHashMap activeSessions; - - /// 채널별 참여자 매핑 (빠른 조회용) - private final ConcurrentHashMap channelToParticipant; - - /// 참여자별 세션 매핑 - private final ConcurrentHashMap participantToSession; - - // - // Security Components - // - - /// 서버의 장기 키페어 (인증용) - private SensitiveDataContainer serverPublicKey; - private SensitiveDataContainer serverSecretKey; - - // - // Thread Management - // - - private Thread acceptThread; - private Thread eventLoopThread; - private ScheduledExecutorService scheduledExecutor; - - // - // Event Listeners - // - - private final CopyOnWriteArrayList eventListeners; - - // - // Constructor - // - - /// 지정된 설정으로 서버를 생성합니다. - /// - /// @param config 서버 설정 - public Server(@NotNull ServerConfig config) { - this.config = config; - this.state = new AtomicReference<>(ServerState.CREATED); - this.running = new AtomicBoolean(false); - - this.activeSessions = new ConcurrentHashMap<>(); - this.channelToParticipant = new ConcurrentHashMap<>(); - this.participantToSession = new ConcurrentHashMap<>(); - - this.eventListeners = new CopyOnWriteArrayList<>(); - - log.debug("서버 인스턴스 생성됨: 포트 {}", config.getPort()); - } - - /// 기본 설정으로 서버를 생성합니다. - /// - /// @param port 서버 포트 - public Server(int port) { - this(ServerConfig.builder().port(port).build()); - } - - // - // Lifecycle Management - // - - /// 서버를 시작합니다. - /// - /// @throws IOException 네트워크 오류 발생 시 - public void start() throws IOException { - if (!state.compareAndSet(ServerState.CREATED, ServerState.STARTING)) { - throw new IllegalStateException("서버가 이미 시작되었거나 종료된 상태입니다: " + state.get()); - } - - try { - initializeServerKeys(); - initializeNetwork(); - initializeThreads(); - - running.set(true); - state.set(ServerState.RUNNING); - - notifyListeners(listener -> listener.onServerStarted(this)); - log.info("서버 시작됨: {}:{}", config.getBindAddress(), config.getPort()); - } catch (Throwable e) { - state.set(ServerState.FAILED); - cleanup(); - throw new IOException("서버 시작 실패", e); - } - } - - /// 서버를 정상 종료합니다. - public void stop() { - if (!running.compareAndSet(true, false)) { - return; - } - - state.set(ServerState.STOPPING); - log.info("서버 종료 중..."); - - try { - // 모든 세션 종료 - activeSessions.values().forEach(Session::close); - - // 스레드 종료 대기 - if (scheduledExecutor != null) { - scheduledExecutor.shutdown(); - scheduledExecutor.awaitTermination(5, TimeUnit.SECONDS); - } - - // 네트워크 리소스 정리 - cleanup(); - - state.set(ServerState.STOPPED); - notifyListeners(listener -> listener.onServerStopped(this)); - log.info("서버 종료 완료"); - } catch (Exception e) { - state.set(ServerState.FAILED); - log.error("서버 종료 중 오류", e); - } - } - - @Override - public void close() { - stop(); - } - - // - // Initialization - // - - private void initializeServerKeys() throws Throwable { - // 서버 장기 키페어 생성 (기본 KEM 타입 사용) - KEMType kemType = config.getSessionConfig().getDefaultKemType(); - EntLibAsymmetricKeyStrategy keyStrategy = - EntLibCryptoRegistry.getKeyStrategy(kemType, EntLibAsymmetricKeyStrategy.class); - - if (keyStrategy instanceof X25519MLKEM768KeyStrategy hybridKey) { - X25519KeyStrategy x25519KeyStrategy = EntLibCryptoRegistry.getKeyStrategy(KEMType.X25519, X25519KeyStrategy.class); - MLKEMKeyStrategy mlkem768KeyStrategy = EntLibCryptoRegistry.getKeyStrategy(KEMType.ML_KEM_768, MLKEMKeyStrategy.class); - hybridKey.setX25519Key(x25519KeyStrategy); - hybridKey.setMlkem768Key(mlkem768KeyStrategy); - log.debug("X25519MLKEM768 하이브리드 알고리즘에 따른 개별 스트레티지 주입"); - } - - Pair keyPair = keyStrategy.generateKeyPair(); - this.serverPublicKey = keyPair.getFirst(); - this.serverSecretKey = keyPair.getSecond(); - - log.debug("서버 키페어 생성 완료: {}", kemType); - } - - private void initializeNetwork() throws IOException { - // Selector 생성 - this.selector = Selector.open(); - - // 서버 소켓 채널 설정 - this.serverChannel = ServerSocketChannel.open(); - serverChannel.configureBlocking(false); - serverChannel.socket().setReuseAddress(true); - - // 바인드 - SocketAddress bindAddress = new InetSocketAddress( - config.getBindAddress(), - config.getPort() - ); - serverChannel.bind(bindAddress, config.getBacklog()); - - // Accept 이벤트 등록 - serverChannel.register(selector, SelectionKey.OP_ACCEPT); - - log.debug("네트워크 초기화 완료: {}", bindAddress); - } - - private void initializeThreads() { - // 이벤트 루프 스레드 - this.eventLoopThread = new Thread(this::eventLoop, "TLS-Server-EventLoop"); - eventLoopThread.setDaemon(true); - eventLoopThread.start(); - - // 스케줄러 (타임아웃 체크 등) - this.scheduledExecutor = Executors.newSingleThreadScheduledExecutor(r -> { - Thread t = new Thread(r, "TLS-Server-Scheduler"); - t.setDaemon(true); - return t; - }); - - // 핸드셰이크 타임아웃 체크 - scheduledExecutor.scheduleAtFixedRate( - this::checkHandshakeTimeouts, - HANDSHAKE_CHECK_INTERVAL_MS, - HANDSHAKE_CHECK_INTERVAL_MS, - TimeUnit.MILLISECONDS - ); - - // 세션 타임아웃 체크 - scheduledExecutor.scheduleAtFixedRate( - this::checkSessionTimeouts, - 5000, - 5000, - TimeUnit.MILLISECONDS - ); - - log.debug("스레드 초기화 완료"); - } - - // - // Event Loop - // - - private void eventLoop() { - log.debug("이벤트 루프 시작"); - - while (running.get()) { - try { - int readyCount = selector.select(SELECT_TIMEOUT_MS); - - if (readyCount == 0) { - continue; - } - - Set selectedKeys = selector.selectedKeys(); - Iterator iterator = selectedKeys.iterator(); - - while (iterator.hasNext()) { - SelectionKey key = iterator.next(); - iterator.remove(); - - try { - if (!key.isValid()) { - continue; - } - - if (key.isAcceptable()) { - handleAccept(key); - } else if (key.isReadable()) { - handleRead(key); - } else if (key.isWritable()) { - handleWrite(key); - } - } catch (CancelledKeyException e) { - log.debug("취소된 키: {}", key); - } catch (Exception e) { - log.error("키 처리 중 오류", e); - closeChannel(key); - } - } - } catch (ClosedSelectorException e) { - log.debug("Selector 종료됨"); - break; - } catch (Exception e) { - if (running.get()) { - log.error("이벤트 루프 오류", e); - } - } - } - - log.debug("이벤트 루프 종료"); - } - - // - // Connection Handling - // - - private void handleAccept(SelectionKey key) throws IOException { - ServerSocketChannel serverCh = (ServerSocketChannel) key.channel(); - SocketChannel clientChannel = serverCh.accept(); - - if (clientChannel == null) { - return; - } - - clientChannel.configureBlocking(false); - - // 참여자 생성 - String participantId = UUID.randomUUID().toString(); - ParticipantSecurityContext securityContext = new ParticipantSecurityContext(null, null, null); - Participant participant = new Participant( - participantId, - ParticipantRole.RESPONDER, - clientChannel, - config.getSessionConfig().getBufferSize(), - securityContext - ); - - // 채널 매핑 - channelToParticipant.put(clientChannel, participant); - - // 세션 생성 또는 기존 세션에 추가 - Session session = getOrCreateSession(); - try { - session.addParticipant(participant); - participantToSession.put(participantId, session); - } catch (EntLibSessionException e) { - log.error("참여자 추가 실패", e); - clientChannel.close(); - channelToParticipant.remove(clientChannel); - return; - } - - // READ 이벤트 등록 - clientChannel.register(selector, SelectionKey.OP_READ, participant); - - // 핸드셰이크 시작 - participant.transitionState(ConnectionState.HANDSHAKING); - - notifyListeners(listener -> listener.onClientConnected(this, participant)); - log.debug("새 클라이언트 연결: {} ({})", participantId, clientChannel.getRemoteAddress()); - } - - private void handleRead(SelectionKey key) throws IOException { - SocketChannel channel = (SocketChannel) key.channel(); - Participant participant = (Participant) key.attachment(); - - if (participant == null) { - participant = channelToParticipant.get(channel); - } - - if (participant == null) { - log.warn("알 수 없는 채널에서 읽기 이벤트"); - closeChannel(key); - return; - } - - ByteBuffer buffer = participant.getInboundBuffer(); - int bytesRead; - - try { - bytesRead = channel.read(buffer); - } catch (IOException e) { - log.debug("읽기 오류: {}", e.getMessage()); - closeChannel(key); - return; - } - - if (bytesRead == -1) { - // 연결 종료 - log.debug("클라이언트 연결 종료: {}", participant.getId()); - closeChannel(key); - return; - } - - if (bytesRead > 0) { - buffer.flip(); - - ConnectionState state = participant.getState().get(); - if (state == ConnectionState.HANDSHAKING) { - processHandshake(participant, buffer); - } else if (state == ConnectionState.ESTABLISHED) { - processApplicationData(participant, buffer); - } - - buffer.compact(); - } - } - - private void handleWrite(SelectionKey key) throws IOException { - Participant participant = (Participant) key.attachment(); - if (participant == null) { - key.cancel(); - return; - } - - // 큐에 있는 데이터를 채널로 밀어넣음 - boolean hasRemaining = participant.flushOutbound(); - - // 더 이상 보낼 데이터가 없다면 OP_WRITE 관심 해제 (CPU 점유 방지) - if (!hasRemaining) { - key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE); - } - } - - // - // Handshake Processing - // - - private void processHandshake(Participant participant, ByteBuffer data) { - try { - // 간단한 핸드셰이크 프로토콜 구현 - // 1. 클라이언트 Hello (공개키 포함) - // 2. 서버 Hello (캡슐화된 공유 비밀) - // 3. 핸드셰이크 완료 - - if (data.remaining() < 1) { - return; - } - - byte messageType = data.get(); - - switch (messageType) { - case HandshakeMessage.CLIENT_HELLO -> handleClientHello(participant, data); - case HandshakeMessage.FINISHED -> handleClientFinished(participant, data); - default -> log.warn("알 수 없는 핸드셰이크 메시지: {}", messageType); - } - } catch (Exception e) { - log.error("핸드셰이크 처리 오류: {}", participant.getId(), e); - closeParticipant(participant); - } - } - - private void handleClientHello(Participant participant, ByteBuffer data) throws Exception { - // 클라이언트 공개키 읽기 - // 헤더(길이 필드) 확인 가능한지 체크 - if (data.remaining() < 4) return; - - // 포지션 이동 없이 길이만 확인(PEEK) - data.mark(); - int keyLength = data.getInt(); - - if (keyLength < 0 || keyLength > MAX_HANDSHAKE_MSG_SIZE) { - throw new EntLibServerSecurityWarningException("핸드셰이크 상한선 초과", - "클라이언트 헬로(handleClientHello) 과정에서 키 길이 '" + keyLength + "'를 전달받았지만 방어했습니다!\n" + - "\t이는 단순 연산 오류일 수 있지만, 공격자의 악의적 소행일 가능성이 상대적으로 높습니다. 공격자는 다음의 공격을 시도했을 수 있습니다.\n" + - "\t\t1. '" + Integer.MAX_VALUE + "' 이상의 데이터를 전송하여 2GB 이상의 (바이트) 배열을 할당하려 시도합니다.\n" + - "\t\t2. 그럼, 서버 측은 수십 개의 연결만으로도 힙 메모리가 고갈되어 `OutOfMemoryError`로 서버가 다운될 수 있습니다.\n" + - "\t예외적으로 전달받은 값이 '0' 미만일 수 있습니다만, 이 경우는 사실 말이 안 됩니다.\n" + - "\t우선 어찌됐던 이 시도를 막아내었지만, 추가적인 보안 조치가 필요할 수 있습니다." - ); - } - - // 전체 바디가 도착했는지 확인 - if (data.remaining() < keyLength) { - data.reset(); // 포지션 원복 - return; // 다음 패킷 기다림 - } - - // 데이터 읽기(COMMIT) - byte[] clientPublicKeyBytes = new byte[keyLength]; - data.get(clientPublicKeyBytes); - - // 공개키 저장 - SensitiveDataContainer clientPublicKey = new SensitiveDataContainer(keyLength); - clientPublicKey.getMemorySegment().asByteBuffer().put(clientPublicKeyBytes); - - // KEM 캡슐화 수행 - KEMType kemType = config.getSessionConfig().getDefaultKemType(); - NativeKEMStrategy kemStrategy = EntLibCryptoRegistry.getAlgStrategy(kemType, NativeKEMStrategy.class); - - final SensitiveDataContainer encapResult = kemStrategy.encapsulate(clientPublicKey); - - // 공유 비밀 추출 및 세션 키 설정 - // encapResult는 (sharedSecret, ciphertext)를 포함 - // TODO: 실제 세션 키 설정 로직 - - // 서버 Hello 응답 전송 - sendServerHello(participant, encapResult); - - log.debug("Client Hello 처리 완료: {}", participant.getId()); - } - - private void sendServerHello(Participant participant, SensitiveDataContainer encapResult) throws IOException { - SocketChannel channel = participant.getChannel(); - - // 메시지 구성: [타입(1)] + [서버 공개키 길이(4)] + [서버 공개키] + [캡슐화 결과 길이(4)] + [캡슐화 결과] - int serverPkLength = (int) serverPublicKey.getMemorySegment().byteSize(); - int encapLength = (int) encapResult.getMemorySegment().byteSize(); - - ByteBuffer response = ByteBuffer.allocate(1 + 4 + serverPkLength + 4 + encapLength); - response.put(HandshakeMessage.SERVER_HELLO); - response.putInt(serverPkLength); - - ByteBuffer pkBuffer = serverPublicKey.getMemorySegment().asByteBuffer(); - response.put(pkBuffer); - - response.putInt(encapLength); - ByteBuffer encapBuffer = encapResult.getMemorySegment().asByteBuffer(); - response.put(encapBuffer); - - response.flip(); - channel.write(response); - - log.debug("Server Hello 전송: {}", participant.getId()); - } - - private void handleClientFinished(Participant participant, ByteBuffer data) { - // 핸드셰이크 완료 처리 - participant.transitionState(ConnectionState.ESTABLISHED); - - notifyListeners(listener -> listener.onHandshakeCompleted(this, participant)); - log.info("핸드셰이크 완료: {}", participant.getId()); - } - - // - // Application Data Processing - // - - private void processApplicationData(Participant participant, ByteBuffer data) { - // 암호화된 애플리케이션 데이터 처리 - // TODO: 실제 복호화 및 데이터 처리 로직 - - notifyListeners(listener -> listener.onDataReceived(this, participant, data)); - } - - // - // Session Management - // - - private Session getOrCreateSession() { - // 단일 세션 모드 (설정에 따라 다중 세션 지원 가능) - if (config.isSingleSessionMode() && !activeSessions.isEmpty()) { - return activeSessions.values().iterator().next(); - } - - Session session = Session.create(config.getSessionConfig()); - try { - session.activate(); - } catch (EntLibSessionException e) { - log.error("세션 활성화 실패", e); - } - activeSessions.put(session.getSessionId(), session); - return session; - } - - /// 특정 세션을 조회합니다. - /// - /// @param sessionId 세션 ID - /// @return 세션 (없으면 null) - @Nullable - public Session getSession(@NotNull String sessionId) { - return activeSessions.get(sessionId); - } - - /// 모든 활성 세션을 반환합니다. - /// - /// @return 활성 세션 맵 (불변) - @NotNull - public Map getActiveSessions() { - return Map.copyOf(activeSessions); - } - - /// 현재 연결된 클라이언트 수를 반환합니다. - /// - /// @return 연결된 클라이언트 수 - public int getConnectedClientCount() { - return channelToParticipant.size(); - } - - // - // Timeout Management - // - - private void checkHandshakeTimeouts() { - long now = System.currentTimeMillis(); - long timeout = config.getSessionConfig().getHandshakeTimeoutMillis(); - - channelToParticipant.forEach((_, participant) -> { - if (participant.getState().get() == ConnectionState.HANDSHAKING) { - long elapsed = now - participant.getConnectedAt(); - if (elapsed > timeout) { - log.warn("핸드셰이크 타임아웃: {}", participant.getId()); - closeParticipant(participant); - } - } - }); - } - - private void checkSessionTimeouts() { - long now = System.currentTimeMillis(); - - activeSessions.forEach((id, session) -> { - // 세션 전체 타임아웃 - if (session.getDuration() > session.getConfig().getSessionTimeoutMillis()) { - log.info("세션 타임아웃 (전체): {}", id); - session.close(); - activeSessions.remove(id); - return; - } - - // 유휴 타임아웃 - if (session.getIdleTime() > session.getConfig().getIdleTimeoutMillis()) { - log.info("세션 타임아웃 (유휴): {}", id); - session.close(); - activeSessions.remove(id); - } - }); - } - - // - // Cleanup - // - - private void closeChannel(SelectionKey key) { - try { - SocketChannel channel = (SocketChannel) key.channel(); - Participant participant = channelToParticipant.remove(channel); - - if (participant != null) { - closeParticipant(participant); - } - - key.cancel(); - channel.close(); - } catch (IOException e) { - log.debug("채널 종료 오류", e); - } - } - - private void closeParticipant(Participant participant) { - participant.transitionState(ConnectionState.CLOSED); - channelToParticipant.remove(participant.getChannel()); - - Session session = participantToSession.remove(participant.getId()); - if (session != null) { - session.removeParticipant(participant.getId()); - } - - try { - participant.getChannel().close(); - } catch (IOException e) { - log.debug("참여자 채널 종료 오류", e); - } - - notifyListeners(listener -> listener.onClientDisconnected(this, participant)); - } - - private void cleanup() { - try { - if (serverChannel != null && serverChannel.isOpen()) { - serverChannel.close(); - } - if (selector != null && selector.isOpen()) { - selector.close(); - } - - // 키 정리 - if (serverSecretKey != null) { - serverSecretKey.close(); - } - if (serverPublicKey != null) { - serverPublicKey.close(); - } - - } catch (IOException e) { - log.error("리소스 정리 오류", e); - } - } - - // - // Data Transmission - // - - /// 특정 참여자에게 데이터를 전송합니다. - /// 데이터는 즉시 전송을 시도하며, 소켓 버퍼가 가득 찬 경우 - /// 참여자의 OutboundQueue에 적재되고 OP_WRITE 이벤트를 통해 비동기로 처리됩니다. - /// - /// @param participant 대상 참여자 - /// @param data 전송할 데이터 - /// @throws IOException 전송 오류 시 - public void send(@NotNull Participant participant, @NotNull ByteBuffer data) throws IOException, EntLibServerIllegalStateException, EntLibSecureIllegalArgumentException, EntLibSecureIllegalStateException, EntLibCryptoCipherProcessException { - if (participant.getState().get() != ConnectionState.ESTABLISHED) { - throw new EntLibServerIllegalStateException("연결이 설정되지 않은 참여자입니다!"); - } - - { - final byte[] plaintext = new byte[data.remaining()]; - long writeSequence = participant.getParticipantSecurityContext().getNextWriteSequence(); - BlockCipherStrategy aes256GCM = EntLibCryptoRegistry.getAlgStrategy(CipherType.AES_256, BlockCipherStrategy.class) - .setMode(Mode.AEAD_GCM) - .setPadding(Padding.NO); - aes256GCM.iv(CipherStrategy.calculateNonce(SensitiveDataContainer.generateSafeRandomBytes(12), writeSequence)); - SensitiveDataContainer enc = aes256GCM.encrypt( - participant.getParticipantSecurityContext().getSessionKey(), - data.get(plaintext), - false); - participant.enqueueMessage(enc.getSegmentDataToByteBuffer()); - } // 암호화 및 enqueue - - boolean hasRemaining = participant.flushOutbound(); - - if (hasRemaining) { - SelectionKey key = participant.getChannel().keyFor(selector); - if (key != null && key.isValid()) { - key.interestOps(key.interestOps() | SelectionKey.OP_WRITE); - selector.wakeup(); - } - } - } - - /// 특정 세션의 모든 참여자에게 데이터를 브로드캐스트합니다. - /// - /// @param session 대상 세션 - /// @param data 전송할 데이터 - public void broadcast(@NotNull Session session, @NotNull ByteBuffer data) { - session.getAllParticipants().forEach(participant -> { - try { - if (participant.isSecure()) { - ByteBuffer copy = data.duplicate(); - send(participant, copy); - } - } catch (EntLibSecureIllegalArgumentException | EntLibCryptoCipherProcessException | - EntLibSecureIllegalStateException | EntLibServerIllegalStateException | IOException e) { - log.error("브로드캐스트 전송 오류: {}", participant.getId(), e); - } - }); - } - - // - // Event Listener Management - // - - /// 서버 이벤트 리스너를 등록합니다. - /// - /// @param listener 등록할 리스너 - public void addEventListener(@NotNull ServerEventListener listener) { - eventListeners.add(listener); - } - - /// 서버 이벤트 리스너를 제거합니다. - /// - /// @param listener 제거할 리스너 - public void removeEventListener(@NotNull ServerEventListener listener) { - eventListeners.remove(listener); - } - - private void notifyListeners(java.util.function.Consumer action) { - for (ServerEventListener listener : eventListeners) { - try { - action.accept(listener); - } catch (Exception e) { - log.error("리스너 실행 오류", e); - } - } - } - - // - // Status Methods - // - - /// 서버가 실행 중인지 확인합니다. - /// - /// @return 실행 중 여부 - public boolean isRunning() { - return running.get() && state.get() == ServerState.RUNNING; - } - - /// 서버 상태를 반환합니다. - /// - /// @return 서버 상태 - @NotNull - public ServerState getState() { - return state.get(); - } - - @Override - public String toString() { - return "Server{" + - "port=" + config.getPort() + - ", state=" + state.get() + - ", sessions=" + activeSessions.size() + - ", clients=" + channelToParticipant.size() + - '}'; - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/communication/tls/ServerConfig.java b/src/main/java/space/qu4nt/entanglementlib/security/communication/tls/ServerConfig.java deleted file mode 100644 index e52c83e..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/communication/tls/ServerConfig.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.communication.tls; - -import lombok.Builder; -import lombok.Getter; -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.security.communication.session.SessionConfig; - -/// TLS 서버 설정을 담는 불변 클래스입니다. -/// -/// ## 사용 예시 -/// ```java -/// ServerConfig config = ServerConfig.builder() -/// .port(8443) -/// .bindAddress("0.0.0.0") -/// .backlog(128) -/// .sessionConfig(SessionConfig.highSecurity()) -/// .build(); -/// ``` -/// -/// @author Q. T. Felix -/// @see Server -/// @see SessionConfig -/// @since 1.1.0 -@Getter -@Builder -public class ServerConfig { - - /// 기본 포트 - public static final int DEFAULT_PORT = 8443; - - /// 기본 바인드 주소 - public static final String DEFAULT_BIND_ADDRESS = "0.0.0.0"; - - /// 기본 백로그 크기 - public static final int DEFAULT_BACKLOG = 128; - - /// 서버 포트 - @Builder.Default - private final int port = DEFAULT_PORT; - - /// 바인드 주소 - @Builder.Default - private final String bindAddress = DEFAULT_BIND_ADDRESS; - - /// 연결 대기열 크기 (백로그) - @Builder.Default - private final int backlog = DEFAULT_BACKLOG; - - /// 세션 설정 - @Builder.Default - private final SessionConfig sessionConfig = SessionConfig.defaults(); - - /// 단일 세션 모드 여부 (모든 클라이언트가 하나의 세션 공유) - @Builder.Default - private final boolean singleSessionMode = false; - - /// 서버 이름 (디버깅/로깅용) - private final String serverName; - - /// 기본 설정으로 ServerConfig를 생성합니다. - /// - /// @return 기본 설정의 ServerConfig - @NotNull - public static ServerConfig defaults() { - return ServerConfig.builder().build(); - } - - /// 개발용 설정으로 ServerConfig를 생성합니다. - /// - /// @param port 서버 포트 - /// @return 개발용 설정의 ServerConfig - @NotNull - public static ServerConfig development(int port) { - return ServerConfig.builder() - .port(port) - .sessionConfig(SessionConfig.lightweight()) - .singleSessionMode(true) - .build(); - } - - /// 프로덕션용 고보안 설정으로 ServerConfig를 생성합니다. - /// - /// @param port 서버 포트 - /// @return 프로덕션용 고보안 설정의 ServerConfig - @NotNull - public static ServerConfig production(int port) { - return ServerConfig.builder() - .port(port) - .backlog(256) - .sessionConfig(SessionConfig.highSecurity()) - .build(); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/communication/tls/ServerEventListener.java b/src/main/java/space/qu4nt/entanglementlib/security/communication/tls/ServerEventListener.java deleted file mode 100644 index d7519d0..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/communication/tls/ServerEventListener.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.communication.tls; - -import space.qu4nt.entanglementlib.security.communication.session.Participant; - -import java.nio.ByteBuffer; - -/// TLS 서버 이벤트를 수신하는 리스너 인터페이스입니다. -/// -/// 서버의 생명주기, 클라이언트 연결, 핸드셰이크, 데이터 수신 등의 -/// 이벤트를 처리할 수 있습니다. 모든 메소드는 기본 구현(no-op)을 -/// 제공하므로 필요한 이벤트만 선택적으로 오버라이드할 수 있습니다. -/// -/// ## 사용 예시 -/// ```java -/// server.addEventListener(new ServerEventListener() { -/// @Override -/// public void onClientConnected(Server server, Participant participant) { -/// System.out.println("클라이언트 연결: " + participant.getId()); -/// } -/// -/// @Override -/// public void onHandshakeCompleted(Server server, Participant participant) { -/// System.out.println("핸드셰이크 완료: " + participant.getId()); -/// } -/// }); -/// ``` -/// -/// @author Q. T. Felix -/// @see Server -/// @since 1.1.0 -public interface ServerEventListener { - - /// 서버가 시작되었을 때 호출됩니다. - /// - /// @param server 서버 인스턴스 - default void onServerStarted(Server server) { - } - - /// 서버가 종료되었을 때 호출됩니다. - /// - /// @param server 서버 인스턴스 - default void onServerStopped(Server server) { - } - - /// 새 클라이언트가 연결되었을 때 호출됩니다. - /// 핸드셰이크 시작 전에 호출됩니다. - /// - /// @param server 서버 인스턴스 - /// @param participant 연결된 참여자 - default void onClientConnected(Server server, Participant participant) { - } - - /// 클라이언트 연결이 종료되었을 때 호출됩니다. - /// - /// @param server 서버 인스턴스 - /// @param participant 연결이 종료된 참여자 - default void onClientDisconnected(Server server, Participant participant) { - } - - /// 클라이언트와의 핸드셰이크가 완료되었을 때 호출됩니다. - /// 이 시점부터 보안 통신이 가능합니다. - /// - /// @param server 서버 인스턴스 - /// @param participant 핸드셰이크가 완료된 참여자 - default void onHandshakeCompleted(Server server, Participant participant) { - } - - /// 클라이언트로부터 데이터를 수신했을 때 호출됩니다. - /// 핸드셰이크 완료 후 애플리케이션 데이터만 전달됩니다. - /// - /// @param server 서버 인스턴스 - /// @param participant 데이터를 보낸 참여자 - /// @param data 수신된 데이터 (복호화된 상태) - default void onDataReceived(Server server, Participant participant, ByteBuffer data) { - } - - /// 서버에서 오류가 발생했을 때 호출됩니다. - /// - /// @param server 서버 인스턴스 - /// @param throwable 발생한 오류 - default void onServerError(Server server, Throwable throwable) { - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/communication/tls/ServerState.java b/src/main/java/space/qu4nt/entanglementlib/security/communication/tls/ServerState.java deleted file mode 100644 index 024cac3..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/communication/tls/ServerState.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.communication.tls; - -/// TLS 서버의 생명주기 상태를 나타내는 열거형입니다. -/// -/// @author Q. T. Felix -/// @see Server -/// @since 1.1.0 -public enum ServerState { - - /// 서버가 생성되었지만 아직 시작되지 않았습니다. - CREATED, - - /// 서버가 시작 중입니다. 리소스를 초기화하고 있습니다. - STARTING, - - /// 서버가 실행 중이며 클라이언트 연결을 수락할 준비가 되었습니다. - RUNNING, - - /// 서버가 종료 중입니다. 연결을 정리하고 있습니다. - STOPPING, - - /// 서버가 정상적으로 종료되었습니다. - STOPPED, - - /// 서버가 오류로 인해 실패했습니다. - FAILED -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/AbstractStrategyBundle.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/AbstractStrategyBundle.java deleted file mode 100644 index 93f7352..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/AbstractStrategyBundle.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.security.crypto.key.EntLibKey; -import space.qu4nt.entanglementlib.security.crypto.strategy.EntLibCryptoStrategy; - -import java.util.Collections; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -/// 암호화 스트레티지와 키 스트레티지를 함께 래핑하여 레지스트리에 등록하기 위한 추상 클래스입니다. -/// -/// 이 클래스를 상속하는 번들은 생성 시 자동으로 전역 번들 목록에 등록되며, -/// [EntLibCryptoRegistry]에서 모든 번들의 스트레티지를 수집하여 사용합니다. -/// ## 사용법 -/// -/// ```java -/// public final class AESStrategyBundle extends AbstractStrategyBundle { -/// // 싱글톤 인스턴스 - static 블록에서 자동 등록 -/// private static final AESStrategyBundle INSTANCE = new AESStrategyBundle(); -/// private AESStrategyBundle() {} -/// @Override -/// protected void registerStrategies() { -/// register(CipherType.AES_128, new AESStrategy(CipherType.AES_128)); -/// register(CipherType.AES_192, new AESStrategy(CipherType.AES_192)); -/// register(CipherType.AES_256, new AESStrategy(CipherType.AES_256)); -/// } -/// } -/// ``` -/// -/// @author Q. T. Felix -/// @since 1.1.0 -public abstract class AbstractStrategyBundle implements RegistrableStrategy { - - /** - * 등록된 모든 번들을 저장하는 전역 집합입니다. - *

- * Thread-safe한 {@link Set}을 사용합니다. - */ - private static final Set REGISTERED_BUNDLES = ConcurrentHashMap.newKeySet(); - - /** - * 이 번들이 관리하는 스트레티지 맵입니다. - */ - private final Map algStrategies = new ConcurrentHashMap<>(); - - private final Map keyStrategies = new ConcurrentHashMap<>(); - - /** - * 스트레티지가 이미 등록되었는지 여부를 나타내는 플래그입니다. - */ - private volatile boolean initialized = false; - - /** - * 기본 생성자입니다. - *

- * 인스턴스 생성 시 자동으로 전역 번들 목록에 등록됩니다. - */ - protected AbstractStrategyBundle() { - REGISTERED_BUNDLES.add(this); - } - - /** - * 하위 클래스에서 구현하여 스트레티지들을 등록하는 메소드입니다. - *

- * {@link #register(EntLibAlgorithmType, EntLibCryptoStrategy, EntLibKey)} 메소드를 사용하여 등록합니다. - */ - protected abstract void registerStrategies(); - - /** - * 스트레티지를 내부 맵에 등록하는 메소드입니다. - * - * @param type 알고리즘 타입 - * @param algStrategy 등록할 알고리즘 스트레티지 - * @param keyStrategy 등록할 키 스트레티지 - */ - protected final void register(@NotNull EntLibAlgorithmType type, - @NotNull EntLibCryptoStrategy algStrategy, - @NotNull EntLibKey keyStrategy) { - algStrategies.put(type, algStrategy); - keyStrategies.put(type, keyStrategy); - } - - /** - * 이 번들의 모든 스트레티지를 반환하는 메소드입니다. - *

- * 처음 호출 시 {@link #registerStrategies()}가 실행됩니다. - * - * @return 등록된 스트레티지 맵 (읽기 전용) - */ - @Override - public final Map getAlgStrategies() { - if (!initialized) { - synchronized (this) { - if (!initialized) { - registerStrategies(); - initialized = true; - } - } - } - return Collections.unmodifiableMap(algStrategies); - } - - /** - * 이 번들의 모든 키 스트레티지를 반환하는 메소드입니다. - *

- * 처음 호출 시 {@link #registerStrategies()}가 실행됩니다. - * - * @return 등록된 스트레티지 맵 (읽기 전용) - */ - @Override - public final Map getKeyStrategies() { - if (!initialized) { - synchronized (this) { - if (!initialized) { - registerStrategies(); - initialized = true; - } - } - } - return Collections.unmodifiableMap(keyStrategies); - } - - /** - * 등록된 모든 번들에서 스트레티지를 수집하여 반환하는 메소드입니다. - * - * @return 모든 번들의 스트레티지를 합친 맵 - */ - public static Map collectAllAlgStrategies() { - Map result = new ConcurrentHashMap<>(); - for (AbstractStrategyBundle bundle : REGISTERED_BUNDLES) { - result.putAll(bundle.getAlgStrategies()); - } - return result; - } - - /** - * 등록된 모든 번들에서 키 스트레티지를 수집하여 반환하는 메소드입니다. - * - * @return 모든 번들의 키 스트레티지를 합친 맵 - */ - public static Map collectAllKeyStrategies() { - Map result = new ConcurrentHashMap<>(); - for (AbstractStrategyBundle bundle : REGISTERED_BUNDLES) { - result.putAll(bundle.getKeyStrategies()); - } - return result; - } - - /** - * 등록된 번들의 수를 반환하는 메소드입니다. - *

- * 테스트 및 디버깅 용도로 사용됩니다. - * - * @return 등록된 번들 수 - */ - public static int registeredBundleCount() { - return REGISTERED_BUNDLES.size(); - } - -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/CipherType.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/CipherType.java deleted file mode 100644 index 11c24ae..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/CipherType.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto; - -import lombok.Getter; - -import java.util.Arrays; -import java.util.Objects; - -import static space.qu4nt.entanglementlib.security.crypto.CryptoFamily.*; - -/// `BouncyCastle` 공급자 서비스 `Cipher`에 포함된 암호화 알고리즘을 열거한 클래스입니다. -/// -/// 이 열거형은 [EntLibAlgorithmType] 인터페이스를 구현하여 각 암호화 알고리즘의 -/// 패밀리, 키 크기, PQC(Post-Quantum Cryptography) 여부 등의 속성을 제공합니다. -/// -/// @author Q. T. Felix -/// @see EntLibAlgorithmType -/// @see CryptoFamily -/// @since 1.1.0 -@Getter -public enum CipherType implements EntLibAlgorithmType { - - /** - * AES 128비트 키 암호화 알고리즘입니다. - */ - AES_128(AES, ParameterSizeDetail.symmetric(128), false), - /** - * AES 192비트 키 암호화 알고리즘입니다. - */ - AES_192(AES, ParameterSizeDetail.symmetric(192), false), - /** - * AES 256비트 키 암호화 알고리즘입니다. - */ - AES_256(AES, ParameterSizeDetail.symmetric(256), false), - - /** - * Triple DES 128비트 키 암호화 알고리즘입니다. - */ - DESede_128(DES, ParameterSizeDetail.symmetric(128), false), - /** - * Triple DES 168비트 키 암호화 알고리즘입니다. - */ - DESede_168(DES, ParameterSizeDetail.symmetric(168), false), - /** - * Triple DES 192비트 키 암호화 알고리즘입니다. - */ - DESede_192(DES, ParameterSizeDetail.symmetric(192), false), - - /** - * ChaCha20 스트림 암호화 알고리즘입니다. - */ - CHACHA20(CHACHA, ParameterSizeDetail.symmetric(256), false), - /** - * ChaCha20-Poly1305 AEAD 암호화 알고리즘입니다. - */ - CHACHA20_POLY1305(CHACHA, ParameterSizeDetail.symmetric(256), false), - - /** - * RSA 2048비트 키 암호화 알고리즘입니다. - */ - RSA_2048(RSA, ParameterSizeDetail.symmetric(2048), false), - /** - * RSA 4096비트 키 암호화 알고리즘입니다. - */ - RSA_4096(RSA, ParameterSizeDetail.symmetric(4096), false), - - /** - * SM2 with SHA-224 암호화 알고리즘입니다. - */ - SM2withSHA224(SM2, ParameterSizeDetail.symmetric(256), false), - /** - * SM2 with SHA-256 암호화 알고리즘입니다. - */ - SM2withSHA256(SM2, ParameterSizeDetail.symmetric(256), false), - /** - * SM2 with SHA-384 암호화 알고리즘입니다. - */ - SM2withSHA384(SM2, ParameterSizeDetail.symmetric(256), false), - /** - * SM2 with SHA-512 암호화 알고리즘입니다. - */ - SM2withSHA512(SM2, ParameterSizeDetail.symmetric(256), false), - - /** - * ARIA 128비트 키 암호화 알고리즘입니다. - */ - ARIA_128(ARIA, ParameterSizeDetail.symmetric(128), false), - /** - * ARIA 192비트 키 암호화 알고리즘입니다. - */ - ARIA_192(ARIA, ParameterSizeDetail.symmetric(192), false), - /** - * ARIA 256비트 키 암호화 알고리즘입니다. - */ - ARIA_256(ARIA, ParameterSizeDetail.symmetric(256), false), - ; - - /** - * 암호화 알고리즘 패밀리입니다. - */ - private final CryptoFamily family; - - /** - * 암호화 키의 비트 크기입니다. - */ - private final ParameterSizeDetail parameterSizeDetail; - - /** - * PQC(Post-Quantum Cryptography) 알고리즘 여부입니다. - */ - private final boolean pQC; - - private final String name = name(); - - /** - * {@link CipherType} 열거형 생성자입니다. - * - * @param family 암호화 알고리즘 패밀리 - * @param parameterSizeDetail 키 크기 (비트 단위) - * @param pQC PQC 알고리즘 여부 - */ - CipherType(CryptoFamily family, ParameterSizeDetail parameterSizeDetail, boolean pQC) { - this.family = family; - this.parameterSizeDetail = parameterSizeDetail; - this.pQC = pQC; - } - - - /** - * 이 암호화 알고리즘의 카테고리를 반환하는 메소드입니다. - * - * @return {@link EntLibCryptoCategory#CIPHER} - */ - @Override - public EntLibCryptoCategory getCategory() { - return EntLibCryptoCategory.CIPHER; - } - - public static CipherType getByContextIdentifier(String identifier) { - return Arrays.stream(CipherType.values()) - .filter(type -> Objects.equals(type.getContextIdentifier(), identifier)) - .findFirst() - .orElseThrow(); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/CryptoFamily.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/CryptoFamily.java deleted file mode 100644 index af2cf33..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/CryptoFamily.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto; - -/// 암호화 알고리즘 패밀리(종류별 분류)를 정의하는 열거형 클래스입니다. -/// -/// 각 암호화 알고리즘이 속하는 패밀리를 분류하며, 대칭 키 암호, 비대칭 키 암호, -/// 스트림 암호, PQC(Post-Quantum Cryptography) 알고리즘 등을 포함합니다. -/// -/// @author Q. T. Felix -/// @see EntLibAlgorithmType -/// @since 1.1.0 -public enum CryptoFamily { - - /** - * AES(Advanced Encryption Standard) 대칭 키 블록 암호 패밀리입니다. - */ - AES, - - /** - * DES(Data Encryption Standard) 대칭 키 블록 암호 패밀리입니다. - */ - DES, - - /** - * ChaCha 스트림 암호 패밀리입니다. - */ - CHACHA, - - /** - * RSA 비대칭 키 암호 패밀리입니다. - */ - RSA, - - /** - * SM2 중국 국가 표준 비대칭 키 암호 패밀리입니다. - */ - SM2, - - /** - * ARIA 대칭 키 블록 암호 패밀리입니다. (대한민국 국가 표준) - */ - ARIA, - - /** - * ML-DSA(Module-Lattice Digital Signature Algorithm) PQC 서명 알고리즘 패밀리입니다. - */ - ML_DSA, - - /** - * ML-KEM(Module-Lattice Key Encapsulation Mechanism) PQC 키 캡슐화 알고리즘 패밀리입니다. - */ - ML_KEM, - - /** - * SLH-DSA(Stateless Hash-based Digital Signature Algorithm) PQC 서명 알고리즘 패밀리입니다. - */ - SLH_DSA, - - Curves, - - HYBRID - -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/CryptoMethod.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/CryptoMethod.java deleted file mode 100644 index da61dce..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/CryptoMethod.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto; - -public enum CryptoMethod { - - /** - * KEM(Key Encapsulate Mechanism) - */ - KEM, - - /** - * 전자 서명 알고리즘 - */ - SIGN, - - /** - * 대칭 키 암호화 알고리즘 - *

- * 대용량 데이터 암호화에 적합한 알고리즘이며, - * 암호화와 복호화에 동일한 키를 사용하는 알고리즘입니다. - *

- * 단, 암호화 키를 안전하게 공유해야 하는 것이 중요합니다. - */ - SYMMETRIC, - - /** - * 공개 키(비대칭) 암호화 알고리즘 - *

- * 암호화할 때는 공개 키를 사용하고, 복호화할 때는 개인 키를 사용하는 알고리즘입니다. - */ - ASYMMETRIC, - - /** - * 스트림 암호화 알고리즘 - *

- * 데이터를 비트(bit)나 바이트(byte) 단위로 연속적으로 암호화하는 알고리즘입니다. - * 실시간 통신에 주로 사용됩니다. - */ - STREAM, - - /** - * 복합 알고리즘 - */ - COMPOSITE -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/Digest.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/Digest.java deleted file mode 100644 index b2cda98..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/Digest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto; - -import lombok.Getter; -import space.qu4nt.entanglementlib.util.StringUtil; - -/// 지원되는 다이제스트 알고리즘을 열거한 클래스입니다. -/// -/// `SHA3`는 다른 프로덕션에서 호환되지 않을 수 있습니다. -/// -/// @author Q. T. Felix -/// @since 1.0.0 -@Getter -public enum Digest { - - // - // Not Recommended - start - // - - /** - * MD5 - * @deprecated MD5는 절대 권장되지 않습니다. {@link #SHA_224} 이상의 다이제스트를 사용하세요. - */ - @Deprecated - MD5, - - /** - * SHA_1 - @deprecated SHA_1는 절대 권장되지 않습니다. {@link #SHA_224} 이상의 다이제스트를 사용하세요. - */ - @Deprecated - SHA_1, - - // - // Not Recommended - end - // - - /** - * SHA_224 - */ - SHA_224, - /** - * SHA_256 - */ - SHA_256, - /** - * SHA_384 - */ - SHA_384, - /** - * SHA_512 - */ - SHA_512, - - /** - * SHA3_224 - */ - SHA3_224, - /** - * SHA3_256 - */ - SHA3_256, - /** - * SHA3_384 - */ - SHA3_384, - /** - * SHA3_512 - */ - SHA3_512; - - private final String name = StringUtil.replace(name(), "_", "-").startsWith("SHA-") ? - StringUtil.replace(name(), "_", "") : StringUtil.replace(name(), "_", "-"); -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/EntLibAlgorithmType.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/EntLibAlgorithmType.java deleted file mode 100644 index dca1cb0..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/EntLibAlgorithmType.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto; - -import java.util.Locale; -import java.util.Objects; - -/// 암호화 알고리즘의 공통 속성을 정의하는 인터페이스입니다. -/// -/// 모든 암호화 알고리즘 타입([CipherType], [SignatureType] 등)은 -/// 이 인터페이스를 구현하여 알고리즘의 카테고리, 패밀리, 키 크기, PQC 여부 등의 -/// 속성을 제공합니다. -/// -/// @author Q. T. Felix -/// @see CipherType -/// @see SignatureType -/// @see EntLibCryptoCategory -/// @see CryptoFamily -/// @since 1.1.0 -public interface EntLibAlgorithmType { - - // todo: 레지스트리에서 원활한 타입 추론을 위해 이 인터페이스를 클래스로 변경하는 등 크게 개편 - - String getName(); - - /** - * 이 알고리즘의 카테고리를 반환하는 메소드입니다. - * - * @return 알고리즘 카테고리 (암호화, 서명, 키 합의 등) - */ - EntLibCryptoCategory getCategory(); - - /** - * 이 알고리즘이 속하는 패밀리를 반환하는 메소드입니다. - * - * @return 알고리즘 패밀리 (AES, RSA, ML-DSA 등) - */ - CryptoFamily getFamily(); - - /// 이 알고리즘의 사이즈 디테일을 반환합니다. - /// - /// @return 정의된 [ParameterSizeDetail] 객체 - ParameterSizeDetail getParameterSizeDetail(); - - /** - * 이 알고리즘이 PQC(Post-Quantum Cryptography) 알고리즘인지 여부를 반환하는 메소드입니다. - * - * @return PQC 알고리즘이면 {@code true}, 아니면 {@code false} - */ - boolean isPQC(); - - default String getContextIdentifier() { - return Objects.requireNonNull(getName()).toLowerCase(Locale.ROOT).replace('-', '_').trim(); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/EntLibCryptoCategory.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/EntLibCryptoCategory.java deleted file mode 100644 index 6ff61ac..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/EntLibCryptoCategory.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto; - -/// 암호화 알고리즘의 유형을 분류하는 열거형 클래스입니다. -/// -/// 이 클래스는 `BouncyCastle Lightweight API` 환경에서 사용되는 다양한 암호화 서비스 중, -/// 실질적인 암호화 연산, 서명, 키 합의 등을 수행하는 핵심 알고리즘 카테고리를 정의합니다. -/// -/// @author Q. T. Felix -/// @since 1.1.0 -public enum EntLibCryptoCategory { - - /** - * 데이터의 기밀성을 보장하기 위한 암호화 및 복호화 알고리즘 카테고리입니다. - *

- * 대칭 키 암호(block cipher, stream cipher) 및 비대칭 키 암호 알고리즘이 포함됩니다. - */ - CIPHER, - - /** - * 데이터의 무결성과 인증을 보장하기 위한 전자 서명 알고리즘 카테고리입니다. - *

- * 메시지 서명 및 검증을 수행하는 알고리즘들이 포함됩니다. - */ - SIGNATURE, - - /** - * 두 당사자 간에 비밀 키를 공유하기 위한 키 합의 프로토콜 카테고리입니다. - *

- * 안전하지 않은 채널을 통해 공통의 비밀 키를 생성하는 알고리즘들이 포함됩니다. - */ - KEY_AGREEMENT, - - /** - * 메시지의 무결성과 인증을 확인하기 위한 메시지 인증 코드(MAC) 알고리즘 카테고리입니다. - *

- * 비밀 키를 사용하여 메시지의 위변조 여부를 확인하는 알고리즘들이 포함됩니다. - */ - MAC, - - /** - * 임의의 길이의 데이터를 고정된 길이의 해시 값으로 변환하는 메시지 다이제스트 알고리즘 카테고리입니다. - *

- * 데이터의 고유한 지문(fingerprint)을 생성하는 알고리즘들이 포함됩니다. - */ - MESSAGE_DIGEST, - - /** - * 키 캡슐화 메커니즘(Key Encapsulation Mechanism, KEM) 알고리즘 카테고리입니다. - *

- * 공개 키 암호화를 사용하여 대칭 키를 안전하게 전송하기 위한 기법으로, - * 포스트 퀀텀 암호화(PQC) 등에서 주로 사용됩니다. - */ - KEM, - -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/EntLibCryptoRegistry.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/EntLibCryptoRegistry.java deleted file mode 100644 index 91c900c..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/EntLibCryptoRegistry.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.security.crypto.bundle.BundleStaticCaller; -import space.qu4nt.entanglementlib.security.crypto.key.EntLibKey; -import space.qu4nt.entanglementlib.security.crypto.strategy.EntLibCryptoStrategy; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/// 암호화 스트레티지 레지스트리 클래스입니다. -/// -/// 모든 암호화 스트레티지와 키 스트레티지는 [AbstractStrategyBundle]을 통해 -/// 이 레지스트리에 자동으로 등록됩니다. -/// ## 새로운 알고리즘 추가 방법 -/// -/// 1. [AbstractStrategyBundle]을 상속하는 번들 클래스 생성 -/// 2. 번들 클래스에서 `registerStrategies()` 메소드 구현 -/// 3. 이 클래스의 static 블록에서 번들 인스턴스 참조 추가 -/// -/// @author Q. T. Felix -/// @see AbstractStrategyBundle -/// @see RegistrableStrategy -/// @since 1.1.0 -public final class EntLibCryptoRegistry { - - /** - * 알고리즘 타입과 스트레티지를 매핑하는 레지스트리 맵입니다. - */ - static final Map ALG_REGISTRY = new ConcurrentHashMap<>(); - - /** - * 알고리즘 타입과 키 스트레티지를 매핑하는 레지스트리 맵입니다. - */ - static final Map KEY_REGISTRY = new ConcurrentHashMap<>(); - - static { - // 각 번들의 싱글톤 인스턴스를 참조하여 자동 등록 트리거 - // AbstractStrategyBundle 생성자에서 전역 번들 목록에 자동 추가됨 - BundleStaticCaller.call(); - - // 모든 등록된 번들에서 스트레티지 수집 - ALG_REGISTRY.putAll(AbstractStrategyBundle.collectAllAlgStrategies()); - KEY_REGISTRY.putAll(AbstractStrategyBundle.collectAllKeyStrategies()); - } - - /** - * 인스턴스화를 방지하기 위한 private 생성자입니다. - */ - private EntLibCryptoRegistry() { - } - - /** - * 지정된 알고리즘 타입에 해당하는 스트레티지를 반환하는 메소드입니다. - * - * @param type 알고리즘 타입 - * @param clazz 반환받을 스트레티지 클래스 타입 - * @param 스트레티지 타입 - * @return 해당 타입의 스트레티지 - * @throws UnsupportedOperationException 지원하지 않는 타입인 경우 - */ - public static T getAlgStrategy(@NotNull EntLibAlgorithmType type, Class<@NotNull T> clazz) { - EntLibCryptoStrategy algorithm = ALG_REGISTRY.get(type); - if (clazz.isInstance(algorithm)) - return clazz.cast(algorithm); - throw new UnsupportedOperationException("Not supported: " + clazz.getSimpleName()); - } - - /** - * 지정된 알고리즘 타입에 해당하는 키 스트레티지를 반환하는 메소드입니다. - * - * @param type 알고리즘 타입 - * @param clazz 반환받을 키 스트레티지 클래스 타입 - * @param 키 스트레티지 타입 - * @return 해당 타입의 키 스트레티지 - * @throws UnsupportedOperationException 지원하지 않는 타입인 경우 - */ - public static T getKeyStrategy(@NotNull EntLibAlgorithmType type, Class<@NotNull T> clazz) { - EntLibKey algorithm = KEY_REGISTRY.get(type); - if (clazz.isInstance(algorithm)) - return clazz.cast(algorithm); - throw new UnsupportedOperationException("Not supported: " + clazz.getSimpleName()); - } - - /** - * 등록된 모든 스트레티지의 수를 반환하는 메소드입니다. - * - * @return 등록된 스트레티지 수 - */ - public static int registeredCount() { - return ALG_REGISTRY.size() + KEY_REGISTRY.size(); - } - - /** - * 지정된 알고리즘 타입이 등록되어 있는지 확인하는 메소드입니다. - * - * @param type 확인할 알고리즘 타입 - * @return 등록되어 있으면 {@code true}, 아니면 {@code false} - */ - public static boolean isAlgRegistered(@NotNull EntLibAlgorithmType type) { - return ALG_REGISTRY.containsKey(type); - } - - /** - * 지정된 키 알고리즘 타입이 등록되어 있는지 확인하는 메소드입니다. - * - * @param type 확인할 키 알고리즘 타입 - * @return 등록되어 있으면 {@code true}, 아니면 {@code false} - */ - public static boolean isKeyRegistered(@NotNull EntLibAlgorithmType type) { - return KEY_REGISTRY.containsKey(type); - } - -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/KEMType.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/KEMType.java deleted file mode 100644 index 966ff9d..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/KEMType.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto; - -import lombok.Getter; - -import java.util.Arrays; -import java.util.Objects; - -import static space.qu4nt.entanglementlib.security.crypto.CryptoFamily.*; - -/// 지원되는 KEM 알고리즘을 열거한 클래스입니다. -/// -/// 이 열거형은 [EntLibAlgorithmType] 인터페이스를 구현하여 각 서명 알고리즘의 -/// 패밀리, 키 크기, PQC(Post-Quantum Cryptography) 여부 등의 속성을 제공합니다. -/// -/// @author Q. T. Felix -/// @see EntLibAlgorithmType -/// @see CryptoFamily -/// @since 1.1.0 -@Getter -public enum KEMType implements EntLibAlgorithmType { - - /** - * ML-KEM-512 PQC KEM 알고리즘입니다. - */ - ML_KEM_512(ML_KEM, ParameterSizeDetail.kem( - 0x320, - 0x660, - 0x300, - 0x20), true), - /** - * ML-KEM-768 PQC KEM 알고리즘입니다. - */ - ML_KEM_768(ML_KEM, ParameterSizeDetail.kem( - 0x4a0, - 0x960, - 0x440, - 0x20), true), - /** - * ML-KEM-1024(QC KEM 알고리즘입니다. - */ - ML_KEM_1024(ML_KEM, ParameterSizeDetail.kem( - 0x620, - 0xc60, - 0x620, - 0x20), true), - - X25519(Curves, ParameterSizeDetail.kem( - 0x20, - 0x20, - 0x20, - 0x20), false), - - X25519MLKEM768(HYBRID, ParameterSizeDetail.kem( - 0x4a0 + 0x20, - 0x960 + 0x20, - 0x440 + 0x20, - 0x20 + 0x20 - ), true) - ; - - /** - * 서명 알고리즘 패밀리입니다. - */ - private final CryptoFamily family; - - /** - * 파라미터 디테일 객체입니다. - */ - private final ParameterSizeDetail parameterSizeDetail; - - /** - * PQC(Post-Quantum Cryptography) 알고리즘 여부입니다. - */ - private final boolean pQC; - - private final String name = name(); - - /** - * {@link KEMType} 열거형 생성자입니다. - * - * @param family KEM 알고리즘 패밀리 - * @param parameterSizeDetail 키 크기 (비트 단위) - * @param pQC PQC 알고리즘 여부 - */ - KEMType(CryptoFamily family, ParameterSizeDetail parameterSizeDetail, boolean pQC) { - this.family = family; - this.parameterSizeDetail = parameterSizeDetail; - this.pQC = pQC; - } - - /** - * 이 KEM 알고리즘의 카테고리를 반환하는 메소드입니다. - * - * @return {@link EntLibCryptoCategory#SIGNATURE} - */ - @Override - public EntLibCryptoCategory getCategory() { - return EntLibCryptoCategory.KEM; - } - - public static KEMType getByContextIdentifier(String identifier) { - return Arrays.stream(KEMType.values()) - .filter(type -> Objects.equals(type.getContextIdentifier(), identifier)) - .findFirst() - .orElseThrow(); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/Mode.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/Mode.java deleted file mode 100644 index b64fecd..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/Mode.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto; - -import lombok.Getter; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalArgumentException; -import space.qu4nt.entanglementlib.security.crypto.strategy.EntLibCryptoStrategy; -import space.qu4nt.entanglementlib.util.StringUtil; - -@Getter -public enum Mode { - - /** - * Mode: Electronic Codebook - *

- * 얽힘 라이브러리에선 보안상 권장하지 않는 모드입니다. - */ - ECB, - /** - * Mode: Cipher Block Chaining - */ - CBC, - /** - * Mode: Cipher Feedback - */ - CFB, - /** - * Mode: Output Feedback - */ - OFB, - /** - * Mode: Counter - */ - CTR, - - /** - * Mode: Galois/Counter - */ - AEAD_GCM, - /** - * Mode: Counter with CBC-MAC - */ - AEAD_CCM; - - private final boolean aead = name().startsWith("AEAD_"); - private final String name = aead ? name().replace("AEAD_", "") : name(); - - public static String getFullName(final @NotNull String algorithmName, final @NotNull Mode mode, final @NotNull Padding padding, @Nullable Digest digest) - throws EntLibSecureIllegalArgumentException { - if (digest == null) { - return String.format("%s/%s/%s", algorithmName, mode.name, padding.getName()); - } - // AEAD - if (algorithmName.equalsIgnoreCase("AES") || algorithmName.equalsIgnoreCase("ChaCha20")) { - String fullPaddingName = StringUtil.replace(padding.getName(), "{digest}", digest.getName()); - return String.format("%s/%s/%s", algorithmName, mode.name, fullPaddingName); - } - throw new EntLibSecureIllegalArgumentException(EntLibCryptoStrategy.class, "alg-not-support-aead-exc", null, algorithmName); - } - - public static String getFullName(final @NotNull String algorithmName, final @NotNull Mode mode, final @NotNull Padding padding) - throws EntLibSecureIllegalArgumentException { - return getFullName(algorithmName, mode, padding, null); - } - -} \ No newline at end of file diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/Padding.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/Padding.java deleted file mode 100644 index e791b01..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/Padding.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto; - -import lombok.Getter; - -@Getter -public enum Padding { - - PKCS5(CryptoMethod.SYMMETRIC, "PKCS5Padding"), - /** - * {@link #PKCS5} 와 근본적으로는 동일하지만 차별화 - */ - PKCS7(CryptoMethod.SYMMETRIC, "PKCS7Padding"), - ISO7816(CryptoMethod.SYMMETRIC, "ISO7816Padding"), - ISO10126(CryptoMethod.SYMMETRIC, "ISO10126Padding"), - ZERO_BYTE(CryptoMethod.SYMMETRIC, "ZeroBytePadding"), - - /** - * {@link #PKCS5} 와 근본적으로 다름 - */ - PKCS1(CryptoMethod.ASYMMETRIC, "PKCS1Padding"), - OAEP_AND_MGF1(CryptoMethod.ASYMMETRIC, "OAEPWith{digest}AndMGF1Padding"), - - NO("NoPadding"), - ; - - private final CryptoMethod type; - private final String name; - - Padding(String name) { - this(null, name); - } - - Padding(CryptoMethod type, String name) { - this.type = type; - this.name = name; - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/ParameterSizeDetail.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/ParameterSizeDetail.java deleted file mode 100644 index 064b1ca..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/ParameterSizeDetail.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -/// 파라미터의 데이터 바이트 사이즈를 관리할 수 있는 클래스입니다. -/// 기존 키 사이즈를 정수 배열로 관리하는 방식에서 변경되었습니다. -/// -/// @author Q. T. Felix -/// @since 1.1.0 -@Getter -@NoArgsConstructor(access = AccessLevel.PRIVATE) -@AllArgsConstructor(access = AccessLevel.PRIVATE) -public class ParameterSizeDetail { - - // Symmetric - private int secretKeySize; - - // Asymmetric - private int publicKeySize; - private int privateKeySize; - - // Signature - private int signatureSize; - - // KEM, etc - private int encapsulationKeySize; - private int decapsulationKeySize; - private int ciphertextSize; - private int sharedSecretKeySize; - - private ParameterSizeDetail(int secretKeySize) { - this.secretKeySize = secretKeySize; - } - - public ParameterSizeDetail(int publicKeySize, int privateKeySize) { - this.publicKeySize = publicKeySize; - this.privateKeySize = privateKeySize; - } - - public ParameterSizeDetail(int publicKeySize, int privateKeySize, int signatureSize) { - this.publicKeySize = publicKeySize; - this.privateKeySize = privateKeySize; - this.signatureSize = signatureSize; - } - - public ParameterSizeDetail(int encapsulationKeySize, int decapsulationKeySize, int ciphertextSize, int sharedSecretKeySize) { - this.encapsulationKeySize = encapsulationKeySize; - this.decapsulationKeySize = decapsulationKeySize; - this.ciphertextSize = ciphertextSize; - this.sharedSecretKeySize = sharedSecretKeySize; - } - - public static ParameterSizeDetail symmetric(int secretKeySize) { - return new ParameterSizeDetail(secretKeySize); - } - - public static ParameterSizeDetail asymmetric(int publicKeySize, int privateKeySize) { - return new ParameterSizeDetail(publicKeySize, privateKeySize); - } - - public static ParameterSizeDetail sign(int publicKeySize, int privateKeySize, int signatureSize) { - return new ParameterSizeDetail(publicKeySize, privateKeySize, signatureSize); - } - - public static ParameterSizeDetail kem(int encapsulationKeySize, int decapsulationKeySize, int ciphertextSize, int sharedSecretKeySize) { - return new ParameterSizeDetail(encapsulationKeySize, decapsulationKeySize, ciphertextSize, sharedSecretKeySize); - } - - public static ParameterSizeDetail empty() { - return new ParameterSizeDetail(); - } - - public ParameterSizeDetail secretKeySize(int value) { - this.secretKeySize = value; - return this; - } - - public ParameterSizeDetail publicKeySize(int value) { - this.publicKeySize = value; - return this; - } - - public ParameterSizeDetail privateKeySize(int value) { - this.privateKeySize = value; - return this; - } - - public ParameterSizeDetail signatureSize(int value) { - this.signatureSize = value; - return this; - } - - public ParameterSizeDetail encapsulationKeySize(int value) { - this.encapsulationKeySize = value; - return this; - } - - public ParameterSizeDetail decapsulationKeySize(int value) { - this.decapsulationKeySize = value; - return this; - } - - public ParameterSizeDetail ciphertextSize(int value) { - this.ciphertextSize = value; - return this; - } - - public ParameterSizeDetail sharedSecretKeySize(int value) { - this.sharedSecretKeySize = value; - return this; - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/README.md b/src/main/java/space/qu4nt/entanglementlib/security/crypto/README.md deleted file mode 100644 index 0fbdfa8..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/README.md +++ /dev/null @@ -1,345 +0,0 @@ -# Securirt Crypto: Strategy Pattern Overview - -얽힘 라이브러리는 `1.1.0` 릴리즈부터 전략(strategy) 패턴을 통해 알고리즘을 사용할 수 있다. 이 문서는 전략 패턴을 통해 호출되는 개별 알고리즘의 작동 원리를 기술한다. - -# 카테고리 - -암호화 알고리즘은 다음 카테고리를 가진다. 빗금 처리된 카테고리는 아직 구현된 알고리즘이 없음을 의미한다. - -- Cipher(AEAD 가능) - - Block Cipher - - Stream Cipher -- Key Encapsulate Mechanism -- Digital Signature -- Key Establishment - - Key Exchange - - Key Agreement -- ~~Key Derivation Function~~ - -여기서 각 카테고리의 개념을 소개하지는 않겠다. - ---- - -# 구현 구체화 - -얽힘 라이브러리에서 암호화는 전략적으로 구현되어 있다고 했다. 전략 패턴은 쉽게 부모와 자식 관계로 이루어진 객체 지향적인 패턴을 말한다. 모든 전략 구현체는 하나의 레지스트리에 등록되고, 외부에서 레지스트리로부터 특정 암호화 알고리즘과 알고리즘을 수행하기 위해 필요한 키를 호출 및 생성할 수 있다. - -## 레지스트리 - -레지스트리 클래스는 `entanglementlib/security/crypto/EntLibCryptoRegistry.java` 클래스를 말한다. - -> 이제부터 특별한 언급이 없는 이상 클래스의 네임스페이스에서 최상위 패키지를 가리키는 `entanglementlib/security/crypto/`와 확장자명은 생략하기로 약속한다. 이 패키지를 기준으로 상대 경로로 표현하겠다. - -`EntLibCryptoRegistry` 클래스는 "팩토리(factory)" 패턴으로 이루어져 있다. 개별 전략 구현체는 이 클래스에 직접적으로 소속(상속 또는 일관된 상호 작용)하지 않는다. - -구체적으로, 개별 전략 구현체는 각 알고리즘에 대응하는 번들(bundle) 클래스를 가지고 있고 개별 번들은 `AbstractStrategyBundle` 추상 클래스를 구현한다. 이 추상 클래스는 인스턴스 생성 시 추상 클래스의 '번들 전역 변수'에 담기게 되고, `#collectAllKeyStrategies()` 또는 `#collectAllAlgStrategies()` 정적 메소드를 레지스트리 클래스에서 호출하여 개별 번들의 키, 알고리즘 구현체를 수집하도록 설계되어있다. - -> 번들 구현체가 만들어진 이유는 AES 알고리즘으로 예를 들어, 하나의 알고리즘이지만 세부적으로 `128`, `192`, `256`처럼 다양한 키 사이즈를 가질 수 있기 때문에 이러한 구현을 세분화하기 위해 마련됐다. ML-DSA 전자 서명 알고리즘의 경우는 `44`, `65`, `87` 파라미터 세트를 가지는 등, 매우 방대하다. - -이해를 돕기 위해 모든 번들 구현체의 상속 구조를 다이어그램으로 나타냈다. -![](../../../../../../../../assets/registry.png) -동떨어져있는 `bundle/BundleStaticCaller` 클래스는 모든 번들 구현체의 인스턴스를 생성하는 역할만 한다. 왜냐하면 모든 번들 구현체의 생성자는 `package-private`상태라서 외부 패키지에서는 호출이 불가능하기 때문이다(이 패턴은 추 후 JPMS를 통해 보완될 예정이다). - -다음과 같이`bundle/BundleStaticCaller#call()` 정적 메소드를 레지스트리 클래스의 `static` 블럭에서 호출한다. -![](../../../../../../../../assets/registry-1.png) -레지스트리 클래스의 정적 메소드인 `getAlgStrategy(...)`, `getKeyStrategy(...)` 를 통해 사용하고자 하는 알고리즘 전략 클래스를 다음과 같이 호출할 수 있다. -```java -// AES-256 암호화 스트레티지 가져오기 -CipherStrategy aesStrategy = EntLibCryptoRegistry.getAlgStrategy(CipherType.AES_256, CipherStrategy.class); - -// ML-DSA-44 서명 스트레티지 가져오기 -NativeSignatureStrategy mldsaStrategy = EntLibCryptoRegistry.getAlgStrategy(SignatureType.ML_DSA_44, NativeSignatureStrategy.class); -``` -키 전략 패턴도 일관된 방식으로 호출할 수 있다. - -구현체 클래스가 아닌 부모-레벨의 객체를 불러오는 이유는 기본적으로 '자체 캡슐화(self-encapsulation)' 패턴을 지향하기 때문이다. 라이브러리 자체는 캡슐화 되어있어도, 사용자의 환경에서 잘못 사용하는 경우 이 캡슐화가 깨질 수 있기 때문에 최대한 부모 레벨을 호출하여 유연하고 안전하게 사용하자는 일종의 '약속'인 것이다. 부모 객체의 자식(구현체)을 숨기기 위한 이유가 가장 우선적이다. - -## 키 생성 - -암호화 알고리즘이 가질 수 있는 키는 매우 다양하다. 그 중에서도 "대칭 키(symmetric key; 비밀 키, secret key)"와 "비대칭 키(asymmetric key)"로 차별화할 수 있다. 비대칭 키는 "공개 키(public key)"와 "개인 키(private key)"로 이루어진 "키 페어(pair; 쌍)"로 산출된다. 키는 정말 다양한 방식으로 얻어낼 수 있다. 예를 들어, 키 유도 함수를 통해 키를 얻으면 그것이 비밀 키가 된다. 서로가 $100 + 200 \times 5$의 결과를 키라고 합의 한다면, 그것도 비밀 키가 된다. - -얽힘 라이브러리는 사용하고자 하는 암호화 알고리즘에서 사용 가능한 키를 직관적으로 "객체"로 표현하기 위해 키 생성 패턴도 차별화했다. 알고리즘 구현체를 통해 키를 생성할 수 있도록 설계할 수도 있었지만, "키(즉, 민감 정보, sensitivity content 또는 data)"와 키를 통해 사용 가능한 "알고리즘"을 명확하게 분리함으로써 라이브러리의 캡슐화를 이루었다. 보통은 요리(암호화)에 필요한 재료(키)를 따로 준비하지, 요리에서 재료를 얻진 않으니까. - -키 전략 구현체들의 상속 구조는 다음의 다이어그램으로 나타낼 수 있다. -![](../../../../../../../../assets/key.png) -전혀 복잡하지 않다. 아까부터 자주 보이는 `Native...` 는 해당 구현체의 연산이 `entlib-native` 라이브러리에서 이루어짐을 의미한다. 이에 관해선 후술할것이다. 어쨌든, `key/strategy/EntLibKey`인터페이스는 레지스트리 추상 클래스(`AbstractStrategyBundle`)에서 사용(호출)되는 마커 인터페이스(구현이 필요치 않으며, 단순히 확장 클래스를 포괄하여 호출하기 위한 객체 지향 패턴)이다. - -다이어그램을 보면 상술했듯 크게 `Symmetric`, `Asymmetric`으로 이루어져 있는 것을 확인할 수 있다. 대칭 키는 대칭 키만의 생성 로직을 수행하는 것이고, 비대칭 키는 비대칭 키만의 생성 로직을 수행하는 것이다. 그 자식들(키 전략 구현체)은 자신이 대칭 키 또는 비대칭 키를 만들기 위해 어떤 작업을 수행해야 하는지 알고 있다. - -대칭 키를 생성하는 구현체는 `key/strategy/EntLibSymmetricKeyStrategy#generateKey()` 메소드를, 비대칭 키를 생성하는 구현체는 `key/strategy/EntLibAsymmetricKeyStrategy#generateKeyPair()` 메소드를 사용하여 결과(키)를 얻을 수 있다. - -## 민감 정보 컨테이너 - -암호화나 키 생성 구현을 기술하기 전에 한 가지 더 필요한 개념이 있다. 민감 정보 컨테이너(Sensitive Data Conatainer, SDC)는 보안이 중요시되는 데이터를 단순히 바이트 배열로 호출 또는 사용할 필요가 없도록 하기 위해 만들어졌다. 예를 들어 사용자가 키를 생성하거나, 평문을 필요로 하는 알고리즘에 '평문'을 안전하게 선언하고 전달하기 위함인 것이다. 암호화 결과가 암호문인 경우에 이 '암호문'도 민감 정보가 된다. - -이 개념은 꽤 많은 배경 지식을 요구하기 때문에 이 문서에 독립적으로 기술되어야 할 개념은 아니다. 깊은 기술적 명세를 확인하고 싶다면 [이 곳](https://velog.io/@quant-t-f/entanglementlib-sdc)을 참고해라. - -그렇기 때문에 간결히 소개하자면, 민감 정보를 Java에서 받고, 네이티브(Rust)에서 사용하도록 한다. 이렇게 되면 이 정보를 더 이상 사용할 필요가 없을 때 네이티브에서 '매우 안전하게' 소거된다. - -쉽게 말해 해당 민감 정보에 대한 '소유권(ownership)'을 완전히 얽힘 라이브러리에 귀속시키는 것이다. 얽힘 라이브러리가 민감 정보에 대해 소유권을 가질 수 없다면, 그 즉시 데이터를 파기(wipe, destroy; 영소거, zeroing)함으로서 전체적인 보안성을 유지시킨다. 사용자는 컨테이너 생성 시점에 민감 정보의 소유권을 넘겨줄지, 파기할지를 결정할 수 있다. - -> 얽힘 라이브러리의 모든 로직에선 기본적으로 바이트 배열(`byte[]`)이 `Unsafe`하다고 예상한다. -> Java는 메모리 주소에 직접 접근하는 것을 방어하고, -> 얽힘 라이브러리는 메모리에 할당되는 행위 자체를 방어한다. - -## 명세: BlockCipher - -블럭 암호화 카테고리는 AES, ARIA 등 다양한 암호화 알고리즘을 일관된 인터페이스(`BlockCipherStrategy`)로 추상화한다. 내부적으로는 `BouncyCastle`의 저수준 API(`BlockCipher`, `BufferedBlockCipher`, `AEADBlockCipher`)를 래핑하여 구현되었으며, SDC와의 유기적인 통합을 통해 키(key) 및 평문(plaintext) 데이터의 메모리 잔존 위협을 원천 차단하는 것을 목표로 한다. - -이 카테고리는 현재 다음의 알고리즘 구현체를 통해 사용할 수 있다. - -1. AES -2. ARIA - -상속 구조는 다음과 같다. - -![](../../../../../../../../assets/blockciphers.png) - -### 아키텍처 및 클래스 계층 - -시스템은 확장성과 유지보수성을 위해 다음과 같은 계층적 구조를 따른다. - -#### 인터페이스 계층 - - - **`EntLibCryptoStrategy`**: 모든 암호화 전략의 최상위 인터페이스로, 알고리즘 이름과 타입을 반환하는 메소드를 정의한다. -- **`CipherStrategy`**: 암호화(`encrypt`) 및 복호화(`decrypt`)의 핵심 동작을 정의하며, 초기화 벡터(Initialization Vector, IV) 설정 및 TLS 1.3 스타일의 Nonce 계산 로직을 포함한다. -- **`BlockCipherStrategy`**: `CipherStrategy`를 확장하여 블록 암호 특유의 설정인 **운영 모드(Mode)**, **패딩(Padding)**, **다이제스트(Digest)** 설정 메소드를 제공한다. - -#### 구현 계층 - -- **`AbstractBlockCipher`**: 블록 암호의 공통 로직을 처리하는 추상 클래스다. `BouncyCastle` 엔진의 초기화, 버퍼링된 암호(`BufferedBlockCipher`) 또는 `AEAD` 암호(`AEADBlockCipher`) 생성, 그리고 메모리 소거(`KeyDestroyHelper.zeroing`) 로직을 수행한다. -- **`AESStrategy`**: `AbstractBlockCipher`를 상속받은 구체 클래스(Concrete Class)로, `BouncyCastle`의 `AESEngine`을 사용하여 AES 알고리즘(128/192/256 bit)을 구현한다. - -### 주요 기능 및 보안 메커니즘 - -#### 메모리 보안 - -이 카테고리는 `SensitiveDataContainer`를 입출력 타입으로 강제하거나 지원함으로써 힙(Heap) 메모리 내 민감 정보 노출을 방지한다. - -- **Zero-Inference Rule:** 암호화/복호화 과정에서 SDC로부터 추출된 네이티브 데이터(byte array)는 연산 직후 `KeyDestroyHelper#zeroing`을 통해 즉시 덮어씌워진다. -- **Native Handling:** 키 데이터는 네이티브 메모리 세그먼트에서 관리되며, 사용 시점에만 일시적으로 힙으로 복사된 후 소거된다. - -#### 운영 모드 및 패딩 - -`BlockCipherStrategy`는 `Mode`와 `Padding` 열거형(Enum)을 통해 다양한 암호화 구성을 지원한다. - -- **지원 운영 모드 (`Mode`)**: - - **Standard** - - `ECB` (보안상 비권장) - - `CBC` - - `CFB` - - `OFB` - - `CTR` - - **AEAD** - - `AEAD_GCM` - - `AEAD_CCM` -- **지원 패딩 (`Padding`)**: - - `PKCS5` - - `PKCS7` - - `ISO7816` - - `ISO10126` - - `ZERO_BYTE` - - `NO` (NoPadding) - -#### IV 관리 및 체이닝 - -초기화 벡터(IV)의 생성, 검증, 그리고 전달 방식에 대해 엄격한 규칙을 적용한다. - -- **IV 길이 검증:** AEAD 모드인 경우 12바이트, 일반 모드인 경우 16바이트의 IV 길이를 강제한다. -- **자동 생성 및 체이닝:** `encrypt` 수행 시 IV가 설정되지 않았다면 자동으로 생성하며, `ivChaining` 옵션이 `true`일 경우 결과 데이터의 앞부분에 IV를 연결($IV || CipherText$)하여 반환한다. -- **IV 추론 (Inference):** `decrypt` 수행 시 `ivInference`가 `true`라면, 암호문 앞단에서 규격에 맞는 IV를 자동으로 추출하여 복호화에 사용한다. - -#### 인증된 암호화 (AEAD) - -GCM 및 CCM 모드 사용 시 `AEADBlockCipher`를 통해 구현되며, 추가 인증 데이터(AAD)를 `updateAAD` 메소드로 설정할 수 있다. - -### API 명세 - -#### 암호화 (Encrypt) - -```java -SensitiveDataContainer encrypt(@NotNull SensitiveDataContainer keyContainer, final Object plain, boolean ivChaining) -``` - -- **설명** - - 평문 데이터를 암호화한다. `keyContainer`는 암호화 키를 포함하며, `plain`은 `byte[]`, `SensitiveDataContainer`, 또는 `ByteBuffer` 타입이어야 한다. -- **IV 처리** - - `ivChaining`이 `true`이고 ECB 모드가 아닌 경우, 반환되는 컨테이너의 데이터 구조는 $IV || EncryptedData$ 형태를 가진다. - -#### 복호화 (Decrypt) - -```java -SensitiveDataContainer decrypt(@NotNull SensitiveDataContainer keyContainer, final SensitiveDataContainer ciphertext, boolean ivInference) -``` - -- **설명** - - 암호문 컨테이너를 복호화한다. -- **IV 추론** - - `ivInference`가 `true`일 경우 암호문의 선두(12 또는 16바이트)를 잘라내어 IV로 사용한다. 반대로 `false`인 경우 사전에 `iv()` 메소드를 통해 설정된 값을 사용한다. - -#### 블럭 암호화 구성 (Configuration) - -- `setMode(Mode mode)`: 블록 암호 운영 모드 설정 -- `setPadding(Padding padding)`: 패딩 방식 설정 -- `iv(Object raw)`: 명시적 IV 설정 (`Integer` 입력 시 빈 SDC 생성, `byte[]` 또는 `SDC` 입력 시 값 설정) - -## 명세: StreamCipher - -스트림 암호화 카테고리는 ChaCha20와 같은 스트림 암호 알고리즘을 일관된 인터페이스(`StreamCipherStrategy`)로 추상화한다. 내부적으로는 `BouncyCastle`의 스트림 암호 엔진(`StreamCipher`, `ChaCha7539Engine`) 및 AEAD 엔진(`ChaCha20Poly1305`)을 래핑하여 구현되었으며, SDC와의 유기적인 통합을 통해 키(key) 및 평문(plaintext) 데이터의 메모리 잔존 위협을 원천 차단하는 것을 목표로 한다. - -이 카테고리는 현재 다음의 알고리즘 구현체를 통해 사용할 수 있다. - -1. ChaCha20 (IETF, [RFC 8439](https://datatracker.ietf.org/doc/html/rfc8439)) -2. ChaCha20-Poly1305 (AEAD) - -### 아키텍처 및 클래스 계층 - -시스템은 확장성과 유지보수성을 위해 다음과 같은 계층적 구조를 따른다. - -#### 인터페이스 계층 - -- **`EntLibCryptoStrategy`**: 모든 암호화 전략의 최상위 인터페이스로, 알고리즘 이름과 타입을 반환하는 메소드를 정의한다. -- **`CipherStrategy`**: 암호화(`encrypt`) 및 복호화(`decrypt`)의 핵심 동작을 정의하며, IV 또는 Nonce 설정 및 TLS 1.3 스타일의 Nonce 계산 로직을 포함한다. -- **`StreamCipherStrategy`**: `CipherStrategy`를 확장하여 스트림 암호 및 AEAD 스트림 암호(Poly1305)를 위한 **AAD(Additional Authenticated Data)** 설정 메소드를 제공한다. - -#### 구현 계층 - -- **`AbstractStreamCipher`**: 스트림 암호의 공통 로직을 처리하는 추상 클래스다. `BouncyCastle` 엔진의 초기화, 스트림 처리, 그리고 메모리 소거(`KeyDestroyHelper.zeroing`) 로직을 수행한다. -- **`ChaCha20Strategy`**: `AbstractStreamCipher`를 상속받은 구체 클래스(Concrete Class)로, `BouncyCastle`의 `ChaCha7539Engine`을 사용하여 IETF 표준 ChaCha20 알고리즘을 구현한다. -- **`ChaCha20Poly1305Strategy`**: `AbstractStreamCipher`를 상속받으며, 인증된 암호화(AEAD)를 위해 `ChaCha20Poly1305` 엔진을 사용하여 데이터 기밀성과 무결성을 동시에 보장한다. - -### 주요 기능 및 보안 메커니즘 - -#### 메모리 보안 - -이 카테고리는 `SensitiveDataContainer`를 입출력 타입으로 강제하거나 지원함으로써 힙(Heap) 메모리 내 민감 정보 노출을 방지한다. - -- **Zero-Inference Rule:** 암호화/복호화 과정에서 SDC로부터 추출된 네이티브 데이터(byte array)는 연산 직후 `KeyDestroyHelper#zeroing`을 통해 즉시 덮어씌워진다. -- **Native Handling:** 키 데이터는 네이티브 메모리 세그먼트에서 관리되며, 사용 시점에만 일시적으로 힙으로 복사된 후 소거된다. - -#### IV(Nonce) 관리 및 체이닝 - -스트림 암호에서 필수적인 Nonce(IV)의 생성, 검증, 그리고 전달 방식에 대해 엄격한 규칙을 적용한다. - -- **Nonce 길이 검증:** ChaCha20 및 ChaCha20-Poly1305 알고리즘 모두 IETF 표준에 따라 12바이트(96-bit)의 Nonce 길이를 강제한다. -- **자동 생성 및 체이닝:** `encrypt` 수행 시 Nonce가 설정되지 않았다면 자동으로 생성하며, `ivChaining` 옵션이 `true`일 경우 결과 데이터의 앞부분에 Nonce를 연결($Nonce || CipherText$)하여 반환한다. -- **IV 추론 (Inference):** `decrypt` 수행 시 `ivInference`가 `true`라면, 암호문 앞단(12바이트)에서 규격에 맞는 Nonce를 자동으로 추출하여 복호화에 사용한다. - -#### 인증된 암호화 (AEAD) - -`ChaCha20Poly1305Strategy` 사용 시 적용되며, 평문 데이터 암호화와 동시에 Poly1305 MAC(Message Authentication Code)을 생성한다. 추가 인증 데이터(AAD)는 `updateAAD` 메소드를 통해 설정할 수 있다. - -### API 명세 - -#### 암호화 (Encrypt) - -```java -SensitiveDataContainer encrypt(@NotNull SensitiveDataContainer keyContainer, final Object plain, boolean ivChaining) -``` - -- **설명** - - 평문 데이터를 암호화한다. `keyContainer`는 암호화 키를 포함하며, `plain`은 `byte[]`, `SensitiveDataContainer`, 또는 `ByteBuffer` 타입이어야 한다. -- **IV 처리** - - `ivChaining`이 `true`인 경우, 반환되는 컨테이너의 데이터 구조는 $Nonce || EncryptedData$ 형태를 가진다. - -#### 복호화 (Decrypt) - -```java -SensitiveDataContainer decrypt(@NotNull SensitiveDataContainer keyContainer, final SensitiveDataContainer ciphertext, boolean ivInference) -``` - -- **설명** - - 암호문 컨테이너를 복호화한다. -- **IV 추론** - - `ivInference`가 `true`일 경우 암호문의 선두(12바이트)를 잘라내어 Nonce로 사용한다. 반대로 `false`인 경우 사전에 `iv()` 메소드를 통해 설정된 값을 사용한다. - -#### 스트림 암호화 구성 (Configuration) - -- `iv(Object raw)`: 명시적 Nonce 설정 (`Integer` 입력 시 빈 SDC 생성, `byte[]` 또는 `SDC` 입력 시 값 설정) -- `updateAAD(byte[] aad)`: AEAD(Poly1305)를 위한 추가 인증 데이터(AAD) 설정 - -## 명세: KEM (Key Encapsulation Mechanism) - -KEM(키 캡슐화 메커니즘) 카테고리는 공개 키 암호화 시스템을 사용하여 대칭 키(공유 비밀; Shared Secret, SS)를 안전하게 교환하기 위한 메커니즘을 정의한다. 얽힘 라이브러리(EntanglementLib)는 NIST가 표준화한 PQC 알고리즘인 **ML-KEM**을 중심으로, 고전적인 타원 곡선 알고리즘(**X25519**) 및 이 둘을 결합한 **하이브리드(Hybrid)** 방식을 지원한다. - -모든 연산은 `NativeKEMStrategy` 인터페이스를 통해 추상화되며, 실제 암호학적 연산은 `entlib-native`의 Rust 코드로 위임되어 수행된다. 이는 고성능 연산과 함께 메모리 안전성을 보장하기 위함이다. - -이 카테고리는 현재 다음의 알고리즘 구현체를 통해 사용할 수 있다. - -1. **ML-KEM (Module-Lattice-based KEM)**: NIST FIPS 203 표준 - - `ML-KEM-512` - - `ML-KEM-768` - - `ML-KEM-1024` -2. **X25519**: `Curve25519` 기반의 고전적 KEM (ECDH) -3. **Hybrid**: X25519 + ML-KEM-768 결합 모드 - -### 아키텍처 및 클래스 계층 - -시스템은 PQC 전환기와 고전적 환경을 모두 지원하기 위해 유연한 계층 구조를 가진다. - -#### 인터페이스 계층 - -- **`EntLibCryptoStrategy`**: 모든 암호화 전략의 최상위 인터페이스로, 알고리즘 식별을 담당한다. -- **`NativeKEMStrategy`**: 캡슐화(`encapsulate`) 및 디캡슐화(`decapsulate`)의 핵심 동작을 정의한다. 입출력 데이터는 반드시 `SensitiveDataContainer`를 통해 전달되어야 하며, 힙 메모리 노출을 최소화하도록 설계되었다. - -#### 구현 계층 - -- **`MLKEMStrategy`**: `NativeKEMStrategy`를 구현한 클래스로, ML-KEM 알고리즘을 처리한다. `MLKEMStrategyBundle`을 통해 네이티브 핸들과 상호 작용하며, 파라미터 크기 검증 및 결과 컨테이너 바인딩(Binding)을 수행한다. -- **`MLKEMKeyStrategy`**: 비대칭 키 페어(공개 키, 개인 키) 생성을 담당한다. `NativeEntLibAsymmetricKeyStrategy`를 구현하며, 생성된 키는 즉시 네이티브 메모리에 할당된다. - -### 주요 기능 및 보안 메커니즘 - -#### 메모리 보안 및 네이티브 위임 - -KEM 연산 과정에서 공유 비밀(Shared Secret)과 개인 키(Private Key)는 공격자에게 절대 노출되어서는 안 되는 가장 민감한 정보이다. - -- **SDC 강제:** 모든 키와 결과값(공유 비밀, 암호문)은 `SensitiveDataContainer`에 저장된다. -- **Native Execution:** `encapsulate` 및 `decapsulate` 호출 시 JVM 힙이 아닌 네이티브 메모리 세그먼트의 포인터가 전달되며, 실제 연산은 `entlib-native` 라이브러리 내부에서 수행된다. -- **Zeroing:** 연산 중 발생한 임시 데이터나 에러 발생 시 생성된 컨테이너는 `close()`를 통해 즉시 소거(zeroize)된다. - -#### 엄격한 파라미터 검증 (Rigorous Validation) - -PQC 알고리즘은 키와 암호문의 크기가 고정되어 있으며, 이를 위반한 입력은 잠재적인 공격 시도로 간주된다. - -- **Size Check:** `MLKEMStrategy`는 연산 수행 전 입력된 공개 키나 암호문의 바이트 크기가 `ParameterSizeDetail`에 정의된 규격과 정확히 일치하는지 검사한다. 일치하지 않을 경우 `EntLibCryptoKEMProcessingException` 또는 `EntLibSecurityError`를 발생시켜 연산을 중단한다. - -#### 하이브리드 암호화 (Hybrid Cryptography) - -`X25519MLKEM768` 타입은 고전 암호의 검증된 보안성과 PQC의 미래 보안성을 동시에 제공한다. 내부적으로 두 알고리즘의 키와 암호문을 결합(Concatenate)하여 처리하며, 파라미터 사이즈 또한 두 알고리즘의 합으로 계산된다. - -### API 명세 - -#### 캡슐화 (Encapsulate) - -```java -SensitiveDataContainer encapsulate(@NotNull SensitiveDataContainer keyPublic) -``` - -- **설명** - - 수신자의 공개 키(`keyPublic`)를 사용하여 공유 비밀(Shared Secret)과 암호문(Ciphertext)을 생성한다. -- **동작** - - `keyPublic`이 개인 키를 포함하지 않은 순수 공개 키 컨테이너라고 가정한다. - - 반환되는 SDC는 공유 비밀을 담고 있으며, 하위 컨테이너(binding)로 암호문을 포함한다. - -#### 디캡슐화 (Decapsulate) - -```java -SensitiveDataContainer decapsulate(@NotNull SensitiveDataContainer secretKeyContainer, @NotNull SensitiveDataContainer ciphertext) -``` - -- **설명** - - 수신자의 비밀 키(`secretKeyContainer`)와 수신된 암호문(`ciphertext`)을 사용하여 공유 비밀(Shared Secret)을 복원한다. -- **동작** - - `secretKeyContainer`는 공개 키를 포함하지 않은 순수 비밀 키 컨테이너라고 가정한다. - - 입력된 `ciphertext`의 크기를 검증한 후 네이티브 함수를 호출하여 공유 비밀을 복원하고 이를 SDC에 담아 반환한다. - -#### 키 페어 생성 (Key Generation) - -```java -Pair generateKeyPair() -``` - -- **설명** - - 선택된 알고리즘(`KEMType`)에 맞는 공개 키와 비밀 키 쌍을 생성한다. -- **반환** - - `Pair` 형태의 SDC 쌍을 반환한다. 각 키는 네이티브 메모리에 안전하게 할당된다. \ No newline at end of file diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/RegistrableStrategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/RegistrableStrategy.java deleted file mode 100644 index e1d537a..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/RegistrableStrategy.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto; - -import space.qu4nt.entanglementlib.security.crypto.key.EntLibKey; -import space.qu4nt.entanglementlib.security.crypto.strategy.EntLibCryptoStrategy; - -import java.util.Map; - -/// 레지스트리에 등록 가능한 스트레티지를 위한 인터페이스입니다. -/// -/// 암호화 스트레티지 또는 키 스트레티지 구현체가 이 인터페이스를 구현하면 -/// [EntLibCryptoRegistry]에 자동으로 등록됩니다. -/// -/// @author Q. T. Felix -/// @see AbstractStrategyBundle -/// @see EntLibCryptoRegistry -/// @since 1.1.0 -public interface RegistrableStrategy { - - /** - * 이 스트레티지 제공자가 등록할 스트레티지들을 반환하는 메소드입니다. - * - * @return 알고리즘 타입과 스트레티지의 매핑 - */ - Map getAlgStrategies(); - - /** - * 이 스트레티지 제공자가 등록할 키 스트레티지들을 반환하는 메소드입니다. - * - * @return 알고리즘 타입과 키 스트레티지의 매핑 - */ - Map getKeyStrategies(); - -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/SignatureType.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/SignatureType.java deleted file mode 100644 index 566ad28..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/SignatureType.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto; - -import lombok.Getter; - -import java.util.Arrays; -import java.util.Objects; - -import static space.qu4nt.entanglementlib.security.crypto.CryptoFamily.*; - -/// 지원되는 전자 서명 알고리즘을 열거한 클래스입니다. -/// -/// 이 열거형은 [EntLibAlgorithmType] 인터페이스를 구현하여 각 서명 알고리즘의 -/// 패밀리, 키 크기, PQC(Post-Quantum Cryptography) 여부 등의 속성을 제공합니다. -/// -/// @author Q. T. Felix -/// @see EntLibAlgorithmType -/// @see CryptoFamily -/// @since 1.1.0 -@Getter -public enum SignatureType implements EntLibAlgorithmType { - - /** - * ML-DSA-44 (NIST 보안 레벨 2) PQC 서명 알고리즘입니다. - */ - ML_DSA_44(ML_DSA, ParameterSizeDetail.sign( - 0x520, - 0xa00, - 0x974), true), - /** - * ML-DSA-65 (NIST 보안 레벨 3) PQC 서명 알고리즘입니다. - */ - ML_DSA_65(ML_DSA, ParameterSizeDetail.sign( - 0x7a0, - 0xfc0, - 0xced), true), - /** - * ML-DSA-87 (NIST 보안 레벨 5) PQC 서명 알고리즘입니다. - */ - ML_DSA_87(ML_DSA, ParameterSizeDetail.sign( - 0xa20, - 0x1320, - 0x1213), true), - - // - // SLH-DSA bundle - start - // - SLH_DSA_SHA2_128s(SLH_DSA, ParameterSizeDetail.empty(), true), - SLH_DSA_SHA2_128f(SLH_DSA, ParameterSizeDetail.empty(), true), - SLH_DSA_SHA2_192s(SLH_DSA, ParameterSizeDetail.empty(), true), - SLH_DSA_SHA2_192f(SLH_DSA, ParameterSizeDetail.empty(), true), - SLH_DSA_SHA2_256s(SLH_DSA, ParameterSizeDetail.empty(), true), - SLH_DSA_SHA2_256f(SLH_DSA, ParameterSizeDetail.empty(), true), - SLH_DSA_SHAKE_128s(SLH_DSA, ParameterSizeDetail.empty(), true), - SLH_DSA_SHAKE_128f(SLH_DSA, ParameterSizeDetail.empty(), true), - SLH_DSA_SHAKE_192s(SLH_DSA, ParameterSizeDetail.empty(), true), - SLH_DSA_SHAKE_192f(SLH_DSA, ParameterSizeDetail.empty(), true), - SLH_DSA_SHAKE_256s(SLH_DSA, ParameterSizeDetail.empty(), true), - SLH_DSA_SHAKE_256f(SLH_DSA, ParameterSizeDetail.empty(), true), - // - // SLH-DSA bundle - end - // - - /** - * RSA 2048비트 키 서명 알고리즘입니다. - */ - RSA_2048(RSA, ParameterSizeDetail.empty(), false), - /** - * RSA 4096비트 키 서명 알고리즘입니다. - */ - RSA_4096(RSA, ParameterSizeDetail.empty(), false), - ; - - /** - * 서명 알고리즘 패밀리입니다. - */ - private final CryptoFamily family; - - /** - * 파라미터 디테일 객체입니다. - */ - private final ParameterSizeDetail parameterSizeDetail; - - /** - * PQC(Post-Quantum Cryptography) 알고리즘 여부입니다. - */ - private final boolean pQC; - - private final String name = name(); - - /** - * {@link SignatureType} 열거형 생성자입니다. - * - * @param family 서명 알고리즘 패밀리 - * @param parameterSizeDetail 키 크기 (비트 단위) - * @param pQC PQC 알고리즘 여부 - */ - SignatureType(CryptoFamily family, ParameterSizeDetail parameterSizeDetail, boolean pQC) { - this.family = family; - this.parameterSizeDetail = parameterSizeDetail; - this.pQC = pQC; - } - - /** - * 이 서명 알고리즘의 카테고리를 반환하는 메소드입니다. - * - * @return {@link EntLibCryptoCategory#SIGNATURE} - */ - @Override - public EntLibCryptoCategory getCategory() { - return EntLibCryptoCategory.SIGNATURE; - } - - public static SignatureType getByContextIdentifier(String identifier) { - return Arrays.stream(SignatureType.values()) - .filter(type -> Objects.equals(type.getContextIdentifier(), identifier)) - .findFirst() - .orElseThrow(); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/USAGE.md b/src/main/java/space/qu4nt/entanglementlib/security/crypto/USAGE.md deleted file mode 100644 index 35e0ea6..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/USAGE.md +++ /dev/null @@ -1,119 +0,0 @@ -# EF: Crypto Strategy Pattern - -이 패키지는 `BouncyCastle` 라이브러리의 저수준 API를 기반으로 한 몇 가지의 암호화 알고리즘과, `entlib-native`에서 연산이 이루어지는 전자 서명, 키 관리 기능 등을 제공하는 주력 암호화 모듈입니다. - -저수준 API 기능은 `exp-bc-lightweight-api` 브랜치에서 우선 공개되지만, 네이티브 라이브러이 기능에 대해선 포함되지 않습니다. 어찌 뙜던 이 모든 기능은 `1.1.0` 릴리즈에 포함됩니다. - -## 주요 기능 - -* 암호화 알고리즘 지원: AES, ARIA, ChaCha20 등 다양한 대칭 키 암호화 알고리즘을 지원합니다. -* 전자 서명 지원: RSA 및 FIPS 203, 204, 205 표준에 명시된 PQC(Post-Quantum Cryptography) 알고리즘을 지원합니다(단, FIPS 205 명세에 따른 `SLH-DSA` 알고리즘은 현재 개발 중에 있습니다). -* 확장 가능한 아키텍처: `AbstractStrategyBundle` 및 `EntLibCryptoRegistry`등의 팩토리 레지스트리를 통해 새로운 알고리즘을 쉽게 추가하고 관리할 수 있습니다. -* 안전한 민감 데이터 관리: `SensitiveDataContainer` 클래스를 통해 암호화 키의 생성, 저장, 폐기를 안전하게 처리합니다. - -## 패키지 구조 - -### 핵심 인터페이스 및 클래스 - -* `EntLibAlgorithmType`: 모든 암호화 알고리즘 타입의 공통 인터페이스입니다. 카테고리, 패밀리, 키 크기, PQC 여부 등의 속성을 정의합니다. -* `EntLibCryptoCategory`: 암호화 알고리즘의 유형(CIPHER, SIGNATURE, KEY_AGREEMENT 등)을 분류하는 열거형입니다. -* `CryptoFamily`: 암호화 알고리즘의 패밀리(AES, RSA, ML_DSA 등)를 분류하는 열거형입니다. -* `EntLibCryptoRegistry`: 모든 암호화 스트레티지와 키 스트레티지를 관리하는 중앙 레지스트리입니다. -* `AbstractStrategyBundle`: 여러 스트레티지를 묶어서 레지스트리에 등록하는 번들 클래스의 기본 구현체입니다. -* `RegistrableStrategy`: 레지스트리에 등록 가능한 스트레티지 인터페이스입니다. - -### 알고리즘 타입 - -* `CipherType`: 지원하는 암호화 알고리즘 목록 -* `SignatureType`: 지원하는 전자 서명 알고리즘 목록 (ML-DSA, RSA 등) - -### 키 관리 (key 패키지) - -* `EntLibCryptoKey`: 네이티브 메모리(`Arena`, `MemorySegment`)를 사용하여 암호화 키를 안전하게 저장하고 관리하는 클래스입니다. `AutoCloseable`을 구현하여 사용 후 - 자동으로 메모리를 소거(`wipe`)합니다. 현재 스레드 한정(`Arena.ofConfined`)으로 동작하므로 생성한 스레드에서만 접근 가능합니다. -* `KeyWiper`: 키 소거 작업을 수행하는 함수형 인터페이스입니다. -* `EntLibSymmetricKeyStrategy`: 대칭 키 생성 전략 인터페이스입니다. (`generateKey`) -* `EntLibAsymmetricKeyStrategy`: 비대칭 키 쌍 생성 전략 인터페이스입니다. (`generateKeyPair`) - -### 스트레티지 (strategy 패키지) - -* `EntLibCryptoStrategy`: 모든 암호화 스트레티지의 최상위 인터페이스입니다. -* `CipherStrategy`: 암호화/복호화 연산을 수행하는 인터페이스입니다. (`encrypt`, `decrypt`) -* `SignatureStrategy`: 전자 서명 및 검증을 수행하는 인터페이스입니다. (`sign`, `verify`) -* `BlockCipherStrategy`: 블록 암호화 전략 인터페이스입니다. 운영 모드(`Mode`), 패딩(`Padding`), 다이제스트(`Digest`) 설정 기능을 제공합니다. -* `StreamCipherStrategy`: 스트림 암호화 전략 인터페이스입니다. `ByteBuffer`를 이용한 스트리밍 암호화/복호화(`streamEncrypt`, `streamDecrypt`)를 지원합니다. -* `AEADCipherStrategy`: AEAD 암호화 전략 인터페이스입니다. AAD(`updateAAD`) 설정 기능을 제공합니다. - -### 번들 (bundle 패키지) - -* `AESStrategyBundle`: AES 암호화 스트레티지를 등록하는 번들입니다. -* `ARIAStrategyBundle`: ARIA 암호화 스트레티지를 등록하는 번들입니다. -* `ChaCha20StrategyBundle`: ChaCha20 암호화 스트레티지를 등록하는 번들입니다. -* `MLDSAStrategyBundle`: ML-DSA 서명 스트레티지를 등록하는 번들입니다. -* `SLHDSAStrategyBundle`: ML-DSA 서명 스트레티지를 등록하는 번들입니다. - -### 상세 구현 (strategy.detail 패키지) - -* `AbstractBlockCipher`: 블록 암호 알고리즘의 공통 기능을 제공하는 추상 클래스입니다. `BlockCipherStrategy`와 `AEADCipherStrategy`를 구현하며, - BouncyCastle 엔진을 사용하여 실제 암호화/복호화를 수행합니다. -* `AbstractStreamCipher`: 스트림 암호 알고리즘의 공통 기능을 제공하는 추상 클래스입니다. `StreamCipherStrategy`를 구현합니다. -* `AESStrategy`: AES 알고리즘 구현체입니다. `AESEngine`을 사용하며, ECB 모드를 제외하고 IV를 자동으로 생성 및 관리합니다. -* `ARIAStrategy`: ARIA 알고리즘 구현체입니다. `ARIAEngine`을 사용하며, 대한민국 국가 표준 블록 암호입니다. -* `MLDSAStrategy`: ML-DSA PQC 서명 알고리즘 구현체입니다. `MLDSASigner`를 사용하여 서명 및 검증을 수행합니다. -* `ChaCha20Strategy`: ChaCha20 스트림 암호 구현체입니다. `ChaChaEngine`을 사용하며, 8바이트 Nonce(IV)를 사용합니다. -* `ChaCha20Poly1305Strategy`: ChaCha20-Poly1305 AEAD 암호 구현체입니다. 12바이트 Nonce(IV)와 16바이트 MAC을 사용하며, 스트리밍 암호화 시 청크 단위로 - 처리합니다. - -### 키 생성 전략 (key.strategy.detail 패키지) - -* `AESSymmetricKeyStrategy`: AES 키 생성 전략입니다. -* `ARIASymmetricKeyStrategy`: ARIA 키 생성 전략입니다. -* `ChaCha20SymmetricKeyStrategy`: ChaCha20 키 생성 전략입니다. -* `ChaCha20Poly1305SymmetricKeyStrategy`: ChaCha20-Poly1305 키 생성 전략입니다. -* `MLDSAKeyStrategy`: ML-DSA 키 쌍 생성 전략입니다. -* `InternalKeyGenerator`: 내부적으로 사용되는 안전한 난수 기반 키 생성 유틸리티입니다. - -## 사용 방법 - -### 1. 스트레티지 가져오기 - -`EntLibCryptoRegistry`를 통해 원하는 알고리즘의 스트레티지를 가져올 수 있습니다. - -```java -// AES-256 암호화 스트레티지 가져오기 -CipherStrategy aesStrategy = EntLibCryptoRegistry.getStrategy(CipherType.AES_256, CipherStrategy.class); - -// ML-DSA-44 서명 스트레티지 가져오기 -SignatureStrategy mldsaStrategy = EntLibCryptoRegistry.getStrategy(SignatureType.ML_DSA_44, SignatureStrategy.class); -``` - -### 2. 민감 데이터 관리 - -`SensitiveDataContainer`를 사용하여 민감 데이터를 네이티브 메모리에 안전하게 저장하고 관리합니다. -`close()` 호출 시 `entlib-native`를 통해 메모리가 완전히 소거됩니다. - -```java -byte[] sensitiveData = ...; // 민감 데이터 -// forceWipe=true: 원본 배열 즉시 소거 (소유권 이전) -try (SensitiveDataContainer container = new SensitiveDataContainer(sensitiveData, true)) { - MemorySegment segment = container.getMemorySegment(); - // 데이터 사용 -} // try 블록 종료 시 네이티브 메모리 자동 소거 - -// 암호학적으로 안전한 랜덤 바이트 생성 -byte[] randomBytes = SensitiveDataContainer.generateSafeRandomBytes(32); -``` - -### 3. 새로운 알고리즘 추가 - -새로운 알고리즘을 추가하려면 다음 단계를 따르세요. - -1. `AbstractStrategyBundle`을 상속받는 번들 클래스를 생성합니다. -2. `registerStrategies()` 메소드에서 해당 알고리즘의 스트레티지를 등록합니다. -3. `EntLibCryptoRegistry`의 static 블록에 새 번들의 인스턴스 참조를 추가하여 자동 등록되도록 합니다. - -## 보안 고려사항 - -* `EntLibCryptoKey`는 `Arena.ofConfined()`를 사용하므로 생성된 스레드에서만 접근 가능합니다. 다른 스레드로 키를 전달하려면 `Arena.ofShared()`를 사용하는 방식으로 변경이 - 필요할 수 있습니다. -* `EntLibCryptoKey.toByteArray()` 메소드는 힙 메모리에 키의 복사본을 생성합니다. 사용 후 반드시 `KeyDestroyHelper.zeroing()` 등을 사용하여 소거해야 합니다. \ No newline at end of file diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/AESStrategyBundle.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/AESStrategyBundle.java deleted file mode 100644 index 6c629cd..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/AESStrategyBundle.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.bundle; - -import space.qu4nt.entanglementlib.security.crypto.AbstractStrategyBundle; -import space.qu4nt.entanglementlib.security.crypto.CipherType; -import space.qu4nt.entanglementlib.security.crypto.EntLibCryptoRegistry; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.detail.AESSymmetricKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.AESStrategy; - -/// AES 알고리즘 스트레티지 번들 클래스입니다. -/// -/// AES-128, AES-192, AES-256 스트레티지를 [EntLibCryptoRegistry]에 등록합니다. -/// -/// @author Q. T. Felix -/// @see AbstractStrategyBundle -/// @see AESStrategy -/// @since 1.1.0 -final class AESStrategyBundle extends AbstractStrategyBundle { - - /** - * 외부 인스턴스화를 방지하기 위한 private 생성자입니다. - */ - AESStrategyBundle() { - } - - /** - * AES 암호화 스트레티지들을 레지스트리에 등록하는 메소드입니다. - *

- * {@link CipherType#AES_128}, {@link CipherType#AES_192}, {@link CipherType#AES_256} - * 타입에 해당하는 스트레티지를 등록합니다. - */ - @Override - protected void registerStrategies() { - register(CipherType.AES_128, AESStrategy.create(CipherType.AES_128), AESSymmetricKeyStrategy.create(128)); - register(CipherType.AES_192, AESStrategy.create(CipherType.AES_192), AESSymmetricKeyStrategy.create(192)); - register(CipherType.AES_256, AESStrategy.create(CipherType.AES_256), AESSymmetricKeyStrategy.create(256)); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/ARIAStrategyBundle.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/ARIAStrategyBundle.java deleted file mode 100644 index 3bccc24..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/ARIAStrategyBundle.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.bundle; - -import space.qu4nt.entanglementlib.security.crypto.AbstractStrategyBundle; -import space.qu4nt.entanglementlib.security.crypto.CipherType; -import space.qu4nt.entanglementlib.security.crypto.EntLibCryptoRegistry; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.detail.ARIASymmetricKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.ARIAStrategy; - -/// ARIA 알고리즘 스트레티지 번들 클래스입니다. -/// -/// ARIA-128, ARIA-192, ARIA-256 스트레티지를 [EntLibCryptoRegistry]에 등록합니다. -/// ARIA는 대한민국 국가 표준 블록 암호 알고리즘입니다. -/// -/// @author Q. T. Felix -/// @see AbstractStrategyBundle -/// @see ARIAStrategy -/// @since 1.1.0 -public final class ARIAStrategyBundle extends AbstractStrategyBundle { - - /** - * 외부 인스턴스화를 방지하기 위한 private 생성자입니다. - */ - ARIAStrategyBundle() { - } - - /** - * ARIA 암호화 스트레티지들을 레지스트리에 등록하는 메소드입니다. - *

- * {@link CipherType#ARIA_128}, {@link CipherType#ARIA_192}, {@link CipherType#ARIA_256} - * 타입에 해당하는 스트레티지를 등록합니다. - */ - @Override - protected void registerStrategies() { - register(CipherType.ARIA_128, ARIAStrategy.create(CipherType.ARIA_128), ARIASymmetricKeyStrategy.create(128)); - register(CipherType.ARIA_192, ARIAStrategy.create(CipherType.ARIA_192), ARIASymmetricKeyStrategy.create(192)); - register(CipherType.ARIA_256, ARIAStrategy.create(CipherType.ARIA_256), ARIASymmetricKeyStrategy.create(256)); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/BundleStaticCaller.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/BundleStaticCaller.java deleted file mode 100644 index 7a79bba..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/BundleStaticCaller.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.bundle; - -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Internal -public class BundleStaticCaller { - - /// 조만간 JPMS로 방어해야겠네 - @ApiStatus.Internal - public static void call() { - new AESStrategyBundle(); - new ARIAStrategyBundle(); - new ChaCha20StrategyBundle(); - new MLDSAStrategyBundle(); - new MLKEMStrategyBundle(); - new SLHDSAStrategyBundle(); - new X25519StrategyBundle(); - new X25519MLKEM768StrategyBundle(); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/ChaCha20StrategyBundle.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/ChaCha20StrategyBundle.java deleted file mode 100644 index 809fc85..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/ChaCha20StrategyBundle.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.bundle; - -import space.qu4nt.entanglementlib.security.crypto.AbstractStrategyBundle; -import space.qu4nt.entanglementlib.security.crypto.CipherType; -import space.qu4nt.entanglementlib.security.crypto.EntLibCryptoRegistry; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.detail.ChaCha20Poly1305SymmetricKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.detail.ChaCha20SymmetricKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.AESStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.ChaCha20Poly1305Strategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.ChaCha20Strategy; - -/// ChaCha20, ChaCha20-Poly1305 알고리즘 스트레티지 번들 클래스입니다. -/// -/// 각 암호화 알고리즘을 [EntLibCryptoRegistry]에 등록합니다. -/// -/// 헷갈리지 마세요! 이 번들 클래스는 `RFC 8439`표준에 따른 알고리즘을 -/// 포함합니다. -/// -/// @author Q. T. Felix -/// @see AbstractStrategyBundle -/// @see ChaCha20Strategy -/// @see ChaCha20Poly1305Strategy -/// @since 1.1.0 -public final class ChaCha20StrategyBundle extends AbstractStrategyBundle { - - /** - * 외부 인스턴스화를 방지하기 위한 private 생성자입니다. - */ - ChaCha20StrategyBundle() { - } - - /** - * AES 암호화 스트레티지들을 레지스트리에 등록하는 메소드입니다. - *

- * {@link CipherType#AES_128}, {@link CipherType#AES_192}, {@link CipherType#AES_256} - * 타입에 해당하는 스트레티지를 등록합니다. - */ - @Override - protected void registerStrategies() { - register(CipherType.CHACHA20, ChaCha20Strategy.create(), ChaCha20SymmetricKeyStrategy.create()); - register(CipherType.CHACHA20_POLY1305, ChaCha20Poly1305Strategy.create(), ChaCha20Poly1305SymmetricKeyStrategy.create()); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/MLDSAStrategyBundle.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/MLDSAStrategyBundle.java deleted file mode 100644 index 3a86164..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/MLDSAStrategyBundle.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.bundle; - -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.CallerResponsibility; -import space.qu4nt.entanglementlib.InternalFactory; -import space.qu4nt.entanglementlib.entlibnative.NativeLinkerManager; -import space.qu4nt.entanglementlib.security.crypto.AbstractStrategyBundle; -import space.qu4nt.entanglementlib.security.crypto.EntLibAlgorithmType; -import space.qu4nt.entanglementlib.security.crypto.EntLibCryptoRegistry; -import space.qu4nt.entanglementlib.security.crypto.SignatureType; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.detail.MLDSAKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.MLDSAStrategy; -import space.qu4nt.entanglementlib.util.wrapper.Pair; - -import java.lang.foreign.MemoryLayout; -import java.lang.foreign.ValueLayout; -import java.lang.invoke.MethodHandle; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - -/// ML-DSA(Module Lattice-based Digital Signature Algorithm) 스트레티지 번들 클래스입니다. -/// -/// ML-DSA 각 파라미터 세트 스트레티지를 [EntLibCryptoRegistry]에 등록합니다. -/// ML-DSA는 NIST에서 표준화한 PQC(Post-Quantum Cryptography) 전자 서명 알고리즘입니다. -/// -/// 이 암호화 연산은 `entlib-native`에서 수행됩니다. 정적 블럭에 네이티브 호출을 위한 -/// 레이아웃을 정의하고 사용 함수를 정의해야 합니다. -/// -/// @author Q. T. Felix -/// @see AbstractStrategyBundle -/// @see MLDSAStrategy -/// @since 1.1.0 -@Slf4j -public final class MLDSAStrategyBundle extends AbstractStrategyBundle { - - // - // EntLib-Native - start - // - - static { - NativeLinkerManager entLibNative = InternalFactory.callNativeLib(); - MemoryLayout[] keyGenLayouts = new MemoryLayout[]{ValueLayout.ADDRESS, ValueLayout.ADDRESS}; - MemoryLayout[] signLayouts = new MemoryLayout[]{ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG, ValueLayout.ADDRESS}; - MemoryLayout[] verifyLayouts = new MemoryLayout[]{ValueLayout.ADDRESS, ValueLayout.JAVA_LONG, ValueLayout.ADDRESS, ValueLayout.ADDRESS}; - - log.debug("> ML-DSA 네이티브 함수 로드 중..."); - entLibNative - // ML-DSA-44 - .addReturnableMethodHandle("ml_dsa_44_keygen", ValueLayout.JAVA_INT, keyGenLayouts) - .addReturnableMethodHandle("ml_dsa_44_sign", ValueLayout.JAVA_INT, signLayouts) - .addReturnableMethodHandle("ml_dsa_44_verify", ValueLayout.JAVA_INT, verifyLayouts) - - // ML-DSA-65 - .addReturnableMethodHandle("ml_dsa_65_keygen", ValueLayout.JAVA_INT, keyGenLayouts) - .addReturnableMethodHandle("ml_dsa_65_sign", ValueLayout.JAVA_INT, signLayouts) - .addReturnableMethodHandle("ml_dsa_65_verify", ValueLayout.JAVA_INT, verifyLayouts) - - // ML-DSA-87 - .addReturnableMethodHandle("ml_dsa_87_keygen", ValueLayout.JAVA_INT, keyGenLayouts) - .addReturnableMethodHandle("ml_dsa_87_sign", ValueLayout.JAVA_INT, signLayouts) - .addReturnableMethodHandle("ml_dsa_87_verify", ValueLayout.JAVA_INT, verifyLayouts); - log.debug("> ML-DSA 알고리즘에 대한 모든 네이티브 함수 등록 완료"); - } - - @NotNull - @ApiStatus.Internal - @CallerResponsibility - public static MethodHandle callNativeMLDSAHandle(final @NotNull EntLibAlgorithmType mldsaType, final int typeId) { - return InternalFactory.callNativeLib().getHandle( - mldsaType.getName().toLowerCase(Locale.ROOT) + "_" + - (typeId == 0 ? "keygen" : typeId == 1 ? "sign" : "verify")); - } - - // - // EntLib-Native - end - // - - /** - * 외부 인스턴스화를 방지하기 위한 private 생성자입니다. - */ - MLDSAStrategyBundle() { - } - - /** - * ML-DSA 서명 스트레티지들을 레지스트리에 등록하는 메소드입니다. - *

- * {@link SignatureType#ML_DSA_44}, {@link SignatureType#ML_DSA_65}, {@link SignatureType#ML_DSA_87} - * 타입에 해당하는 스트레티지를 등록합니다. - */ - @Override - protected void registerStrategies() { - register(SignatureType.ML_DSA_44, MLDSAStrategy.create(SignatureType.ML_DSA_44), MLDSAKeyStrategy.create(SignatureType.ML_DSA_44)); - register(SignatureType.ML_DSA_65, MLDSAStrategy.create(SignatureType.ML_DSA_65), MLDSAKeyStrategy.create(SignatureType.ML_DSA_65)); - register(SignatureType.ML_DSA_87, MLDSAStrategy.create(SignatureType.ML_DSA_87), MLDSAKeyStrategy.create(SignatureType.ML_DSA_87)); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/MLKEMStrategyBundle.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/MLKEMStrategyBundle.java deleted file mode 100644 index 890507c..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/MLKEMStrategyBundle.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.bundle; - -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.CallerResponsibility; -import space.qu4nt.entanglementlib.InternalFactory; -import space.qu4nt.entanglementlib.entlibnative.NativeLinkerManager; -import space.qu4nt.entanglementlib.security.crypto.*; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.detail.MLDSAKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.detail.MLKEMKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.MLDSAStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.MLKEMStrategy; - -import java.lang.foreign.MemoryLayout; -import java.lang.foreign.ValueLayout; -import java.lang.invoke.MethodHandle; -import java.util.Locale; - -/// ML-KEM(Module Lattice-based Key Encapsulate Mechanism) 스트레티지 번들 클래스입니다. -/// -/// ML-KEM 각 파라미터 세트 스트레티지를 [EntLibCryptoRegistry]에 등록합니다. -/// ML-KEM는 NIST에서 표준화한 PQC(Post-Quantum Cryptography) 전자 서명 알고리즘입니다. -/// -/// 이 암호화 연산은 `entlib-native`에서 수행됩니다. 정적 블럭에 네이티브 호출을 위한 -/// 레이아웃을 정의하고 사용 함수를 정의해야 합니다. -/// -/// @author Q. T. Felix -/// @see AbstractStrategyBundle -/// @see MLKEMStrategy -/// @since 1.1.0 -@Slf4j -public final class MLKEMStrategyBundle extends AbstractStrategyBundle { - - // - // EntLib-Native - start - // - - static { - NativeLinkerManager entLibNative = InternalFactory.callNativeLib(); - MemoryLayout[] keyGenLayouts = new MemoryLayout[]{ValueLayout.ADDRESS, ValueLayout.ADDRESS}; - MemoryLayout[] encapsulateLayouts = new MemoryLayout[]{ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.ADDRESS}; - MemoryLayout[] decapsulateLayouts = new MemoryLayout[]{ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.ADDRESS}; - - log.debug("> ML-KEM 네이티브 함수 로드 중..."); - entLibNative - // ML-KEM-512 - .addReturnableMethodHandle("ml_kem_512_keygen", ValueLayout.JAVA_INT, keyGenLayouts) - .addReturnableMethodHandle("ml_kem_512_encapsulate", ValueLayout.JAVA_INT, encapsulateLayouts) - .addReturnableMethodHandle("ml_kem_512_decapsulate", ValueLayout.JAVA_INT, decapsulateLayouts) - - // ML-KEM-768 - .addReturnableMethodHandle("ml_kem_768_keygen", ValueLayout.JAVA_INT, keyGenLayouts) - .addReturnableMethodHandle("ml_kem_768_encapsulate", ValueLayout.JAVA_INT, encapsulateLayouts) - .addReturnableMethodHandle("ml_kem_768_decapsulate", ValueLayout.JAVA_INT, decapsulateLayouts) - - // ML-KEM-1024 - .addReturnableMethodHandle("ml_kem_1024_keygen", ValueLayout.JAVA_INT, keyGenLayouts) - .addReturnableMethodHandle("ml_kem_1024_encapsulate", ValueLayout.JAVA_INT, encapsulateLayouts) - .addReturnableMethodHandle("ml_kem_1024_decapsulate", ValueLayout.JAVA_INT, decapsulateLayouts); - log.debug("> ML-KEM 알고리즘에 대한 모든 네이티브 함수 등록 완료"); - } - - @NotNull - @ApiStatus.Internal - @CallerResponsibility - public static MethodHandle callNativeMLKEMHandle(final @NotNull EntLibAlgorithmType mlkemType, final int typeId) { - return InternalFactory.callNativeLib().getHandle( - mlkemType.getName().toLowerCase(Locale.ROOT) + "_" + - (typeId == 0 ? "keygen" : typeId == 1 ? "encapsulate" : "decapsulate")); - } - - // - // EntLib-Native - end - // - - /** - * 외부 인스턴스화를 방지하기 위한 private 생성자입니다. - */ - MLKEMStrategyBundle() { - } - - /** - * ML-KEM 스트레티지들을 레지스트리에 등록하는 메소드입니다. - */ - @Override - protected void registerStrategies() { - register(KEMType.ML_KEM_512, MLKEMStrategy.create(KEMType.ML_KEM_512), MLKEMKeyStrategy.create(KEMType.ML_KEM_512)); - register(KEMType.ML_KEM_768, MLKEMStrategy.create(KEMType.ML_KEM_768), MLKEMKeyStrategy.create(KEMType.ML_KEM_768)); - register(KEMType.ML_KEM_1024, MLKEMStrategy.create(KEMType.ML_KEM_1024), MLKEMKeyStrategy.create(KEMType.ML_KEM_1024)); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/SLHDSAStrategyBundle.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/SLHDSAStrategyBundle.java deleted file mode 100644 index cfcde64..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/SLHDSAStrategyBundle.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.bundle; - -import org.bouncycastle.pqc.crypto.slhdsa.SLHDSAParameters; -import space.qu4nt.entanglementlib.security.crypto.AbstractStrategyBundle; -import space.qu4nt.entanglementlib.security.crypto.EntLibCryptoRegistry; -import space.qu4nt.entanglementlib.security.crypto.SignatureType; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.detail.SLHDSAKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.MLDSAStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.SLHDSAStrategy; - -/// SLH-DSA(Stateless Hash-based Digital Signature Algorithm) 스트레티지 번들 클래스입니다. -/// -/// 모든 SLH-DSA 알고리즘 관련 스트레티지를 [EntLibCryptoRegistry]에 등록합니다. -/// SLH-DSA는 NIST에서 표준화한 PQC(Post-Quantum Cryptography) 전자 서명 알고리즘입니다. -/// -/// @author Q. T. Felix -/// @see AbstractStrategyBundle -/// @see MLDSAStrategy -/// @since 1.1.0 -public final class SLHDSAStrategyBundle extends AbstractStrategyBundle { - - /** - * 외부 인스턴스화를 방지하기 위한 private 생성자입니다. - */ - SLHDSAStrategyBundle() { - } - - /** - * SLH-DSA 서명 스트레티지들을 레지스트리에 등록하는 메소드입니다. - *

- * SLH-DSA 번들 타입에 해당하는 스트레티지를 등록합니다. - */ - @Override - protected void registerStrategies() { - register(SignatureType.SLH_DSA_SHA2_128s, SLHDSAStrategy.create(SignatureType.SLH_DSA_SHA2_128s), SLHDSAKeyStrategy.create(SLHDSAParameters.sha2_128s)); - register(SignatureType.SLH_DSA_SHA2_128f, SLHDSAStrategy.create(SignatureType.SLH_DSA_SHA2_128f), SLHDSAKeyStrategy.create(SLHDSAParameters.sha2_128f)); - register(SignatureType.SLH_DSA_SHA2_192s, SLHDSAStrategy.create(SignatureType.SLH_DSA_SHA2_192s), SLHDSAKeyStrategy.create(SLHDSAParameters.sha2_192s)); - register(SignatureType.SLH_DSA_SHA2_192f, SLHDSAStrategy.create(SignatureType.SLH_DSA_SHA2_192f), SLHDSAKeyStrategy.create(SLHDSAParameters.sha2_192f)); - register(SignatureType.SLH_DSA_SHA2_256s, SLHDSAStrategy.create(SignatureType.SLH_DSA_SHA2_256s), SLHDSAKeyStrategy.create(SLHDSAParameters.sha2_256s)); - register(SignatureType.SLH_DSA_SHA2_256f, SLHDSAStrategy.create(SignatureType.SLH_DSA_SHA2_256f), SLHDSAKeyStrategy.create(SLHDSAParameters.sha2_256f)); - register(SignatureType.SLH_DSA_SHAKE_128s, SLHDSAStrategy.create(SignatureType.SLH_DSA_SHAKE_128s), SLHDSAKeyStrategy.create(SLHDSAParameters.shake_128s)); - register(SignatureType.SLH_DSA_SHAKE_128f, SLHDSAStrategy.create(SignatureType.SLH_DSA_SHAKE_128f), SLHDSAKeyStrategy.create(SLHDSAParameters.shake_128f)); - register(SignatureType.SLH_DSA_SHAKE_192s, SLHDSAStrategy.create(SignatureType.SLH_DSA_SHAKE_192s), SLHDSAKeyStrategy.create(SLHDSAParameters.shake_192s)); - register(SignatureType.SLH_DSA_SHAKE_192f, SLHDSAStrategy.create(SignatureType.SLH_DSA_SHAKE_192f), SLHDSAKeyStrategy.create(SLHDSAParameters.shake_192f)); - register(SignatureType.SLH_DSA_SHAKE_256s, SLHDSAStrategy.create(SignatureType.SLH_DSA_SHAKE_256s), SLHDSAKeyStrategy.create(SLHDSAParameters.shake_256s)); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/X25519MLKEM768StrategyBundle.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/X25519MLKEM768StrategyBundle.java deleted file mode 100644 index ed7cd2a..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/X25519MLKEM768StrategyBundle.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.bundle; - -import space.qu4nt.entanglementlib.security.crypto.AbstractStrategyBundle; -import space.qu4nt.entanglementlib.security.crypto.CipherType; -import space.qu4nt.entanglementlib.security.crypto.EntLibCryptoRegistry; -import space.qu4nt.entanglementlib.security.crypto.KEMType; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.detail.AESSymmetricKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.detail.X25519MLKEM768KeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.AESStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.hybrid.X25519MLKEM768Strategy; - -/// @author Q. T. Felix -/// @since 1.1.0 -final class X25519MLKEM768StrategyBundle extends AbstractStrategyBundle { - - /** - * 외부 인스턴스화를 방지하기 위한 private 생성자입니다. - */ - X25519MLKEM768StrategyBundle() { - } - - /** - * X25519MLKEM768 암호화 스트레티지들을 레지스트리에 등록하는 메소드입니다. - */ - @Override - protected void registerStrategies() { - register(KEMType.X25519MLKEM768, - X25519MLKEM768Strategy.create(null, null), - X25519MLKEM768KeyStrategy.create(null, null)); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/X25519StrategyBundle.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/X25519StrategyBundle.java deleted file mode 100644 index 5b62f1b..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/bundle/X25519StrategyBundle.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.bundle; - -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.CallerResponsibility; -import space.qu4nt.entanglementlib.InternalFactory; -import space.qu4nt.entanglementlib.entlibnative.NativeLinkerManager; -import space.qu4nt.entanglementlib.security.crypto.*; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.detail.X25519KeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.X25519Strategy; - -import java.lang.foreign.MemoryLayout; -import java.lang.foreign.ValueLayout; -import java.lang.invoke.MethodHandle; -import java.util.Locale; - -/// X25519 Diffie-Hellman 키 교환 스트레티지 번들 클래스입니다. -/// -/// X25519 스트레티지를 [EntLibCryptoRegistry]에 등록합니다. -/// X25519는 Curve25519 타원 곡선을 사용한 고보안 ECDH 키 교환 알고리즘입니다. -/// -/// 이 암호화 연산은 `entlib-native`에서 수행됩니다. 정적 블럭에 네이티브 호출을 위한 -/// 레이아웃을 정의하고 사용 함수를 정의해야 합니다. -/// -/// @author Q. T. Felix -/// @see AbstractStrategyBundle -/// @see X25519Strategy -/// @since 1.1.0 -@Slf4j -public final class X25519StrategyBundle extends AbstractStrategyBundle { - - // - // EntLib-Native - start - // - - static { - NativeLinkerManager entLibNative = InternalFactory.callNativeLib(); - MemoryLayout[] keyGenLayouts = new MemoryLayout[]{ValueLayout.ADDRESS, ValueLayout.ADDRESS}; - MemoryLayout[] secretToPublicLayouts = new MemoryLayout[]{ValueLayout.ADDRESS, ValueLayout.ADDRESS}; - MemoryLayout[] dhLayouts = new MemoryLayout[]{ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.ADDRESS}; - - log.debug("> X25519 네이티브 함수 로드 중..."); - entLibNative - .addReturnableMethodHandle("x25519_keygen", ValueLayout.JAVA_INT, keyGenLayouts) - .addReturnableMethodHandle("x25519_sk_to_pk", ValueLayout.JAVA_INT, secretToPublicLayouts) - .addReturnableMethodHandle("x25519_dh", ValueLayout.JAVA_INT, dhLayouts); - log.debug("> X25519 알고리즘에 대한 모든 네이티브 함수 등록 완료"); - } - - @NotNull - @ApiStatus.Internal - @CallerResponsibility - public static MethodHandle callNativeX25519Handle(final int typeId) { - return InternalFactory.callNativeLib().getHandle(KEMType.X25519 - .getName().toLowerCase(Locale.ROOT) + "_" + - (typeId == 0 ? "keygen" : typeId == 1 ? "sk_to_pk" : "dh")); - } - - // - // EntLib-Native - end - // - - /** - * 외부 인스턴스화를 방지하기 위한 private 생성자입니다. - */ - X25519StrategyBundle() { - } - - /** - * X25519 스트레티지를 레지스트리에 등록하는 메소드입니다. - */ - @Override - protected void registerStrategies() { - register(KEMType.X25519, X25519Strategy.create(), X25519KeyStrategy.create()); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/EntLibCryptoKey.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/EntLibCryptoKey.java deleted file mode 100644 index 1e5d1ce..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/EntLibCryptoKey.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.key; - -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.Nullable; -import space.qu4nt.entanglementlib.CallerResponsibility; -import space.qu4nt.entanglementlib.InternalFactory; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.security.KeyDestroyHelper; - -import java.lang.foreign.Arena; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.ValueLayout; - -/// 암호화 키를 네이티브 메모리에 안전하게 저장하고 관리하는 클래스입니다. -/// -/// 현재 [Arena#ofConfined()]를 사용하고 있어 이 키 객체를 생성한 스레드에서만 접근 가능합니다. -/// 만약 클라이언트-서버 통신 과정에서 여러 스레드가 이 키를 공유해야 한다면 -/// [Arena#ofShared()]로 변경을 고려해야 합니다. -/// -/// 이 클래스는 [AutoCloseable]을 구현하여 *try-with-resources* 구문과 함께 -/// 사용할 때 자동으로 키 메모리가 소거됩니다. -/// -/// Rust로 작성된 `entanglement_secure_wipe` 네이티브 함수를 바인딩하여, -/// 키 소거 시 컴파일러 최적화를 방지하고 강제적인 메모리 덮어쓰기(volatile write)를 수행합니다. -/// -/// 마크다운이 편하네요, 이 클래스부터 천천히 마크다운이 적용됩니다. -/// -/// @author Q. T. Felix -/// @see KeyWiper -/// @since 1.1.0 -/// @deprecated 이제 민감 데이터는 [SensitiveDataContainer] 클래스가 소유합니다. -@Slf4j -@Deprecated -public class EntLibCryptoKey implements AutoCloseable { - - /** - * 네이티브 메모리 관리를 위한 {@link Arena} 인스턴스입니다. - */ - private final Arena arena; - - /** - * 키 데이터가 저장된 네이티브 메모리 세그먼트입니다. - */ - private final MemorySegment keySegment; - - /** - * 원시 키 바이트 배열로부터 {@link EntLibCryptoKey} 인스턴스를 생성하는 생성자입니다. - *

- * 생성 시 원본 키 데이터는 네이티브 메모리로 복사된 후 즉시 소거됩니다. - * - * @param rawKey 원시 키 바이트 배열 - */ - public EntLibCryptoKey(byte[] rawKey) { - // 현재 스레드에서만 접근 가능한 메모리 세션 오픈 - this.arena = Arena.ofConfined(); - // Native 영역에 메모리 할당 - this.keySegment = this.arena.allocate(rawKey.length); - - // rawKey를 힙 세그먼트로 래핑하여 네이티브 메모리로 복사 - MemorySegment sourceSegment = MemorySegment.ofArray(rawKey); - MemorySegment.copy(sourceSegment, 0, this.keySegment, 0, rawKey.length); - - // rawKey 복사 후 즉시 원본 rawKey 소거 - KeyDestroyHelper.zeroing(rawKey); - } - - /** - * 키가 저장된 네이티브 메모리 세그먼트를 반환하는 메소드입니다. - * - * @return 키 데이터가 저장된 {@link MemorySegment} - * @throws IllegalStateException 키 세그먼트가 이미 닫힌 경우 - */ - public MemorySegment getKeySegment() { - if (!arena.scope().isAlive()) - throw new IllegalStateException("Key segment is already closed."); - return keySegment; - } - - /** - * 키 데이터를 바이트 배열로 변환하여 반환하는 메소드입니다. - *

- * 반환된 바이트 배열은 힙 메모리에 복사되므로 사용 후 반드시 소거해야 합니다. - * - * @return 키 바이트 배열, 또는 스레드 불일치 시 {@code null} - */ - @CallerResponsibility - public byte @Nullable [] toByteArray() { - try { - return getKeySegment().toArray(ValueLayout.JAVA_BYTE); - } catch (WrongThreadException e) { - log.error("키 스레드가 일치하지 않습니다!", e); - } - return null; - } - - /** - * 키 메모리를 안전하게 소거하는 메소드입니다. - *

- * 바인딩된 러스트 네이티브 함수 {@code entanglement_secure_wipe}를 호출하여 - * 최적화 없는 강제 메모리 영소거(zeroing)를 수행합니다. - * 네이티브 호출이 불가능한 경우 Java의 {@link MemorySegment#fill(byte)}을 사용하여 폴백합니다. - */ - public void wipe() { - if (arena.scope().isAlive()) { - boolean nativeWipeSuccess = false; - try { - // entlib-native 함수 호출 - InternalFactory.callNativeLib().getHandle("entanglement_secure_wipe") - .invokeExact(keySegment, keySegment.byteSize()); - nativeWipeSuccess = true; - } catch (Throwable t) { - log.error("네이티브 보안 소거 중 치명적 예외가 발생했습니다!", t); - } - - // 네이티브 소거 실패 시 java레벨에서 소거 - // 근데 보통 이 경우는 확실히 문제가 있는게 맞음 - if (!nativeWipeSuccess) - KeyDestroyHelper.zeroing(keySegment); - } - } - - /** - * 키 리소스를 정리하고 메모리를 해제하는 메소드입니다. - *

- * 키 메모리를 소거한 후 {@link Arena}를 닫습니다. - */ - @Override - public void close() { - wipe(); - arena.close(); - } -} \ No newline at end of file diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/EntLibKey.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/EntLibKey.java deleted file mode 100644 index aeef74e..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/EntLibKey.java +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.key; - -public interface EntLibKey { -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/KeyWiper.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/KeyWiper.java deleted file mode 100644 index 8ca1d97..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/KeyWiper.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.key; - -import org.jetbrains.annotations.ApiStatus; -import space.qu4nt.entanglementlib.exception.secure.EntLibSensitiveDataException; - -import java.util.Objects; - -/** - * 키 소거 작업을 수행하는 함수형 인터페이스입니다. - *

- * 이 인터페이스는 암호화 키를 안전하게 소거하기 위한 연산을 정의합니다. - * {@link java.util.function.Consumer}와 유사하지만 {@link EntLibSensitiveDataException}을 던질 수 있습니다. - * - * @param 소거할 키의 타입 - * @author Q. T. Felix - * @since 1.1.0 - */ -@ApiStatus.Obsolete -@FunctionalInterface -public interface KeyWiper { - - /** - * 전달받은 키에 대해 소거 연산을 수행하는 메소드입니다. - *

- * 연산 중 {@link EntLibSensitiveDataException} 예외가 발생할 수 있습니다. - * - * @param t 소거할 키 객체 - * @throws EntLibSensitiveDataException 키 소거 중 발생 가능한 예외 - */ - void accept(T t) throws EntLibSensitiveDataException; - - /** - * 이 소거 연산 후에 추가 소거 연산을 수행하는 합성 {@link KeyWiper}를 반환하는 메소드입니다. - * - * @param after 이 연산 후에 수행할 소거 연산 - * @return 합성된 {@link KeyWiper} - * @throws EntLibSensitiveDataException 키 소거 중 발생 가능한 예외 - */ - default KeyWiper andThen(KeyWiper after) throws EntLibSensitiveDataException { - Objects.requireNonNull(after); - return (T t) -> { - accept(t); - after.accept(t); - }; - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/EntLibAsymmetricKeyStrategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/EntLibAsymmetricKeyStrategy.java deleted file mode 100644 index eaf43d2..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/EntLibAsymmetricKeyStrategy.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.key.strategy; - -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.security.crypto.key.EntLibKey; -import space.qu4nt.entanglementlib.util.wrapper.Pair; - -/// 네이티브에서 수행되는 비대칭 키 페어 생성 전략을 정의하는 인터페이스입니다. -/// 각 알고리즘별 구현체가 이 인터페이스를 구현하여 해당 알고리즘에 적합한 키 페어를 생성합니다. -/// -/// @author Q. T. Felix -/// @see SensitiveDataContainer -/// @see EntLibSymmetricKeyStrategy 대칭 키 생성 전략 인터페이스 -/// @since 1.1.0 -public interface EntLibAsymmetricKeyStrategy extends EntLibKey { - - /** - * 비대칭 키 페어(공개 키, 개인 키)을 생성하여 반환하는 메소드입니다. - *

- * 반환되는 {@link Pair}의 첫 번째 요소는 공개 키, 두 번째 요소는 개인 키입니다. - * 생성된 키 페어는 {@link SensitiveDataContainer}로 래핑되어 네이티브 메모리에 - * 안전하게 저장됩니다. - * - * @return 공개 키와 개인 키의 페어 - */ - Pair generateKeyPair() throws Throwable; - -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/EntLibSymmetricKeyStrategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/EntLibSymmetricKeyStrategy.java deleted file mode 100644 index 4c4e7e8..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/EntLibSymmetricKeyStrategy.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.key.strategy; - -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.security.crypto.key.EntLibKey; - -/// 네이티브에서 수행되는 대칭 키 생성 전략을 정의하는 인터페이스입니다. -/// -/// AES, ARIA, ChaCha20 등의 대칭 키 암호화 알고리즘에 사용되는 비밀 키를 생성합니다. -/// 각 알고리즘별 구현체가 이 인터페이스를 구현하여 해당 알고리즘에 적합한 키를 생성합니다. -/// -/// @author Q. T. Felix -/// @see SensitiveDataContainer -/// @see EntLibAsymmetricKeyStrategy 비대칭 키 생성 전략 인터페이스 -/// @since 1.1.0 -public interface EntLibSymmetricKeyStrategy extends EntLibKey { - - /// 대칭 키를 생성하여 반환하는 메소드입니다. - /// - /// 생성된 키는 [SensitiveDataContainer]로 래핑되어 네이티브 메모리에 안전하게 저장됩니다. - /// `heap` 메모리에 잔류하는 키 바이트 배열은 즉시 소거됩니다. - /// - /// @return 생성된 대칭 키 - SensitiveDataContainer generateKey(); - -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/AESSymmetricKeyStrategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/AESSymmetricKeyStrategy.java deleted file mode 100644 index a3f27fe..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/AESSymmetricKeyStrategy.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.key.strategy.detail; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.security.crypto.key.EntLibCryptoKey; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.EntLibSymmetricKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.AESStrategy; - -import java.util.stream.IntStream; - -/// AES 알고리즘을 위한 대칭 키 생성 전략 클래스입니다. -/// -/// 128, 192, 256비트 키 크기를 지원하며, [AESStrategy]와 함께 사용됩니다. -/// -/// @author Q. T. Felix -/// @see EntLibSymmetricKeyStrategy -/// @see AESStrategy -/// @since 1.1.0 -public final class AESSymmetricKeyStrategy implements EntLibSymmetricKeyStrategy { - - /** - * AES에서 지원하는 키 크기 목록입니다. (128, 192, 256비트) - */ - final int[] POSSIBLE_KEY_SIZES = new int[]{128, 192, 256}; - - /** - * 생성할 키의 비트 크기입니다. - */ - private final int keySize; - - /** - * {@link AESStrategy}로부터 키 크기를 추출하여 인스턴스를 생성하는 생성자입니다. - * - * @param aesStrategy AES 암호화 전략 - */ - AESSymmetricKeyStrategy(int keySize) { - this.keySize = IntStream.of(POSSIBLE_KEY_SIZES) - .filter(p -> p == keySize) - .findFirst() - .orElse(256); - } - - /** - * {@link AESSymmetricKeyStrategy} 인스턴스를 생성하는 팩토리 메소드입니다. - * - * @param keySize AES 암호화 키 사이즈 - * @return 새 {@link AESSymmetricKeyStrategy} 인스턴스 - */ - public static AESSymmetricKeyStrategy create(final int keySize) { - return new AESSymmetricKeyStrategy(keySize); - } - - /** - * AES 대칭 키를 생성하여 반환하는 메소드입니다. - * - * @return 생성된 AES 키 - */ - @Override - public SensitiveDataContainer generateKey() { - return new SensitiveDataContainer(InternalKeyGenerator.initializedCipherKeyGenerator(keySize).generateKey(), true); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/ARIASymmetricKeyStrategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/ARIASymmetricKeyStrategy.java deleted file mode 100644 index 8385cbe..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/ARIASymmetricKeyStrategy.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.key.strategy.detail; - -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.EntLibSymmetricKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.ARIAStrategy; - -import java.util.stream.IntStream; - -/// ARIA 알고리즘을 위한 대칭 키 생성 전략 클래스입니다. -/// -/// 128, 192, 256비트 키 크기를 지원하며, [ARIAStrategy]와 함께 사용됩니다. -/// ARIA는 대한민국 국가 표준 블록 암호 알고리즘입니다. -/// -/// @author Q. T. Felix -/// @see EntLibSymmetricKeyStrategy -/// @see ARIAStrategy -/// @since 1.1.0 -public final class ARIASymmetricKeyStrategy implements EntLibSymmetricKeyStrategy { - - /** - * ARIA에서 지원하는 키 크기 목록입니다. (128, 192, 256비트) - */ - final int[] POSSIBLE_KEY_SIZES = new int[]{128, 192, 256}; - - /** - * 생성할 키의 비트 크기입니다. - */ - private final int keySize; - - /** - * {@link ARIAStrategy}로부터 키 크기를 추출하여 인스턴스를 생성하는 생성자입니다. - * - * @param keySize ARIA 암호화 키 사이즈 - */ - ARIASymmetricKeyStrategy(int keySize) { - this.keySize = IntStream.of(POSSIBLE_KEY_SIZES) - .filter(p -> p == keySize) - .findFirst() - .orElse(256); - } - - /** - * {@link ARIASymmetricKeyStrategy} 인스턴스를 생성하는 팩토리 메소드입니다. - * - * @param keySize ARIA 키 사이즈 - * @return 새 {@link ARIASymmetricKeyStrategy} 인스턴스 - */ - public static ARIASymmetricKeyStrategy create(final int keySize) { - return new ARIASymmetricKeyStrategy(keySize); - } - - /** - * ARIA 대칭 키를 생성하여 반환하는 메소드입니다. - * - * @return 생성된 ARIA 키 - */ - @Override - public SensitiveDataContainer generateKey() { - return new SensitiveDataContainer(InternalKeyGenerator.initializedCipherKeyGenerator(keySize).generateKey(), true); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/ChaCha20Poly1305SymmetricKeyStrategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/ChaCha20Poly1305SymmetricKeyStrategy.java deleted file mode 100644 index 11d73aa..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/ChaCha20Poly1305SymmetricKeyStrategy.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.key.strategy.detail; - -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.EntLibSymmetricKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.ChaCha20Poly1305Strategy; - -/// ChaCha20-Poly1305 AEAD 암호 알고리즘을 위한 대칭 키 생성 전략 클래스입니다. -/// -/// ChaCha20-Poly1305는 ChaCha20 스트림 암호와 Poly1305 MAC을 결합한 AEAD(Authenticated Encryption with Associated Data) -/// 알고리즘으로, 256비트 키를 사용합니다. [ChaCha20Poly1305Strategy]와 함께 사용됩니다. -/// -/// @author Q. T. Felix -/// @see EntLibSymmetricKeyStrategy -/// @see ChaCha20Poly1305Strategy -/// @since 1.1.0 -public final class ChaCha20Poly1305SymmetricKeyStrategy implements EntLibSymmetricKeyStrategy { - - /** - * 생성할 키의 비트 크기입니다. - */ - private final int keySize; - - /** - * {@link ChaCha20Poly1305Strategy}로부터 키 크기를 추출하여 인스턴스를 생성하는 생성자입니다. - */ - ChaCha20Poly1305SymmetricKeyStrategy() { - this.keySize = 256; - } - - /** - * {@link ChaCha20Poly1305SymmetricKeyStrategy} 인스턴스를 생성하는 팩토리 메소드입니다. - * - * @return 새 {@link ChaCha20Poly1305SymmetricKeyStrategy} 인스턴스 - */ - public static ChaCha20Poly1305SymmetricKeyStrategy create() { - return new ChaCha20Poly1305SymmetricKeyStrategy(); - } - - /** - * ChaCha20-Poly1305 대칭 키를 생성하여 반환하는 메소드입니다. - * - * @return 생성된 ChaCha20-Poly1305 키 - */ - @Override - public SensitiveDataContainer generateKey() { - return new SensitiveDataContainer(InternalKeyGenerator.initializedCipherKeyGenerator(keySize).generateKey(), true); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/ChaCha20SymmetricKeyStrategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/ChaCha20SymmetricKeyStrategy.java deleted file mode 100644 index c7ac08e..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/ChaCha20SymmetricKeyStrategy.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.key.strategy.detail; - -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.EntLibSymmetricKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.ChaCha20Strategy; - -/// ChaCha20 스트림 암호 알고리즘을 위한 대칭 키 생성 전략 클래스입니다. -/// -/// ChaCha20은 256비트 키를 사용하는 고성능 스트림 암호입니다. -/// [ChaCha20Strategy]와 함께 사용됩니다. -/// -/// @author Q. T. Felix -/// @see EntLibSymmetricKeyStrategy -/// @see ChaCha20Strategy -/// @since 1.1.0 -public final class ChaCha20SymmetricKeyStrategy implements EntLibSymmetricKeyStrategy { - - /** - * 생성할 키의 비트 크기입니다. - */ - private final int keySize; - - /** - * {@link ChaCha20Strategy}로부터 키 크기를 추출하여 인스턴스를 생성하는 생성자입니다. - */ - ChaCha20SymmetricKeyStrategy() { - this.keySize = 256; - } - - /** - * {@link ChaCha20SymmetricKeyStrategy} 인스턴스를 생성하는 팩토리 메소드입니다. - * - * @return 새 {@link ChaCha20SymmetricKeyStrategy} 인스턴스 - */ - public static ChaCha20SymmetricKeyStrategy create() { - return new ChaCha20SymmetricKeyStrategy(); - } - - /** - * ChaCha20 대칭 키를 생성하여 반환하는 메소드입니다. - * - * @return 생성된 ChaCha20 키 - */ - @Override - public SensitiveDataContainer generateKey() { - return new SensitiveDataContainer(InternalKeyGenerator.initializedCipherKeyGenerator(keySize).generateKey(), true); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/InternalKeyGenerator.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/InternalKeyGenerator.java deleted file mode 100644 index 90ed5e1..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/InternalKeyGenerator.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.key.strategy.detail; - -import org.bouncycastle.crypto.CipherKeyGenerator; -import org.bouncycastle.crypto.KeyGenerationParameters; -import space.qu4nt.entanglementlib.InternalFactory; - -/** - * 내부적으로 사용되는 암호화 키 생성기 유틸리티 클래스입니다. - *

- * {@link CipherKeyGenerator}를 초기화하여 지정된 크기의 안전한 난수 키를 생성합니다. - * 이 클래스는 패키지 내부에서만 사용되며 외부에 노출되지 않습니다. - * - * @author Q. T. Felix - * @since 1.1.0 - */ -final class InternalKeyGenerator { - - /** - * 인스턴스화를 방지하기 위한 private 생성자입니다. - */ - private InternalKeyGenerator() { - } - - /** - * 지정된 키 크기로 초기화된 {@link CipherKeyGenerator}를 반환하는 메소드입니다. - *

- * 안전한 난수 생성기({@link space.qu4nt.entanglementlib.InternalFactory#getSafeRandom()})를 - * 사용하여 키 생성기를 초기화합니다. - * - * @param keySize 생성할 키의 비트 크기 - * @return 초기화된 {@link CipherKeyGenerator} - */ - static CipherKeyGenerator initializedCipherKeyGenerator(final int keySize) { - final CipherKeyGenerator keyGenerator = new CipherKeyGenerator(); - keyGenerator.init(new KeyGenerationParameters(InternalFactory.getSafeRandom(), keySize)); - return keyGenerator; - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/MLDSAKeyStrategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/MLDSAKeyStrategy.java deleted file mode 100644 index a43e334..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/MLDSAKeyStrategy.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.key.strategy.detail; - -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.entlibnative.ProgressResult; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.exception.critical.EntLibNativeError; -import space.qu4nt.entanglementlib.security.crypto.ParameterSizeDetail; -import space.qu4nt.entanglementlib.security.crypto.SignatureType; -import space.qu4nt.entanglementlib.security.crypto.bundle.MLDSAStrategyBundle; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.EntLibAsymmetricKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.MLDSAStrategy; -import space.qu4nt.entanglementlib.util.wrapper.Pair; - -/// ML-DSA(Module Lattice-based Digital Signature Algorithm) 알고리즘을 위한 비대칭 키 페어 생성 전략 클래스입니다. -/// -/// ML-DSA는 NIST에서 표준화한 PQC(Post-Quantum Cryptography) 전자 서명 알고리즘으로, -/// ML-DSA 파라미터 세트를 지원합니다. [MLDSAStrategy]와 함께 사용됩니다. -/// -/// 해당 알고리즘에 대한 암호학적 연산은 `entlib-native` 네이티브 라이브러리에서 진행됩니다. -/// -/// @author Q. T. Felix -/// @see EntLibAsymmetricKeyStrategy -/// @see MLDSAStrategy -/// @since 1.1.0 -@Slf4j -public final class MLDSAKeyStrategy implements EntLibAsymmetricKeyStrategy { - - private final SignatureType mldsaType; - - private MLDSAKeyStrategy(SignatureType mldsaType) { - this.mldsaType = mldsaType; - } - - @ApiStatus.Internal - public static MLDSAKeyStrategy create(@NotNull SignatureType mldsaType) { - return new MLDSAKeyStrategy(mldsaType); - } - - @Override - public Pair generateKeyPair() throws Throwable { - ParameterSizeDetail detail = mldsaType.getParameterSizeDetail(); - // 주의! 특정 키 내에 다른 키 컨테이너를 포함해선 안됨 - final SensitiveDataContainer pkC = new SensitiveDataContainer(detail.getPublicKeySize()); - final SensitiveDataContainer skC = new SensitiveDataContainer(detail.getPrivateKeySize()); - // ml_dsa_xx_keygen(sk_ptr, pk_ptr) - sk가 먼저, pk가 나중 - int code = (int) MLDSAStrategyBundle - .callNativeMLDSAHandle(mldsaType, 0) - .invokeExact(skC.getMemorySegment(), pkC.getMemorySegment()); - final ProgressResult result = ProgressResult.fromCode(code); - if (!result.equals(ProgressResult.SUCCESS)) - throw new EntLibNativeError("네이티브 함수 수행 결과가 유효하지 않습니다!"); - return new Pair<>(pkC, skC); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/MLKEMKeyStrategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/MLKEMKeyStrategy.java deleted file mode 100644 index 1d84d41..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/MLKEMKeyStrategy.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.key.strategy.detail; - -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.entlibnative.ProgressResult; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.exception.critical.EntLibNativeError; -import space.qu4nt.entanglementlib.security.crypto.KEMType; -import space.qu4nt.entanglementlib.security.crypto.ParameterSizeDetail; -import space.qu4nt.entanglementlib.security.crypto.bundle.MLKEMStrategyBundle; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.EntLibAsymmetricKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.MLDSAStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.MLKEMStrategy; -import space.qu4nt.entanglementlib.util.wrapper.Pair; - -/// ML-KEM(Module Lattice-based Key Encapsulate Mechanism) 알고리즘을 위한 비대칭 키 페어 생성 전략 클래스입니다. -/// -/// ML-KEM는 NIST에서 표준화한 PQC(Post-Quantum Cryptography) 키 캡슐화 메커니즘으로, -/// ML-KEM 파라미터 세트를 지원합니다. [MLDSAStrategy]와 함께 사용됩니다. -/// -/// 해당 알고리즘에 대한 암호학적 연산은 `entlib-native` 네이티브 라이브러리에서 진행됩니다. -/// -/// @author Q. T. Felix -/// @see EntLibAsymmetricKeyStrategy -/// @see MLKEMStrategy -/// @since 1.1.0 -@Slf4j -public final class MLKEMKeyStrategy implements EntLibAsymmetricKeyStrategy { - - private final KEMType mlkemType; - - private MLKEMKeyStrategy(KEMType mlkemType) { - this.mlkemType = mlkemType; - } - - public static MLKEMKeyStrategy create(@NotNull KEMType mlkemType) { - return new MLKEMKeyStrategy(mlkemType); - } - - @Override - public Pair generateKeyPair() throws Throwable { - ParameterSizeDetail detail = mlkemType.getParameterSizeDetail(); - final SensitiveDataContainer pkC = new SensitiveDataContainer(detail.getEncapsulationKeySize()); - final SensitiveDataContainer skC = new SensitiveDataContainer(detail.getDecapsulationKeySize()); - int code = (int) MLKEMStrategyBundle - .callNativeMLKEMHandle(mlkemType, 0) - .invokeExact(skC.getMemorySegment(), pkC.getMemorySegment()); - final ProgressResult result = ProgressResult.fromCode(code); - if (!result.equals(ProgressResult.SUCCESS)) - throw new EntLibNativeError("네이티브 함수 수행 결과가 유효하지 않습니다!"); - return new Pair<>(pkC, skC); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/SLHDSAKeyStrategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/SLHDSAKeyStrategy.java deleted file mode 100644 index 2da993d..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/SLHDSAKeyStrategy.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.key.strategy.detail; - -import lombok.extern.slf4j.Slf4j; -import org.bouncycastle.pqc.crypto.slhdsa.SLHDSAParameters; -import org.bouncycastle.util.Arrays; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.Unsafe; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.EntLibAsymmetricKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.SLHDSAStrategy; -import space.qu4nt.entanglementlib.util.wrapper.Pair; -import space.qu4nt.entanglementlib.util.wrapper.Tuple; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -/// SLH-DSA(Stateless Hash-based Digital Signature Algorithm) 알고리즘을 위한 비대칭 키 쌍 생성 전략 클래스입니다. -/// -/// SLH-DSA는 NIST에서 표준화한 PQC 전자 서명 알고리즘으로, 많은 파라미터 세트를 지원합니다. -/// [SLHDSAStrategy]와 함께 사용됩니다. -/// -/// # Safety -/// -/// 이 알고리즘에 대해, 모든 로직이 네이티브에서 아직 안정화되지 않았습니다. -/// -/// @author Q. T. Felix -/// @see EntLibAsymmetricKeyStrategy -/// @see SLHDSAStrategy -/// @since 1.1.0 -@Unsafe -@Slf4j -@ApiStatus.Obsolete -public final class SLHDSAKeyStrategy implements EntLibAsymmetricKeyStrategy { - - /** - * SLH-DSA 알고리즘 파라미터입니다. - */ - private final SLHDSAParameters slhdsaParameters; - - /** - * {@link SLHDSAStrategy}로부터 파라미터를 추출하여 인스턴스를 생성하는 생성자입니다. - * - * @param slhdsaParameters SLH-DSA 서명 BC 파라미터 - */ - private SLHDSAKeyStrategy(SLHDSAParameters slhdsaParameters) { - this.slhdsaParameters = slhdsaParameters; - } - - /** - * {@link SLHDSAKeyStrategy} 인스턴스를 생성하는 팩토리 메소드입니다. - * - * @param slhdsaParameters SLH-DSA 서명 BC 파라미터 - * @return 새 {@link SLHDSAKeyStrategy} 인스턴스 - */ - @ApiStatus.Internal - public static SLHDSAKeyStrategy create(@NotNull SLHDSAParameters slhdsaParameters) { - return new SLHDSAKeyStrategy(slhdsaParameters); - } - - /** - * SLH-DSA 공개 키와 개인 키 쌍을 생성하여 반환하는 메소드입니다. - *

- * 반환되는 {@link Pair}의 첫 번째 요소는 공개 키, 두 번째 요소는 개인 키입니다. - * - * @return 공개 키와 개인 키의 쌍 - */ - @Override - public Pair generateKeyPair() { - try { - final Internal in = new Internal(slhdsaParameters); - return in.gen(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - /// SLH-DSA 키 생성을 위한 내부 헬퍼 클래스입니다. - /// - /// BouncyCastle 라이브러리에서 SLH-DSA는 sha2 엔진과 shake 엔진이 나누어져 있습니다. - /// [SLHDSAParameters] 클래스 내에 package-private 처리된 엔진 호출 메소드를 - /// 사용하면 개별 엔진 클래스를 따로 호출할 필요 없이 사용 가능하지만, 리플렉션을 사용하여 - /// 접근하기 때문에 별도의 엔진 메소드를 차별화했습니다. - /// - /// # Safety - /// - /// 이 기능은 `BouncyCastle`의 `SLH-DSA` 엔진을 리플렉션으로 호출하여 키 생성을 - /// 수행하도록 하는 내부 클래스입니다. 이 기능은 `BC`의존성이 제거됨과 동시에 - /// 제거됩니다. - /// - /// @author Q. T. Felix - /// @since 1.1.0 - @Unsafe - @ApiStatus.Obsolete(since = "1.1.0") - @ApiStatus.Internal - private static class Internal { - SLHDSAParameters baseParam; - Object engine; - - private final Tuple tuple; - - Internal(SLHDSAParameters baseParam) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, NoSuchFieldException { - this.baseParam = baseParam; - this.tuple = calc(baseParam.getName().contains("with") || (baseParam.getName().startsWith("sha2-"))); - } - - // pk, sk - Pair gen() throws NoSuchFieldException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { - byte[] pkRoot = pkC(); // htpubkey(pkroot) - byte[] sk = skC(pkRoot); - final byte[] safeMixPK = Arrays.concatenate(tuple.getThird(), pkRoot); - return new Pair<>( - new SensitiveDataContainer(safeMixPK, true), - new SensitiveDataContainer(sk, true) - ); - } - - private byte[] pkC() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException { - Method init = engine.getClass().getDeclaredMethod("init", byte[].class); - init.setAccessible(true); - //noinspection PrimitiveArrayArgumentToVarargsMethod - init.invoke(engine, tuple.getThird()); - - Class engineParent = Class.forName("org.bouncycastle.pqc.crypto.slhdsa.SLHDSAEngine"); - Class ht = Class.forName("org.bouncycastle.pqc.crypto.slhdsa.HT"); - Constructor htConst = ht.getDeclaredConstructor(engineParent, byte[].class, byte[].class); - htConst.setAccessible(true); - @SuppressWarnings("JavaReflectionInvocation") Object htObj = htConst.newInstance(engine, tuple.getFirst(), tuple.getThird()); - Field htPubKeyField = htObj.getClass().getDeclaredField("htPubKey"); - htPubKeyField.setAccessible(true); - return (byte[]) htPubKeyField.get(htObj); - } - - private byte[] skC(final byte[] pkroot) { - return Arrays.concatenate(new byte[][]{tuple.getFirst(), tuple.getSecond(), tuple.getThird(), pkroot}); - } - - // skSeed, skPrf, pkSeed - private Tuple calc(boolean sha2) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, NoSuchFieldException { - this.engine = sha2 ? castSha2Engine() : castShakeEngine(); - Field nField = engine.getClass().getSuperclass().getDeclaredField("N"); - nField.setAccessible(true); - int n = (int) nField.get(engine); - byte[] skSeed = SensitiveDataContainer.generateSafeRandomBytes(n); - byte[] skPrf = SensitiveDataContainer.generateSafeRandomBytes(n); - byte[] pkSeed = SensitiveDataContainer.generateSafeRandomBytes(n); - return new Tuple<>(skSeed, skPrf, pkSeed); - } - - Object castSha2Engine() throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException { - Class sha2EngineProviderClass = Class.forName("org.bouncycastle.pqc.crypto.slhdsa.SLHDSAEngine$Sha2Engine"); - final int n = baseParam.getN(); // 키 사이즈 바이트 - Constructor engineConst = sha2EngineProviderClass.getDeclaredConstructor(int.class, int.class, int.class, int.class, int.class, int.class); - engineConst.setAccessible(true); - - int[] constParamVals; - if (baseParam.equals(SLHDSAParameters.sha2_128f)) { - constParamVals = new int[]{22, 6, 33, 66}; - } else if (baseParam.equals(SLHDSAParameters.sha2_128s)) { - constParamVals = new int[]{7, 12, 14, 63}; - } else if (baseParam.equals(SLHDSAParameters.sha2_192f)) { - constParamVals = new int[]{22, 8, 33, 66}; - } else if (baseParam.equals(SLHDSAParameters.sha2_192s)) { - constParamVals = new int[]{7, 14, 17, 63}; - } else if (baseParam.equals(SLHDSAParameters.sha2_256f)) { - constParamVals = new int[]{17, 9, 35, 68}; - } else if (baseParam.equals(SLHDSAParameters.sha2_256s)) { - constParamVals = new int[]{8, 14, 22, 64}; - } else { - constParamVals = new int[]{8, 14, 22, 64}; - } - - return engineConst.newInstance(n, 16, constParamVals[0], constParamVals[1], constParamVals[2], constParamVals[3]); - } - - Object castShakeEngine() throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException { - Class shakeEngineProviderClass = Class.forName("org.bouncycastle.pqc.crypto.slhdsa.SLHDSAEngine$Shake256Engine"); - final int n = baseParam.getN(); // 키 사이즈 바이트 - Constructor engineConst = shakeEngineProviderClass.getDeclaredConstructor(int.class, int.class, int.class, int.class, int.class, int.class); - engineConst.setAccessible(true); - - int[] constParamVals; - if (baseParam.equals(SLHDSAParameters.shake_128f)) { - constParamVals = new int[]{22, 6, 33, 66}; - } else if (baseParam.equals(SLHDSAParameters.shake_128s)) { - constParamVals = new int[]{7, 12, 14, 63}; - } else if (baseParam.equals(SLHDSAParameters.shake_192f)) { - constParamVals = new int[]{22, 8, 33, 66}; - } else if (baseParam.equals(SLHDSAParameters.shake_192s)) { - constParamVals = new int[]{7, 14, 17, 63}; - } else if (baseParam.equals(SLHDSAParameters.shake_256f)) { - constParamVals = new int[]{17, 9, 35, 68}; - } else if (baseParam.equals(SLHDSAParameters.shake_256s)) { - constParamVals = new int[]{8, 14, 22, 64}; - } else { - constParamVals = new int[]{8, 14, 22, 64}; - } - - return engineConst.newInstance(n, 16, constParamVals[0], constParamVals[1], constParamVals[2], constParamVals[3]); - } - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/X25519KeyStrategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/X25519KeyStrategy.java deleted file mode 100644 index f80ae39..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/X25519KeyStrategy.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.key.strategy.detail; - -import lombok.extern.slf4j.Slf4j; -import space.qu4nt.entanglementlib.entlibnative.ProgressResult; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.exception.critical.EntLibNativeError; -import space.qu4nt.entanglementlib.security.crypto.bundle.X25519StrategyBundle; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.EntLibAsymmetricKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.X25519Strategy; -import space.qu4nt.entanglementlib.util.wrapper.Pair; - -/// X25519 알고리즘을 위한 비대칭 키 페어 생성 전략 클래스입니다. -/// -/// X25519는 `Curve25519` 타원 곡선을 사용한 고보안 `ECDH` 키 교환 알고리즘입니다. -/// -/// 해당 알고리즘에 대한 암호학적 연산은 `entlib-native` 네이티브 라이브러리에서 진행됩니다. -/// -/// @author Q. T. Felix -/// @see EntLibAsymmetricKeyStrategy -/// @see X25519Strategy -/// @since 1.1.0 -@Slf4j -public final class X25519KeyStrategy implements EntLibAsymmetricKeyStrategy { - - private X25519KeyStrategy() { - } - - public static X25519KeyStrategy create() { - return new X25519KeyStrategy(); - } - - @Override - public Pair generateKeyPair() throws Throwable { - final Pair keySizePair = new Pair<>(0x20, 0x20); - final SensitiveDataContainer pkC = new SensitiveDataContainer(keySizePair.getFirst()); - final SensitiveDataContainer skC = new SensitiveDataContainer(keySizePair.getSecond()); - int code = (int) X25519StrategyBundle - .callNativeX25519Handle(0) - .invokeExact(skC.getMemorySegment(), pkC.getMemorySegment()); - final ProgressResult result = ProgressResult.fromCode(code); - if (!result.equals(ProgressResult.SUCCESS)) - throw new EntLibNativeError("네이티브 함수 수행 결과가 유효하지 않습니다!"); - return new Pair<>(pkC, skC); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/X25519MLKEM768KeyStrategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/X25519MLKEM768KeyStrategy.java deleted file mode 100644 index fd4e848..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/key/strategy/detail/X25519MLKEM768KeyStrategy.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.key.strategy.detail; - -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.Nullable; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalStateException; -import space.qu4nt.entanglementlib.exception.secure.crypto.EntLibCryptoException; -import space.qu4nt.entanglementlib.security.crypto.KEMType; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.EntLibAsymmetricKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.hybrid.X25519MLKEM768Strategy; -import space.qu4nt.entanglementlib.util.wrapper.Pair; - -import java.lang.foreign.MemorySegment; - -/// X25519MLKEM768 하이브리드 알고리즘을 위한 키 페어 생성 전략 클래스입니다. -/// -/// 이 알고리즘의 키 페어를 생성하기 위해 사전에 [X25519KeyStrategy]와 -/// ML-KEM-768 알고리즘에 대한 [MLKEMKeyStrategy] 키 페어를 준비해야 합니다. -/// 이 클래스는 해당 객체를 전달받고 키 페어를 생성합니다. -/// -/// 하이브리드 키 페어는 모두 `X25519` 값이 먼저 할당됩니다. 예를 들어, 공개 키의 경우 -/// 다음의 수식으로 표현됩니다. -/// ``` -/// HPK = XPK || MPK -/// ``` -/// 여기서 XPK(X25519 공개 키)는 32바이트, MPK(ML-KEM-768 공개 키)는 -/// 1184바이트입니다. 비밀 키도 동일한 방식으로 생성됩니다. -/// -/// @author Q. T. Felix -/// @see EntLibAsymmetricKeyStrategy -/// @see X25519MLKEM768Strategy -/// @since 1.1.0 -@Slf4j -public final class X25519MLKEM768KeyStrategy implements EntLibAsymmetricKeyStrategy { - - @Setter - @Nullable - private X25519KeyStrategy x25519Key; - @Setter - @Nullable - private MLKEMKeyStrategy mlkem768Key; - - private X25519MLKEM768KeyStrategy(final @Nullable X25519KeyStrategy x25519Key, final @Nullable MLKEMKeyStrategy mlkem768Key) { - this.x25519Key = x25519Key; - this.mlkem768Key = mlkem768Key; - } - - public static X25519MLKEM768KeyStrategy create(final X25519KeyStrategy x25519Key, final MLKEMKeyStrategy mlkem768Key) { - return new X25519MLKEM768KeyStrategy(x25519Key, mlkem768Key); - } - - @Override - public Pair generateKeyPair() throws Throwable { - if (x25519Key == null || mlkem768Key == null) - throw new EntLibSecureIllegalStateException("X25519MLKEM768 알고리즘에 대한 키 생성을 수행할 수 없습니다! 이 작업을 수행하기 전에 X25519, ML-KEM-768 키 스트레티지를 생성해야 합니다."); - // 하이브리드 페어 산출물 - final SensitiveDataContainer hPKContainer = new SensitiveDataContainer(KEMType.X25519MLKEM768.getParameterSizeDetail().getEncapsulationKeySize()); - final SensitiveDataContainer hSKContainer = new SensitiveDataContainer(KEMType.X25519MLKEM768.getParameterSizeDetail().getDecapsulationKeySize()); - - // 중간 산출물 - Pair xPair = null; - Pair mPair = null; - try { - // X25519 - xPair = x25519Key.generateKeyPair(); - SensitiveDataContainer xPk = xPair.getFirst(); - SensitiveDataContainer xSk = xPair.getSecond(); - - // ML-KEM-768 - mPair = mlkem768Key.generateKeyPair(); - SensitiveDataContainer mPk = mPair.getFirst(); - SensitiveDataContainer mSk = mPair.getSecond(); - - // 키 사이즈 - final int x25519KeySize = KEMType.X25519.getParameterSizeDetail().getEncapsulationKeySize(); - final int mlkem768PKSize = KEMType.ML_KEM_768.getParameterSizeDetail().getEncapsulationKeySize(); - final int mlkem768SKSize = KEMType.ML_KEM_768.getParameterSizeDetail().getDecapsulationKeySize(); - - // 공개 키 결합 - MemorySegment.copy(xPk.getMemorySegment(), 0, hPKContainer.getMemorySegment(), 0, x25519KeySize); - MemorySegment.copy(mPk.getMemorySegment(), 0, hPKContainer.getMemorySegment(), x25519KeySize, mlkem768PKSize); - - // 비밀 키 결합 - MemorySegment.copy(xSk.getMemorySegment(), 0, hSKContainer.getMemorySegment(), 0, x25519KeySize); - MemorySegment.copy(mSk.getMemorySegment(), 0, hSKContainer.getMemorySegment(), x25519KeySize, mlkem768SKSize); - - return new Pair<>(hPKContainer, hSKContainer); - } catch (Exception e) { - hPKContainer.close(); - hSKContainer.close(); - throw new EntLibCryptoException(e); - } finally { - if (xPair != null) { - xPair.getFirst().close(); - xPair.getSecond().close(); - } - if (mPair != null) { - mPair.getFirst().close(); - mPair.getSecond().close(); - } - } - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/AEADCipherStrategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/AEADCipherStrategy.java deleted file mode 100644 index bf51a57..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/AEADCipherStrategy.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.strategy; - -/// AEAD(Authenticated Encryption with Associated Data)를 지원하는 암호화 전략 인터페이스입니다. -/// -/// GCM, CCM 등의 AEAD 모드에서 AAD(Additional Authenticated Data)를 설정할 수 있는 기능을 제공합니다. -/// ChaCha20-Poly1305와 같은 AEAD 알고리즘도 이 인터페이스를 구현합니다. -/// -/// @author Q. T. Felix -/// @see CipherStrategy -/// @since 1.1.0 -public interface AEADCipherStrategy extends CipherStrategy { - - /** - * AAD(Additional Authenticated Data)를 설정하는 메소드입니다. - *

- * AAD는 암호화되지 않지만 무결성이 보장됩니다. - * 암호화/복호화 수행 전에 호출되어야 합니다. - * - * @param aad 추가 인증 데이터 - * @return 메소드 체이닝을 위한 {@code this} - */ - AEADCipherStrategy updateAAD(byte[] aad); - -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/BlockCipherStrategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/BlockCipherStrategy.java deleted file mode 100644 index 814a2aa..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/BlockCipherStrategy.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.strategy; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import space.qu4nt.entanglementlib.security.crypto.Digest; -import space.qu4nt.entanglementlib.security.crypto.Mode; -import space.qu4nt.entanglementlib.security.crypto.Padding; - -/// 블록 암호화 전략 인터페이스입니다. -/// -/// AES, ARIA 등의 블록 암호 알고리즘을 위한 전략을 정의합니다. -/// 운영 모드([Mode]), 패딩([Padding]), 다이제스트([Digest]) 설정을 지원합니다. -/// -/// @author Q. T. Felix -/// @see CipherStrategy -/// @see Mode -/// @see Padding -/// @since 1.1.0 -public interface BlockCipherStrategy extends CipherStrategy { - - /** - * 현재 설정된 운영 모드를 반환하는 메소드입니다. - * - * @return 운영 모드 (CBC, GCM, CTR 등) - */ - @NotNull Mode getMode(); - - /** - * 운영 모드를 설정하는 메소드입니다. - * - * @param mode 설정할 운영 모드 - * @return 메소드 체이닝을 위한 {@code this} - */ - BlockCipherStrategy setMode(@NotNull Mode mode); - - /** - * 현재 설정된 패딩을 반환하는 메소드입니다. - * - * @return 패딩 방식 - */ - @NotNull Padding getPadding(); - - /** - * 패딩을 설정하는 메소드입니다. - * - * @param padding 설정할 패딩 방식 - * @return 메소드 체이닝을 위한 {@code this} - */ - BlockCipherStrategy setPadding(@NotNull Padding padding); - - /** - * 현재 설정된 다이제스트를 반환하는 메소드입니다. - * - * @return 다이제스트, 설정되지 않은 경우 {@code null} - */ - @Nullable Digest getDigest(); - - /** - * 다이제스트를 설정하는 메소드입니다. - * - * @param digest 설정할 다이제스트 - * @return 메소드 체이닝을 위한 {@code this} - */ - BlockCipherStrategy setDigest(@NotNull Digest digest); - -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/CipherStrategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/CipherStrategy.java deleted file mode 100644 index d50da37..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/CipherStrategy.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.strategy; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalArgumentException; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalStateException; -import space.qu4nt.entanglementlib.exception.secure.crypto.EntLibCryptoCipherProcessException; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.ChaCha20Strategy; - -import java.util.Arrays; - -/// 암호화/복호화 연산을 수행하는 전략 인터페이스입니다. -/// -/// 블록 암호([BlockCipherStrategy]), 스트림 암호([StreamCipherStrategy]), -/// AEAD 암호([AEADCipherStrategy]) 등이 이 인터페이스를 확장합니다. -/// -/// @author Q. T. Felix -/// @see BlockCipherStrategy -/// @see StreamCipherStrategy -/// @see AEADCipherStrategy -/// @since 1.1.0 -public interface CipherStrategy extends EntLibCryptoStrategy { - - void iv(Object raw) throws EntLibSecureIllegalArgumentException; - - /// 평문을 암호화하는 메소드입니다. - /// - /// 평문 객체의 경우, `byte[]`, [SensitiveDataContainer] 또는 - /// [java.nio.ByteBuffer] 타입을 전달해야 합니다. 이 메소드를 - /// 수행하기 전, 입력값의 유효성을 검사하는 로직을 수행하세요. - /// - /// TLS 1.3 과 같은 통신 프로토콜에서 사용하는 경우, `ivChaining` - /// 논리 값을 `false`로 전달하여 결과에 `iv`값을 포함하지 않도록 - /// 할 수 있습니다. - /// - /// [ChaCha20Strategy] 에서 사용하는 경우, `ivChaining` - /// 매개변수의 값을 전달하는 - /// - /// @param keyContainer 암호화에 사용할 키 컨테이너 - /// @param plain 암호화할 평문 객체 - /// @param ivChaining `true` 시 결과에 IV값 연결 - /// @return 암호화된 암호문 컨테이너 - SensitiveDataContainer encrypt(@NotNull SensitiveDataContainer keyContainer, final Object plain, boolean ivChaining) throws EntLibSecureIllegalStateException, EntLibCryptoCipherProcessException, EntLibSecureIllegalArgumentException; - - /// 암호문을 복호화하는 메소드입니다. - /// - /// @param keyContainer 복호화에 사용할 키 컨테이너 - /// @param ciphertext 복호화할 암호문 바이트 배열 - /// @param ivInference true 시 암호문에 IV가 존재한다고 예상 - /// @return 복호화된 평문 바이트 배열 - SensitiveDataContainer decrypt(@NotNull SensitiveDataContainer keyContainer, final SensitiveDataContainer ciphertext, boolean ivInference) throws EntLibSecureIllegalStateException, EntLibCryptoCipherProcessException, EntLibSecureIllegalArgumentException; - - /// TLS 1.3 방식의 Nonce 생성 헬퍼 메소드입니다. - /// - /// BaseIV(12bytes) XOR Sequence(8bytes) - static SensitiveDataContainer calculateNonce(byte[] baseIV, long sequence) { - byte[] nonce = Arrays.copyOf(baseIV, 12); // 12바이트 GCM IV 길이 - - // 시퀀스 번호를 뒤에서부터 XOR (Big Endian 처리) - for (int i = 0; i < 8; i++) - nonce[11 - i] ^= (byte) (sequence >> (i * 8)); - - return new SensitiveDataContainer(nonce, true); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/EntLibCryptoStrategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/EntLibCryptoStrategy.java deleted file mode 100644 index 8bedbd6..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/EntLibCryptoStrategy.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.strategy; - -import space.qu4nt.entanglementlib.security.crypto.EntLibAlgorithmType; -import space.qu4nt.entanglementlib.security.crypto.EntLibCryptoRegistry; - -/// 암호화 스트레티지의 최상위 인터페이스입니다. -/// -/// 모든 암호화 스트레티지(블록 암호, 스트림 암호, AEAD, 서명 등)는 이 인터페이스를 구현합니다. -/// [EntLibCryptoRegistry]에 등록되어 관리됩니다. -/// -/// @author Q. T. Felix -/// @see CipherStrategy -/// @see SignatureStrategy -/// @since 1.1.0 -public interface EntLibCryptoStrategy { - - /** - * 이 스트레티지의 알고리즘 이름을 반환하는 메소드입니다. - * - * @return 알고리즘 이름 - */ - String getAlgorithmName(); - - /** - * 이 스트레티지의 알고리즘 타입을 반환하는 메소드입니다. - * - * @return 알고리즘 타입 - */ - EntLibAlgorithmType getAlgorithmType(); - -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/NativeECDHStrategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/NativeECDHStrategy.java deleted file mode 100644 index 343794d..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/NativeECDHStrategy.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.strategy; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.exception.secure.crypto.EntLibCryptoKEMProcessingException; - -/// 네이티브 라이브러리를 사용하여 디피-헬만 키 교환(ECDH)를 수행하는 전략 인터페이스입니다. -/// 공유 비밀 계산 기능을 제공합니다. -/// -/// @author Q. T. Felix -/// @see EntLibCryptoStrategy -/// @see SensitiveDataContainer -/// @since 1.1.0 -public interface NativeECDHStrategy extends EntLibCryptoStrategy { - - SensitiveDataContainer computeSharedSecret(SensitiveDataContainer secretKeyContainer, SensitiveDataContainer peerPublicKeyContainer); -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/NativeKEMStrategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/NativeKEMStrategy.java deleted file mode 100644 index 9379586..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/NativeKEMStrategy.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.strategy; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalStateException; -import space.qu4nt.entanglementlib.exception.secure.crypto.EntLibCryptoKEMProcessingException; -import space.qu4nt.entanglementlib.exception.secure.crypto.EntLibCryptoSignatureProcessingException; - -/// 네이티브 라이브러리를 사용하여 KEM을 수행하는 전략 인터페이스입니다. -/// 캡슐화 및 디캡슐화 기능을 제공합니다. -/// -/// @author Q. T. Felix -/// @see EntLibCryptoStrategy -/// @see SensitiveDataContainer -/// @since 1.1.0 -public interface NativeKEMStrategy extends EntLibCryptoStrategy { - - /// 공개 키를 사용하여 공유 비밀과 암호문을 생성하는 메소드입니다. - /// - /// 공개 키 매개변수 [SensitiveDataContainer] 객체는 컨테이너 내에 비밀 키 - /// 컨테이너가 포함되어 `있지 않다고` 예상합니다. - /// - /// @param keyPublic 캡슐화에 사용할 공개 키에 대한 `단일` 민감 데이터 컨테이너 - /// @return 생성된 공유 비밀과 암호문에 대한 민감 데이터 컨테이너 - SensitiveDataContainer encapsulate(@NotNull SensitiveDataContainer keyPublic) throws EntLibCryptoKEMProcessingException, EntLibSecureIllegalStateException; - - /// 비밀 키와 수신한 암호문을 사용하여 공유 비밀을 복원하는 메소드입니다. - /// - /// 비밀 키 매개변수 [SensitiveDataContainer] 객체는 컨테이너 내에 공개 키 - /// 컨테이너가 포함되어 `있지 않다고` 예상합니다. 이 과정에 비밀 키 메모리 - /// 세그먼트를 전달하면 공격자에게 탈취당할 위험이 있습니다. - /// - /// @param secretKeyContainer 비밀 키에 대한 민감 데이터 컨테이너 - /// @param ciphertext 수신한 암호문에 대한 민감 데이터 컨테이너 - /// @return 복원한 공유 비밀에 대한 민감 데이터 컨테이너 - SensitiveDataContainer decapsulate(@NotNull SensitiveDataContainer secretKeyContainer, @NotNull SensitiveDataContainer ciphertext) throws EntLibCryptoKEMProcessingException, EntLibSecureIllegalStateException; - -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/NativeSignatureStrategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/NativeSignatureStrategy.java deleted file mode 100644 index 1d69eeb..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/NativeSignatureStrategy.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.strategy; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalStateException; -import space.qu4nt.entanglementlib.exception.secure.crypto.EntLibCryptoSignatureProcessingException; - -/// 네이티브 라이브러리를 사용하여 전자 서명을 수행하는 전략 인터페이스입니다. -/// 서명 생성과 서명 검증 기능을 제공합니다. -/// -/// @author Q. T. Felix -/// @see EntLibCryptoStrategy -/// @see SensitiveDataContainer -/// @since 1.1.0 -public interface NativeSignatureStrategy extends EntLibCryptoStrategy { - - /// 개인 키를 사용하여 데이터에 서명하는 메소드입니다. - /// - /// 비밀 키 매개변수 [SensitiveDataContainer] 객체는 컨테이너 내에 공개 키 - /// 컨테이너가 포함되어 `있지 않다고` 예상합니다. 이 부분에 대해 내부적으로는 크게 - /// 신경쓰지 않지만, 보안 상 이 부분을 명확히 할 필요가 있습니다. - /// - /// 서명이 완료되면 컨테이너에 공개 키를 추가하여 사용자에게 전송하도록 설계할 수 - /// 있습니다. - /// - /// @param keyPrivate 서명에 사용할 개인 키에 대한 `단일` 민감 데이터 컨테이너 - /// @param plainBytes 서명할 원본 데이터 - /// @return 생성된 서명 바이트 배열과 평문에 대한 민감 데이터 컨테이너 - SensitiveDataContainer sign(@NotNull SensitiveDataContainer keyPrivate, byte[] plainBytes) throws EntLibCryptoSignatureProcessingException, EntLibSecureIllegalStateException; - - /// 공개 키를 사용하여 서명을 검증하는 메소드입니다. - /// - /// 공개 키 매개변수 [SensitiveDataContainer] 객체는 컨테이너 내에 비밀 키 - /// 컨테이너가 포함되어 `있지 않다고` 예상합니다. 이 과정에 비밀 키 메모리 - /// 세그먼트를 전달하면 공격자에게 탈취당할 위험이 있습니다. - /// - /// @param container 생성된 서명 바이트 배열과 평문, 공개 키에 대한 민감 데이터 컨테이너 - /// @return 서명이 유효하면 `true`, 아니면 `false` - boolean verify(@NotNull SensitiveDataContainer container) throws EntLibCryptoSignatureProcessingException; - -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/SignatureStrategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/SignatureStrategy.java deleted file mode 100644 index e0bfe17..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/SignatureStrategy.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.strategy; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.security.crypto.key.EntLibCryptoKey; - -/// 전자 서명을 수행하는 전략 인터페이스입니다. -/// -/// ML-DSA, RSA 등의 전자 서명 알고리즘을 위한 전략을 정의합니다. -/// 서명 생성과 서명 검증 기능을 제공합니다. -/// -/// @author Q. T. Felix -/// @see EntLibCryptoStrategy -/// @since 1.1.0 -public interface SignatureStrategy extends EntLibCryptoStrategy { - - /** - * 개인 키를 사용하여 데이터에 서명하는 메소드입니다. - * - * @param keyPrivate 서명에 사용할 개인 키 - * @param plainBytes 서명할 원본 데이터 - * @return 생성된 서명 바이트 배열 - */ - byte @NotNull [] sign(@NotNull EntLibCryptoKey keyPrivate, byte[] plainBytes); - - /** - * 공개 키를 사용하여 서명을 검증하는 메소드입니다. - * - * @param keyPublic 검증에 사용할 공개 키 - * @param plainBytes 원본 데이터 - * @param signature 검증할 서명 - * @return 서명이 유효하면 {@code true}, 아니면 {@code false} - */ - boolean verify(@NotNull EntLibCryptoKey keyPublic, byte[] plainBytes, byte[] signature); - -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/StreamCipherStrategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/StreamCipherStrategy.java deleted file mode 100644 index 5680965..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/StreamCipherStrategy.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.strategy; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalArgumentException; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalStateException; -import space.qu4nt.entanglementlib.exception.secure.crypto.EntLibCryptoCipherIllegalIVStateException; -import space.qu4nt.entanglementlib.exception.secure.crypto.EntLibCryptoCipherProcessException; -import space.qu4nt.entanglementlib.security.crypto.key.EntLibCryptoKey; - -import java.nio.ByteBuffer; - -/// 스트림 암호화 전략 인터페이스입니다. -/// -/// ChaCha20 등의 스트림 암호 알고리즘을 위한 전략을 정의합니다. -/// 대용량 데이터를 스트리밍 방식으로 암호화/복호화할 때 사용됩니다. -/// -/// @author Q. T. Felix -/// @see CipherStrategy -/// @since 1.1.0 -public interface StreamCipherStrategy extends CipherStrategy { - - /** - * 입력 버퍼의 데이터를 스트리밍 방식으로 암호화하여 출력 버퍼에 쓰는 메소드입니다. - * - * @param keyContainer 암호화에 사용할 키 - * @param inputBuffer 암호화할 데이터가 담긴 입력 버퍼 - * @param outputBuffer 암호화된 데이터를 쓸 출력 버퍼 - * @return 처리된 바이트 수 - */ - int streamEncrypt(@NotNull SensitiveDataContainer keyContainer, final @NotNull ByteBuffer inputBuffer, final @NotNull ByteBuffer outputBuffer) throws EntLibSecureIllegalStateException, EntLibCryptoCipherIllegalIVStateException, EntLibSecureIllegalArgumentException; - - /** - * 입력 버퍼의 데이터를 스트리밍 방식으로 복호화하여 출력 버퍼에 쓰는 메소드입니다. - * - * @param keyContainer 복호화에 사용할 키 - * @param inputBuffer 복호화할 데이터가 담긴 입력 버퍼 - * @param outputBuffer 복호화된 데이터를 쓸 출력 버퍼 - * @return 처리된 바이트 수 - */ - int streamDecrypt(@NotNull SensitiveDataContainer keyContainer, final @NotNull ByteBuffer inputBuffer, final @NotNull ByteBuffer outputBuffer) throws EntLibSecureIllegalStateException, EntLibCryptoCipherProcessException, EntLibSecureIllegalArgumentException; -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/AESStrategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/AESStrategy.java deleted file mode 100644 index 19e3bf8..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/AESStrategy.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.strategy.detail; - -import lombok.extern.slf4j.Slf4j; -import org.bouncycastle.crypto.CipherParameters; -import org.bouncycastle.crypto.engines.AESEngine; -import org.bouncycastle.crypto.params.AEADParameters; -import org.bouncycastle.crypto.params.KeyParameter; -import org.bouncycastle.crypto.params.ParametersWithIV; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalArgumentException; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalStateException; -import space.qu4nt.entanglementlib.exception.secure.crypto.EntLibCryptoCipherIllegalIVStateException; -import space.qu4nt.entanglementlib.exception.secure.crypto.EntLibCryptoCipherProcessException; -import space.qu4nt.entanglementlib.security.KeyDestroyHelper; -import space.qu4nt.entanglementlib.security.crypto.CipherType; -import space.qu4nt.entanglementlib.security.crypto.Mode; - -import java.util.Arrays; - -/// AES(Advanced Encryption Standard) 블록 암호 알고리즘 전략 클래스입니다. -/// -/// BouncyCastle의 [AESEngine]을 사용하여 AES 암호화/복호화를 수행합니다. -/// AES-128, AES-192, AES-256 키 길이를 지원하며, 다양한 운영 모드(CBC, GCM, CTR 등)와 -/// 패딩 방식을 설정할 수 있습니다. -/// -/// 키는 네이티브 메모리에서 관리되며, 사용 후 즉시 소거됩니다. -/// -/// @author Q. T. Felix -/// @see AbstractBlockCipher -/// @see CipherType#AES_128 -/// @see CipherType#AES_192 -/// @see CipherType#AES_256 -/// @since 1.1.0 -@Slf4j -public final class AESStrategy extends AbstractBlockCipher { - - /** - * AESStrategy 생성자입니다. - * - * @param base AES 암호화 타입 ({@link CipherType#AES_128}, {@link CipherType#AES_192}, {@link CipherType#AES_256}) - */ - AESStrategy(@NotNull CipherType base) { - super(base, AESEngine.newInstance()); - } - - /** - * AESStrategy 인스턴스를 생성하는 팩토리 메소드입니다. - * - * @param base AES 암호화 타입 ({@link CipherType#AES_128}, {@link CipherType#AES_192}, {@link CipherType#AES_256}) - * @return 새 {@link AESStrategy} 인스턴스 - */ - @ApiStatus.Internal - public static AESStrategy create(@NotNull CipherType base) { - return new AESStrategy(base); - } - - /** - * 평문을 AES 알고리즘으로 암호화하는 메소드입니다. - *

- * ECB 모드를 제외한 모든 모드에서 IV(초기화 벡터)가 자동으로 생성되며, - * 결과는 {@code IV + CipherText} 형식으로 반환됩니다. - * AEAD 모드(GCM, CCM)에서는 12바이트 IV를, 그 외 모드에서는 16바이트 IV를 사용합니다. - * - * @param keyContainer 암호화에 사용할 키 - * @param plain 암호화할 평문 바이트 배열 - * @return 암호화된 바이트 배열 (IV + CipherText) - * @throws IllegalStateException 암호화 실패 시 - */ - @Override - public SensitiveDataContainer encrypt(@NotNull SensitiveDataContainer keyContainer, final Object plain, boolean ivChaining) - throws EntLibSecureIllegalStateException, EntLibCryptoCipherProcessException { - boolean isAead = mode.isAead(); - CipherParameters params; - - // 네이티브 메모리에서 키 바이트 배열 추출 - keyContainer.exportData(); - byte[] keyBytes = keyContainer.getSegmentData(); - if (keyBytes == null) - throw new EntLibSecureIllegalStateException("네이티브 메모리에서 키 바이트 배열을 추출하지 못했습니다!"); - KeyParameter keyParam = new KeyParameter(keyBytes); - - // 사용 후 힙에 복사된 키 바이트 즉시 소거 - KeyDestroyHelper.zeroing(keyBytes); - - // iv 호출 - iv.exportData(); - byte[] ivH = iv.getSegmentData(); - if (ivH == null) - throw new EntLibSecureIllegalStateException("IV를 생성해야 합니다!"); - - if (mode != Mode.ECB) { - if (isAead) { - // AEAD 모드는 AEADParameters 사용 (macSize = 128 bits) - params = new AEADParameters(keyParam, 128, ivH); - } else { - params = new ParametersWithIV(keyParam, ivH); - } - } else { - params = keyParam; - } - - byte[] output = processCipher(true, params, plain); - - if (mode != Mode.ECB && ivChaining) { - // IV + CipherText - byte[] result = new byte[ivH.length + output.length]; - System.arraycopy(ivH, 0, result, 0, ivH.length); - System.arraycopy(output, 0, result, ivH.length, output.length); - KeyDestroyHelper.zeroing(output); - KeyDestroyHelper.zeroing(ivH); - return new SensitiveDataContainer(result, true); - } else { - KeyDestroyHelper.zeroing(ivH); - return new SensitiveDataContainer(output, true); - } - } - - /** - * 암호문을 AES 알고리즘으로 복호화하는 메소드입니다. - *

- * 입력된 암호문에서 IV를 추출하여 복호화를 수행합니다. - * AEAD 모드에서는 12바이트, 그 외 모드에서는 16바이트의 IV를 추출합니다. - * - * @param keyContainer 복호화에 사용할 키 - * @param ciphertext 복호화할 암호문 바이트 배열 (IV + CipherText) - * @return 복호화된 평문 바이트 배열 - * @throws IllegalStateException 복호화 실패 시 - * @throws IllegalArgumentException 암호문이 IV보다 짧은 경우 - */ - @Override - public SensitiveDataContainer decrypt(@NotNull SensitiveDataContainer keyContainer, final SensitiveDataContainer ciphertext, boolean ivInference) - throws EntLibSecureIllegalStateException, EntLibCryptoCipherProcessException { - boolean isAead = mode.isAead(); - byte[] actualCiphertext; - CipherParameters params; - - // 네이티브 메모리에서 키 바이트 배열 추출 - keyContainer.exportData(); - byte[] keyBytes = keyContainer.getSegmentData(); - if (keyBytes == null) - throw new EntLibSecureIllegalStateException("네이티브 메모리에서 키 바이트 배열을 추출하지 못했습니다!"); - KeyParameter keyParam = new KeyParameter(keyBytes); - - // 사용 후 힙에 복사된 키 바이트 즉시 소거 - KeyDestroyHelper.zeroing(keyBytes); - - ciphertext.exportData(); - byte[] ciphertextH = ciphertext.getSegmentData(); - if (ciphertextH == null) - throw new EntLibSecureIllegalStateException("네이티브 메모리에서 암호문 바이트 배열을 추출하지 못했습니다!"); - - if (mode != Mode.ECB) { - byte[] ivBytes; - - if (ivInference) { - // 암호문에서 IV 추출 - int ivLength = isAead ? 12 : 16; - if (ciphertextH.length < ivLength) - throw new EntLibCryptoCipherIllegalIVStateException("IV를 포함하기에는 너무 짧은 암호문입니다!"); - - ivBytes = Arrays.copyOfRange(ciphertextH, 0, ivLength); - actualCiphertext = Arrays.copyOfRange(ciphertextH, ivLength, ciphertextH.length); - } else { - // 외부에서 설정된 IV 사용 - iv.exportData(); - ivBytes = iv.getSegmentData(); - if (ivBytes == null) - throw new EntLibCryptoCipherIllegalIVStateException("IV를 설정해야 합니다!"); - actualCiphertext = ciphertextH; - } - - if (isAead) { - params = new AEADParameters(keyParam, 128, ivBytes); - } else { - params = new ParametersWithIV(keyParam, ivBytes); - } - } else { - actualCiphertext = ciphertextH; - params = keyParam; - } - - return new SensitiveDataContainer(processCipher(false, params, actualCiphertext), true); - } - - /** - * 알고리즘 이름을 반환하는 메소드입니다. - * - * @return 알고리즘 이름 "AES" - */ - @Override - public String getAlgorithmName() { - return "AES"; - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/ARIAStrategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/ARIAStrategy.java deleted file mode 100644 index 4711376..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/ARIAStrategy.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.strategy.detail; - -import lombok.extern.slf4j.Slf4j; -import org.bouncycastle.crypto.CipherParameters; -import org.bouncycastle.crypto.engines.ARIAEngine; -import org.bouncycastle.crypto.params.AEADParameters; -import org.bouncycastle.crypto.params.KeyParameter; -import org.bouncycastle.crypto.params.ParametersWithIV; -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalStateException; -import space.qu4nt.entanglementlib.exception.secure.crypto.EntLibCryptoCipherIllegalIVStateException; -import space.qu4nt.entanglementlib.exception.secure.crypto.EntLibCryptoCipherProcessException; -import space.qu4nt.entanglementlib.security.KeyDestroyHelper; -import space.qu4nt.entanglementlib.security.crypto.CipherType; -import space.qu4nt.entanglementlib.security.crypto.Mode; - -import java.util.Arrays; - -/// ARIA 블록 암호 알고리즘 전략 클래스입니다. -/// -/// ARIA는 대한민국 국가 표준 암호 알고리즘으로, 128비트 블록 크기를 사용합니다. -/// BouncyCastle의 [ARIAEngine]을 사용하여 암호화/복호화를 수행합니다. -/// ARIA-128, ARIA-192, ARIA-256 키 길이를 지원하며, 다양한 운영 모드와 -/// 패딩 방식을 설정할 수 있습니다. -/// -/// 키는 네이티브 메모리에서 관리되며, 사용 후 즉시 소거됩니다. -/// -/// @author Q. T. Felix -/// @see AbstractBlockCipher -/// @see CipherType#ARIA_128 -/// @see CipherType#ARIA_192 -/// @see CipherType#ARIA_256 -/// @since 1.1.0 -@Slf4j -public final class ARIAStrategy extends AbstractBlockCipher { - - /** - * ARIAStrategy 생성자입니다. - * - * @param base ARIA 암호화 타입 ({@link CipherType#ARIA_128}, {@link CipherType#ARIA_192}, {@link CipherType#ARIA_256}) - */ - ARIAStrategy(@NotNull CipherType base) { - super(base, new ARIAEngine()); - } - - /** - * ARIAStrategy 인스턴스를 생성하는 팩토리 메소드입니다. - * - * @param base ARIA 암호화 타입 ({@link CipherType#ARIA_128}, {@link CipherType#ARIA_192}, {@link CipherType#ARIA_256}) - * @return 새 {@link ARIAStrategy} 인스턴스 - */ - public static ARIAStrategy create(@NotNull CipherType base) { - return new ARIAStrategy(base); - } - - @Override - public SensitiveDataContainer encrypt(@NotNull SensitiveDataContainer keyContainer, final Object plain, boolean ivChaining) - throws EntLibSecureIllegalStateException, EntLibCryptoCipherProcessException { - boolean isAead = mode.isAead(); - CipherParameters params; - - // 네이티브 메모리에서 키 바이트 배열 추출 - keyContainer.exportData(); - byte[] keyBytes = keyContainer.getSegmentData(); - if (keyBytes == null) - throw new EntLibSecureIllegalStateException("네이티브 메모리에서 키 바이트 배열을 추출하지 못했습니다!"); - KeyParameter keyParam = new KeyParameter(keyBytes); - ; - - // 사용 후 힙에 복사된 키 바이트 즉시 소거 - KeyDestroyHelper.zeroing(keyBytes); - - // iv 호출 - iv.exportData(); - byte[] ivH = iv.getSegmentData(); - if (ivH == null) - throw new EntLibSecureIllegalStateException("IV를 생성해야 합니다!"); - - if (mode != Mode.ECB) { - if (isAead) { - params = new AEADParameters(keyParam, 128, ivH); - } else { - params = new ParametersWithIV(keyParam, ivH); - } - } else { - params = keyParam; - } - - byte[] output = processCipher(true, params, plain); - - if (mode != Mode.ECB && ivChaining) { - // IV + CipherText - byte[] result = new byte[ivH.length + output.length]; - System.arraycopy(ivH, 0, result, 0, ivH.length); - System.arraycopy(output, 0, result, ivH.length, output.length); - return new SensitiveDataContainer(result, true); - } else { - return new SensitiveDataContainer(output, true); - } - } - - @Override - public SensitiveDataContainer decrypt(@NotNull SensitiveDataContainer keyContainer, final SensitiveDataContainer ciphertext, boolean ivInference) - throws EntLibSecureIllegalStateException, EntLibCryptoCipherProcessException { - boolean isAead = mode.isAead(); - byte[] actualCiphertext; - CipherParameters params; - - // 네이티브 메모리에서 키 바이트 배열 추출 - keyContainer.exportData(); - byte[] keyBytes = keyContainer.getSegmentData(); - if (keyBytes == null) - throw new EntLibSecureIllegalStateException("네이티브 메모리에서 키 바이트 배열을 추출하지 못했습니다!"); - KeyParameter keyParam = new KeyParameter(keyBytes); - - // 사용 후 힙에 복사된 키 바이트 즉시 소거 - KeyDestroyHelper.zeroing(keyBytes); - - ciphertext.exportData(); - byte[] ciphertextH = ciphertext.getSegmentData(); - if (ciphertextH == null) - throw new EntLibSecureIllegalStateException("네이티브 메모리에서 암호문 바이트 배열을 추출하지 못했습니다!"); - - if (mode != Mode.ECB) { - byte[] ivBytes; - - if (ivInference) { - // 암호문에서 IV 추출 - int ivLength = isAead ? 12 : 16; - if (ciphertextH.length < ivLength) - throw new EntLibCryptoCipherIllegalIVStateException("IV를 포함하기에는 너무 짧은 암호문입니다!"); - - ivBytes = Arrays.copyOfRange(ciphertextH, 0, ivLength); - actualCiphertext = Arrays.copyOfRange(ciphertextH, ivLength, ciphertextH.length); - } else { - // 외부에서 설정된 IV 사용 - iv.exportData(); - ivBytes = iv.getSegmentData(); - if (ivBytes == null) - throw new EntLibCryptoCipherIllegalIVStateException("IV를 설정해야 합니다!"); - actualCiphertext = ciphertextH; - } - - if (isAead) { - params = new AEADParameters(keyParam, 128, ivBytes); - } else { - params = new ParametersWithIV(keyParam, ivBytes); - } - } else { - actualCiphertext = ciphertextH; - params = keyParam; - } - - return new SensitiveDataContainer(processCipher(false, params, actualCiphertext), true); - } - - /** - * 알고리즘 이름을 반환하는 메소드입니다. - * - * @return 알고리즘 이름 "ARIA" - */ - @Override - public String getAlgorithmName() { - return "ARIA"; - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/AbstractBlockCipher.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/AbstractBlockCipher.java deleted file mode 100644 index 02972b8..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/AbstractBlockCipher.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.strategy.detail; - -import lombok.Getter; -import org.bouncycastle.crypto.*; -import org.bouncycastle.crypto.modes.*; -import org.bouncycastle.crypto.paddings.*; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalArgumentException; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalStateException; -import space.qu4nt.entanglementlib.exception.secure.crypto.EntLibCryptoCipherProcessException; -import space.qu4nt.entanglementlib.security.KeyDestroyHelper; -import space.qu4nt.entanglementlib.security.crypto.*; -import space.qu4nt.entanglementlib.security.crypto.Digest; -import space.qu4nt.entanglementlib.security.crypto.strategy.AEADCipherStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.BlockCipherStrategy; - -import java.nio.ByteBuffer; -import java.util.Arrays; - -/// 블록 암호 알고리즘의 공통 기능을 제공하는 추상 클래스입니다. -/// -/// AES, ARIA 등의 블록 암호 구현체가 이 클래스를 상속합니다. -/// 다양한 운영 모드(CBC, GCM, CTR 등)와 패딩 방식을 지원합니다. -/// -/// @author Q. T. Felix -/// @see BlockCipherStrategy -/// @see AEADCipherStrategy -/// @since 1.1.0 -public abstract class AbstractBlockCipher implements BlockCipherStrategy, AEADCipherStrategy { - - /** - * 암호화 알고리즘 타입입니다. - */ - private final @NotNull CipherType base; - - /** - * BouncyCastle 블록 암호 엔진입니다. - */ - private final @NotNull BlockCipher blockCipherEngine; - - protected SensitiveDataContainer iv; - - /** - * 현재 설정된 운영 모드입니다. - */ - @Getter - Mode mode; - - /** - * 현재 설정된 패딩 방식입니다. - */ - @Getter - Padding padding; - - /** - * 현재 설정된 다이제스트입니다. - */ - @Getter - @Nullable Digest digest; - - /** - * AAD(Additional Authenticated Data) 데이터입니다. - */ - private byte[] aad; // todo: sdc - - /** - * 블록 암호 추상 클래스의 생성자입니다. - * - * @param base 암호화 알고리즘 타입 - * @param blockCipherEngine BouncyCastle 블록 암호 엔진 - */ - protected AbstractBlockCipher(final @NotNull CipherType base, @NotNull BlockCipher blockCipherEngine) { - this.base = base; - this.blockCipherEngine = blockCipherEngine; - this.mode = Mode.CBC; - this.padding = Padding.PKCS7; - this.digest = null; - this.aad = null; - } - - @Override - public void iv(Object raw) throws EntLibSecureIllegalArgumentException { - boolean isAead = mode.isAead(); - switch (raw) { - case byte[] b -> this.iv = new SensitiveDataContainer(b, true); - case Integer i -> { - if (isAead && i != 12) - throw new EntLibSecureIllegalArgumentException("AEAD 지원 모드의 경우 IV의 길이는 12이어야 합니다!"); - if (!isAead && i != 16) - throw new EntLibSecureIllegalArgumentException("일반 모드에서 IV의 길이는 16이어야 합니다!"); - this.iv = new SensitiveDataContainer(i); - } - case SensitiveDataContainer s -> this.iv = s; - case null, default -> throw new EntLibSecureIllegalArgumentException("유효한 IV 할당 타입이 아닙니다!"); - } - } - - @Override - public AbstractBlockCipher setMode(@NotNull Mode mode) { - this.mode = mode; - return this; - } - - @Override - public AbstractBlockCipher setPadding(@NotNull Padding padding) { - this.padding = padding; - return this; - } - - @Override - public AbstractBlockCipher setDigest(@NotNull Digest digest) { - this.digest = digest; - return this; - } - - @Override - public AbstractBlockCipher updateAAD(byte[] aad) { - if (aad != null) { - this.aad = Arrays.copyOf(aad, aad.length); - } else { - this.aad = null; - } - return this; - } - - @Override - public EntLibAlgorithmType getAlgorithmType() { - return base; - } - - protected byte[] processCipher(boolean encrypt, CipherParameters params, Object input) throws EntLibCryptoCipherProcessException, EntLibSecureIllegalStateException { - boolean isAead = mode.isAead(); - byte[] output; - int len; - - if (input instanceof byte[] b) { - input = b.clone(); - KeyDestroyHelper.zeroing(b); - } else if (input instanceof SensitiveDataContainer s) { - s.exportData(); - input = s.getSegmentData(); - } else if (input instanceof ByteBuffer bb) { - final byte[] arr = new byte[bb.remaining()]; - input = bb.get(arr); - KeyDestroyHelper.zeroing(arr); - } - - try { - byte[] inputWrap = (byte[]) input; - if (inputWrap == null) - throw new EntLibSecureIllegalStateException(""" - 입력값의 명시적 형 변환 결과(역참조)가 null을 가리킵니다! 입력값이 가진 타입 또는 그 자체에 문제가 있을 수 있습니다. - \t\t1. 입력값의 타입은 '바이트 배열', 'SensitiveDataContainer', 'ByteBuffer' 중 하나여야 합니다. - \t\t2. 타입 할당에 문제가 없는데 이 예외가 발생했다면, 입력값이 올바른 값을 가리키지 않고 있거나, 배열 사이즈가 잘못 할당되었을 수 있습니다."""); - if (isAead) { - AEADBlockCipher cipher = createAeadCipher(); - cipher.init(encrypt, params); - - // AAD 설정 - if (aad != null) - cipher.processAADBytes(aad, 0, aad.length); - - output = new byte[cipher.getOutputSize(inputWrap.length)]; - len = cipher.processBytes(inputWrap, 0, inputWrap.length, output, 0); - len += cipher.doFinal(output, len); - } else { - BufferedBlockCipher cipher = createBufferedCipher(); - cipher.init(encrypt, params); - output = new byte[cipher.getOutputSize(inputWrap.length)]; - len = cipher.processBytes(inputWrap, 0, inputWrap.length, output, 0); - len += cipher.doFinal(output, len); - } - } catch (Exception e) { - throw new EntLibCryptoCipherProcessException("BlockCipher 암호화에 실패했습니다! 이 이유는 여러가지가 있지만, 대표적으로 다음과 같습니다.\n" + - "\t\t1. IV(초기화 벡터) 값이 외부에서 할당되었고, 암호문에 체이닝하지 않은 상태지만, 복호화 과정에서 IV를 추론하려고 하는 경우\n" + - "\t\t2. 암호문(ciphertext) 데이터 컨테이너의 내부 상태가 변질되었거나 소거된 경우\n" + - "\t\t3. 키 데이터 컨테이너의 내부 상태가 변질되었거나 소거된 경우", e); - } - -// todo: KeyDestroyHelper.destroy(params); - - if (len < output.length) { - return Arrays.copyOf(output, len); - } - return output; - } - - BufferedBlockCipher createBufferedCipher() { - BlockCipher modeCipher = switch (mode) { - case ECB -> blockCipherEngine; - case CBC -> CBCBlockCipher.newInstance(blockCipherEngine); - case CFB -> CFBBlockCipher.newInstance(blockCipherEngine, 128); - case OFB -> new OFBBlockCipher(blockCipherEngine, 128); - case CTR -> SICBlockCipher.newInstance(blockCipherEngine); - default -> throw new UnsupportedOperationException("Unsupported mode: " + mode); - }; - - BlockCipherPadding paddingImpl = getPaddingImpl(); - if (paddingImpl != null) { - return new PaddedBufferedBlockCipher(modeCipher, paddingImpl); - } - return new DefaultBufferedBlockCipher(modeCipher); - } - - AEADBlockCipher createAeadCipher() { - return switch (mode) { - case AEAD_GCM -> GCMBlockCipher.newInstance(blockCipherEngine); - case AEAD_CCM -> CCMBlockCipher.newInstance(blockCipherEngine); - default -> throw new UnsupportedOperationException("Unsupported AEAD mode: " + mode); - }; - } - - BlockCipherPadding getPaddingImpl() { - return switch (padding) { - case PKCS5, PKCS7 -> new PKCS7Padding(); - case ISO7816 -> new ISO7816d4Padding(); - case ISO10126 -> new ISO10126d2Padding(); - case ZERO_BYTE -> new ZeroBytePadding(); - case NO, PKCS1, OAEP_AND_MGF1 -> null; - }; - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/AbstractStreamCipher.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/AbstractStreamCipher.java deleted file mode 100644 index 770ba0a..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/AbstractStreamCipher.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.strategy.detail; - -import org.bouncycastle.crypto.CipherParameters; -import org.bouncycastle.crypto.StreamCipher; -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalArgumentException; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalStateException; -import space.qu4nt.entanglementlib.security.KeyDestroyHelper; -import space.qu4nt.entanglementlib.security.crypto.CipherType; -import space.qu4nt.entanglementlib.security.crypto.EntLibAlgorithmType; -import space.qu4nt.entanglementlib.security.crypto.strategy.StreamCipherStrategy; - -import java.nio.ByteBuffer; - -/// 스트림 암호 알고리즘의 공통 기능을 제공하는 추상 클래스입니다. -/// -/// ChaCha20 등의 스트림 암호 구현체가 이 클래스를 상속합니다. -/// -/// @author Q. T. Felix -/// @see StreamCipherStrategy -/// @since 1.1.0 -public abstract class AbstractStreamCipher implements StreamCipherStrategy { - - /** - * 암호화 알고리즘 타입입니다. - */ - private final @NotNull CipherType base; - - /** - * BouncyCastle 스트림 암호 엔진입니다. - */ - private final @NotNull StreamCipher streamCipherEngine; - - protected SensitiveDataContainer iv; - - /** - * 스트림 암호 추상 클래스의 생성자입니다. - * - * @param base 암호화 알고리즘 타입 - * @param streamCipherEngine BouncyCastle 스트림 암호 엔진 - */ - protected AbstractStreamCipher(final @NotNull CipherType base, @NotNull StreamCipher streamCipherEngine) { - this.base = base; - this.streamCipherEngine = streamCipherEngine; - } - - @Override - public void iv(Object raw) throws EntLibSecureIllegalArgumentException { - switch (raw) { - case byte[] b -> this.iv = new SensitiveDataContainer(b, true); - case Integer i -> { - if (i != 12) - throw new EntLibSecureIllegalArgumentException("AEAD 지원 모드의 경우 IV의 길이는 12이어야 합니다!"); - this.iv = new SensitiveDataContainer(i); - } - case SensitiveDataContainer s -> this.iv = s; - case null, default -> throw new EntLibSecureIllegalArgumentException("유효한 IV 할당 타입이 아닙니다!"); - } - } - - /** - * 이 스트레티지의 알고리즘 타입을 반환하는 메소드입니다. - * - * @return 알고리즘 타입 - */ - @Override - public EntLibAlgorithmType getAlgorithmType() { - return base; - } - - /** - * 스트림 암호 처리를 수행하는 메소드입니다. - * - * @param encrypt 암호화이면 {@code true}, 복호화이면 {@code false} - * @param params 암호화 파라미터 - * @param input 입력 바이트 배열 - * @param inOff 입력 시작 오프셋 - * @param length 처리할 길이 - * @param output 출력 바이트 배열 - * @param outOff 출력 시작 오프셋 - * @return 처리된 바이트 수 - */ - protected int processStreamCipher(boolean encrypt, CipherParameters params, byte[] input, int inOff, int length, byte[] output, int outOff) throws EntLibSecureIllegalArgumentException { - try { - streamCipherEngine.init(encrypt, params); - } catch (IllegalArgumentException e) { - throw new EntLibSecureIllegalArgumentException("StreamCipher 암호화에 실패했습니다! 아마 다음의 이유로 실패했을 겁니다.\n" + - "\t\t1. IV(초기화 벡터) 사이즈가 8 미만이거나 초과하는 경우", e); - } - return streamCipherEngine.processBytes(input, inOff, length, output, outOff); - } - - /** - * 단일 바이트에 대해 스트림 암호 처리를 수행하는 메소드입니다. - * - * @param encrypt 암호화이면 {@code true}, 복호화이면 {@code false} - * @param params 암호화 파라미터 - * @param input 입력 바이트 - * @return 처리된 바이트 - */ - protected byte processStreamByte(boolean encrypt, CipherParameters params, byte input) throws EntLibSecureIllegalArgumentException { - try { - streamCipherEngine.init(encrypt, params); - } catch (IllegalArgumentException e) { - throw new EntLibSecureIllegalArgumentException("StreamCipher 복호화에 실패했습니다! 아마 다음의 이유로 실패했을 겁니다.\n" + - "\t\t1. IV(초기화 벡터) 사이즈가 8 미만이거나 초과하는 경우", e); - } - return streamCipherEngine.returnByte(input); - } - - protected byte[] plainCaster(Object plain) throws EntLibSecureIllegalStateException { - if (plain instanceof byte[] b) { - plain = b.clone(); - KeyDestroyHelper.zeroing(b); - } else if (plain instanceof SensitiveDataContainer s) { - s.exportData(); - plain = s.getSegmentData(); - } else if (plain instanceof ByteBuffer bb) { - final byte[] arr = new byte[bb.remaining()]; - plain = bb.get(arr); - KeyDestroyHelper.zeroing(arr); - } - //noinspection DataFlowIssue - return (byte[]) plain; - } -} \ No newline at end of file diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/ChaCha20Poly1305Strategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/ChaCha20Poly1305Strategy.java deleted file mode 100644 index ffe793d..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/ChaCha20Poly1305Strategy.java +++ /dev/null @@ -1,419 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.strategy.detail; - -import lombok.extern.slf4j.Slf4j; -import org.bouncycastle.crypto.InvalidCipherTextException; -import org.bouncycastle.crypto.modes.ChaCha20Poly1305; -import org.bouncycastle.crypto.params.AEADParameters; -import org.bouncycastle.crypto.params.KeyParameter; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalArgumentException; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalStateException; -import space.qu4nt.entanglementlib.exception.secure.crypto.EntLibCryptoCipherIllegalIVStateException; -import space.qu4nt.entanglementlib.exception.secure.crypto.EntLibCryptoCipherProcessException; -import space.qu4nt.entanglementlib.security.KeyDestroyHelper; -import space.qu4nt.entanglementlib.security.crypto.CipherType; -import space.qu4nt.entanglementlib.security.crypto.EntLibAlgorithmType; -import space.qu4nt.entanglementlib.security.crypto.strategy.AEADCipherStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.StreamCipherStrategy; -import space.qu4nt.entanglementlib.util.chunk.ByteArrayChunkProcessor; -import space.qu4nt.entanglementlib.util.io.EntFile; - -import java.nio.ByteBuffer; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.function.BiConsumer; - -/// ChaCha20-Poly1305 AEAD 암호화 전략 클래스입니다. -/// -/// ChaCha20은 AES의 대안으로 사용되는 고속 스트림 암호이고, -/// Poly1305는 고속 메시지 인증 코드(MAC) 알고리즘입니다. -/// ChaCha20-Poly1305는 이 두 알고리즘을 결합한 AEAD(Authenticated Encryption with Associated Data) -/// 암호화 방식으로, 기밀성과 무결성을 동시에 보장합니다. -/// -/// 스트림 암호화는 [EntFile#byteBufferStreaming(Path, Path, int, int, BiConsumer)] -/// 메소드를 사용하여 대용량 파일을 청크 단위로 처리할 수 있습니다. -/// IV(Nonce) 길이는 12바이트(96비트)로 고정되어 있으며, MAC 크기는 16바이트(128비트)입니다. -/// -/// 키는 네이티브 메모리에서 관리되며, 사용 후 즉시 소거됩니다. -/// -/// @author Q. T. Felix -/// @see StreamCipherStrategy -/// @see AEADCipherStrategy -/// @see CipherType#CHACHA20_POLY1305 -/// @since 1.1.0 -@Slf4j -public final class ChaCha20Poly1305Strategy implements StreamCipherStrategy, AEADCipherStrategy { - - /** - * 청크 처리 크기 (64KB)입니다. - */ - private static final int CHUNK_SIZE = 64 * 1024; // 64KB - - /** - * MAC 크기 (128비트, 16바이트)입니다. - */ - private static final int MAC_SIZE = 16; // 128 bits - - /** - * Nonce(IV) 크기 (96비트, 12바이트)입니다. - */ - private static final int NONCE_SIZE = 12; // 96 bits - - private SensitiveDataContainer iv; - - /** - * AAD(Additional Authenticated Data) 데이터입니다. - */ - private byte @Nullable [] aad; - // todo: Thread-safe하지 않은 엔진이므로 각 호출마다 새로 생성하거나 ThreadLocal 사용 고려 - - /** - * ChaCha20Poly1305Strategy 생성자입니다. - */ - ChaCha20Poly1305Strategy() { - } - - /** - * ChaCha20Poly1305Strategy 인스턴스를 생성하는 팩토리 메소드입니다. - * - * @return 새 {@link ChaCha20Poly1305Strategy} 인스턴스 - */ - public static ChaCha20Poly1305Strategy create() { - return new ChaCha20Poly1305Strategy(); - } - - @Override - public void iv(Object raw) throws EntLibSecureIllegalArgumentException { - switch (raw) { - case byte[] b -> this.iv = new SensitiveDataContainer(b, true); - case Integer i -> { - if (i != 12) - throw new EntLibSecureIllegalArgumentException("AEAD 지원 모드의 경우 IV의 길이는 12이어야 합니다!"); - this.iv = new SensitiveDataContainer(i); - } - case SensitiveDataContainer s -> this.iv = s; - case null, default -> throw new EntLibSecureIllegalArgumentException("유효한 IV 할당 타입이 아닙니다!"); - } - } - - /** - * AAD(Additional Authenticated Data)를 설정하는 메소드입니다. - *

- * AAD는 암호화되지 않지만 무결성이 보장됩니다. - * 암호화/복호화 수행 전에 호출되어야 합니다. - * - * @param aad 추가 인증 데이터 - * @return 메소드 체이닝을 위한 {@code this} - */ - @Override - public AEADCipherStrategy updateAAD(byte @NotNull [] aad) { - if (aad != null) { - this.aad = Arrays.copyOf(aad, aad.length); - // 전달받은 aad 원본은 호출자가 관리하거나 필요시 소거한다고 가정 (여기서는 복사본을 저장) - } else { - this.aad = null; - } - return this; - } - - /** - * 입력 버퍼의 데이터를 스트리밍 방식으로 암호화하여 출력 버퍼에 쓰는 메소드입니다. - *

- * 청크 단위(64KB)로 데이터를 처리하며, 각 청크마다 독립적인 IV(Nonce)가 생성됩니다. - * 출력 형식은 각 청크당 {@code IV(12바이트) + CipherText + MAC(16바이트)}입니다. - * - * @param keyContainer 암호화에 사용할 키 - * @param inputBuffer 암호화할 데이터가 담긴 입력 버퍼 - * @param outputBuffer 암호화된 데이터를 쓸 출력 버퍼 - * @return 처리된 바이트 수 - * @throws IllegalArgumentException 출력 버퍼가 너무 작은 경우 - * @throws RuntimeException 키가 {@code null}인 경우 - */ - @Override - public int streamEncrypt(@NotNull SensitiveDataContainer keyContainer, @NotNull ByteBuffer inputBuffer, @NotNull ByteBuffer outputBuffer) throws EntLibSecureIllegalStateException { - // 스트림 암호화 시 청크 단위로 처리 - // 각 청크는 독립적인 IV(Nonce)를 가져야 안전함 (또는 카운터 증가 방식) - // 여기서는 단순화를 위해 전체 데이터를 읽어서 처리하는 기존 방식 대신, - // ByteBuffer에서 읽어온 데이터를 청크 단위로 암호화하여 출력 버퍼에 씀 - - // 주의: 이 메소드는 단일 호출로 간주되므로, 내부적으로 청크 처리를 수행함. - // 입력 버퍼의 모든 데이터를 읽어서 암호화. - - byte[] input = new byte[inputBuffer.remaining()]; - inputBuffer.get(input); - - // 결과 크기 예측: (청크 개수 * (IV + MAC)) + 원본 크기 - int chunkCount = (input.length + CHUNK_SIZE - 1) / CHUNK_SIZE; - int overheadPerChunk = NONCE_SIZE + MAC_SIZE; - int estimatedOutputSize = input.length + (chunkCount * overheadPerChunk); - - if (outputBuffer.remaining() < estimatedOutputSize) { - throw new IllegalArgumentException("Output buffer is too small. Required: " + estimatedOutputSize); - } - - // 네이티브 메모리에서 키 바이트 배열 추출 - keyContainer.exportData(); - byte[] keyBytes = keyContainer.getSegmentData(); - if (keyBytes == null) - throw new EntLibSecureIllegalStateException("네이티브 메모리에서 키 바이트 배열을 추출하지 못했습니다!"); - KeyParameter keyParam = new KeyParameter(keyBytes); - - ByteArrayChunkProcessor.processInChunks(input, CHUNK_SIZE, (data, offset, length) -> { - // 각 청크마다 고유한 IV 생성 - byte[] iv = SensitiveDataContainer.generateSafeRandomBytes(NONCE_SIZE); - AEADParameters params = new AEADParameters(keyParam, MAC_SIZE * 8, iv, aad); - - ChaCha20Poly1305 cipher = new ChaCha20Poly1305(); - cipher.init(true, params); - - byte[] chunkOutput = new byte[cipher.getOutputSize(length)]; - int len = cipher.processBytes(data, offset, length, chunkOutput, 0); - len += cipher.doFinal(chunkOutput, len); - - // 출력: IV + EncryptedChunk(MAC 포함) - outputBuffer.put(iv); - outputBuffer.put(chunkOutput, 0, len); - }); - KeyDestroyHelper.zeroing(keyBytes); - - return estimatedOutputSize; // 실제 쓰여진 바이트 수와 다를 수 있으나, put으로 위치가 이동됨. - } - - /** - * 입력 버퍼의 데이터를 스트리밍 방식으로 복호화하여 출력 버퍼에 쓰는 메소드입니다. - *

- * 암호화된 청크 구조({@code IV + CipherText + MAC})를 인식하여 처리합니다. - * 각 청크의 MAC을 검증하여 무결성을 확인합니다. - * - * @param keyContainer 복호화에 사용할 키 - * @param inputBuffer 복호화할 데이터가 담긴 입력 버퍼 - * @param outputBuffer 복호화된 데이터를 쓸 출력 버퍼 - * @return 복호화된 총 바이트 수 - * @throws IllegalStateException MAC 검증 실패 시 - * @throws RuntimeException 키가 {@code null}인 경우 - */ - @Override - public int streamDecrypt(@NotNull SensitiveDataContainer keyContainer, @NotNull ByteBuffer inputBuffer, @NotNull ByteBuffer outputBuffer) throws EntLibSecureIllegalStateException, EntLibCryptoCipherProcessException { - // 복호화는 암호화된 청크 구조(IV + CipherText + MAC)를 인식해야 함. - // 암호화 시 CHUNK_SIZE 단위로 평문을 잘랐지만, 암호문 청크 크기는 CHUNK_SIZE + NONCE_SIZE + MAC_SIZE 임. - // 마지막 청크는 더 작을 수 있음. - - // 스트림 복호화는 구조상 복잡함. 입력 버퍼가 전체 데이터를 포함한다고 가정하고 처리. - // 만약 입력 버퍼가 부분 데이터라면 상태 관리가 필요하지만, 현재 인터페이스는 상태를 유지하지 않음. - - byte[] input = new byte[inputBuffer.remaining()]; - inputBuffer.get(input); - - // 네이티브 메모리에서 키 바이트 배열 추출 - keyContainer.exportData(); - byte[] keyBytes = keyContainer.getSegmentData(); - if (keyBytes == null) - throw new EntLibSecureIllegalStateException("네이티브 메모리에서 키 바이트 배열을 추출하지 못했습니다!"); - KeyParameter keyParam = new KeyParameter(keyBytes); - - int readOffset = 0; - int totalDecrypted = 0; - - while (readOffset < input.length) { - // 1. IV 읽기 - if (readOffset + NONCE_SIZE > input.length) break; // 데이터 부족 - byte[] iv = Arrays.copyOfRange(input, readOffset, readOffset + NONCE_SIZE); - readOffset += NONCE_SIZE; - - // 2. 청크 데이터 길이 계산 - // 암호화 시 평문 CHUNK_SIZE -> 암호문 CHUNK_SIZE + MAC_SIZE - // 마지막 청크일 수 있으므로 남은 길이 확인 - int remaining = input.length - readOffset; - int currentCipherChunkSize = Math.min(remaining, CHUNK_SIZE + MAC_SIZE); - - if (currentCipherChunkSize < MAC_SIZE) break; // 최소 MAC 크기보다 작으면 오류 - - AEADParameters params = new AEADParameters(keyParam, MAC_SIZE * 8, iv, aad); - ChaCha20Poly1305 cipher = new ChaCha20Poly1305(); - cipher.init(false, params); - - byte[] chunkOutput = new byte[cipher.getOutputSize(currentCipherChunkSize)]; - int len = cipher.processBytes(input, readOffset, currentCipherChunkSize, chunkOutput, 0); - try { - len += cipher.doFinal(chunkOutput, len); - } catch (InvalidCipherTextException e) { - throw new EntLibCryptoCipherProcessException(e); - } - - outputBuffer.put(chunkOutput, 0, len); - totalDecrypted += len; - - readOffset += currentCipherChunkSize; - } - KeyDestroyHelper.zeroing(keyBytes); - - return totalDecrypted; - } - - /** - * 평문을 ChaCha20-Poly1305 알고리즘으로 암호화하는 메소드입니다. - *

- * 12바이트의 Nonce(IV)가 자동으로 생성되며, - * 결과는 {@code IV(12바이트) + CipherText + MAC(16바이트)} 형식으로 반환됩니다. - * AAD가 설정된 경우 함께 인증에 사용됩니다. - * - * @param keyContainer 암호화에 사용할 키 - * @param plain 암호화할 평문 바이트 배열 - * @return 암호화된 바이트 배열 (IV + CipherText + MAC) - * @throws IllegalStateException 암호화 실패 시 - * @throws RuntimeException 키가 {@code null}인 경우 - */ - @Override - public SensitiveDataContainer encrypt(@NotNull SensitiveDataContainer keyContainer, Object plain, boolean ivChaining) - throws EntLibSecureIllegalStateException, EntLibCryptoCipherProcessException { - // 네이티브 메모리에서 키 바이트 배열 추출 - keyContainer.exportData(); - byte[] keyBytes = keyContainer.getSegmentData(); - if (keyBytes == null) - throw new EntLibSecureIllegalStateException("네이티브 메모리에서 키 바이트 배열을 추출하지 못했습니다!"); - KeyParameter keyParam = new KeyParameter(keyBytes); - KeyDestroyHelper.zeroing(keyBytes); - - iv.exportData(); - byte[] ivH = iv.getSegmentData(); - if (ivH == null) - throw new EntLibCryptoCipherIllegalIVStateException("IV를 생성해야 합니다!"); - - AEADParameters params = new AEADParameters(keyParam, MAC_SIZE * 8, ivH, aad); - ChaCha20Poly1305 cipher = new ChaCha20Poly1305(); - cipher.init(true, params); - - byte[] inputWrap = plainCaster(plain); - if (inputWrap == null) - throw new EntLibSecureIllegalStateException(""" - 입력값의 명시적 형 변환 결과(역참조)가 null을 가리킵니다! 입력값이 가진 타입 또는 그 자체에 문제가 있을 수 있습니다. - \t\t1. 입력값의 타입은 '바이트 배열', 'SensitiveDataContainer', 'ByteBuffer' 중 하나여야 합니다. - \t\t2. 타입 할당에 문제가 없는데 이 예외가 발생했다면, 입력값이 올바른 값을 가리키지 않고 있거나, 배열 사이즈가 잘못 할당되었을 수 있습니다."""); - - byte[] output = new byte[cipher.getOutputSize(inputWrap.length)]; - int len = cipher.processBytes(inputWrap, 0, inputWrap.length, output, 0); - try { - len += cipher.doFinal(output, len); - } catch (InvalidCipherTextException e) { - throw new EntLibCryptoCipherProcessException(e); - } - - if (ivChaining) { - byte[] result = new byte[ivH.length + len]; - System.arraycopy(ivH, 0, result, 0, ivH.length); - System.arraycopy(output, 0, result, ivH.length, len); - return new SensitiveDataContainer(result, true); - } - return new SensitiveDataContainer(output, true); - } - - /** - * 암호문을 ChaCha20-Poly1305 알고리즘으로 복호화하는 메소드입니다. - *

- * 입력된 암호문에서 12바이트의 IV를 추출하고 MAC을 검증하여 복호화를 수행합니다. - * AAD가 설정된 경우 함께 인증에 사용됩니다. - * - * @param keyContainer 복호화에 사용할 키 - * @param ciphertext 복호화할 암호문 바이트 배열 (IV + CipherText + MAC) - * @return 복호화된 평문 바이트 배열 - * @throws IllegalArgumentException 암호문이 너무 짧은 경우 - * @throws IllegalStateException MAC 검증 실패 시 - * @throws RuntimeException 키가 {@code null}인 경우 - */ - @Override - public SensitiveDataContainer decrypt(@NotNull SensitiveDataContainer keyContainer, final SensitiveDataContainer ciphertext, boolean ivInference) - throws EntLibSecureIllegalStateException, EntLibCryptoCipherProcessException, EntLibSecureIllegalArgumentException { - ciphertext.exportData(); - byte[] ciphertextH = ciphertext.getSegmentData(); - if (ciphertextH == null) - throw new EntLibSecureIllegalStateException("네이티브 메모리에서 암호문 바이트 배열을 추출하지 못했습니다!"); - - byte[] ivBytes; - byte[] actualCiphertext; - - if (ivInference) { - // 암호문에서 IV 추출 - if (ciphertextH.length < NONCE_SIZE + MAC_SIZE) - throw new EntLibSecureIllegalArgumentException("암호문이 너무 작습니다!"); - - ivBytes = Arrays.copyOfRange(ciphertextH, 0, NONCE_SIZE); - actualCiphertext = Arrays.copyOfRange(ciphertextH, NONCE_SIZE, ciphertextH.length); - } else { - // 외부에서 설정된 IV 사용 - if (ciphertextH.length < MAC_SIZE) - throw new EntLibSecureIllegalArgumentException("암호문이 너무 작습니다!"); - - iv.exportData(); - ivBytes = iv.getSegmentData(); - if (ivBytes == null) - throw new EntLibCryptoCipherIllegalIVStateException("IV를 설정해야 합니다!"); - actualCiphertext = ciphertextH; - } - - // 네이티브 메모리에서 키 바이트 배열 추출 - keyContainer.exportData(); - byte[] keyBytes = keyContainer.getSegmentData(); - if (keyBytes == null) - throw new EntLibSecureIllegalStateException("네이티브 메모리에서 키 바이트 배열을 추출하지 못했습니다!"); - KeyParameter keyParam = new KeyParameter(keyBytes); - KeyDestroyHelper.zeroing(keyBytes); - - AEADParameters params = new AEADParameters(keyParam, MAC_SIZE * 8, ivBytes, aad); - - ChaCha20Poly1305 cipher = new ChaCha20Poly1305(); - cipher.init(false, params); - - byte[] output = new byte[cipher.getOutputSize(actualCiphertext.length)]; - int len = cipher.processBytes(actualCiphertext, 0, actualCiphertext.length, output, 0); - try { - len += cipher.doFinal(output, len); - } catch (InvalidCipherTextException e) { - throw new EntLibCryptoCipherProcessException(e); - } - - return new SensitiveDataContainer(Arrays.copyOf(output, len), true); - } - - private byte[] plainCaster(Object plain) throws EntLibSecureIllegalStateException { - if (plain instanceof byte[] b) { - plain = b.clone(); - KeyDestroyHelper.zeroing(b); - } else if (plain instanceof SensitiveDataContainer s) { - s.exportData(); - plain = s.getSegmentData(); - } else if (plain instanceof ByteBuffer bb) { - final byte[] arr = new byte[bb.remaining()]; - plain = bb.get(arr); - KeyDestroyHelper.zeroing(arr); - } - //noinspection DataFlowIssue - return (byte[]) plain; - } - - /** - * 알고리즘 이름을 반환하는 메소드입니다. - * - * @return 알고리즘 이름 "ChaCha20-Poly1305" - */ - @Override - public String getAlgorithmName() { - return "ChaCha20-Poly1305"; - } - - /** - * 이 스트레티지의 알고리즘 타입을 반환하는 메소드입니다. - * - * @return {@link CipherType#CHACHA20_POLY1305} - */ - @Override - public EntLibAlgorithmType getAlgorithmType() { - return CipherType.CHACHA20_POLY1305; - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/ChaCha20Strategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/ChaCha20Strategy.java deleted file mode 100644 index 167c7a4..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/ChaCha20Strategy.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.strategy.detail; - -import lombok.extern.slf4j.Slf4j; -import org.bouncycastle.crypto.engines.ChaChaEngine; -import org.bouncycastle.crypto.params.KeyParameter; -import org.bouncycastle.crypto.params.ParametersWithIV; -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalArgumentException; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalStateException; -import space.qu4nt.entanglementlib.exception.secure.crypto.EntLibCryptoCipherIllegalIVStateException; -import space.qu4nt.entanglementlib.security.KeyDestroyHelper; -import space.qu4nt.entanglementlib.security.crypto.CipherType; -import space.qu4nt.entanglementlib.security.crypto.strategy.StreamCipherStrategy; -import space.qu4nt.entanglementlib.util.io.EntFile; - -import java.nio.ByteBuffer; -import java.nio.file.Path; -import java.util.function.BiConsumer; - -/// ChaCha20 스트림 암호 알고리즘 전략 클래스입니다. -/// -/// ChaCha20은 AES의 대안으로 사용되는 고속 스트림 암호 알고리즘입니다. -/// BouncyCastle의 [ChaChaEngine]을 사용하여 암호화/복호화를 수행합니다. -/// -/// 스트림 암호화는 [EntFile#byteBufferStreaming(Path, Path, int, int, BiConsumer)] -/// 메소드를 사용하여 대용량 파일을 효율적으로 처리할 수 있습니다. -/// IV(Nonce) 길이는 8바이트(64비트)를 사용합니다. -/// -/// 키는 네이티브 메모리에서 관리되며, 사용 후 즉시 소거됩니다. -/// -/// @author Q. T. Felix -/// @see AbstractStreamCipher -/// @see StreamCipherStrategy -/// @see CipherType#CHACHA20 -/// @since 1.1.0 -@Slf4j -public final class ChaCha20Strategy extends AbstractStreamCipher { - - /** - * ChaCha20Strategy 생성자입니다. - */ - ChaCha20Strategy() { - super(CipherType.CHACHA20, new ChaChaEngine()); - } - - /** - * ChaCha20Strategy 인스턴스를 생성하는 팩토리 메소드입니다. - * - * @return 새 {@link ChaCha20Strategy} 인스턴스 - */ - public static ChaCha20Strategy create() { - return new ChaCha20Strategy(); - } - - @Override - public int streamEncrypt(@NotNull SensitiveDataContainer keyContainer, @NotNull ByteBuffer inputBuffer, @NotNull ByteBuffer outputBuffer) - throws EntLibSecureIllegalStateException, EntLibCryptoCipherIllegalIVStateException, EntLibSecureIllegalArgumentException { - if (outputBuffer.remaining() < 8 + inputBuffer.remaining()) - throw new StackOverflowError("출력 버퍼가 너무 작습니다!"); - final SensitiveDataContainer input = new SensitiveDataContainer(inputBuffer.remaining()); - final SensitiveDataContainer output = encrypt(keyContainer, input, false); - outputBuffer.put(output.getSegmentDataToByteBuffer()); - return Math.toIntExact(output.getMemorySegment().byteSize()); - } - - @Override - public int streamDecrypt(@NotNull SensitiveDataContainer keyContainer, @NotNull ByteBuffer inputBuffer, @NotNull ByteBuffer outputBuffer) - throws EntLibSecureIllegalStateException, EntLibCryptoCipherIllegalIVStateException, EntLibSecureIllegalArgumentException { - if (inputBuffer.remaining() < 8) - throw new StackOverflowError("입력 버퍼가 너무 작습니다!"); - final SensitiveDataContainer input = new SensitiveDataContainer(inputBuffer.remaining()); - final SensitiveDataContainer output = decrypt(keyContainer, input, false); - outputBuffer.put(output.getSegmentDataToByteBuffer()); - return Math.toIntExact(output.getMemorySegment().byteSize()); - } - - @Override - public SensitiveDataContainer encrypt(@NotNull SensitiveDataContainer keyContainer, final Object plain, boolean ivChaining) - throws EntLibSecureIllegalStateException, EntLibCryptoCipherIllegalIVStateException, EntLibSecureIllegalArgumentException { - iv.exportData(); - byte[] ivH = iv.getSegmentData(); - if (ivH == null) - throw new EntLibCryptoCipherIllegalIVStateException("IV를 설정해야 합니다!"); - - // 네이티브 메모리에서 키 바이트 배열 추출 - keyContainer.exportData(); - byte[] keyBytes = keyContainer.getSegmentData(); - if (keyBytes == null) - throw new EntLibSecureIllegalStateException("네이티브 메모리에서 키 바이트 배열을 추출하지 못했습니다!"); - KeyParameter keyParam = new KeyParameter(keyBytes); - - ParametersWithIV params = new ParametersWithIV(keyParam, ivH); - - // 사용 후 힙에 복사된 키 바이트 즉시 소거 - KeyDestroyHelper.zeroing(keyBytes); - - byte[] plainWrap = plainCaster(plain); - - byte[] output = new byte[plainWrap.length]; - processStreamCipher(true, params, plainWrap, 0, plainWrap.length, output, 0); - - // 결과 반환: IV + CipherText - if (ivChaining) { - byte[] result = new byte[ivH.length + output.length]; - System.arraycopy(ivH, 0, result, 0, ivH.length); - System.arraycopy(output, 0, result, ivH.length, output.length); - KeyDestroyHelper.zeroing(output); - KeyDestroyHelper.zeroing(ivH); - return new SensitiveDataContainer(result, true); - } else { - KeyDestroyHelper.zeroing(ivH); - return new SensitiveDataContainer(output, true); - } - } - - @Override - public SensitiveDataContainer decrypt(@NotNull SensitiveDataContainer keyContainer, final SensitiveDataContainer ciphertext, boolean ivInference) throws EntLibSecureIllegalStateException, EntLibCryptoCipherIllegalIVStateException, EntLibSecureIllegalArgumentException { - ciphertext.exportData(); - byte[] ciphertextH = ciphertext.getSegmentData(); - if (ciphertextH == null) - throw new EntLibSecureIllegalStateException("네이티브 메모리에서 암호문 바이트 배열을 추출하지 못했습니다!"); - - byte[] ivBytes; - byte[] actualCiphertext; - - if (ivInference) { - // 암호문에서 IV 추출 - if (ciphertextH.length < 8) - throw new EntLibCryptoCipherIllegalIVStateException("IV를 포함하기에는 너무 짧은 암호문입니다!"); - - ivBytes = new byte[8]; - System.arraycopy(ciphertextH, 0, ivBytes, 0, 8); - - actualCiphertext = new byte[ciphertextH.length - 8]; - System.arraycopy(ciphertextH, 8, actualCiphertext, 0, actualCiphertext.length); - } else { - // 외부에서 설정된 IV 사용 - iv.exportData(); - ivBytes = iv.getSegmentData(); - if (ivBytes == null) - throw new EntLibCryptoCipherIllegalIVStateException("IV를 설정해야 합니다!"); - actualCiphertext = ciphertextH; - } - - // 네이티브 메모리에서 키 바이트 배열 추출 - keyContainer.exportData(); - byte[] keyBytes = keyContainer.getSegmentData(); - if (keyBytes == null) - throw new EntLibSecureIllegalStateException("네이티브 메모리에서 키 바이트 배열을 추출하지 못했습니다!"); - KeyParameter keyParam = new KeyParameter(keyBytes); - - // 사용 후 힙에 복사된 키 바이트 즉시 소거 - KeyDestroyHelper.zeroing(keyBytes); - - ParametersWithIV params = new ParametersWithIV(keyParam, ivBytes); - byte[] output = new byte[actualCiphertext.length]; - processStreamCipher(false, params, actualCiphertext, 0, actualCiphertext.length, output, 0); - - return new SensitiveDataContainer(output, true); - } - - /** - * 알고리즘 이름을 반환하는 메소드입니다. - * - * @return 알고리즘 이름 "ChaCha20" - */ - @Override - public String getAlgorithmName() { - return "ChaCha20"; - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/MLDSAStrategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/MLDSAStrategy.java deleted file mode 100644 index 276c026..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/MLDSAStrategy.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.strategy.detail; - -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.entlibnative.ProgressResult; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.exception.critical.EntLibNativeError; -import space.qu4nt.entanglementlib.exception.critical.EntLibSecurityError; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalStateException; -import space.qu4nt.entanglementlib.exception.secure.crypto.EntLibCryptoSignatureProcessingException; -import space.qu4nt.entanglementlib.security.crypto.EntLibAlgorithmType; -import space.qu4nt.entanglementlib.security.crypto.EntLibCryptoRegistry; -import space.qu4nt.entanglementlib.security.crypto.ParameterSizeDetail; -import space.qu4nt.entanglementlib.security.crypto.SignatureType; -import space.qu4nt.entanglementlib.security.crypto.bundle.MLDSAStrategyBundle; -import space.qu4nt.entanglementlib.security.crypto.strategy.NativeSignatureStrategy; - -import java.util.Optional; - -/// # Memo -/// -/// 네이티브 연동 후 pqc 알고리즘 수행에 있어 생명주기를 다음과 같이 명심해야 함. -/// -/// 1. 키 페어 생성 -/// - 키 페어는 서명 및 검증 작업이 완료된 이후에 소거되어야 함. -/// 2. 서명 -/// - 서명 시 `평문(TBSData)`을 최초로 받음. 다만 서명이 완료되어도 평문은 검증에 사용되기 때문에 이 시점에 소거되어선 안 됨. -/// - 서명이 완료된 후 메모리에 잔류하는 비밀 키 데이터 모두 소거. -/// - 서명이 완료되어 통신 상대방에게 서명을 전달하고 사용자가 정상적으로 받은 경우에 서버 측에 잔류하는 서명 데이터 소거. -/// 3. 검증 -/// - 상대방이 자신의 공개 키와 평문으로 서명을 검증 완료하면 평문 소거. -/// -/// 평문의 경우 Java에서 바이트 배열로 받기 때문에, [SensitiveDataContainer]에 평문 넘기면 평문이 위치한 heap은 자동으로 소거됨. -/// 현재 heap 메모리에 남은 데이터를 지우고 난 뒤, 메모리 구역을 만들어 Rust 로 넘기는 방식임. -/// -/// @author Q. T. Felix -/// @since 1.1.0 -@Slf4j -public final class MLDSAStrategy implements NativeSignatureStrategy { - - private final SignatureType type; - - MLDSAStrategy(@NotNull SignatureType type) { - this.type = type; - } - - /// MLDSAStrategy 인스턴스를 생성합니다. - /// - /// 주의하세요! 이 메소드를 사용하여 전략 패턴을 선언하는 것은 올바르지 않습니다. - /// 이 메소드는 [EntLibCryptoRegistry] 레지스트리에 등록할 때 사용되는 - /// 메소드입니다. - /// - /// @param type SignatureType (ML_DSA_44, ML_DSA_65, ML_DSA_87) - /// @return 새 MLDSAStrategy 인스턴스 - @ApiStatus.Internal - public static MLDSAStrategy create(@NotNull SignatureType type) { - return new MLDSAStrategy(type); - } - - @Override - public SensitiveDataContainer sign(@NotNull SensitiveDataContainer keyPrivate, byte[] plainBytes) - throws EntLibCryptoSignatureProcessingException, EntLibSecureIllegalStateException { - ParameterSizeDetail detail = type.getParameterSizeDetail(); - if (keyPrivate.getMemorySegment().byteSize() != detail.getPrivateKeySize()) - throw new EntLibSecurityError( - "주요 데이터의 바이트 사이즈가 올바르지 않습니다! 세션 체계 참여자가 악의적인 데이터를 전송했을 수 있습니다." - ); - SensitiveDataContainer complexContainer = new SensitiveDataContainer(detail.getSignatureSize()); - SensitiveDataContainer plain = complexContainer.addContainerData(plainBytes, true); - try { - final ProgressResult result = ProgressResult.fromCode((int) MLDSAStrategyBundle - .callNativeMLDSAHandle(type, 1) // sign type - .invokeExact( - complexContainer.getMemorySegment(), - plain.getMemorySegment(), - (long) plainBytes.length, - keyPrivate.getMemorySegment())); - if (result.isFail()) - throw new EntLibCryptoSignatureProcessingException("서명에 실패했습니다! 네이티브 코드 반환값: " + result.getCode()); - } catch (Throwable e) { - complexContainer.close(); - throw new EntLibNativeError("네이티브 에러", e); - } - // 서명 완료 후 공개 키를 컨테이너 추가 - return complexContainer; - } - - @Override - public boolean verify(@NotNull SensitiveDataContainer container) - throws EntLibCryptoSignatureProcessingException { - ParameterSizeDetail detail = type.getParameterSizeDetail(); - // 평문 - Optional plainOpt = container.get(0); - if (plainOpt.isEmpty()) - throw new EntLibCryptoSignatureProcessingException("평문 컨테이너가 존재하지 않습니다!"); - SensitiveDataContainer plain = plainOpt.get(); - - // 공개 키 - Optional pkOpt = container.get(1); - if (pkOpt.isEmpty()) - throw new EntLibCryptoSignatureProcessingException("공개 키 컨테이너가 존재하지 않습니다!"); - SensitiveDataContainer keyPublic = pkOpt.get(); - - if (container.getMemorySegment().byteSize() != detail.getSignatureSize() || - keyPublic.getMemorySegment().byteSize() != detail.getPublicKeySize()) - throw new EntLibSecurityError( - "일부 데이터의 바이트 사이즈가 올바르지 않습니다! 세션 체계 또는 인프라에 중간자 공격 또는 패킷 탈취 등의 악의적인 공격을 예상할 수 있습니다." - ); - - try { - final ProgressResult result = ProgressResult.fromCode((int) MLDSAStrategyBundle - .callNativeMLDSAHandle(type, 2) // verify type - .invokeExact( - plain.getMemorySegment(), - plain.getMemorySegment().byteSize(), - container.getMemorySegment(), - keyPublic.getMemorySegment())); - if (result.isFail()) - throw new EntLibCryptoSignatureProcessingException("서명 검증에 실패했습니다! 네이티브 코드 반환값: " + result.getCode()); - return true; - } catch (Throwable e) { - throw new EntLibNativeError("네이티브 에러", e); - } - } - - @Override - public String getAlgorithmName() { - return "ML-DSA"; - } - - @Override - public EntLibAlgorithmType getAlgorithmType() { - return type; - } -} \ No newline at end of file diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/MLKEMStrategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/MLKEMStrategy.java deleted file mode 100644 index 720293a..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/MLKEMStrategy.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.strategy.detail; - -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.entlibnative.ProgressResult; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.exception.critical.EntLibNativeError; -import space.qu4nt.entanglementlib.exception.critical.EntLibSecurityError; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalStateException; -import space.qu4nt.entanglementlib.exception.secure.crypto.EntLibCryptoKEMProcessingException; -import space.qu4nt.entanglementlib.security.crypto.EntLibAlgorithmType; -import space.qu4nt.entanglementlib.security.crypto.EntLibCryptoRegistry; -import space.qu4nt.entanglementlib.security.crypto.KEMType; -import space.qu4nt.entanglementlib.security.crypto.ParameterSizeDetail; -import space.qu4nt.entanglementlib.security.crypto.bundle.MLKEMStrategyBundle; -import space.qu4nt.entanglementlib.security.crypto.strategy.NativeKEMStrategy; - -/// @author Q. T. Felix -/// @since 1.1.0 -@Slf4j -public final class MLKEMStrategy implements NativeKEMStrategy { - - private final KEMType type; - - MLKEMStrategy(@NotNull KEMType type) { - this.type = type; - } - - /// MLKEMStrategy 인스턴스를 생성합니다. - /// - /// 주의하세요! 이 메소드를 사용하여 전략 패턴을 선언하는 것은 올바르지 않습니다. - /// 이 메소드는 [EntLibCryptoRegistry] 레지스트리에 등록할 때 사용되는 - /// 메소드입니다. - /// - /// @param type ML-KEM 타입 - /// @return 새 MLKEMStrategy 인스턴스 - @ApiStatus.Internal - public static MLKEMStrategy create(@NotNull KEMType type) { - return new MLKEMStrategy(type); - } - - @Override - public SensitiveDataContainer encapsulate(@NotNull SensitiveDataContainer keyPublic) - throws EntLibCryptoKEMProcessingException, EntLibSecureIllegalStateException { - ParameterSizeDetail detail = type.getParameterSizeDetail(); - if (keyPublic.getMemorySegment().byteSize() != detail.getEncapsulationKeySize()) - throw new EntLibCryptoKEMProcessingException( - "주요 데이터의 바이트 사이즈가 올바르지 않습니다! 세션 체계 참여자가 악의적인 데이터를 전송했을 수 있습니다." - ); - SensitiveDataContainer ssContainer = new SensitiveDataContainer(detail.getSharedSecretKeySize()); - SensitiveDataContainer ctContainer = ssContainer.addContainerData(detail.getCiphertextSize()); - try { - final ProgressResult result = ProgressResult.fromCode((int) MLKEMStrategyBundle - .callNativeMLKEMHandle(type, 1) // encap type - .invokeExact( - ctContainer.getMemorySegment(), - ssContainer.getMemorySegment(), - keyPublic.getMemorySegment() - )); - if (result.isFail()) - throw new EntLibCryptoKEMProcessingException("키 캡슐화에 실패했습니다! 네이티브 코드 반환값: " + result.getCode()); - } catch (Throwable e) { - ssContainer.close(); - throw new EntLibNativeError("네이티브 에러", e); - } - return ssContainer; - } - - @Override - public SensitiveDataContainer decapsulate(@NotNull SensitiveDataContainer secretKeyContainer, @NotNull SensitiveDataContainer ciphertext) { - ParameterSizeDetail detail = type.getParameterSizeDetail(); - if (ciphertext.getMemorySegment().byteSize() != detail.getCiphertextSize()) - throw new EntLibSecurityError( - "주요 데이터의 바이트 사이즈가 올바르지 않습니다! 세션 체계 참여자가 악의적인 데이터를 전송했을 수 있습니다." - ); - - SensitiveDataContainer ssContainer = new SensitiveDataContainer(detail.getSharedSecretKeySize()); - try { - final ProgressResult result = ProgressResult.fromCode((int) MLKEMStrategyBundle - .callNativeMLKEMHandle(type, 2) // decap type - .invokeExact( - ssContainer.getMemorySegment(), - ciphertext.getMemorySegment(), - secretKeyContainer.getMemorySegment() - )); - if (result.isFail()) { - throw new EntLibCryptoKEMProcessingException("키 디캡슐화에 실패했습니다! 네이티브 코드 반환값: " + result.getCode()); - } - } catch (Throwable e) { - ssContainer.close(); - throw new EntLibNativeError("네이티브 에러", e); - } - return ssContainer; - } - - @Override - public String getAlgorithmName() { - return "ML-KEM"; - } - - @Override - public EntLibAlgorithmType getAlgorithmType() { - return type; - } -} \ No newline at end of file diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/SLHDSAStrategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/SLHDSAStrategy.java deleted file mode 100644 index 732231e..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/SLHDSAStrategy.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.strategy.detail; - -import lombok.extern.slf4j.Slf4j; -import org.bouncycastle.pqc.crypto.slhdsa.SLHDSAParameters; -import org.bouncycastle.pqc.crypto.slhdsa.SLHDSAPrivateKeyParameters; -import org.bouncycastle.pqc.crypto.slhdsa.SLHDSAPublicKeyParameters; -import org.bouncycastle.pqc.crypto.slhdsa.SLHDSASigner; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import space.qu4nt.entanglementlib.security.crypto.EntLibAlgorithmType; -import space.qu4nt.entanglementlib.security.crypto.SignatureType; -import space.qu4nt.entanglementlib.security.crypto.key.EntLibCryptoKey; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.detail.SLHDSAKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.SignatureStrategy; -import space.qu4nt.entanglementlib.util.wrapper.Hex; -import space.qu4nt.entanglementlib.util.wrapper.Pair; - -import java.lang.foreign.ValueLayout; -import java.nio.charset.StandardCharsets; - -/** - * @author Q. T. Felix - * @since 1.1.0 - */ -@Slf4j -public final class SLHDSAStrategy implements SignatureStrategy { - - private final SignatureType type; - - SLHDSAStrategy(@NotNull SignatureType type) { - this.type = type; - } - - /** - * SLHDSAStrategy 인스턴스를 생성합니다. - * - * @param type SignatureType (SLH_DSA bundle) - * @return 새 SLHDSAStrategy 인스턴스 - */ - public static SLHDSAStrategy create(@NotNull SignatureType type) { - return new SLHDSAStrategy(type); - } - - @Override - public byte @NotNull [] sign(@NotNull EntLibCryptoKey keyPrivate, byte[] plainBytes) { - if (plainBytes == null) { - throw new RuntimeException("plain null"); - } - // 서명 low-level api 호출 - SLHDSASigner signer = new SLHDSASigner(); - - // 네이티브 메모리에서 키 바이트 배열 추출 - byte @Nullable [] keyBytes = keyPrivate.toByteArray(); - if (keyBytes == null) - throw new RuntimeException("key null"); - SLHDSAPrivateKeyParameters skParams = new SLHDSAPrivateKeyParameters(findInternalParameters(), keyBytes); - - // 서명기 초기화 - signer.init(true, skParams); - - if (skParams == null && !skParams.isPrivate()) - throw new RuntimeException("key is not private"); - - return signer.generateSignature(plainBytes); - } - - @Override - public boolean verify(@NotNull EntLibCryptoKey keyPublic, byte[] plainBytes, byte[] signature) { - if (plainBytes == null || signature == null) { - throw new RuntimeException("plain or sig null"); - } - // 서명 low-level api 호출 - SLHDSASigner signer = new SLHDSASigner(); - - // 네이티브 메모리에서 키 바이트 배열 추출 - byte @Nullable [] keyBytes = keyPublic.toByteArray(); - if (keyBytes == null) - throw new RuntimeException("key null"); - SLHDSAPublicKeyParameters pkParams = new SLHDSAPublicKeyParameters(findInternalParameters(), keyBytes); - - // 검증기 초기화 - signer.init(false, pkParams); - - if (pkParams == null && pkParams.isPrivate()) - throw new RuntimeException("key is not public"); - - return signer.verifySignature(plainBytes, signature); - } - - @Override - public String getAlgorithmName() { - return "ML-DSA"; - } - - @Override - public EntLibAlgorithmType getAlgorithmType() { - return type; - } - - public SLHDSAParameters findInternalParameters() { - return switch (type) { - case SLH_DSA_SHA2_128s -> SLHDSAParameters.sha2_128s; - case SLH_DSA_SHA2_128f -> SLHDSAParameters.sha2_128f; - case SLH_DSA_SHA2_192s -> SLHDSAParameters.sha2_192s; - case SLH_DSA_SHA2_192f -> SLHDSAParameters.sha2_192f; - case SLH_DSA_SHA2_256s -> SLHDSAParameters.sha2_256s; - case SLH_DSA_SHA2_256f -> SLHDSAParameters.sha2_256f; - case SLH_DSA_SHAKE_128s -> SLHDSAParameters.shake_128s; - case SLH_DSA_SHAKE_128f -> SLHDSAParameters.shake_128f; - case SLH_DSA_SHAKE_192s -> SLHDSAParameters.shake_192s; - case SLH_DSA_SHAKE_192f -> SLHDSAParameters.shake_192f; - case SLH_DSA_SHAKE_256s -> SLHDSAParameters.shake_256s; - case SLH_DSA_SHAKE_256f -> SLHDSAParameters.shake_256f; - default -> SLHDSAParameters.sha2_256s; - }; - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/X25519Strategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/X25519Strategy.java deleted file mode 100644 index be4d9bd..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/X25519Strategy.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.strategy.detail; - -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.entlibnative.ProgressResult; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.exception.critical.EntLibNativeError; -import space.qu4nt.entanglementlib.exception.critical.EntLibSecurityError; -import space.qu4nt.entanglementlib.exception.secure.crypto.EntLibCryptoKEMProcessingException; -import space.qu4nt.entanglementlib.security.crypto.EntLibAlgorithmType; -import space.qu4nt.entanglementlib.security.crypto.EntLibCryptoRegistry; -import space.qu4nt.entanglementlib.security.crypto.KEMType; -import space.qu4nt.entanglementlib.security.crypto.bundle.X25519StrategyBundle; -import space.qu4nt.entanglementlib.security.crypto.strategy.NativeECDHStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.NativeKEMStrategy; - -/// X25519 Diffie-Hellman 키 교환을 KEM 스타일로 래핑한 전략 클래스입니다. -/// -/// X25519는 전통적인 KEM이 아니지만, 임시 키페어 생성과 DH 연산을 통해 -/// KEM과 유사한 인터페이스로 사용할 수 있습니다. -/// -/// - encapsulate: 임시 키페어 생성 후 수신자 공개키와 DH 수행 -/// - decapsulate: 자신의 비밀키와 상대방 임시 공개키로 DH 수행 -/// -/// @author Q. T. Felix -/// @since 1.1.0 -@Slf4j -public final class X25519Strategy implements NativeKEMStrategy, NativeECDHStrategy { - // TODO: 키 유도 관련 로직 추가 - - static final int _SK_SIZE = 0x20; - static final int _PK_SIZE = 0x20; - static final int _CT_SIZE = 0x20; - static final int _SS_SIZE = 0x20; - - private final KEMType type = KEMType.X25519; - - X25519Strategy() { - } - - /// X25519Strategy 인스턴스를 생성합니다. - /// - /// 주의하세요! 이 메소드를 사용하여 전략 패턴을 선언하는 것은 올바르지 않습니다. - /// 이 메소드는 [EntLibCryptoRegistry] 레지스트리에 등록할 때 사용되는 - /// 메소드입니다. - /// - /// @return 새 X25519Strategy 인스턴스 - @ApiStatus.Internal - public static X25519Strategy create() { - return new X25519Strategy(); - } - - /// X25519 KEM 스타일 캡슐화를 수행합니다. - /// - /// 1. 임시 키페어(ephemeral_sk, ephemeral_pk) 생성 - /// 2. 공유 비밀 = x25519_dh(ephemeral_sk, recipient_pk) - /// 3. 반환: (공유 비밀, 임시 공개키를 암호문으로) - /// - /// @param keyPublic 수신자의 공개키 - /// @return 공유 비밀과 암호문(임시 공개키)을 포함하는 컨테이너 - @Override - public SensitiveDataContainer encapsulate(@NotNull SensitiveDataContainer keyPublic) - throws EntLibCryptoKEMProcessingException { - if (keyPublic.getMemorySegment().byteSize() != _PK_SIZE) - throw new EntLibCryptoKEMProcessingException( - "주요 데이터의 바이트 사이즈가 올바르지 않습니다! 세션 체계 참여자가 악의적인 데이터를 전송했을 수 있습니다." - ); - - SensitiveDataContainer ephemeralSkContainer = new SensitiveDataContainer(_SK_SIZE); - SensitiveDataContainer ephemeralPkContainer = new SensitiveDataContainer(_CT_SIZE); - - try { - // ECDHE 에 마지막 E는 임시(Ephemeral) - final ProgressResult ecdhePair = ProgressResult.fromCode((int) X25519StrategyBundle - .callNativeX25519Handle(0) // keygen type - .invokeExact( - ephemeralSkContainer.getMemorySegment(), - ephemeralPkContainer.getMemorySegment() - )); - if (ecdhePair.isFail()) { - ephemeralSkContainer.close(); - ephemeralPkContainer.close(); - throw new EntLibCryptoKEMProcessingException( - "ECDHE 키 페어 생성에 실패했습니다! 네이티브 코드 반환값: " + ecdhePair.getCode()); - } - - SensitiveDataContainer ssContainer = new SensitiveDataContainer(_SS_SIZE); - SensitiveDataContainer ctContainer = ssContainer.addContainerData(_CT_SIZE); - - final ProgressResult dhResult = ProgressResult.fromCode((int) X25519StrategyBundle - .callNativeX25519Handle(2) // dh type - .invokeExact( - ssContainer.getMemorySegment(), - ephemeralSkContainer.getMemorySegment(), - keyPublic.getMemorySegment() - )); - if (dhResult.isFail()) { - ephemeralSkContainer.close(); - ephemeralPkContainer.close(); - ssContainer.close(); - throw new EntLibCryptoKEMProcessingException( - "DH 연산에 실패했습니다! 네이티브 코드 반환값: " + dhResult.getCode()); - } - - ctContainer.getMemorySegment().copyFrom(ephemeralPkContainer.getMemorySegment()); - - ephemeralSkContainer.close(); - ephemeralPkContainer.close(); - - return ssContainer; - } catch (EntLibCryptoKEMProcessingException e) { - throw e; - } catch (Throwable e) { - ephemeralSkContainer.close(); - ephemeralPkContainer.close(); - throw new EntLibNativeError("네이티브 에러", e); - } - } - - /// X25519 KEM 스타일 디캡슐화를 수행합니다. - /// - /// 공유 비밀 = x25519_dh(my_sk, ephemeral_pk) - /// - /// @param secretKeyContainer 자신의 비밀키 - /// @param ciphertext 상대방의 임시 공개키 (암호문) - /// @return 복원된 공유 비밀 - @Override - public SensitiveDataContainer decapsulate(@NotNull SensitiveDataContainer secretKeyContainer, - @NotNull SensitiveDataContainer ciphertext) - throws EntLibCryptoKEMProcessingException { - if (secretKeyContainer.getMemorySegment().byteSize() != _SK_SIZE || ciphertext.getMemorySegment().byteSize() != _CT_SIZE) - throw new EntLibSecurityError( - "주요 데이터의 바이트 사이즈가 올바르지 않습니다! 세션 체계 참여자가 악의적인 데이터를 전송했을 수 있습니다." - ); - - SensitiveDataContainer ssContainer = new SensitiveDataContainer(_SS_SIZE); - try { - final ProgressResult result = ProgressResult.fromCode((int) X25519StrategyBundle - .callNativeX25519Handle(2) // dh type - .invokeExact( - ssContainer.getMemorySegment(), - secretKeyContainer.getMemorySegment(), - ciphertext.getMemorySegment() - )); - if (result.isFail()) { - ssContainer.close(); - throw new EntLibCryptoKEMProcessingException( - "키 디캡슐화(DH)에 실패했습니다! 네이티브 코드 반환값: " + result.getCode()); - } - } catch (EntLibCryptoKEMProcessingException e) { - throw e; - } catch (Throwable e) { - ssContainer.close(); - throw new EntLibNativeError("네이티브 에러", e); - } - return ssContainer; - } - - /// X25519 Diffie-Hellman 공유 비밀을 계산합니다. - /// - /// 공유 비밀 = x25519_dh(my_sk, peer_pk) - /// - /// @param secretKeyContainer 자신의 비밀키 - /// @param peerPublicKeyContainer 상대방의 공개키 - /// @return 계산된 공유 비밀 - @Override - public SensitiveDataContainer computeSharedSecret(SensitiveDataContainer secretKeyContainer, - SensitiveDataContainer peerPublicKeyContainer) { - if (secretKeyContainer.getMemorySegment().byteSize() != _SK_SIZE || peerPublicKeyContainer.getMemorySegment().byteSize() != _PK_SIZE) - throw new EntLibSecurityError( - "주요 데이터의 바이트 사이즈가 올바르지 않습니다! 세션 체계 참여자가 악의적인 데이터를 전송했을 수 있습니다." - ); - - SensitiveDataContainer ssContainer = new SensitiveDataContainer(_SS_SIZE); - try { - final ProgressResult result = ProgressResult.fromCode((int) X25519StrategyBundle - .callNativeX25519Handle(2) // dh type - .invokeExact( - ssContainer.getMemorySegment(), - secretKeyContainer.getMemorySegment(), - peerPublicKeyContainer.getMemorySegment() - )); - if (result.isFail()) { - ssContainer.close(); - throw new EntLibSecurityError( - "공유 비밀 계산에 실패했습니다! 네이티브 코드 반환값: " + result.getCode()); - } - } catch (Throwable e) { - ssContainer.close(); - throw new EntLibNativeError("네이티브 에러", e); - } - return ssContainer; - } - - @Override - public String getAlgorithmName() { - return "X25519"; - } - - @Override - public EntLibAlgorithmType getAlgorithmType() { - return type; - } -} \ No newline at end of file diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/hybrid/README.md b/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/hybrid/README.md deleted file mode 100644 index d7ad5b0..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/hybrid/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# EF: X25519 and ML-KEM-768 Hybrid - -이제 얽힘 라이브러리 `1.1.0` 릴리즈부턴 하이브리드 알고리즘을 지원합니다. 예를 들어, -타원곡선 디피-헬만(ECDH) `Curve25519` 알고리즘과 양자-내성 키 캡슐화 메커니즘인 `ML-KEM-768` 알고리즘을 조합한 `X25519MLKEM768`이 주력입니다. - -이 알고리즘은 `securiry/crypto/strategy/detail/hybrid` 패키지에 포함됩니다. 다른 알고리즘을 레지스트리에서 호출하여 사용할 수 있듯이 해당 알고리즘도 동일하게 사용할 수 있습니다. -다만 다른 알고리즘 호출에 비해 한 가지 기술적인 차이점이 존재합니다. - -## 사용 - -`X25519MLKEM768` 알고리즘은 레지스트리로부터 다음과 같이 호출할 수 있습니다. - -```java -void main() { - // 알고리즘 키 스트레티지 - final X25519MLKEM768KeyStrategy keyStrategy = - EntLibCryptoRegistry.getKeyStrategy(KEMType.X25519MLKEM768, X25519MLKEM768KeyStrategy.class); - - // 알고리즘 수행 스트레티지 - final X25519MLKEM768Strategy strategy = EntLibCryptoRegistry.getAlgStrategy(KEMType.X25519MLKEM768, X25519MLKEM768Strategy.class); -} -``` - -다른 알고리즘 클래스와는 다르게 하위 구현부 클래스를 직접 가져와야 합니다. 구현부 클래스에서 사용 가능한 메소드가 있기 때문입니다. - -`X25519MLKEM768` 스트레티지 클래스는 모두 자식 레벨에서 할당해주어야 하는 다음의 필드 두 가지가 공통적으로 존재합니다. - -- `MLKEMStrategy` 및 `MLKEMKeyStrategy` -- `X25519Strategy` 및 `X25519KeyStrategy` - -이유는 간단합니다. 해당 알고리즘은 위 두 알고리즘의 하이브리드 알고리즘이기 때문입니다. `X25519MLKEM768` 알고리즘 클래스들을 호출하는 시점에 개별 알고리즘 클래스(X25519, MLKEM)를 생성하게 -되면 세션이 같아도 유효한 디캡슐화(decapsulate) 결과를 얻을 수 없습니다. 이 문제를 해결하기 위해 자식 레벨에서 위 두 개별 알고리즘에 대한 스트레티지를 등록하도록 설계했습니다. 위 코드는 다음과 같이 -수정 가능합니다. - -```java -void main() { - // 알고리즘 키 스트레티지 - final X25519MLKEM768KeyStrategy keyStrategy = - EntLibCryptoRegistry.getKeyStrategy(KEMType.X25519MLKEM768, X25519MLKEM768KeyStrategy.class); - strategy.setX25519Strategy((X25519Strategy) x25519Strategy); - strategy.setMlkemStrategy((MLKEMStrategy) mlkem768Strategy); - - // 알고리즘 수행 스트레티지 - final X25519MLKEM768Strategy strategy = EntLibCryptoRegistry.getAlgStrategy(KEMType.X25519MLKEM768, X25519MLKEM768Strategy.class); - ecdhePairStrategy.setX25519Key((X25519KeyStrategy) x25519KeyStrategy); - ecdhePairStrategy.setMlkem768Key((MLKEMKeyStrategy) mlkem768KeyStrategy); -} -``` - -말인 즉슨, 사전에 `X25519`, `ML-Kem-768` 알고리즘에 대한 스트레티지를 생성해두어야 한다는 것입니다. - -객체를 생성하고 나서는 그저 캡슐화와 디캡슐화 메소드를 사용하여 작업을 수행하면 됩니다. - diff --git a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/hybrid/X25519MLKEM768Strategy.java b/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/hybrid/X25519MLKEM768Strategy.java deleted file mode 100644 index 400b9d3..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/hybrid/X25519MLKEM768Strategy.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.strategy.detail.hybrid; - -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.exception.critical.EntLibNativeError; -import space.qu4nt.entanglementlib.exception.critical.EntLibSecurityError; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalStateException; -import space.qu4nt.entanglementlib.exception.secure.crypto.EntLibCryptoKEMProcessingException; -import space.qu4nt.entanglementlib.security.crypto.EntLibAlgorithmType; -import space.qu4nt.entanglementlib.security.crypto.EntLibCryptoRegistry; -import space.qu4nt.entanglementlib.security.crypto.KEMType; -import space.qu4nt.entanglementlib.security.crypto.ParameterSizeDetail; -import space.qu4nt.entanglementlib.security.crypto.strategy.NativeKEMStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.MLKEMStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.X25519Strategy; - -import java.lang.foreign.MemorySegment; -import java.lang.foreign.ValueLayout; - -/// X25519 ECDH와 ML-KEM-768 양자-내성 키 캡슐화 메커니즘을 결합한 하이브리드 전략 클래스입니다. -/// -/// 이 클래스는 [SensitiveDataContainer]의 바인딩(Binding) 기능을 활용하여 -/// 공유 비밀(SS)과 암호문(CT)을 논리적으로 연결하고, [ParameterSizeDetail]을 통해 -/// 복합적인 파라미터 사이즈를 관리합니다. -/// -/// ## 키 결합 구조 -/// -/// - 공개키(PK): `PK_X25519 || PK_MLKEM768` -/// - 비밀키(SK): `SK_X25519 || SK_MLKEM768` -/// - 암호문(CT): `CT_X25519 || CT_MLKEM768` (SS 컨테이너에 바인딩됨) -/// - 공유 비밀(SS): `SS_X25519 || SS_MLKEM768` -/// -/// @author Q. T. Felix -/// @since 1.1.0 -@Slf4j -public final class X25519MLKEM768Strategy implements NativeKEMStrategy { - - @Setter - @Nullable - private X25519Strategy x25519Strategy; - @Setter - @Nullable - private MLKEMStrategy mlkemStrategy; - - // 하이브리드 알고리즘 규격 정의 - private final ParameterSizeDetail hybridDetail; - private final ParameterSizeDetail x25519Detail; - private final ParameterSizeDetail mlkemDetail; - - private final KEMType type = KEMType.X25519MLKEM768; - - private X25519MLKEM768Strategy(final @Nullable X25519Strategy x25519Strategy, final @Nullable MLKEMStrategy mlkemStrategy) { - this.x25519Strategy = x25519Strategy; - this.mlkemStrategy = mlkemStrategy; - - // 파라미터 사이즈 상세 초기화 - this.x25519Detail = KEMType.X25519.getParameterSizeDetail(); - this.mlkemDetail = KEMType.ML_KEM_768.getParameterSizeDetail(); - this.hybridDetail = KEMType.X25519MLKEM768.getParameterSizeDetail(); - } - - /// X25519MLKEM768Strategy 인스턴스를 생성합니다. - /// - /// [EntLibCryptoRegistry] 레지스트리 등록용 팩토리 메소드입니다. - /// - /// @return 새 하이브리드 전략 인스턴스 - @ApiStatus.Internal - public static X25519MLKEM768Strategy create(final X25519Strategy x25519Strategy, final MLKEMStrategy mlkemStrategy) { - return new X25519MLKEM768Strategy(x25519Strategy, mlkemStrategy); - } - - /// 하이브리드 캡슐화를 수행합니다. - /// - /// 1. 입력된 하이브리드 공개키를 분리합니다. - /// 2. X25519와 ML-KEM의 `encapsulate`를 각각 수행합니다. - /// 3. 결과로 나온 공유 비밀(SS)들을 병합합니다. - /// 4. 각 전략 결과에 바인딩된 암호문(CT)들을 추출하여 병합 후, 최종 SS 컨테이너에 바인딩합니다. - /// - /// @param keyPublic 하이브리드 공개키 컨테이너 - /// @return 하이브리드 공유 비밀(SS)과 바인딩된 암호문(CT)을 포함하는 컨테이너 - @Override - public SensitiveDataContainer encapsulate(@NotNull SensitiveDataContainer keyPublic) - throws EntLibCryptoKEMProcessingException, EntLibSecureIllegalStateException { - if (x25519Strategy == null || mlkemStrategy == null) - throw new EntLibSecureIllegalStateException("X25519MLKEM768 알고리즘에 대한 캡슐화를 수행할 수 없습니다! 이 작업을 수행하기 전에 X25519, ML-KEM-768 스트레티지를 생성해야 합니다."); - long inputSize = keyPublic.getMemorySegment().byteSize(); - if (inputSize != hybridDetail.getEncapsulationKeySize()) { - throw new EntLibCryptoKEMProcessingException( - "주요 데이터의 바이트 사이즈가 올바르지 않습니다! 세션 체계 참여자가 악의적인 데이터를 전송했을 수 있습니다." - ); - } - - // 1. 하위 컨테이너 준비 (Resource Management) - SensitiveDataContainer x25519Pk = null; - SensitiveDataContainer mlkemPk = null; - SensitiveDataContainer x25519Res = null; - SensitiveDataContainer mlkemRes = null; - - // 최종 결과 컨테이너 - SensitiveDataContainer hybridSs = new SensitiveDataContainer(hybridDetail.getSharedSecretKeySize()); - - try { - MemorySegment pkSeg = keyPublic.getMemorySegment(); - long xPkSize = x25519Detail.getEncapsulationKeySize(); - long mPkSize = mlkemDetail.getEncapsulationKeySize(); - - // 2. 공개키 분리 및 컨테이너 생성 - // toArray()로 힙에 복사 후 SensitiveDataContainer에 전달하며 forceWipe=true 설정. - // 컨테이너가 데이터를 가져간 후 원본 배열을 즉시 소거하여 메모리 잔류를 방지함. - x25519Pk = new SensitiveDataContainer( - pkSeg.asSlice(0, xPkSize).toArray(ValueLayout.JAVA_BYTE), - true - ); - mlkemPk = new SensitiveDataContainer( - pkSeg.asSlice(xPkSize, mPkSize).toArray(ValueLayout.JAVA_BYTE), - true - ); - - // 3. 위임 (Delegation) - // x25519Res: SS 포함, 내부 bindings[0]에 CT 포함 - x25519Res = x25519Strategy.encapsulate(x25519Pk); - mlkemRes = mlkemStrategy.encapsulate(mlkemPk); - - // 4. 공유 비밀(SS) 병합 - MemorySegment ssTarget = hybridSs.getMemorySegment(); - long xSsSize = x25519Detail.getSharedSecretKeySize(); - long mSsSize = mlkemDetail.getSharedSecretKeySize(); - - ssTarget.asSlice(0, xSsSize).copyFrom(x25519Res.getMemorySegment()); - ssTarget.asSlice(xSsSize, mSsSize).copyFrom(mlkemRes.getMemorySegment()); - - // 5. 암호문(CT) 병합 및 바인딩 - // 각 전략의 결과 컨테이너에서 암호문 컨테이너 추출 (Index 0 가정) - @SuppressWarnings("resource") SensitiveDataContainer xCtContainer = x25519Res.get(0) - .orElseThrow(() -> new EntLibCryptoKEMProcessingException("X25519 암호문 컨테이너 누락")); - @SuppressWarnings("resource") SensitiveDataContainer mCtContainer = mlkemRes.get(0) - .orElseThrow(() -> new EntLibCryptoKEMProcessingException("ML-KEM 암호문 컨테이너 누락")); - - // 하이브리드 CT 컨테이너 생성 및 SS에 바인딩 - SensitiveDataContainer hybridCt = hybridSs.addContainerData(hybridDetail.getCiphertextSize()); - MemorySegment ctTarget = hybridCt.getMemorySegment(); - long xCtSize = x25519Detail.getCiphertextSize(); - long mCtSize = mlkemDetail.getCiphertextSize(); - - ctTarget.asSlice(0, xCtSize).copyFrom(xCtContainer.getMemorySegment()); - ctTarget.asSlice(xCtSize, mCtSize).copyFrom(mCtContainer.getMemorySegment()); - - return hybridSs; - - } catch (Throwable e) { - hybridSs.close(); // 예외 발생 시 생성된 결과물 소거 - throw new EntLibNativeError("네이티브 에러", e); - } finally { - // 중간 컨테이너 리소스 해제 - if (x25519Pk != null) x25519Pk.close(); - if (mlkemPk != null) mlkemPk.close(); - if (x25519Res != null) x25519Res.close(); - if (mlkemRes != null) mlkemRes.close(); - } - } - - /// 하이브리드 디캡슐화를 수행합니다. - /// - /// @param secretKeyContainer 하이브리드 비밀키 - /// @param ciphertext 하이브리드 암호문 (바인딩된 구조가 아닌, 직렬화된 CT 블록을 가정) - /// @return 복원된 하이브리드 공유 비밀 - @Override - public SensitiveDataContainer decapsulate(@NotNull SensitiveDataContainer secretKeyContainer, - @NotNull SensitiveDataContainer ciphertext) throws EntLibSecureIllegalStateException { - if (x25519Strategy == null || mlkemStrategy == null) - throw new EntLibSecureIllegalStateException("X25519MLKEM768 알고리즘에 대한 디캡슐화를 수행할 수 없습니다! 이 작업을 수행하기 전에 X25519, ML-KEM-768 스트레티지를 생성해야 합니다."); - long ctSize = ciphertext.getMemorySegment().byteSize(); - if (ctSize != hybridDetail.getCiphertextSize()) { - throw new EntLibSecurityError("하이브리드 암호문 사이즈 불일치"); - } - - SensitiveDataContainer xSk = null; - SensitiveDataContainer mSk = null; - SensitiveDataContainer xCt = null; - SensitiveDataContainer mCt = null; - SensitiveDataContainer xSs = null; - SensitiveDataContainer mSs = null; - - SensitiveDataContainer hybridSs = new SensitiveDataContainer(hybridDetail.getSharedSecretKeySize()); - - try { - MemorySegment skSeg = secretKeyContainer.getMemorySegment(); - MemorySegment ctSeg = ciphertext.getMemorySegment(); - - long xSkSize = x25519Detail.getDecapsulationKeySize(); // typically 32 - long mSkSize = mlkemDetail.getDecapsulationKeySize(); - long xCtSize = x25519Detail.getCiphertextSize(); - long mCtSize = mlkemDetail.getCiphertextSize(); - - // 1. 데이터 분할 (Copy with wiping source) - xSk = new SensitiveDataContainer(skSeg.asSlice(0, xSkSize).toArray(ValueLayout.JAVA_BYTE), true); - mSk = new SensitiveDataContainer(skSeg.asSlice(xSkSize, mSkSize).toArray(ValueLayout.JAVA_BYTE), true); - - xCt = new SensitiveDataContainer(ctSeg.asSlice(0, xCtSize).toArray(ValueLayout.JAVA_BYTE), true); - mCt = new SensitiveDataContainer(ctSeg.asSlice(xCtSize, mCtSize).toArray(ValueLayout.JAVA_BYTE), true); - - // 2. 위임 - xSs = x25519Strategy.decapsulate(xSk, xCt); - mSs = mlkemStrategy.decapsulate(mSk, mCt); - - // 3. SS 병합 - MemorySegment target = hybridSs.getMemorySegment(); - long xSsSize = x25519Detail.getSharedSecretKeySize(); - long mSsSize = mlkemDetail.getSharedSecretKeySize(); - - target.asSlice(0, xSsSize).copyFrom(xSs.getMemorySegment()); - target.asSlice(xSsSize, mSsSize).copyFrom(mSs.getMemorySegment()); - - } catch (Throwable e) { - hybridSs.close(); - throw new EntLibNativeError("네이티브 에러", e); - } finally { - if (xSk != null) xSk.close(); - if (mSk != null) mSk.close(); - if (xCt != null) xCt.close(); - if (mCt != null) mCt.close(); - if (xSs != null) xSs.close(); - if (mSs != null) mSs.close(); - } - - return hybridSs; - } - - @Override - public String getAlgorithmName() { - return "X25519-ML-KEM-768"; - } - - @Override - public EntLibAlgorithmType getAlgorithmType() { - return type; - } -} \ No newline at end of file diff --git a/src/main/java/space/qu4nt/entanglementlib/security/legacy/EntTCP.java b/src/main/java/space/qu4nt/entanglementlib/security/legacy/EntTCP.java deleted file mode 100644 index 7ead201..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/legacy/EntTCP.java +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.legacy; - -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.InternalFactory; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureCertProcessException; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureJCAJCEStoreProcessException; -import space.qu4nt.entanglementlib.security.legacy.certificate.EntSSL; -import space.qu4nt.entanglementlib.security.legacy.certificate.KeyStoreManager; -import space.qu4nt.entanglementlib.util.security.Password; - -import javax.net.ssl.*; -import java.io.IOException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.security.GeneralSecurityException; -import java.security.PrivateKey; -import java.security.cert.X509Certificate; -import java.util.Objects; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -/** - * 서버 인증서를 사용하여 TLSv1.3 TCP 통신을 수행하기 위한 클래스입니다. - *

- * 이 클래스는 TLS 1.3 서버를 생성하고 실행하는 기능과, - * 서버에 연결하기 위한 클라이언트 소켓을 생성하는 기능을 제공합니다. - * - * @author Q. T. Felix - * @since 1.0.0 - */ -@Slf4j -public class EntTCP { - - public static final Path KEYSTORE_PATH = Paths.get(InternalFactory.envEntanglementHomeDir(), "root", "temp-keystore.bcfks"); - public static final Path TRUSTSTORE_PATH = Paths.get(InternalFactory.envEntanglementHomeDir(), "root", "temp-truststore.bcfks"); - - private final int port; - @Getter - private final EntSSL entSSL; - - private String[] sslParameterSignatureSchemes; - private SSLServerSocket serverSocket; - private ExecutorService executorService; - private volatile boolean running = false; - - /** - * EntTCP 서버를 생성합니다. - * - * @param password 키스토어 및 트러스트스토어에 사용할 문자 배열(비밀번호) - * @param port 서버 포트 - * @param serverPrivateKey 서버의 개인 키 - * @param serverCertificateChain 서버의 인증서 체인 (서버 인증서, ... , 루트 CA 인증서 순서) - * @throws GeneralSecurityException 보안 관련 예외 발생 시 - * @throws IOException I/O 오류 발생 시 - */ - public EntTCP(final char @NotNull [] password, int port, PrivateKey serverPrivateKey, X509Certificate... serverCertificateChain) - throws GeneralSecurityException, IOException, EntLibSecureJCAJCEStoreProcessException { - this.port = port; - - KeyStoreManager keyStoreManager = new KeyStoreManager(); - try { - // 인메모리 KeyStore, TrustStore 초기화 - keyStoreManager.loadKeyStore(KEYSTORE_PATH, password.clone()); - keyStoreManager.loadTrustStore(TRUSTSTORE_PATH, password.clone()); - - // 서버 개인 키 및 인증서 체인 설정 - // setKeyEntry는 내부적으로 전달된 비밀번호 배열을 소거하므로 복제본을 전달 - keyStoreManager.setKeyEntry("server-alias", serverPrivateKey, password.clone(), serverCertificateChain); - - this.entSSL = new EntSSL(keyStoreManager); - } finally { - // 사용된 비밀번호 배열을 메모리에서 안전하게 소거 - Password.wipePassword(password); - } - } - - public void setDefaultAllSSLParameterSignatureSchemes() { - this.sslParameterSignatureSchemes = new String[]{"rsa_pkcs1_sha1", - "ecdsa_sha1", - "rsa_pkcs1_sha256", - "rsa_pkcs1_sha384", - "rsa_pkcs1_sha512", - "ecdsa_secp256r1_sha256", - "ecdsa_secp384r1_sha384", - "ecdsa_secp521r1_sha512", - "rsa_pss_rsae_sha256", - "rsa_pss_rsae_sha384", - "rsa_pss_rsae_sha512", - "ed25519", - "ed448", - "rsa_pss_pss_sha256", - "rsa_pss_pss_sha384", - "rsa_pss_pss_sha512", - "ecdsa_brainpoolP256r1tls13_sha256", - "ecdsa_brainpoolP384r1tls13_sha384", - "ecdsa_brainpoolP512r1tls13_sha512", - "sm2sig_sm3", - "mldsa44", - "mldsa65", - "mldsa87", - "DRAFT_slhdsa_sha2_128s", - "DRAFT_slhdsa_sha2_128f", - "DRAFT_slhdsa_sha2_192s", - "DRAFT_slhdsa_sha2_192f", - "DRAFT_slhdsa_sha2_256s", - "DRAFT_slhdsa_sha2_256f", - "DRAFT_slhdsa_shake_128s", - "DRAFT_slhdsa_shake_128f", - "DRAFT_slhdsa_shake_192s", - "DRAFT_slhdsa_shake_192f", - "DRAFT_slhdsa_shake_256s"}; - } - - public void setSSLParameterSignatureSchemes(final @NotNull String... schemes) { - this.sslParameterSignatureSchemes = schemes; - } - - public void addSSLParameterSignatureScheme(final @NotNull String... schemes) { - if (this.sslParameterSignatureSchemes == null) { - this.sslParameterSignatureSchemes = schemes.clone(); - return; - } - final String[] news = new String[this.sslParameterSignatureSchemes.length + schemes.length]; - System.arraycopy(this.sslParameterSignatureSchemes, 0, news, 0, this.sslParameterSignatureSchemes.length); - System.arraycopy(schemes, 0, news, this.sslParameterSignatureSchemes.length, schemes.length); - this.sslParameterSignatureSchemes = news; - } - - public @NotNull String[] getSSLParameterSignatureSchemes() { - if (this.sslParameterSignatureSchemes == null) { - setDefaultAllSSLParameterSignatureSchemes(); - return sslParameterSignatureSchemes; - } - return sslParameterSignatureSchemes; - } - - /** - * TLS 서버를 시작합니다. - * 클라이언트 연결을 수락하고 각 연결을 별도의 스레드에서 처리합니다. - * - * @throws IOException 서버 소켓 생성 또는 바인딩 실패 시 - */ - public void start(final char @NotNull [] password) throws IOException, EntLibSecureCertProcessException { - if (running) { - log.info("Server is already running."); - return; - } - - final SSLContext sslContext = Objects.requireNonNull(entSSL).createSSLContext(password); - - // 파라미터 주입 - SSLParameters params = sslContext.getDefaultSSLParameters(); - params.setSignatureSchemes(getSSLParameterSignatureSchemes()); - - final SSLServerSocketFactory ssf = sslContext.getServerSocketFactory(); - this.serverSocket = (SSLServerSocket) ssf.createServerSocket(this.port); - serverSocket.setSSLParameters(params); - - // 암호화 스위트 설정 - serverSocket.setEnabledCipherSuites(sslContext.getSocketFactory().getDefaultCipherSuites()); - - this.executorService = Executors.newCachedThreadPool(); - this.running = true; - log.info("EntTCP Server started on port {}", this.port); - - // 클라이언트 연결 수락 루프 - while (running) { - try { - SSLSocket clientSocket = (SSLSocket) serverSocket.accept(); - log.info("클라이언트 연결: {}", clientSocket.getRemoteSocketAddress()); - executorService.submit(() -> handleClient(clientSocket)); - } catch (IOException e) { - if (!running) { - log.info("서버가 새 연결을 더 이상 받지 못했습니다."); - } else { - log.error("클라이언트 연결 수락 오류", e); - } - } - } - - Password.wipePassword(password); - } - - /** - * TLS 서버를 중지합니다. - */ - public void stop() { - this.running = false; - try { - if (serverSocket != null && !serverSocket.isClosed()) { - serverSocket.close(); - } - } catch (IOException e) { - log.error("Error closing server socket: ", e); - } - if (executorService != null) { - executorService.shutdownNow(); - } - log.info("EntTCP Server has been stopped."); - } - - /** - * 클라이언트와의 통신을 처리합니다. - * 현재는 받은 데이터를 그대로 다시 보내는 Echo 방식으로 구현되어 있습니다. - * - * @param clientSocket 클라이언트 소켓 - */ - private void handleClient(SSLSocket clientSocket) { - try (clientSocket) { - // 데이터 통신 로직 (Echo) - java.io.InputStream input = clientSocket.getInputStream(); - if (input == null) { - log.info("input is null EntTCP#handleClient"); - return; - } - java.io.OutputStream output = clientSocket.getOutputStream(); - if (output == null) { - log.info("output is null EntTCP#handleClient"); - return; - } - byte[] buffer = new byte[4096]; - int read; - while ((read = input.read(buffer)) != -1) { - output.write(buffer, 0, read); - output.flush(); - } - } catch (IOException e) { - // 클라이언트 연결 종료 또는 통신 오류 - log.info("Connection with client lost: {}", e.getMessage()); - } finally { - log.info("Client disconnected: {}", clientSocket.getRemoteSocketAddress()); - } - } - - /** - * 지정된 호스트와 포트로 연결하는 TLS 클라이언트 소켓을 생성합니다. - * 클라이언트는 제공된 CA 인증서를 신뢰하여 서버를 인증합니다. - * - * @param host 서버 호스트 - * @param port 서버 포트 - * @param trustedCaCert 신뢰할 루트 CA 인증서 - * @return 연결 및 핸드셰이크가 완료된 SSLSocket - * @throws GeneralSecurityException 보안 관련 예외 발생 시 - * @throws IOException I/O 오류 발생 시 - */ - public static SSLSocket createClientSocket(final char @NotNull [] password, String host, int port, X509Certificate trustedCaCert) - throws GeneralSecurityException, IOException, EntLibSecureJCAJCEStoreProcessException { - - // KeyStoreManager를 사용하여 신뢰할 인증서를 관리 - KeyStoreManager keyStoreManager = new KeyStoreManager(); - - // 인메모리 TrustStore 초기화 (비밀번호 불필요) - keyStoreManager.loadTrustStore(TRUSTSTORE_PATH, password.clone()); - keyStoreManager.setTruststoreCertificateEntry("ca-alias", trustedCaCert); - - // TrustManagerFactory 생성 - TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX", InternalFactory.getBCJSSEProvider()); - tmf.init(keyStoreManager.getTrustStore()); - - // 클라이언트용 SSLContext 생성 및 초기화 - SSLContext sslContext = SSLContext.getInstance("TLSv1.3", InternalFactory.getBCJSSEProvider()); - sslContext.init(null, tmf.getTrustManagers(), null); - - // SSLSocketFactory를 사용하여 소켓 생성 및 연결 - SSLParameters params = sslContext.getDefaultSSLParameters(); - params.setSignatureSchemes(new String[]{"mldsa87", "slhdsa_sha2_256s"}); - SSLSocketFactory sf = sslContext.getSocketFactory(); - SSLSocket socket = (SSLSocket) sf.createSocket(host, port); - socket.setSSLParameters(params); - - // TLS 1.3만 사용하도록 설정 - socket.setEnabledProtocols(new String[]{"TLSv1.3"}); - - // [핵심 수정] 클라이언트가 ML-DSA 서명을 지원함을 명시적으로 알려야 할 수 있음 - // BCJSSE 1.83은 자동 감지하지만, 아래 설정으로 강제하면 핸드셰이크 실패 확률이 줄어듭니다. - SSLParameters sslParams = socket.getSSLParameters(); - - // 필요 시 서명 알고리즘 확인 및 로깅 (디버깅용) - // String[] sigSchemes = sslParams.getSignatureSchemes(); - // log.info("Supported Signatures: " + Arrays.toString(sigSchemes)); - socket.setSSLParameters(sslParams); - - // 핸드셰이크를 명시적으로 시작하여 연결을 즉시 확인 - socket.startHandshake(); - log.info("성공적 연결 " + host + ":" + port); - - return socket; - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/legacy/README.md b/src/main/java/space/qu4nt/entanglementlib/security/legacy/README.md deleted file mode 100644 index e18769d..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/legacy/README.md +++ /dev/null @@ -1,223 +0,0 @@ -# EntLib: 통신 - -TCP는 신뢰성 있는 데이터 전송을 담당하는 **전송 계층 프로토콜**이고, TLS는 그 위에 올라가 **데이터를 암호화하여 보안을 제공하는 프로토콜**로, TCP를 통해 안전한 통신 채널을 만드는 역할을 합니다. - -서버와 클라이언트의 `SSLContext`는 명확히 다릅니다. 클라이언트가 크롬(Chrome) 브라우저를 사용한다고 했을 때, 다음의 과정을 거칩니다. - -1. 준비 (Application Start) - - 브라우저가 실행될 때 이미 내부적으로 `Default SSLContext`가 만들어집니다. 이 안에는 우리가 신뢰하는 인증 기관(Root CA) 목록이 미리 들어 있습니다. 예를 들어 `DigiCert`, - `Let's Encrypt` 등이 있습니다. - - Java의 경우: `$JAVA_HOME/lib/security/cacerts` 파일에 있는 인증서들을 로딩합니다. -2. 연결 시도 (Handshake) - - 사용자가 구글에 접속하는 경우 URL `https://google.com`을 입력하고 엔터를 칩니다. 클라이언트는 구글 서버에 `ClientHello` 메시지를 보냅니다(아직 HTTP 요청을 보내는 게 - 아님, 인사만 함). -3. 검증 (Verification) - - 구글 서버가 자신의 인증서(Certificate)를 클라이언트에게 보냅니다. 이때 클라이언트는 1단계에서 미리 만들어둔 SSLContext(TrustStore)를 꺼내서 확인합니다. - - "이 구글 인증서가 내가 미리 가지고 있는 신뢰 목록(Root CA)에 의해 서명된 게 맞는가?"를 확인합니다. -4. 통신 시작 (HTTP Request) - - 검증이 통과되면 그제서야 암호화 채널이 열리고, 실제 `GET /search...` 같은 데이터 요청을 보냅니다. - -하지만 얽힘 라이브러리는 TCP 통신을 위해 양자-내성 암호화를 사용하여 TLS1.3 및 TLS1.2 프로토콜을 생성할 수 있으며, 이 기술(양자-내성 암호화 알고리즘)은 아직 완전히 상용화되지 않아 대부분 브라우저 -및 웹 서버에선 지원하지 않습니다. 즉, 서버가 `SLH-DSA` 알고리즘을 통해 루트 인증서를 발급해도 브라우저는 알지 못하기 때문에 클라이언트는 이 사실을 브라우저에게 알리기 위해 브라우저에 서버로부터 받은 -인증서(루트 인증서에 서명된 인증서)를 등록해주어야 할 수 있습니다. - -쉽게 말해 사용자가 양자-내성 암호화 연결을 사용하는 웹 서버를 오픈한 경우 접속하는 클라이언트는 양자-내성 암호화 알고리즘으로 서명된 인증서를 가지고 있어야 합니다. - -# 로직 설명 - -이 설명은 이 패키지(`tls`) 내에 존재하는 객체만들 취급함을 명심하세요. - -## 키스토어 관리 - -키스토어, 트러스트스토어 관리를 수월하게 하려면 `tls.certificate.KeyStoreManager.java` 클래스를 활용할 수 있습니다. - -```java -import space.qu4nt.entanglementlib.security.legacy.certificate.KeyStoreManager; -import space.qu4nt.entanglementlib.util.security.Password; - -class Main { - public static void main(String[] args) { - // 매니저 선언 - KeyStoreManager keyStoreManager = new KeyStoreManager(); - - // 키스토어 비밀번호 설정 (Password 객체를 사용하여 안전한 비밀번호 생성) - final char[] pass = Password.generate(); - - // 키스토어 로드 (내부적으로 비밀번호가 소거되기 때문에 복사본 전달) - keyStoreManager.loadKeyStore(keyStorePath, pass.clone()); - - // 트러스트스토어 로드 - keyStoreManager.loadTrustStore(TrustStorePath, pass.clone()); - } -} -``` - -위 과정을 거치면 할당된 경로에 키스토어, 트러스트스토어 파일을 로드합니다. 파일이 존재하지 않는 경우 빈 스토어를 생성합니다. `KeyStoreManager#setKeyEntry(...)` 등의 메소드를 사용하여 -키스토어에 엔트리를 추가하거나 인증서 체인을 추가한 뒤 다음 메소드를 통해 지정된 경로에 저장할 수 있습니다. - -```java -class Main { - public static void main(String[] args) { - final char[] pass = Password.generate(); - - KeyStoreManager keyStoreManager = new KeyStoreManager(); - // ... 키스토어 매니지먼트 로직 - - // 키스토어 저장 - keyStoreManager.storeKeyStore(pass.clone()); - - // 트러스트스토어 저장 - keyStoreManager.storeTrustStore(pass.clone()); - } -} -``` - -또한 `isTrusted(@NotNull X509Certificate certificate)` 메소드를 통해 전달받은 인증서 객체가 트러스트스토어에서 신뢰할 수 있는 인증서인지 검증할 수 있습니다. 반환 결과는 -`boolean`입니다. - -## SSLContext 생성 - -`SSLContext` 객체를 생성하기 위해 `tls.certificate.EntSSL.java` 클래스를 사용할 수 있습니다. 클래스 메소드에 매개변수로 -`tls.certificate.KeyStoreManager.java` 클래스를 전달해야 합니다. - -```java -import space.qu4nt.entanglementlib.security.legacy.certificate.EntSSL; -import space.qu4nt.entanglementlib.security.legacy.certificate.KeyStoreManager; -import space.qu4nt.entanglementlib.util.security.Password; - -import javax.net.ssl.SSLContext; - -class Main { - public static void main(String[] args) { - final char[] pass = Password.generate(); - - KeyStoreManager keyStoreManager = new KeyStoreManager(); - // ... 키스토어 매니지먼트 로직 - - // 객체 선언 - EntSSL entSSL = new EntSSL(keyStoreManager); - - // SSLContext 생성 (내부적으로 비밀번호가 소거되기 때문에 복사본 전달) - SSLContext context = entSSL.createSSLContext(pass.clone()); - } -} -``` - -`createSSLContext` 메소드는 기본적으로 TLS1.3을 통해 `SSLContext`를 생성하려고 시도합니다. 만약 생성에 실패한다면 TLS1.2를 사용하고, 이 마저도 실패 시 Runtime 예외를 -발생시켜 애플리케이션을 종료합니다. TLS1.1은 보안상 얽힘 라이브러리에서 사용할 수 없습니다. - -## TCP 서버 생성 - -`tls.EntTCP.java` 클래스를 사용하여 간편하게 TCP 서버를 생성할 수 있습니다. 이 클래스는 선언 시 포트와 서버의 비밀 키, 인증서 체인을 전달받으며 내부적으로 `KeyStoreManager`, -`SSLContext`객체를 생성하고 전역 변수에 할당합니다. - -사용자가 `ML-DSA` 알고리즘의 `65` 파라미터를 사용하여 TCP서버를 열고자 하는 경우 다음과 같이 할 수 있습니다. - -```java - -import space.qu4nt.entanglementlib.security.algorithm.MLDSA; -import space.qu4nt.entanglementlib.security.algorithm.MLDSAType; -import space.qu4nt.entanglementlib.security.legacy.EntTCP; - -import java.security.cert.X509Certificate; - -class Main { - public static void main(String[] args) { - // 서버 오픈에 사용될 객체 - MLDSA mldsa = MLDSA.create(MLDSAType.ML_DSA_65, "server"); - EntLibKeyPair serverPair = mldsa.generateEntKeyPair(); - - // 루트 CA 인증서 생성 - X509Certificate rootCert = ...; - // 루트 인증서의 주체 정보를 사용해 서버 인증서 생성 - X509Certificate serverCert = ...; - - // 갹체 선언 - EntTCP server = new EntTCP(8443, PrivateKey, rootCert, serverCert); - // ... 서버 상호작용 ... - - // 서버 종료 - server.stop(); - mldsa.close(); // 모든 정보 소거 - } -} -``` - -## 인증서 생성 - -인증서 및 인증서 체인을 생성하기 위해 `tls.certificate.Certificator.java` 클래스를 사용할 수 있습니다. - -먼저 인증서 발급 대상의 정보를 담은 `SubjectString` 객체를 생성하고, `Certificator`의 정적 메소드를 호출하여 인증서를 생성합니다. - -```java - -import space.qu4nt.entanglementlib.security.algorithm.MLDSA; -import space.qu4nt.entanglementlib.security.algorithm.MLDSAType; -import space.qu4nt.entanglementlib.security.legacy.certificate.Certificator; -import space.qu4nt.entanglementlib.security.legacy.certificate.SubjectString; - -import java.security.cert.X509Certificate; - -class Main { - public static void main(String[] args) { - // 1. 키 쌍 생성 - MLDSA rootMldsa = MLDSA.create(MLDSAType.ML_DSA_65, "root"); - EntLibKeyPair rootPair = rootMldsa.generateEntKeyPair(); - - MLDSA serverMldsa = MLDSA.create(MLDSAType.ML_DSA_65, "server"); - EntLibKeyPair serverPair = serverMldsa.generateEntKeyPair(); - - // 2. 주체 정보(SubjectString) 생성 - SubjectString rootSubject = SubjectString.builder() - .commonName("Root CA") - .organization("Org") - .country("KR") - .build(); - - SubjectString serverSubject = SubjectString.builder() - .commonName("Server") - .organization("Org") - .country("KR") - .build(); - - try { - // 3. 루트 CA 인증서 생성 (자체 서명) - X509Certificate rootCert = Certificator.generateRootCACertificate( - MLDSAType.ML_DSA_65, // 알고리즘 사양 - rootPair.getPublic(), // 루트 공개 키 - rootPair.getPrivate(), // 루트 비밀 키 - rootSubject // 루트 주체 정보 - ); - - // 4. 서버 인증서 생성 (루트 CA로 서명) - X509Certificate serverCert = Certificator.generateCAChainCertificate( - rootSubject, // 발급자(Issuer) 정보 - serverSubject, // 주체(Subject) 정보 - MLDSAType.ML_DSA_65, // 알고리즘 사양 - serverPair.getPublic(), // 서버 공개 키 - rootPair.getPrivate() // 루트 비밀 키 (서명용) - ); - } catch (Exception e) { - // 예외 핸들링 - } finally { - // 모든 정보 소거 - rootMldsa.close(); - serverMldsa.close(); - } - } -} -``` - -# 실제 사용 - -실제 사용을 위해 클라이언트 코드는 크게 다음 순서를 따라야 합니다. - -1. `SSLContext` 생성 - - 앱이 켜질 때, 혹은 통신 직전에 수행 - - TrustStore 로딩 -2. `SSLSocketFactory` 추출 - - 생성된 `SSLContext`에서 소켓 팩토리 호출 -3. socket.connect() - - 서버에 접속을 시도 - -이 과정이 정상적으로 수행되면 클라이언트는 서버에 접속할 수 있고, 요청 및 응답을 주고받을 수 있습니다. \ No newline at end of file diff --git a/src/main/java/space/qu4nt/entanglementlib/security/legacy/certificate/Certificator.java b/src/main/java/space/qu4nt/entanglementlib/security/legacy/certificate/Certificator.java deleted file mode 100644 index 7521b46..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/legacy/certificate/Certificator.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.legacy.certificate; - -import lombok.extern.slf4j.Slf4j; -import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; -import org.bouncycastle.cert.CertIOException; -import org.bouncycastle.cert.X509CertificateHolder; -import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; -import org.bouncycastle.operator.OperatorCreationException; -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.InternalFactory; -import space.qu4nt.entanglementlib.security.EntLibParameterSpec; - -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.Objects; - -/** - * 인증서 생성을 위한 클래스입니다. - * - * @author Q. T. Felix - * @since 1.0.0 - */ -@Slf4j -public class Certificator { - - public static final SubjectString DEFAULT_ROOT_CA_QCR3_TBS; - - public static final SubjectString DEFAULT_SERVER_RiS_TBS; - - static { - DEFAULT_ROOT_CA_QCR3_TBS = SubjectString.builder() - .commonName("BlueBridge QCR3 R") - .organization("Quant") - .organizationalUnit("GlobalSign Root Internal Units") - .country("KR") - .locality("Seoul-si") - .stateOrProvince("Gangnam-gu") - .build(); - DEFAULT_SERVER_RiS_TBS = SubjectString.builder() - .commonName("BlueBridge QCR3 RiS") - .organization("Quant") - .organizationalUnit("GlobalSign Units") - .country("KR") - .locality("Seoul-si") - .stateOrProvince("Gangnam-gu") - .build(); - } - - /** - * 지정된 알고리즘으로 X509 루트 CA 인증서를 생성하는 메소드입니다. - *

- * 루트 CA 인증서는 자체 서명되며, {@code BasicConstraints} 확장을 포함하여 CA로서의 역할을 명시합니다. - * - * @param type 사용할 알고리즘 - * @param pk 공개 키 - * @param sk 비밀 키 - * @param subjectString 인증서 발급 정보 - * @return 생성된 X509 루트 CA 인증서 - * @throws OperatorCreationException 인증서 홀더 객체를 사용하여 인증서를 생성하는 도중 예외가 발생한 경우 - * @throws CertificateException 컨버터 객체를 통해 인증서 객체를 생성하는 도중 예외가 발생한 경우 - * @throws CertIOException 인증서에 확장을 추가하는 도중 예외가 발생한 경우 - */ - public static X509Certificate generateRootCACertificate(@NotNull EntLibParameterSpec type, - @NotNull PublicKey pk, - @NotNull PrivateKey sk, - @NotNull SubjectString subjectString) - throws Exception { - Objects.requireNonNull(type); - Objects.requireNonNull(pk); - Objects.requireNonNull(sk); - Objects.requireNonNull(subjectString); - - // PublicKey to SubjectPublicKeyInfo with BouncyCastle - SubjectPublicKeyInfo pkInfo = SubjectPublicKeyInfo.getInstance(pk.getEncoded()); - final TBSData certInfo = TBSData.builder() - .certificateVersion((short) 3) - .issuer(subjectString) - .subjectPublicKeyInfo(pkInfo) - .build(); - final X509CertificateHolder holder = certInfo.toX509v3Certificate(type.getAlgorithmName(), sk, true); - return new JcaX509CertificateConverter() - .setProvider(InternalFactory.getBCNormalProvider()) - .getCertificate(holder); - } - - /** - * 루트 CA 인증서의 주체 정보를 사용하여 새로운 X509 인증서를 생성하는 메소드입니다. - * - * @param rootSubjectDN 루트 주체 정보 - * @param newerSubjectDN 생성자 주체 정보 - * @param type 사용할 알고리즘 - * @param newerPk 생성자 공개 키 - * @param rootSk 루트 비밀 키 - * @return X509 루트 CA 주체 정보로 서명된 인증서 - * @throws OperatorCreationException 인증서 홀더 객체를 사용하여 인증서를 생성하는 도중 예외가 발생한 경우 - * @throws CertificateException 컨버터 객체를 통해 인증서 객체를 생성하는 도중 예외가 발생한 경우 - * @throws CertIOException 인증서에 확장을 추가하는 도중 예외가 발생한 경우 - */ - public static X509Certificate generateCAChainCertificate(@NotNull SubjectString rootSubjectDN, - @NotNull SubjectString newerSubjectDN, - @NotNull EntLibParameterSpec type, - @NotNull PublicKey newerPk, - @NotNull PrivateKey rootSk) - throws Exception { - Objects.requireNonNull(rootSubjectDN); - Objects.requireNonNull(newerSubjectDN); - Objects.requireNonNull(type); - Objects.requireNonNull(newerPk); - Objects.requireNonNull(rootSk); - - SubjectPublicKeyInfo pkInfo = SubjectPublicKeyInfo.getInstance(newerPk.getEncoded()); - final TBSData certInfo = TBSData.builder() - .certificateVersion((short) 3) - .issuer(rootSubjectDN) - .subject(newerSubjectDN) - .subjectPublicKeyInfo(pkInfo) - .build(); - final X509CertificateHolder holder = certInfo.toX509v3Certificate(type.getAlgorithmName(), rootSk, false); - return new JcaX509CertificateConverter() - .setProvider(InternalFactory.getBCNormalProvider()) - .getCertificate(holder); - } - -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/legacy/certificate/EntSSL.java b/src/main/java/space/qu4nt/entanglementlib/security/legacy/certificate/EntSSL.java deleted file mode 100644 index 287bfc4..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/legacy/certificate/EntSSL.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.legacy.certificate; - -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import space.qu4nt.entanglementlib.InternalFactory; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureCertProcessException; -import space.qu4nt.entanglementlib.resource.language.LanguageInstanceBased; -import space.qu4nt.entanglementlib.util.security.Password; - -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManagerFactory; -import java.security.*; -import java.util.Objects; - -/** - * SSL 제어하는 클래스 - * - * @author Q. T. Felix - * @since 1.0.0 - */ -@Slf4j -@Getter -public class EntSSL { - - private static final LanguageInstanceBased lang = LanguageInstanceBased.create(EntSSL.class); - - private static final String TLS_1_3 = "TLSv1.3"; - private static final String TLS_1_2 = "TLSv1.2"; - private static final String PKIX = "PKIX"; - - private final KeyStoreManager keyStoreManager; - - private KeyManagerFactory keyManagerFactory; - private TrustManagerFactory trustManagerFactory; - - private SSLContext context; - - /** - * {@link EntSSL} 인스턴스를 생성합니다. - * - * @param keyStoreManager 키스토어 매니저 인스턴스 - * @throws NullPointerException 인자가 {@code null}인 경우 - */ - public EntSSL(@NotNull KeyStoreManager keyStoreManager) { - this.keyStoreManager = Objects.requireNonNull(keyStoreManager); - } - - @NotNull - public SSLContext createSSLContext(final @NotNull String algorithm, final @Nullable String provider, final char @NotNull [] password) - throws EntLibSecureCertProcessException { - if (context != null) - return context; - - Objects.requireNonNull(keyStoreManager); - - try { - // TLS 1.3 시도 - SSLContext context = createSSLContextWithProtocol(TLS_1_3, algorithm, provider, password); - if (context != null) { - this.context = context; - log.info(lang.msg("ssl-context-created-tls13")); - return context; - } - - // TLS 1.3 실패 시 TLS 1.2 사용 - log.warn(lang.msg("tls13-not-available-fallback")); - context = createSSLContextWithProtocol(TLS_1_2, algorithm, provider, password); - if (context != null) { - this.context = context; - log.info(lang.msg("ssl-context-created-tls12")); - } - return this.context; - } catch (Exception e) { - throw new EntLibSecureCertProcessException(EntSSL.class, "ssl-context-creation-failed-exc", e); - } - } - - // TODO 제거고려 - public SSLContext createSSLContext(final char @NotNull [] password) throws EntLibSecureCertProcessException { - return createSSLContext(PKIX, null, password); - } - - private SSLContext createSSLContextWithProtocol(@NotNull String protocol, @NotNull String algorithm, @Nullable String provider, final char @NotNull [] keyPassword) { - Objects.requireNonNull(protocol); - Objects.requireNonNull(keyStoreManager); - Objects.requireNonNull(algorithm); - - String fixProvider = provider == null ? InternalFactory.getBCJSSEProvider() : provider; - try { - SSLContext context = SSLContext.getInstance(protocol, fixProvider); - - // KeyManager 설정 - this.keyManagerFactory = KeyManagerFactory.getInstance(algorithm, fixProvider); - keyManagerFactory.init(keyStoreManager.getKeyStore(), keyPassword); - - // TrustManager 설정 (인증서 검증 강화) - this.trustManagerFactory = TrustManagerFactory.getInstance(algorithm, fixProvider); - trustManagerFactory.init(keyStoreManager.getTrustStore()); - - // SSLContext 초기화 - context.init( - keyManagerFactory.getKeyManagers(), - trustManagerFactory.getTrustManagers(), - InternalFactory.getSafeRandom() - ); - return context; - } catch (NoSuchAlgorithmException | KeyStoreException | UnrecoverableKeyException | - KeyManagementException e) { - log.error(lang.args("ssl-context-creation-exc", protocol), e); - return null; - } catch (NoSuchProviderException e) { - throw new RuntimeException(e); - } finally { - Password.wipePassword(keyPassword); - } - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/legacy/certificate/KeyStoreManager.java b/src/main/java/space/qu4nt/entanglementlib/security/legacy/certificate/KeyStoreManager.java deleted file mode 100644 index 442add1..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/legacy/certificate/KeyStoreManager.java +++ /dev/null @@ -1,290 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.legacy.certificate; - -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import space.qu4nt.entanglementlib.InternalFactory; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureJCAJCEStoreProcessException; -import space.qu4nt.entanglementlib.resource.language.LanguageInstanceBased; -import space.qu4nt.entanglementlib.security.KeyDestroyHelper; - -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.security.*; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.util.Enumeration; -import java.util.Objects; - -/** - * @author Q. T. Felix - * @since 1.0.0 - */ -@Slf4j -public class KeyStoreManager { - - private static final LanguageInstanceBased lang = - LanguageInstanceBased.create(KeyStoreManager.class); - - public static final String PKCS12 = "PKCS12"; - public static final String JKS = "JKS"; // legacy support - public static final String BCFKS = "BCFKS"; - - private static final String DEFAULT_KEYSTORE_TYPE = BCFKS; - - @Getter - private final KeyStore keyStore; - @Getter - private final KeyStore trustStore; - @Getter - private final String keyStoreType; - - private Path keyStorePath; - private Path trustStorePath; - - /** - * 기본 BCFKS 타입의 키스토어 매니저를 생성합니다. - */ - public KeyStoreManager() throws EntLibSecureJCAJCEStoreProcessException { - this(DEFAULT_KEYSTORE_TYPE); - } - - /** - * 지정된 타입의 키스토어 매니저를 생성합니다. - * - * @param keyStoreType 키스토어 타입 (BCFKS, PKCS12 등) - */ - public KeyStoreManager(@NotNull String keyStoreType) throws EntLibSecureJCAJCEStoreProcessException { - this(keyStoreType, InternalFactory.getBCNormalProvider()); - } - - public KeyStoreManager(@NotNull String keyStoreType, final @Nullable String provider) - throws EntLibSecureJCAJCEStoreProcessException { - this.keyStoreType = Objects.requireNonNull(keyStoreType); - try { - if (provider == null) { - this.keyStore = KeyStore.getInstance(keyStoreType); - this.trustStore = KeyStore.getInstance(keyStoreType); - } else { - this.keyStore = KeyStore.getInstance(keyStoreType, provider); - this.trustStore = KeyStore.getInstance(keyStoreType, provider); - } - } catch (KeyStoreException | NoSuchProviderException e) { - log.error(lang.thr("failed-keystore-init-exc", e, keyStoreType)); - throw new EntLibSecureJCAJCEStoreProcessException(KeyStoreManager.class, "failed-keystore-init-err-exc", e); - } - } - - /** - * 키스토어를 파일에서 로드합니다. - *

- * 비밀번호 배열은 복사본이 전달되어도 사용 후 즉시 영소거됩니다. - * - * @param keyStorePath 키스토어 파일 경로 - * @param password 키스토어 비밀번호 - */ - public void loadKeyStore(@NotNull Path keyStorePath, char @NotNull [] password) - throws IOException, CertificateException, NoSuchAlgorithmException, EntLibSecureJCAJCEStoreProcessException { - Objects.requireNonNull(keyStorePath); - Objects.requireNonNull(password); - - this.keyStorePath = keyStorePath; - - if (Files.exists(keyStorePath)) { - try (InputStream fis = Files.newInputStream(keyStorePath)) { - keyStore.load(fis, password); - log.info(lang.argsNonTopKey("loaded-keystore", keyStorePath)); - } finally { - KeyDestroyHelper.zeroing(password); - } - } else { - // 파일이 없으면 로드하지 않고 초기화 상태 유지 (새로 생성을 위함) - log.warn(lang.argsNonTopKey("loaded-empty-keystore", keyStorePath)); - try { - keyStore.load(null, null); - } catch (IOException e) { - throw new EntLibSecureJCAJCEStoreProcessException(KeyStoreManager.class, "key-store-loading-exc", e); - } finally { - KeyDestroyHelper.zeroing(password); - } - } - } - - /** - * 트러스트스토어를 파일에서 로드합니다. - *

- * 트러스트스토어 로드 시, 비밀번호 배열은 영소거되지 않습니다만 - * 안전한 비밀번호 설계는 여전히 권장됩니다. - * - * @param trustStorePath 트러스트스토어 파일 경로 - * @param password 트러스트스토어 비밀번호 - */ - public void loadTrustStore(@NotNull Path trustStorePath, char @NotNull [] password) - throws IOException, CertificateException, NoSuchAlgorithmException { - Objects.requireNonNull(trustStorePath); - Objects.requireNonNull(password); - - this.trustStorePath = trustStorePath; - - if (Files.exists(trustStorePath)) { - try (InputStream fis = Files.newInputStream(trustStorePath)) { - trustStore.load(fis, password); - log.info(lang.argsNonTopKey("loaded-truststore", trustStorePath)); - } - } else { - log.warn(lang.argsNonTopKey("loaded-empty-truststore", trustStorePath)); - try { - trustStore.load(null, null); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } - - /** - * 변경 사항을 키스토어 파일에 영구 저장합니다. - *

- * 안정성 강화: 임시 파일에 기록 후 원자적 이동을 수행하여 파일 손상을 방지합니다. - * 사용된 비밀번호 배열은 즉시 영소거됩니다. - * - * @param password 저장에 사용할 비밀번호 - */ - public void storeKeyStore(char @NotNull [] password) - throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, EntLibSecureJCAJCEStoreProcessException { - if (this.keyStorePath == null) { - throw new EntLibSecureJCAJCEStoreProcessException(KeyStoreManager.class, "keystore-path-exc"); - } - saveStoreSafe(this.keyStore, this.keyStorePath, password); - } - - /** - * 변경 사항을 트러스트스토어 파일에 영구 저장합니다. - * - * @param password 저장에 사용할 비밀번호 - */ - public void storeTrustStore(char @NotNull [] password) - throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, EntLibSecureJCAJCEStoreProcessException { - if (this.trustStorePath == null) { - throw new EntLibSecureJCAJCEStoreProcessException(KeyStoreManager.class, "truststore-path-exc"); - } - saveStoreSafe(this.trustStore, this.trustStorePath, password); - } - - /** - * 인증서가 트러스트스토어에서 신뢰할 수 있는지 확인합니다. - * - * @param certificate 확인할 인증서 - * @return 신뢰할 수 있으면 true, 그렇지 않으면 false - */ - public boolean isTrusted(@NotNull X509Certificate certificate) { - Objects.requireNonNull(certificate); - - try { - // 직접 별칭으로 찾기 - Enumeration aliases = trustStore.aliases(); - while (aliases.hasMoreElements()) { - String alias = aliases.nextElement(); - Certificate trustedCert = trustStore.getCertificate(alias); - if (trustedCert != null && trustedCert.equals(certificate)) { - return true; - } - } - - // 인증서 체인 검증 - return trustStore.getCertificateAlias(certificate) != null; - } catch (KeyStoreException e) { - log.warn(lang.thr("check-cert-in-exc", e)); - return false; - } - } - - /** - * 안전한 저장을 위한 내부 헬퍼 메소드입니다. - *

- * 저장 후 비밀번호 영소거를 위해 사용됩니다. - */ - private void saveStoreSafe(KeyStore store, Path path, char[] password) - throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException { - - // 1. 임시 파일 생성 (.tmp) - Path tempPath = path.resolveSibling(path.getFileName() + ".tmp"); - - try (FileOutputStream fos = new FileOutputStream(tempPath.toFile())) { - store.store(fos, password); - fos.getFD().sync(); // 디스크 동기화 강제 - } finally { - // 주의! 호출자가 전달한 원본 배열을 수정하기 때문에 호출자가 이 동작을 인지해야 함 - KeyDestroyHelper.zeroing(password); - } - - // 2. 원자적 이동 - // 파일 쓰기 완료 후 교체하기 때문에 쓰기 도중 실패해도 원본 파일은 안전 - Files.move(tempPath, path, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); - log.info(lang.argsNonTopKey("saved-keystore", path)); - } - - // --- Entry Management Methods --- - - public void setKeyEntry(@NotNull String alias, @NotNull PrivateKey privateKey, - char @NotNull [] password, @Nullable Certificate[] chain) - throws KeyStoreException { - Objects.requireNonNull(alias); - Objects.requireNonNull(privateKey); - Objects.requireNonNull(password); - - try { - keyStore.setKeyEntry(alias, privateKey, password, chain); - log.info(lang.argsNonTopKey("save-key-entry", alias)); - } finally { - // 비밀번호는 KeyStore 내부 로직에서 사용된 후 여기서 소거 - KeyDestroyHelper.zeroing(password); - } - } - - public void setKeystoreCertificateEntry(@NotNull String alias, @NotNull Certificate certificate) - throws KeyStoreException { - Objects.requireNonNull(alias); - Objects.requireNonNull(certificate); - keyStore.setCertificateEntry(alias, certificate); - log.info(lang.argsNonTopKey("set-keystore-cert-entry", alias)); - } - - public void setTruststoreCertificateEntry(@NotNull String alias, @NotNull Certificate certificate) - throws KeyStoreException { - Objects.requireNonNull(alias); - Objects.requireNonNull(certificate); - trustStore.setCertificateEntry(alias, certificate); - log.info(lang.argsNonTopKey("set-truststore-cert-entry", alias)); - } - - public void deleteKeystoreEntry(@NotNull String alias) throws KeyStoreException { - Objects.requireNonNull(alias); - if (keyStore.containsAlias(alias)) { - keyStore.deleteEntry(alias); - log.info(lang.args("delete-keystore-entry", alias)); - } else { - log.warn(lang.args("delete-not-exists-alise-in-keystore-entry", alias)); - } - } - - public void deleteTrustedEntry(@NotNull String alias) throws KeyStoreException { - Objects.requireNonNull(alias); - if (trustStore.containsAlias(alias)) { - trustStore.deleteEntry(alias); - log.info(lang.args("delete-truststore-entry", alias)); - } else { - log.warn(lang.args("delete-not-exists-alise-in-truststore-entry", alias)); - } - } -} \ No newline at end of file diff --git a/src/main/java/space/qu4nt/entanglementlib/security/legacy/certificate/SubjectString.java b/src/main/java/space/qu4nt/entanglementlib/security/legacy/certificate/SubjectString.java deleted file mode 100644 index 1d3275b..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/legacy/certificate/SubjectString.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.legacy.certificate; - -import lombok.Builder; -import lombok.Data; -import org.bouncycastle.asn1.x500.X500Name; -import org.jetbrains.annotations.ApiStatus; - -@Data -@Builder -public class SubjectString { - - private String commonName; - private String organization; - private String organizationalUnit; - private String country; - private String locality; - private String stateOrProvince; - - /** - * {@code BouncyCastle} 라이브러리를 사용하여 주체 문자열을 - * {@code X.500} 타입으로 변환하는 메소드입니다. - *

- * 해당 라이브러리가 의존성으로 등록되어 있지 않다면 기능을 사용할 수 없습니다. - * - * @return X.500 타입 - */ - @ApiStatus.Internal - public X500Name toX500Name() { - return new X500Name(toString()); - } - - @Override - public String toString() { - StringBuilder result = new StringBuilder(); - - if (valid(commonName)) { - result.append("CN=").append(commonName).append(", "); - } - if (valid(organization)) { - result.append("O=").append(organization).append(", "); - } - if (valid(organizationalUnit)) { - result.append("OU=").append(organizationalUnit).append(", "); - } - if (valid(country)) { - result.append("C=").append(country).append(", "); - } - if (valid(locality)) { - result.append("L=").append(locality).append(", "); - } - if (valid(stateOrProvince)) { - result.append("ST=").append(stateOrProvince).append(", "); - } - - if (!result.isEmpty() && result.toString().endsWith(", ")) { - return result.substring(0, result.length() - 2); - } - return result.toString(); - } - - private boolean valid(String s) { - return s != null && !s.isEmpty(); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/security/legacy/certificate/TBSData.java b/src/main/java/space/qu4nt/entanglementlib/security/legacy/certificate/TBSData.java deleted file mode 100644 index 6615287..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/security/legacy/certificate/TBSData.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.legacy.certificate; - -import lombok.Builder; -import lombok.Data; -import org.bouncycastle.asn1.x509.*; -import org.bouncycastle.cert.CertIOException; -import org.bouncycastle.cert.X509CertificateHolder; -import org.bouncycastle.cert.X509v3CertificateBuilder; -import org.bouncycastle.operator.ContentSigner; -import org.bouncycastle.operator.OperatorCreationException; -import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; -import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import space.qu4nt.entanglementlib.InternalFactory; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureCertProcessException; - -import java.math.BigInteger; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Date; -import java.util.Objects; - -@Data -@Builder -public class TBSData { - - private short certificateVersion; - - @NotNull - private SubjectString issuer; - - @Nullable - private BigInteger serialNumber; - - @Nullable - private Instant notBefore; - - @Nullable - private Instant notAfter; - - @Nullable - private SubjectString subject; - - @NotNull - private SubjectPublicKeyInfo subjectPublicKeyInfo; - - @SuppressWarnings({"all"}) - public X509CertificateHolder toX509v3Certificate(final @NotNull String algorithm, - final @NotNull PrivateKey sk, - boolean isRootCA) - throws EntLibSecureCertProcessException { - Objects.requireNonNull(algorithm); - Objects.requireNonNull(sk); - Objects.requireNonNull(issuer); - Objects.requireNonNull(subjectPublicKeyInfo); - - if (certificateVersion != 3) - throw new EntLibSecureCertProcessException(TBSData.class, "unsupported-ver-exc", null, certificateVersion); - - // serialNumber - if (serialNumber == null) - serialNumber = new BigInteger(10, InternalFactory.getSafeRandom()); - - // notBefore - if (notBefore == null) - notBefore = Instant.now(); - - // notAfter - if (notAfter == null) - notAfter = Instant.now().plus(365, ChronoUnit.DAYS); - - // subject - if (subject == null) // issuer와 동일하다 간주 - subject = issuer; - - X509v3CertificateBuilder builder = new X509v3CertificateBuilder - (issuer.toX500Name(), serialNumber, Date.from(notBefore), Date.from(notAfter), subject.toX500Name(), subjectPublicKeyInfo); - - ContentSigner signer; - try { - if (isRootCA) { - // BasicConstraints 확장 CA=true, pathLen 무제한 - builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(true)); - // KeyUsage: Certificate Signing, CRL Signing - builder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign)); - } else { - builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(false)); - // KeyUsage: digitalSignature (TLS 핸드셰이크 서명용 필수) - // TODO: 나중에 keyEncipherment 추가 고려 (RSA 등의 경우이나, TLS 1.3 + PQC에서는 digitalSignature가 핵심) - builder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature)); - // ExtendedKeyUsage: serverAuth (TLS 서버 인증용) - builder.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage(KeyPurposeId.id_kp_serverAuth)); - } - - signer = new JcaContentSignerBuilder(algorithm) - .setProvider(InternalFactory.getBCNormalProvider()) - .build(sk); - } catch (OperatorCreationException | CertIOException e) { - throw new EntLibSecureCertProcessException(e); - } - - return builder.build(signer); - } - - public static boolean isCertificateValid( - final @NotNull X509CertificateHolder certHolder, - final @NotNull PublicKey publicKey) throws Exception { - certHolder.isSignatureValid(new JcaContentVerifierProviderBuilder() - .setProvider(InternalFactory.getBCNormalProvider()) - .build(publicKey)); - return true; - } - -} diff --git a/src/main/java/space/qu4nt/entanglementlib/util/io/EntFile.java b/src/main/java/space/qu4nt/entanglementlib/util/io/EntFile.java deleted file mode 100644 index 51ec36a..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/util/io/EntFile.java +++ /dev/null @@ -1,364 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.util.io; - -import com.quant.quantregular.annotations.QuantPerformance; -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import space.qu4nt.entanglementlib.InternalFactory; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalArgumentException; -import space.qu4nt.entanglementlib.resource.language.LanguageInstanceBased; -import space.qu4nt.entanglementlib.security.crypto.Digest; - -import java.io.*; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.file.*; -import java.nio.file.FileSystem; -import java.nio.file.attribute.*; -import java.security.NoSuchAlgorithmException; -import java.util.*; -import java.util.function.BiConsumer; - -/** - * 해당 클래스의 구현에 다음을 명심해야 합니다. - *

    - *
  • 경로 조작(path traversal) 방지
  • - *
  • 화이트리스트 방식의 입력 유효성 검사
  • - *
  • 최소 권한 원칙 적용
  • - *
  • 파일 무결성 점검
  • - *
- *

- * 이 클래스는 {@code ENTFILE_BASE_DIR} 환경 변수에 할당된 경로를 - * 기준으로 트래버셜 보안을 수행합니다. 파일을 코드 내에서 자유롭게 관리할 수 없음을 - * 참고하세요. - *

- * 이 규칙을 사용하지 않는 파일 관리 기능을 사용하려면 - * 내부 {@link Unchecked} 클래스를 사용하세요. - * - * @author Q. T. Felix - * @since 1.0.0 - */ -@Slf4j -public final class EntFile { - - // 허용된 기본 디렉토리 (설정 파일 또는 환경 변수에서 로드) - private static final String BASE_DIR = InternalFactory.envEntanglementHomeDir(); - - /** - * 데이터를 안전하게 파일로 저장하고, 저장된 데이터의 무결성 해시를 반환하는 메소드입니다. - * - * @param relativePath 저장할 파일의 상대 경로 - * @param data 저장할 바이트 데이터 - * @param overwrite 기존 파일 존재 시 덮어쓰기 여부 - * @return 저장된 데이터의 SHA3-256 해시 Hex 문자열 - * @throws IOException 쓰기 과정에서 문제가 발생한 경우 - * @throws EntLibSecureIllegalArgumentException 보안 위반 또는 파일 중복 시 - */ - @QuantPerformance - public static String saveFileSafely(String relativePath, byte[] data, boolean overwrite) - throws IOException, EntLibSecureIllegalArgumentException { - // 입력 검증 강화 - if (relativePath == null || data == null) { - throw new EntLibSecureIllegalArgumentException(EntFile.class, "relative-path-of-data-null-exc"); - } - if (data.length > 1024 * 1024 * 1024) { // 1GB 제한으로 메모리 안정성 확보 - throw new EntLibSecureIllegalArgumentException(EntFile.class, "data-size-exc"); - } - if (!relativePath.matches("^[\\w.-]+(/[\\w.-]+)*$")) { - throw new EntLibSecureIllegalArgumentException(EntFile.class, "whitelist-exc"); - } - - // Path Traversal 방어 강화 - Path basePath = Paths.get(BASE_DIR).toAbsolutePath().normalize(); - if (!basePath.toFile().isDirectory()) { - throw new EntLibSecureIllegalArgumentException(EntFile.class, "base-dir-not-valid-exc"); - } - - Path resolvedPath = basePath.resolve(relativePath).normalize(); - // toRealPath() 대신 부모 디렉토리 real path 확인 (파일 생성 없이 symbolic link 방어) - Path parentDir = resolvedPath.getParent(); - if (parentDir != null) { - try { - parentDir = parentDir.toRealPath(); // 부모 디렉토리 존재 확인 및 real path - } catch (IOException e) { - throw new EntLibSecureIllegalArgumentException(EntFile.class, "failed-parent-dir-exc", e); - } - if (!parentDir.startsWith(basePath)) { - throw new EntLibSecureIllegalArgumentException(EntFile.class, "traversal-attempt-exc"); - } - } - if (!resolvedPath.startsWith(basePath)) { - throw new EntLibSecureIllegalArgumentException(EntFile.class, "in-save-traversal-attempt-exc"); - } - - File file = resolvedPath.toFile(); - - // 덮어쓰기 정책 확인 (TOCTOU 최소화 위해 옵션에 의존) - if (file.exists() && !overwrite) { - throw new EntLibSecureIllegalArgumentException(EntFile.class, "file-already-exists-exc"); - } - - // 부모 디렉토리 생성 (필요 시) 및 권한 설정 - if (parentDir != null && !Files.exists(parentDir)) { - Files.createDirectories(parentDir); - if (basePath.getFileSystem().supportedFileAttributeViews().contains("posix")) { - Set dirPerms = PosixFilePermissions.fromString("rwx------"); - Files.setPosixFilePermissions(parentDir, dirPerms); - } - } - - // 파일 쓰기 옵션 설정 (원자성 및 안전성 고려) - Set options = new HashSet<>(); - options.add(StandardOpenOption.WRITE); - options.add(StandardOpenOption.CREATE); - if (overwrite) { - options.add(StandardOpenOption.TRUNCATE_EXISTING); - } else { - options.add(StandardOpenOption.CREATE_NEW); - } - - // 파일 저장 수행 (메모리 효율성 위해 ByteArrayInputStream 사용) - Path finalFile; - try (InputStream inputStream = new ByteArrayInputStream(data)) { - finalFile = Files.write(resolvedPath, inputStream.readAllBytes(), options.toArray(new StandardOpenOption[0])); - } catch (IOException e) { - throw new EntLibSecureIllegalArgumentException(EntFile.class, "failed-write-disk-exc", e); - } - - // 최소 권한 적용 (POSIX 및 Windows ACL 지원) - try { - FileSystem fs = basePath.getFileSystem(); - if (fs.supportedFileAttributeViews().contains("posix")) { - Set perms = PosixFilePermissions.fromString("rw-------"); - Files.setPosixFilePermissions(resolvedPath, perms); - } else if (fs.supportedFileAttributeViews().contains("acl")) { - AclFileAttributeView aclView = Files.getFileAttributeView(resolvedPath, AclFileAttributeView.class); - UserPrincipal owner = Files.getOwner(resolvedPath); - List acl = new ArrayList<>(); - acl.add(AclEntry.newBuilder() - .setType(AclEntryType.ALLOW) - .setPrincipal(owner) - .setPermissions(AclEntryPermission.READ_DATA, AclEntryPermission.WRITE_DATA) - .build()); - aclView.setAcl(acl); - } - } catch (Exception e) { - log.error(LanguageInstanceBased.create(EntFile.class).args("failed-set-perm", e.getMessage())); - } - - // 무결성 증명을 위한 해시 계산 및 반환 - try { - return Hash.hashFile(finalFile, Digest.SHA3_256); - } catch (NoSuchAlgorithmException e) { - throw new EntLibSecureIllegalArgumentException(EntFile.class, "no-such-alg-exc", e); - } - } - - /** - * 대용량 처리를 위해 파일의 전체 내용을 메모리에 올리지 않고, - * 경로 보안 검증(트래버셜)이 완료된 {@link InputStream}을 반환하는 메소드입니다. - * - * @param relativePath 로드할 파일의 상대 경로 (BASE_DIR 내에서만 허용) - * @return 버퍼링된 입력 스트림 - * @throws IOException 읽기 과정에서 문제가 발생한 경우 - * @throws EntLibSecureIllegalArgumentException 보안 위반 시 (경로 조작 등) - * @see #openStreamSafelyExpectedHash(String, String) 무결성 검증이 필요한 경우 - */ - public static InputStream openStreamSafely(String relativePath) throws IOException, EntLibSecureIllegalArgumentException { - // 입력 검증: null 체크 및 허용 문자만 - if (relativePath == null) { - throw new EntLibSecureIllegalArgumentException(EntFile.class, "relative-path-null-exc"); - } - // 화이트리스트 & Path Traversal 방어 - final Path resolvedPath = checkWhitelistAndPathTraversal(relativePath); - - // 새로운 스트림을 열어 반환 - return new BufferedInputStream(Files.newInputStream(resolvedPath)); - } - - /** - * 대용량 처리를 위해 파일의 전체 내용을 메모리에 올리지 않고, - * 복합 보안 검증(트래버셜, 무결성)이 완료된 {@link InputStream}을 반환하는 메소드입니다. - * - * @param relativePath 로드할 파일의 상대 경로 (BASE_DIR 내에서만 허용) - * @param expectedHash 예상되는 파일의 {@code SHA3-256} 해시 값 (소문자 16진수 문자열) - * @return 버퍼링된 입력 스트림 - * @throws IOException 읽기 과정에서 문제가 발생한 경우 - * @throws EntLibSecureIllegalArgumentException 보안 위반 시 (경로 조작, 무결성 실패 등) - * @see #openStreamSafely(String) 무결성 검증이 필요 없는 경우 - */ - public static InputStream openStreamSafelyExpectedHash(String relativePath, @NotNull String expectedHash) throws IOException, EntLibSecureIllegalArgumentException { - // 입력 검증: null 체크 및 허용 문자만 - if (expectedHash == null) { - throw new EntLibSecureIllegalArgumentException(EntFile.class, "relative-path-or-expected-hash-null-exc"); - } - // 화이트리스트 & Path Traversal 방어 - final Path resolvedPath = checkWhitelistAndPathTraversal(relativePath); - - // 무결성 검사: Digest 클래스를 사용해 안전하고 메모리 효율적으로 해시 계산 - String actualHash; - try { - actualHash = Hash.hashFile(resolvedPath, Digest.SHA3_256); - } catch (NoSuchAlgorithmException e) { - throw new EntLibSecureIllegalArgumentException(EntFile.class, "not-support-jvm-exc", e); - } - - // 타이밍 공격에 안전한 해시 비교 - if (!Hash.isEqual(actualHash, expectedHash.toLowerCase())) { - throw new EntLibSecureIllegalArgumentException(EntFile.class, "load-hash-equal-exc"); - } - - // 새로운 스트림을 열어 반환 - return new BufferedInputStream(Files.newInputStream(resolvedPath)); - } - - /** - * 보안 검증을 위한 헬퍼 메소드입니다. - * - * @param relativePath 기준점 상대 경로 - * @return {@link Path#resolve(Path)}된 최종 경로 - */ - @NotNull - private static Path checkWhitelistAndPathTraversal(final @NotNull String relativePath) - throws EntLibSecureIllegalArgumentException { - // 추 후 화이트리스트 열거 방식으로 수정 - if (!relativePath.matches("^[a-zA-Z0-9_/.-]+$")) { - throw new EntLibSecureIllegalArgumentException(EntFile.class, "whitelist-exc"); - } - - // Path Traversal 방어: 정규화 후 BASE_DIR 범위 검사 - Path basePath = Paths.get(BASE_DIR).toAbsolutePath().normalize(); - if (!basePath.toFile().isDirectory()) { - throw new EntLibSecureIllegalArgumentException(EntFile.class, "not-valid-or-accessible-exc"); - } - - Path resolvedPath = basePath.resolve(relativePath).normalize(); - if (!resolvedPath.startsWith(basePath)) { - throw new EntLibSecureIllegalArgumentException(EntFile.class, "load-traversal-attempt-exc"); - } - - File file = resolvedPath.toFile(); - if (!file.isFile() || !file.canRead()) { - throw new EntLibSecureIllegalArgumentException(EntFile.class, "load-file-not-found-or readable-exc"); - } - return resolvedPath; - } - - /** - * 파일 스트리밍을 통해 데이터를 버퍼 단위로 처리하는 메소드입니다. - * 입력 파일에서 데이터를 읽어와 콜백 함수를 통해 처리한 후 출력 파일에 씁니다. - *

- * 이 메소드는 입/출력 파일의 길이를 예상하지 않습니다. 스택 오버플로우 오류를 - * 예방하기 위해 호출자는 입/출력 파일의 길이를 알고 있어야 합니다. - * - * @param inputPath 입력 파일의 경로 - * @param outputPath 출력 파일의 경로 - * @param inputAllocateSize 입력 데이터를 읽을 버퍼 할당 사이즈 - * @param outputAllocateSize 출력 데이터를 쓸 버퍼 할당 사이즈 - * @param ioByteBufferCallback 각 버퍼 처리 단계에서 호출될 콜백 함수. 입력 버퍼와 출력 버퍼를 인자로 받습니다. - * 콜백이 {@code null}인 경우, 데이터는 단순히 입력에서 출력으로 복사됩니다. - * @return 마지막으로 출력 채널에 쓰여진 바이트 수, 아무것도 쓰여지지 않은 경우 {@code 0} - * @throws IOException 파일 읽기 또는 쓰기 과정에서 문제가 발생한 경우 - */ - public static int byteBufferStreaming(@NotNull Path inputPath, - @NotNull Path outputPath, - int inputAllocateSize, - int outputAllocateSize, - @Nullable BiConsumer<@NotNull ByteBuffer, @NotNull ByteBuffer> ioByteBufferCallback) - throws IOException, EntLibSecureIllegalArgumentException { - Objects.requireNonNull(inputPath); - Objects.requireNonNull(outputPath); - - if (inputAllocateSize < 1 || outputAllocateSize < 1) - throw new EntLibSecureIllegalArgumentException(EntFile.class, "invalid-buffer-size-exc", null, inputAllocateSize, outputAllocateSize); - - try (FileChannel inputChannel = FileChannel.open(inputPath, StandardOpenOption.READ); - FileChannel outputChannel = FileChannel.open(outputPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) { - - ByteBuffer inputBuffer = ByteBuffer.allocate(inputAllocateSize); - ByteBuffer outputBuffer = ByteBuffer.allocate(outputAllocateSize); - - int writtenByte = 0; - while (inputChannel.read(inputBuffer) != -1) { - inputBuffer.flip(); - outputBuffer.clear(); - - if (ioByteBufferCallback != null) { - ioByteBufferCallback.accept(inputBuffer, outputBuffer); - } else { - // 콜백이 없으면 입력 버퍼의 내용을 출력 버퍼로 직접 복사 - outputBuffer.put(inputBuffer); - } - - outputBuffer.flip(); - writtenByte = outputChannel.write(outputBuffer); - inputBuffer.compact(); - } - return writtenByte; - } - } - - /** - * 경로 트래버셜, 무결성 검사를 수행하지 않고 파일 및 디렉토리를 관리하는 클래스입니다. - *

- * 호출 성능에만 초점을 맞추어 개발되었기 때문에 보안이 필요한 로직에는 사용하지 않을 것을 - * 권장합니다. - * - * @author Q. T. Felix - * @since 1.0.0 - */ - @ApiStatus.Obsolete - public static final class Unchecked { - - /** - * 스트림에서 데이터를 읽어와 파일로 저장합니다. - * 이 메소드는 대용량 파일을 효율적으로 처리하기 위해 스트림을 사용하며, - * 결과물에 대해 별도의 권한 작업을 수행하지 않습니다. - *

- * 전달받은 경로의 부모 디렉토리가 존재하지 않는 경우 생성합니다. - * 생성에는 {@link Files#createDirectories(Path, FileAttribute[])} - * 메소드가 사용됩니다. - * - * @param path 저장할 파일의 경로 - * @param inputStream 저장할 데이터의 입력 스트림 - * @param overwrite 기존 파일 존재 시 덮어쓰기 여부 - * @return 결과 경로 - * @throws IOException 쓰기 과정에서 문제가 발생한 경우 - */ - @NotNull - public static Path saveFile(final @NotNull Path path, final @NotNull InputStream inputStream, boolean overwrite) throws IOException { - Path parentDir = path.getParent(); - if (parentDir != null && !Files.exists(parentDir)) - Files.createDirectories(parentDir); - - if (overwrite) { - Files.copy(inputStream, path, StandardCopyOption.REPLACE_EXISTING); - } else { - // StandardCopyOption이 지정되지 않으면 대상 파일이 이미 있는 경우 FileAlreadyExistsException을 던짐 - // 이는 StandardOpenOption.CREATE_NEW와 유사하게 동작 - Files.copy(inputStream, path); - } - return path; - } - - /** - * 파일을 열어 입력 스트림을 반환합니다. - * 대용량 파일을 효율적으로 처리하기 위해 버퍼링된 스트림 객체 - * {@link BufferedInputStream}을 사용합니다. - * - * @param path 열 파일의 경로 - * @return 버퍼링된 입력 스트림 - * @throws IOException 읽기 과정에서 문제가 발생한 경우 - */ - public static InputStream openStream(final @NotNull Path path) throws IOException { - return new BufferedInputStream(Files.newInputStream(path)); - } - } - -} diff --git a/src/main/java/space/qu4nt/entanglementlib/util/io/Hash.java b/src/main/java/space/qu4nt/entanglementlib/util/io/Hash.java deleted file mode 100644 index 0abd8a7..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/util/io/Hash.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.util.io; - -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.security.crypto.Digest; -import space.qu4nt.entanglementlib.util.chunk.ByteArrayChunkProcessor; -import space.qu4nt.entanglementlib.util.wrapper.Hex; - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.RandomAccessFile; -import java.nio.MappedByteBuffer; -import java.nio.channels.Channels; -import java.nio.channels.FileChannel; -import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.HexFormat; -import java.util.Objects; - -public final class Hash { - - /** - * 기본 버퍼 크기: 1MB (대용량 파일에서도 안전합니다.) - */ - private static final int BUFFER_SIZE = 1024 * 1024; // 1MB - - public static String hash(byte @NotNull [] data, Digest digest, int chunkSize) throws NoSuchAlgorithmException { - Objects.requireNonNull(data); - Objects.requireNonNull(digest); - - MessageDigest md = MessageDigest.getInstance(digest.getName()); - if (data.length > 1023 && chunkSize > 0) { - ByteArrayChunkProcessor.processInChunks(data, chunkSize, md::update); - } else { - md.update(data, 0, data.length); - } - return Hex.toHexString(md.digest()); - } - - /** - * 문자 배열의 해시값을 산출하는 메소드입니다. - * 문자열은 {@code UTF-8}로 인코딩되어 처리됩니다. - * - * @param data 해시를 산출할 문자 배열 - * @param digest 사용할 해시 알고리즘 - * @return 소문자 16진수 해시 문자열 - * @throws NoSuchAlgorithmException 지원하지 않는 알고리즘 요청 시 - */ - public static String hash(final char @NotNull [] data, Digest digest) throws NoSuchAlgorithmException { - Objects.requireNonNull(data); - Objects.requireNonNull(digest); - - byte[] bytes = new String(data).getBytes(StandardCharsets.UTF_8); - return hash(bytes, digest, 0); - } - - /** - * 파일의 해시값을 16진수 문자열로 반환하는 메소드입니다. - * - * @param filePath 파일 경로 - * @param digest 사용할 해시 알고리즘 - * @return 소문자 16진수 해시 문자열 - * @throws IOException 파일 읽기 실패 시 - * @throws NoSuchAlgorithmException 지원하지 않는 알고리즘 요청 시 - */ - public static String hashFile(Path filePath, Digest digest) - throws IOException, NoSuchAlgorithmException { - Objects.requireNonNull(filePath); - Objects.requireNonNull(digest); - - MessageDigest md = MessageDigest.getInstance(digest.getName()); - - try (FileChannel channel = FileChannel.open(filePath); - InputStream in = new BufferedInputStream(Channels.newInputStream(channel))) { - - byte[] buffer = new byte[BUFFER_SIZE]; - int bytesRead; - - while ((bytesRead = in.read(buffer)) != -1) { - md.update(buffer, 0, bytesRead); - } - } - - return Hex.toHexString(md.digest()); - } - - /** - * {@link MappedByteBuffer}를 사용하여{@link #hashFile(Path, Digest)} - * 메소드보다 대용량 파일에 더 최적화된 버전의 메소드입니다. - *

- * 메모리 매핑으로 OS가 알아서 페이징 처리하기 때문에 대용량 파일도 안정적입니다. - * - * @param filePath 파일 경로 - * @param digest 사용할 해시 알고리즘 - * @return 소문자 16진수 해시 문자열 - */ - public static String hashFileWithMapping(Path filePath, Digest digest) - throws IOException, NoSuchAlgorithmException { - Objects.requireNonNull(filePath); - Objects.requireNonNull(digest); - - MessageDigest md = MessageDigest.getInstance(digest.getName()); - - try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r"); - FileChannel channel = raf.getChannel()) { - - long position = 0; - long length = channel.size(); - - while (position < length) { - long remaining = length - position; - long chunk = Math.min(BUFFER_SIZE * 16L, remaining); // 최대 16MB씩 매핑 - - MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, position, chunk); - buffer.load(); // 필요 시 메모리에 로드 (선택적) - - byte[] data = new byte[BUFFER_SIZE]; - int bytesToRead; - - while (buffer.hasRemaining()) { - bytesToRead = Math.min(buffer.remaining(), data.length); - buffer.get(data, 0, bytesToRead); - md.update(data, 0, bytesToRead); - } - - position += chunk; - } - } - - return Hex.toHexString(md.digest()); - } - - /** - * 두 해시값을 타이밍 공격에 안전하게 비교하는 메소드입니다. - * - * @return 일치하면 true, 그렇지 않으면 false - */ - public static boolean isEqual(String hexHash1, String hexHash2) { - if (hexHash1 == null || hexHash2 == null) return false; - if (hexHash1.length() != hexHash2.length()) return false; - - byte[] b1 = HexFormat.of().parseHex(hexHash1); - byte[] b2 = HexFormat.of().parseHex(hexHash2); - return MessageDigest.isEqual(b1, b2); - } - - /** - * {@link Digest#SHA_256} 다이제스트를 기본으로 사용하여 파일의 해시값을 확인하는 메소드입니다. - * - * @return 해시값 - */ - public static String sha256(Path filePath) throws IOException, NoSuchAlgorithmException { - return hashFile(filePath, Digest.SHA_256); - } -} diff --git a/src/main/java/space/qu4nt/entanglementlib/util/io/PemUtil.java b/src/main/java/space/qu4nt/entanglementlib/util/io/PemUtil.java deleted file mode 100644 index 3032f5f..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/util/io/PemUtil.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.util.io; - -import lombok.extern.slf4j.Slf4j; -import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; -import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; -import org.bouncycastle.openssl.PEMParser; -import org.bouncycastle.openssl.PKCS8Generator; -import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; -import org.bouncycastle.openssl.jcajce.JcaPEMWriter; -import org.bouncycastle.openssl.jcajce.JcaPKCS8Generator; -import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder; -import org.bouncycastle.operator.InputDecryptorProvider; -import org.bouncycastle.operator.OperatorCreationException; -import org.bouncycastle.operator.OutputEncryptor; -import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; -import org.bouncycastle.pkcs.PKCSException; -import org.bouncycastle.pkcs.jcajce.JcePKCSPBEOutputEncryptorBuilder; -import org.bouncycastle.util.io.pem.PemObject; -import org.bouncycastle.util.io.pem.PemWriter; -import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.InternalFactory; -import space.qu4nt.entanglementlib.util.wrapper.Pair; - -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.PosixFilePermission; -import java.nio.file.attribute.PosixFilePermissions; -import java.security.Key; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.util.Arrays; -import java.util.Objects; -import java.util.Set; - -@Slf4j -public final class PemUtil { - - private static final Path KEY_STORAGE_DIR = Paths.get("internal") // 저장 전용 폴더 - .toAbsolutePath() - .normalize(); - - public static void savePublicKeyToPEM(final @NotNull PublicKey publicKey, final @NotNull String filename) { - Objects.requireNonNull(publicKey); - Objects.requireNonNull(filename); - final Pair pathPair = checkTraversal(filename); - - byte[] encoded = publicKey.getEncoded(); - Path tempPath = null; - try { - tempPath = Files.createTempFile(pathPair.getSecond(), "temp-", ".pem"); - try (PemWriter pemWriter = new PemWriter(new OutputStreamWriter(Files.newOutputStream(tempPath)))) { - pemWriter.writeObject(new PemObject("PUBLIC KEY", encoded)); - } - - // POSIX and move - checkPosixFilePerm(publicKey, tempPath); - Files.move(tempPath, pathPair.getFirst(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); - } catch (IOException e) { - if (tempPath != null && Files.exists(tempPath)) { - try { - Files.delete(tempPath); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - throw new RuntimeException(e); - } finally { - if (encoded != null) - Arrays.fill(encoded, (byte) 0); - } - } - - public static void savePrivateKeyToPEM(final @NotNull PrivateKey privateKey, final @NotNull String filename, final char @NotNull [] password) { - Objects.requireNonNull(privateKey); - Objects.requireNonNull(filename); - Objects.requireNonNull(password); - if (password.length < 8) - throw new IllegalArgumentException("패스워드가 너무 약합니다. 최소 8자 이상 사용하세요."); - final Pair pathPair = checkTraversal(filename); - - byte[] encoded = privateKey.getEncoded(); - Path tempPath = null; - try { - - // PKCS#8 형식으로 개인키를 암호화하기 위한 설정 - OutputEncryptor encryptor = new JcePKCSPBEOutputEncryptorBuilder(PKCS8Generator.AES_256_CBC) - .setProvider(InternalFactory.getBCNormalProvider()) - .setRandom(InternalFactory.getSafeRandom()) - .setIterationCount(100_000) - .build(password); - - JcaPKCS8Generator pkcs8Generator = new JcaPKCS8Generator(privateKey, encryptor); - - tempPath = Files.createTempFile(pathPair.getSecond(), "temp-", ".pem"); - try (JcaPEMWriter pemWriter = new JcaPEMWriter(new OutputStreamWriter(Files.newOutputStream(tempPath)))) { - pemWriter.writeObject(pkcs8Generator); - pemWriter.flush(); - } - - // POSIX and move - checkPosixFilePerm(privateKey, tempPath); - Files.move(tempPath, pathPair.getFirst(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); - } catch (IOException e) { - if (tempPath != null && Files.exists(tempPath)) { - try { - Files.delete(tempPath); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - throw new RuntimeException(e); - } catch (OperatorCreationException e) { - throw new RuntimeException(e); - } finally { - if (encoded != null) - Arrays.fill(encoded, (byte) 0); - Arrays.fill(password, '\0'); - } - } - - public static PublicKey loadPublicKeyFromPEM(final @NotNull String filename) { - Objects.requireNonNull(filename); - final Pair pathPair = checkTraversal(filename); - - try (FileReader fileReader = new FileReader(pathPair.getFirst().toFile()); - PEMParser pemParser = new PEMParser(fileReader)) { - SubjectPublicKeyInfo publicKeyInfo = (SubjectPublicKeyInfo) pemParser.readObject(); - JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(InternalFactory.getBCNormalProvider()); - return converter.getPublicKey(publicKeyInfo); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public static PrivateKey loadPKCS8PrivateKetFromPEM(final @NotNull String filename, final char @NotNull [] password) { - Objects.requireNonNull(filename); - Objects.requireNonNull(password); - final Pair pathPair = checkTraversal(filename); - - try (FileReader fileReader = new FileReader(pathPair.getFirst().toFile()); - PEMParser pemParser = new PEMParser(fileReader)) { - PKCS8EncryptedPrivateKeyInfo pkcs8PrivateKeyInfo = (PKCS8EncryptedPrivateKeyInfo) pemParser.readObject(); - // 암호화된 경우 PKCS#8 형식 - if (password.length == 0) - throw new IllegalArgumentException("비밀키 로드 시 패스워드가 필요합니다!"); - - // 암호화된 키를 복호화하기 위한 제공자 빌더(AES256 복호화에 필요한 정보 포함) - InputDecryptorProvider decryptorProvider = new JceOpenSSLPKCS8DecryptorProviderBuilder() - .setProvider(InternalFactory.getBCNormalProvider()) - .build(password); - - // 개인 키 정보 복호화 - PrivateKeyInfo privateKeyInfo = pkcs8PrivateKeyInfo.decryptPrivateKeyInfo(decryptorProvider); - - final JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(InternalFactory.getBCNormalProvider()); - return converter.getPrivateKey(privateKeyInfo); - } catch (IOException | OperatorCreationException | PKCSException e) { - throw new RuntimeException(e); - } - } - - /// 전자: Full, 후자: Parent - private static Pair checkTraversal(final @NotNull String filename) { - if (filename.isEmpty() || filename.contains("..") || filename.contains("\\") || filename.startsWith("/") || filename.startsWith(File.separator)) - throw new IllegalArgumentException("잘못된 파일명 형식입니다: " + filename); - Path fullPath = KEY_STORAGE_DIR.resolve(filename).normalize(); - if (!fullPath.startsWith(KEY_STORAGE_DIR)) - throw new IllegalArgumentException("허용된 저장 디렉토리를 벗어났습니다."); - Path parent = fullPath.getParent(); - if (parent == null || !Files.exists(parent) || !Files.isWritable(parent)) - throw new IllegalArgumentException("디렉토리가 존재하지 않거나 쓰기 권한이 없습니다: " + parent); - return new Pair<>(fullPath, parent); - } - - /// POSIX 파일 권한 설정 - private static void checkPosixFilePerm(final @NotNull Key key, Path tempPath) throws IOException { - try { - Set perms = PosixFilePermissions.fromString(key instanceof PrivateKey ? "rw-------" : "rw-r--r--"); - Files.setPosixFilePermissions(tempPath, perms); - } catch (UnsupportedOperationException e) { - // Windows 등 non-POSIX: 대체 처리 (예: 로그만) - log.warn("POSIX 권한 설정 미지원 플랫폼입니다. 파일 권한을 수동 확인하세요."); - } - } - -} \ No newline at end of file diff --git a/src/main/java/space/qu4nt/entanglementlib/util/security/Password.java b/src/main/java/space/qu4nt/entanglementlib/util/security/Password.java deleted file mode 100644 index b9b881b..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/util/security/Password.java +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.util.security; - -import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Range; -import space.qu4nt.entanglementlib.InternalFactory; -import space.qu4nt.entanglementlib.security.KeyDestroyHelper; - -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -/** - * 암호학적으로 강화된 비밀번호 생성 및 관리 유틸리티 클래스입니다. - *

- * 본 클래스는 CWE-327(취약한 암호 알고리즘), CWE-330(불충분한 무작위성), CWE-14(메모리 소거 미비) 등의 - * 보안 취약점을 해결하도록 재설계되었습니다. 양자 내성(Quantum Resistance)을 고려하여 - * 엔트로피 임계값을 상향 조정하였으며, 섀넌 엔트로피(Shannon Entropy)에 기반한 정밀한 강도 측정을 수행합니다. - * - * @author Q. T. Felix - * @since 1.0.0 - */ -@Slf4j -public final class Password { - - /** - * 최소 비밀번호 길이. - *

- * {@code Grover} 알고리즘에 의한 대칭키 암호 해독 속도 가속을 고려하여, - * 양자 컴퓨팅 환경에서도 최소한의 안전성을 담보하기 위해 길이를 상향 조정했습니다. - */ - public static final int MINIMUM_LENGTH = 20; - - /** - * 기본 비밀번호 길이. - */ - public static final int DEFAULT_LENGTH = 32; - - /** - * 최대 비밀번호 길이. - *

- * 과도한 메모리 할당(CWE-400) 및 버퍼 관련 취약점을 방지하기 위해 제한폭을 축소했습니다. - */ - public static final int MAXIMUM_LENGTH = 128; - - // 문자 집합 정의 (불변성 보장 및 명시적 선언) - private static final char[] LOWERCASE = "abcdefghijklmnopqrstuvwxyz".toCharArray(); - - private static final char[] UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); - - private static final char[] DIGITS = "0123456789".toCharArray(); - - // OWASP 권장 특수문자 집합 - private static final char[] SPECIAL = "!@#$%^&*()_+-=[]{}|;:,.<>?/~`".toCharArray(); - - private static final char[] ALL_CHARS; - - static { - int totalLength = LOWERCASE.length + UPPERCASE.length + DIGITS.length + SPECIAL.length; - ALL_CHARS = new char[totalLength]; - int index = 0; - System.arraycopy(LOWERCASE, 0, ALL_CHARS, index, LOWERCASE.length); - index += LOWERCASE.length; - System.arraycopy(UPPERCASE, 0, ALL_CHARS, index, UPPERCASE.length); - index += UPPERCASE.length; - System.arraycopy(DIGITS, 0, ALL_CHARS, index, DIGITS.length); - index += DIGITS.length; - System.arraycopy(SPECIAL, 0, ALL_CHARS, index, SPECIAL.length); - } - - private Password() { - throw new UnsupportedOperationException("Singleton"); - } - - /** - * 기본 길이(32자)의 암호학적으로 안전한 비밀번호를 생성합니다. - * - * @return 생성된 비밀번호 (char 배열) - */ - public static char @NotNull [] generate() { - return generate(DEFAULT_LENGTH); - } - - /** - * 지정된 길이의 암호학적으로 안전한 비밀번호를 생성합니다. - * - * @param length 비밀번호 길이 ({@value MINIMUM_LENGTH} ~ {@value MAXIMUM_LENGTH}) - * @return 생성된 비밀번호 - * @throws IllegalArgumentException 길이가 유효 범위를 벗어난 경우 - */ - public static char @NotNull [] generate(final @Range(from = MINIMUM_LENGTH, to = MAXIMUM_LENGTH) int length) { - validateLength(length); - - final char[] password = new char[length]; - - try { - // 각 카테고리에서 최소 1개의 문자를 무작위 위치에 배치하기 위한 임시 리스트가 필요하나, - // 메모리 파편화를 줄이기 위해 배열 내에서 직접 생성 후 셔플 방식 택 - - // 1. 필수 문자군 확보 - password[0] = selectRandomChar(LOWERCASE); - password[1] = selectRandomChar(UPPERCASE); - password[2] = selectRandomChar(DIGITS); - password[3] = selectRandomChar(SPECIAL); - - // 2. 나머지 엔트로피 채움 - for (int i = 4; i < length; i++) { - password[i] = selectRandomChar(ALL_CHARS); - } - - // 3. Fisher-Yates Shuffle로 위치 무작위화 - shufflePassword(password); - - return password; - } catch (Exception e) { - wipePassword(password); // 예외 발생 시 잔존 메모리 즉시 파기 - throw new SecurityException("Failed to generate secure password due to internal error.", e); - } - } - - /** - * CSPRNG를 사용하여 문자 집합에서 무작위 문자를 선택합니다. - */ - private static char selectRandomChar(char @NotNull [] charset) { - // nextInt()는 모듈러 연산의 편향(Modulo Bias)을 내부적으로 처리함 - return charset[InternalFactory.getSafeRandom().nextInt(charset.length)]; - } - - /** - * Fisher-Yates Shuffle 알고리즘을 사용하여 배열을 섞습니다. - *

- * 통계적 편향 없이 $N!$의 순열 중 하나를 등확률로 선택합니다. - */ - private static void shufflePassword(char @NotNull [] password) { - for (int i = password.length - 1; i > 0; i--) { - int j = InternalFactory.getSafeRandom().nextInt(i + 1); - char temp = password[i]; - password[i] = password[j]; - password[j] = temp; - } - } - - /** - * 비밀번호를 메모리에서 안전하게 소거(wiping)합니다. - *

- * 컴파일러 최적화를 방지하기 위해 난수로 1차 덮어쓰기를 수행한 후, 최종적으로 0으로 초기화합니다. - * 이는 물리적 메모리 덤프 시 잔류 자화에 의한 데이터 복원 가능성을 최소화합니다. - * - * @param password 소거할 비밀번호 배열 - */ - public static void wipePassword(char @NotNull [] password) { - if (password == null) return; - - // 1단계: 무작위 노이즈로 덮어쓰기 (Deterministic 패턴 방지) - // 루프 펼치기나 DSE 최적화를 어렵게 만듦 - for (int i = 0; i < password.length; i++) - password[i] = (char) InternalFactory.getSafeRandom().nextInt(Character.MAX_VALUE); - - // 2단계: 0으로 초기화 (Nullification) - KeyDestroyHelper.zeroing(password); - - // 3단계: 메모리 펜스(Memory Fence) 효과를 위한 휘발성 읽기 시도 (선택적 구현) - // Java에서는 volatile 변수가 아닌 배열 요소에 대해 완벽한 강제가 어렵지만, - // 로그 기록 등을 통해 코드 실행 흐름 유지 - log.debug("Sensitive memory segment wiped. Address/Ref: {}", System.identityHashCode(password)); - } - - /** - * 비밀번호의 실제 정보 엔트로피(Shannon entropy)를 계산합니다. - *

- * 단순히 문자 종류의 수가 아닌, 각 문자의 출현 확률 분포를 기반으로 계산하여 - * 취약점 보고서(CWE-327)에서 지적된 엔트로피 과대평가 문제를 해결합니다. - * - * @param password 엔트로피를 계산할 비밀번호 - * @return 계산된 엔트로피 비트 수 (bits) - */ - public static double calculateEntropy(char @NotNull [] password) { - Objects.requireNonNull(password); - int length = password.length; - if (length == 0) return 0.0; - - Map frequencyMap = new HashMap<>(); - for (char c : password) { - frequencyMap.put(c, frequencyMap.getOrDefault(c, 0) + 1); - } - - double entropy = 0.0; - for (int count : frequencyMap.values()) { - double probability = (double) count / length; - entropy -= probability * (Math.log(probability) / Math.log(2)); - } - - // 섀넌 엔트로피는 '심볼 당' 정보량이므로, 전체 비밀번호의 엔트로피는 길이(L)를 곱해야 함 - return entropy * length; - } - - /** - * 비밀번호의 보안 강도를 검증합니다. - *

- * 양자 내성 암호 환경을 고려하여 기준 엔트로피를 상향 조정하였습니다. - * NIST SP 800-63B - * 가이드라인 및 Grover 알고리즘에 대한 저항성을 반영합니다. - * - * @param password 검증할 비밀번호 - * @return 안전한 비밀번호 여부 (True/False) - */ - public static boolean isStrong(char @NotNull [] password) { - Objects.requireNonNull(password); - - // 기본 길이 검증 - if (password.length < MINIMUM_LENGTH) { - log.warn("Password validation failed: Insufficient length."); - return false; - } - - boolean hasLower = false; - boolean hasUpper = false; - boolean hasDigit = false; - boolean hasSpecial = false; - - for (char c : password) { - if (Character.isLowerCase(c)) hasLower = true; - else if (Character.isUpperCase(c)) hasUpper = true; - else if (Character.isDigit(c)) hasDigit = true; - else if (isSpecialChar(c)) hasSpecial = true; - } - - // 문자 집합 복잡성 요구사항 - if (!(hasLower && hasUpper && hasDigit && hasSpecial)) { - log.warn("Password validation failed: Missing character types."); - return false; - } - - // 엔트로피 검증 - // 128비트 보안 강도는 현재 및 가까운 미래의 컴퓨팅 파워(양자 포함)에 대해 안전하다고 간주됨 - // 다만, 랜덤 생성된 비밀번호의 경우 문자 집합 크기의 한계로 인해 길이 20~25자에서 약 100~130비트가 형성 - // 최소 기준을 100비트로 설정하여 현실적인 'Strong' 기준 제시 - double actualEntropy = calculateEntropy(password); - final double REQUIRED_ENTROPY = 100.0; - - if (actualEntropy < REQUIRED_ENTROPY) { - log.warn("Password validation failed: Insufficient entropy ({}/{})", actualEntropy, REQUIRED_ENTROPY); - return false; - } - - return true; - } - - private static boolean isSpecialChar(char c) { - for (char special : SPECIAL) { - if (c == special) return true; - } - return false; - } - - private static void validateLength(int length) { - if (length < MINIMUM_LENGTH || length > MAXIMUM_LENGTH) { - throw new IllegalArgumentException( - String.format("Invalid password length: %d. Must be between %d and %d.", - length, MINIMUM_LENGTH, MAXIMUM_LENGTH)); - } - } -} \ No newline at end of file diff --git a/src/main/java/space/qu4nt/entanglementlib/util/security/SecureCharBuffer.java b/src/main/java/space/qu4nt/entanglementlib/util/security/SecureCharBuffer.java deleted file mode 100644 index 983d6c2..0000000 --- a/src/main/java/space/qu4nt/entanglementlib/util/security/SecureCharBuffer.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.util.security; - -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Range; -import space.qu4nt.entanglementlib.CallerResponsibility; -import space.qu4nt.entanglementlib.security.KeyDestroyHelper; - -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.StandardCharsets; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.function.Consumer; - -/** - * 메모리 내 민감 정보(sensitive data)의 잔존 시간을 최소화하고, 참조 누수(reference leak)를 방지하기 위한 - * 스레드 안전(thread-safe)한 보안 컨테이너 클래스입니다. - *

- * 양자-내성 암호(Post-Quantum Cryptography, PQC)의 개인 키 혹은 대칭키와 같이 높은 보안 수준이 요구되는 데이터를 - * 힙 메모리 상에서 안전하게 격리 및 소거하는 메커니즘을 제공합니다. - *

- * 다음의 주요 특징을 가집니다. - *

    - *
  • 참조 격리 (Reference Isolation): 내부 버퍼의 참조를 직접 반환하지 않고, - * {@link Consumer}를 통한 제어의 역전(IoC) 패턴을 사용하여 접근 범위를 제한합니다.
  • - *
  • 최적화 방지 소거 (Anti-DSE Zeroing): 컴파일러의 Dead Store Elimination 최적화를 우회하여 - * 물리적 메모리 상의 데이터를 확실하게 덮어씁니다.
  • - *
  • 원자적 상태 관리 (Atomic State Management): {@link ReadWriteLock}을 사용하여 데이터 - * 사용(read)과 파기(write) 작업 간의 상호 배제(mutual exclusion)를 보장합니다.
  • - *
- * 이 클래스는 {@link AutoCloseable}을 구현하기 때문에 {@code try-with-resources} 블록 내에서 사용되어야 합니다. - * - * @author Q. T. Felix - * @since 1.0.0 - */ -public final class SecureCharBuffer implements AutoCloseable { - - /** - * 민감 데이터를 저장하는 내부 버퍼입니다. - *

- * {@code transient} 키워드를 사용하여 직렬화 과정에서 평문 데이터가 - * 영구 저장소나 네트워크 스트림으로 유출되는 것을 방지합니다. - */ - private final transient char[] buffer; - - /** - * 버퍼의 파기 여부를 나타내는 원자적 플래그입니다. - * 상태 전이는 {@code False to True} 방향으로만 발생하며, 이는 비가역적입니다. - */ - private final AtomicBoolean destroyed = new AtomicBoolean(false); - - /** - * 동시성 제어를 위한 읽기-쓰기 잠금(Read-Write Lock)입니다. - *

- * 데이터의 유효성을 보장하기 위해 사용 시에는 {@code ReadLock}을, - * 파기 시에는 {@code WriteLock}을 획득하여 경쟁 상태를 방지합니다. - */ - private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); - - /** - * 외부의 문자 배열을 복제하여 안전한 내부 버퍼를 생성합니다. - * 인스턴스 {@link #buffer} 에 복사본을 할당합니다. - *

- * 원본 배열에 대한 외부 참조가 내부 버퍼의 무결성에 영향을 미치지 않도록, - * {@code O(N)} 시간 복잡도를 갖는 심층 복사를 수행하여 데이터를 격리합니다. - * - * @param source 민감 데이터의 원본 배열. {@code null}일 경우 빈 배열로 초기화 - */ - public SecureCharBuffer(char[] source) { - if (source == null) { - this.buffer = new char[0]; - } else { - this.buffer = source.clone(); - } - } - - /** - * 지정된 길이만큼의 무작위 비밀번호를 생성하여 내부 버퍼에 저장하는 메소드입니다. - * - * @param length 생성할 비밀번호의 길이 (0 이상) - */ - public SecureCharBuffer(@Range(from = 0, to = Integer.MAX_VALUE) int length) { - this.buffer = Password.generate(length).clone(); - } - - /** - * 내부 버퍼의 내용을 {@code UTF-8} 인코딩된 바이트 배열로 변환하여 반환하는 메소드입니다. - *

- * 변환 과정에서 생성된 임시 버퍼들은 즉시 소거되어 메모리에 잔존하지 않도록 처리됩니다. - *

- * 결과의 원본을 반환하기 때문에 작업 종료 시 호출자 측에서 반드시 소거 작업을 수행해야 하며, - * 이 메소드는 반드시 콜백 안에서 사용되어야 합니다. - * - * @return {@code UTF-8}로 인코딩된 바이트 배열 - */ - @CallerResponsibility("원본을 반환하기 때문에 소거 필요") - @Contract("-> !null") - byte[] toBytes() { - final CharBuffer charBuffer = CharBuffer.wrap(buffer); - final ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer); - - byte @NotNull [] rBytes = new byte[byteBuffer.remaining()]; - byteBuffer.get(rBytes); - - KeyDestroyHelper.zeroing(byteBuffer.array()); - KeyDestroyHelper.zeroing(charBuffer.array()); - - return rBytes; - } - - /** - * {@link #toBytes()} 메소드를 통해 내부 버퍼에 접근하여 바이트 배열로 변환한 뒤 안전하게 작업을 수행합니다. - *

- * 버퍼의 참조를 반환하는 기존 방식({@code f: S -> R}) 대신, 작업을 버퍼의 스코프 내부로 주입하는 방식 - * ({@code f: (R -> void) -> void})을 채택하여 참조 탈출을 원천적으로 차단합니다. - *

- * {@code ReadLock}을 획득하여 실행되므로, 이 메소드가 수행되는 동안에는 - * {@link #wipe()} 메소드에 의한 데이터 파기가 발생하지 않음이 보장됩니다. - * - * @see #use(Consumer) 문자 배열 작업을 수행하고자 하는 경우 - * @param action 버퍼를 인자로 받아 수행할 작업(Consumer) - * 이 작업은 버퍼의 참조를 외부로 유출하면 안됌 - * @throws IllegalStateException 버퍼가 이미 파기된 상태에서 호출된 경우 - */ - public void useWithBytes(Consumer<@CallerResponsibility byte[]> action) { - Lock readLock = rwLock.readLock(); - readLock.lock(); - try { - if (destroyed.get()) - throw new IllegalStateException("Access denied: Buffer has been destroyed."); - // 콜백 내부에서만 버퍼 접근 가능 - action.accept(toBytes()); - } finally { - readLock.unlock(); - } - } - - /** - * 내부 버퍼에 접근하여 안전하게 작업을 수행합니다. - *

- * 버퍼의 참조를 반환하는 기존 방식({@code f: S -> R}) 대신, 작업을 버퍼의 스코프 내부로 주입하는 방식 - * ({@code f: (R -> void) -> void})을 채택하여 참조 탈출을 원천적으로 차단합니다. - *

- * {@code ReadLock}을 획득하여 실행되므로, 이 메소드가 수행되는 동안에는 - * {@link #wipe()} 메소드에 의한 데이터 파기가 발생하지 않음이 보장됩니다. - * - * @param action 버퍼 바이트 배열를 인자로 받아 수행할 작업(Consumer) - * 이 작업은 버퍼의 참조를 외부로 유출하면 안됌 - * @throws IllegalStateException 버퍼가 이미 파기된 상태에서 호출된 경우 - */ - public void use(Consumer<@CallerResponsibility char[]> action) { - Lock readLock = rwLock.readLock(); - readLock.lock(); - try { - if (destroyed.get()) - throw new IllegalStateException("Access denied: Buffer has been destroyed."); - // 콜백 내부에서만 버퍼 접근 가능 - action.accept(buffer); - } finally { - readLock.unlock(); - } - } - - /** - * 메모리 상의 민감 데이터를 명시적으로 영소거(zeroing)합니다. - *

- * 이 메소드는 {@code WriteLock}을 획득하여 수행되므로, 다른 스레드가 데이터를 사용 중일 때는 - * 대기하며, 파기가 완료될 때까지 새로운 접근을 차단합니다. - *

- * 보안 구현 상세:
- * 단순한 {@code Arrays.fill} 호출은 JIT 컴파일러의 Dead Store Elimination 최적화에 의해 - * 제거될 위험이 있다. 이를 방지하기 위해 데이터에 임의의 값을 덮어쓰고 다시 0으로 초기화하는 - * 다단계 소거 로직을 수행하여, 메모리 쓰기 작업의 부수 효과를 강제합니다. - */ - public void wipe() { - // 이미 파기되었으면 중복 실행 방지 (CAS 연산) - if (!destroyed.compareAndSet(false, true)) - return; - - Lock writeLock = rwLock.writeLock(); - writeLock.lock(); - try { - // 1단계: 0으로 덮어쓰기 (기본 소거) - KeyDestroyHelper.zeroing(buffer); - - // 2단계: 보안 강화를 위한 추가 덮어쓰기 (Anti-Optimization) - // 컴파일러가 최적화(코드 삭제)를 수행하지 못하도록 데이터 의존성 생성 - for (int i = 0; i < buffer.length; i++) - buffer[i] = (char) (i % 0xFF); // 의미 없는 데이터 쓰기 - KeyDestroyHelper.zeroing(buffer); // 다시 소거 - } finally { - writeLock.unlock(); - } - } - - /** - * 리소스 해제 시 자동으로 호출되어 {@link #wipe()}를 수행합니다. - *

- * {@code try-with-resources} 구문을 사용할 경우, 블록을 벗어나는 즉시 - * 메모리 소거가 수행됨을 보장합니다. - *

- * 해당 방식을 사용하지 않을 경우, 작업 완료 시 수동 소거가 필요합니다. - */ - @Override - public void close() { - wipe(); - } - - /** - * 현재 버퍼가 파기되었는지 확인합니다. - * - * @return 파기되었으면 {@code true}, 유효하면 {@code false} - */ - public boolean isDestroyed() { - return destroyed.get(); - } -} diff --git a/src/test/java/space/qu4nt/entanglementlib/NativeTest.java b/src/test/java/space/qu4nt/entanglementlib/NativeTest.java deleted file mode 100644 index 8b10c22..0000000 --- a/src/test/java/space/qu4nt/entanglementlib/NativeTest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib; - -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.exception.secure.crypto.EntLibCryptoSignatureProcessingException; -import space.qu4nt.entanglementlib.security.crypto.EntLibCryptoRegistry; -import space.qu4nt.entanglementlib.security.crypto.SignatureType; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.EntLibAsymmetricKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.NativeSignatureStrategy; -import space.qu4nt.entanglementlib.util.StringUtil; -import space.qu4nt.entanglementlib.util.wrapper.Hex; -import space.qu4nt.entanglementlib.util.wrapper.Pair; - -import java.nio.charset.StandardCharsets; - -@Slf4j -class NativeTest { - - @Test - @DisplayName("Native ML-DSA keygen, sign, verify") - void mlDsaTest() throws Throwable { - // settings - byte[] plains = "Hello, ML-DSA Secure Signature World!".getBytes(StandardCharsets.UTF_8); - NativeSignatureStrategy mldsaStrategy = EntLibCryptoRegistry.getAlgStrategy(SignatureType.ML_DSA_65, NativeSignatureStrategy.class); - - // keygen - EntLibAsymmetricKeyStrategy key = EntLibCryptoRegistry.getKeyStrategy(SignatureType.ML_DSA_65, EntLibAsymmetricKeyStrategy.class); - Pair keyPair = key.generateKeyPair(); - keyPair.getFirst().exportData(); - keyPair.getSecond().exportData(); - log.info("PK: {}, SK: {}", - StringUtil.truncateMiddle(Hex.toHexString(keyPair.getFirst().getSegmentData()), 6, 6), - StringUtil.truncateMiddle(Hex.toHexString(keyPair.getSecond().getSegmentData()), 6, 6)); - - // sign - SensitiveDataContainer complex = mldsaStrategy.sign(keyPair.getSecond(), plains); - complex.addContainerData(keyPair.getFirst()); // 공개 키 전달 - if (complex == null) - throw new RuntimeException("서명 실패"); - complex.exportData(); - log.info("Signature: {}", StringUtil.truncateMiddle(Hex.toHexString(complex.getSegmentData()), 6, 6)); - - // ...통신 - - // verify - var t = new Thread(() -> { - try { - log.info("Verify: {}", mldsaStrategy.verify(complex)); - } catch (EntLibCryptoSignatureProcessingException e) { - throw new RuntimeException(e); - } - }); - t.start(); - t.join(); - - // all wipe - complex.close(); - } -} diff --git a/src/test/java/space/qu4nt/entanglementlib/entlibnative/SDCBindingsConcurrencyTest.java b/src/test/java/space/qu4nt/entanglementlib/entlibnative/SDCBindingsConcurrencyTest.java deleted file mode 100644 index edbd78e..0000000 --- a/src/test/java/space/qu4nt/entanglementlib/entlibnative/SDCBindingsConcurrencyTest.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.entlibnative; - -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.RepeatedTest; -import org.junit.jupiter.api.Test; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalStateException; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/// [SensitiveDataContainer]의 동시성 이슈를 검증하기 위한 테스트 클래스입니다. -/// [`bindings`][SensitiveDataContainer#bindings] 리스트의 복합 -/// 연산(iterate-then-Clear)에 대한 Thread-Safety를 집중적으로 테스트합니다. -/// -/// @author Q. T. Felix -/// @since 1.1.0 -@Slf4j -@SuppressWarnings({"all"}) -class SDCBindingsConcurrencyTest { - - // 테스트 반복 횟수 (동시성 문제는 간헐적으로 발생해서 반복 테스트) - private static final int REPEAT_COUNT = 10; - private static final int THREAD_COUNT = 16; - private static final int CHILDREN_COUNT = 100; - - /// # Scenario 1 - /// - /// 다수의 스레드가 동시에 close()를 호출할 때의 안정성 테스트 - /// - /// # Expected - /// - /// [`bindings`][SensitiveDataContainer#bindings] 리스트에 대한 접근 경합으로 - /// 인해 [IndexOutOfBoundsException] 등이 발생할 가능성이 있음. - /// (현재 구현상 [`close()`][SensitiveDataContainer#close()] 메소드는 동기화 - /// 블록 없이 인덱스로 접근하므로 실패할 확률이 높음) - @RepeatedTest(REPEAT_COUNT) - @DisplayName("동시 close() 호출 시 예외 발생 여부 검증 (Race Condition)") - void testConcurrentClose() throws InterruptedException, EntLibSecureIllegalStateException { - // Given: 많은 자식 컨테이너를 가진 부모 컨테이너 생성 - SensitiveDataContainer parent = new SensitiveDataContainer(1024); - for (int i = 0; i < CHILDREN_COUNT; i++) { - parent.addContainerData(new SensitiveDataContainer(128)); - } - - ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT); - CountDownLatch latch = new CountDownLatch(1); - AtomicInteger exceptionCount = new AtomicInteger(0); - - // When: 동시에 여러 스레드에서 close() 호출 - for (int i = 0; i < THREAD_COUNT; i++) { - executorService.submit(() -> { - try { - latch.await(); // 모든 스레드가 준비될 때까지 대기 - parent.close(); - } catch (Exception e) { - // 예외 발생 시 카운트 (주로 IndexOutOfBoundsException 예상) - exceptionCount.incrementAndGet(); - e.printStackTrace(); // 디버깅용 로그 - } - }); - } - - latch.countDown(); // 땅! 모든 스레드 시작 - executorService.shutdown(); - boolean finished = executorService.awaitTermination(5, TimeUnit.SECONDS); - - // Then - assertTrue(finished, "테스트가 시간 내에 종료되지 않았습니다!"); - assertEquals(0, exceptionCount.get(), - "동시 close() 호출 중 예외가 발생했습니다. Thread-Safety가 보장되지 않습니다!"); - } - - /// # Scenario 2 - /// - /// [`close()`][SensitiveDataContainer#close()]가 진행되는 동안 - /// [`addContainerData()`][SensitiveDataContainer#addContainerData()]가 - /// 호출될 때의 무결성 테스트 - /// - /// # Expected - /// - /// 1. 닫히는 도중 추가된 데이터가 `close()` 되지 않고 유실(leak)되는지 확인 - /// 2. 반복문 인덱스 접근 중 리스트 사이즈 변경으로 인한 예외 확인 - /// - @Test - @DisplayName("close()와 addContainerData() 동시 수행 시 무결성 검증") - void testAddWhileClosing() throws InterruptedException { - // Given - SensitiveDataContainer parent = new SensitiveDataContainer(1024); - int addCount = 1000; - - ExecutorService executorService = Executors.newFixedThreadPool(2); - CountDownLatch startLatch = new CountDownLatch(1); - AtomicInteger successAddCount = new AtomicInteger(0); - AtomicInteger exceptionCount = new AtomicInteger(0); - - // Thread 1: 지속적으로 데이터 추가 - Runnable adder = () -> { - try { - startLatch.await(); - for (int i = 0; i < addCount; i++) { - // 이미 닫힌 경우(Native Memory 해제됨) IllegalStateException 등이 발생할 수 있음 - try { - parent.addContainerData(new SensitiveDataContainer(10)); - successAddCount.incrementAndGet(); - } catch (Exception e) { - // close()에 의해 Arena가 닫힌 후 추가 시도는 실패하는 것이 정상일 수 있는데 - // 리스트 관련 예외(IndexOutOfBounds 등)는 비정상임 - if (!(e instanceof IllegalStateException)) { - exceptionCount.incrementAndGet(); - } - } - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - }; - - // Thread 2: 약간의 지연 후 close() 수행 - Runnable closer = () -> { - try { - startLatch.await(); - Thread.sleep(2); // 추가 작업이 어느 정도 진행된 후 닫기 시도 - parent.close(); - } catch (Exception e) { - exceptionCount.incrementAndGet(); - e.printStackTrace(); - } - }; - - // When - executorService.submit(adder); - executorService.submit(closer); - - startLatch.countDown(); - executorService.shutdown(); - executorService.awaitTermination(5, TimeUnit.SECONDS); - - // Then - // 만약 리스트 동기화가 제대로 안 되었다면 여기서 예외가 포착될 것임 - assertEquals(0, exceptionCount.get(), - "close()와 add() 경합 중 예외가 발생했습니다!"); - } -} \ No newline at end of file diff --git a/src/test/java/space/qu4nt/entanglementlib/entlibnative/SDCSemiPerformanceTest.java b/src/test/java/space/qu4nt/entanglementlib/entlibnative/SDCSemiPerformanceTest.java deleted file mode 100644 index 992974b..0000000 --- a/src/test/java/space/qu4nt/entanglementlib/entlibnative/SDCSemiPerformanceTest.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.entlibnative; - -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -@Slf4j -class SDCSemiPerformanceTest { - - @Test - @DisplayName("랜덤 바이트 생성 퍼포먼스(32)") - void randomBytesTest() throws InterruptedException { - log.info("안정화 로드: 5초 대기"); - Thread.sleep(5000); - final int loop = 10_000_000; - final int gen = 32; - - long start = System.nanoTime(); - log.info("bytes(to hex): {}", loop); - for (int i = 0; i < loop; i++) { - SensitiveDataContainer.generateSafeRandomBytes(gen); -// if ((i & 1) == 0) -// log.info("{}: {}", i, Hex.toHexString(SensitiveDataContainer.generateSafeRandomBytes(gen))); - } - log.info("total: {}s", (System.nanoTime() - start) / 1_000_000_000.0); - - start = System.nanoTime(); - log.info(""); - log.info("bytes(to base64): {}", loop); - for (int i = 0; i < loop; i++) { - SensitiveDataContainer.generateBase64String(gen); -// if ((i & 1) == 0) -// log.info("{}: {}", i, SensitiveDataContainer.generateBase64String(gen)); - } - log.info("total: {}s", (System.nanoTime() - start) / 1_000_000_000.0); - } -} \ No newline at end of file diff --git a/src/test/java/space/qu4nt/entanglementlib/security/communication/SessionTest.java b/src/test/java/space/qu4nt/entanglementlib/security/communication/SessionTest.java deleted file mode 100644 index f12b49e..0000000 --- a/src/test/java/space/qu4nt/entanglementlib/security/communication/SessionTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.communication; - -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.exception.session.EntLibSessionException; -import space.qu4nt.entanglementlib.exception.session.EntLibSessionIllegalStateException; -import space.qu4nt.entanglementlib.security.communication.session.*; -import space.qu4nt.entanglementlib.security.crypto.KEMType; -import space.qu4nt.entanglementlib.security.crypto.SignatureType; - -import java.io.IOException; -import java.nio.channels.SocketChannel; -import java.nio.charset.StandardCharsets; - -@Slf4j -class SessionTest { - - @Test - @DisplayName("경량형 세션 테스트") - void lightweightSessionTest() throws EntLibSessionException, IOException, EntLibSessionIllegalStateException { - final Session session = Session.create("test-s1", SessionConfig.defaults()); - session.addEventListener(new SessionEventListener() { - @Override - public void onParticipantJoined(Session session, Participant participant) { - log.info("테스트: 세션: {}, 참여자: {}", session.getSessionId(), participant.getId()); - } - }); - - SessionSecurityContext sessionSecurityContext = new SessionSecurityContext(); - sessionSecurityContext.initialize( - new SensitiveDataContainer("This-is-master-key".getBytes(StandardCharsets.UTF_8), false), - new SensitiveDataContainer(SensitiveDataContainer.generateSafeRandomBytes(16), false), - KEMType.X25519MLKEM768, - SignatureType.ML_DSA_65, - false - ); - session.setSessionSecurityContext(sessionSecurityContext); - - Participant p1 = new Participant("part-1", ParticipantRole.OBSERVER, SocketChannel.open(), 0, null); - Participant p2 = new Participant("part-2", ParticipantRole.OBSERVER, SocketChannel.open(), 0, null); - - session.addParticipant(p1); - - session.activate(); - - session.addParticipant(p2); - - log.info("session: {}", session); - } -} \ No newline at end of file diff --git a/src/test/java/space/qu4nt/entanglementlib/security/communication/tls/ServerTest.java b/src/test/java/space/qu4nt/entanglementlib/security/communication/tls/ServerTest.java deleted file mode 100644 index f7e7c9e..0000000 --- a/src/test/java/space/qu4nt/entanglementlib/security/communication/tls/ServerTest.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.communication.tls; - -import org.junit.jupiter.api.*; -import space.qu4nt.entanglementlib.security.communication.session.SessionConfig; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.nio.ByteBuffer; -import java.nio.channels.SocketChannel; - -import static org.junit.jupiter.api.Assertions.*; - -class ServerTest { - - private static final int TEST_PORT = 9999; - private static Server server; - private static Thread serverThread; - - @BeforeAll - static void setup() throws IOException { - // 테스트용 서버 설정 (싱글 세션 모드, 가벼운 설정) - ServerConfig config = ServerConfig.builder() - .port(TEST_PORT) - .bindAddress("127.0.0.1") - .singleSessionMode(true) - .sessionConfig(SessionConfig.lightweight()) // 테스트 속도를 위해 경량 설정 - .build(); - - server = new Server(config); - - // 서버 시작 (비동기 스레드에서 실행되지 않으므로 별도 스레드 필요 여부 확인 -> start()는 non-blocking인가? - // Server.start() 내부 구현을 보면 eventLoopThread를 시작하고 바로 리턴하므로 메인 스레드 블로킹 없음.) - server.start(); - - // 서버가 RUNNING 상태가 될 때까지 잠시 대기 - awaitServerState(ServerState.RUNNING); - } - - @AfterAll - static void teardown() { - if (server != null && server.isRunning()) { - server.stop(); - } - } - - @Test - @Order(1) - @DisplayName("서버 생명주기 테스트 - 시작 및 상태 확인") - void testServerLifecycle() { - assertTrue(server.isRunning(), "서버가 실행 중이어야 합니다."); - assertEquals(ServerState.RUNNING, server.getState(), "서버 상태는 RUNNING이어야 합니다."); - assertNotNull(server.getActiveSessions(), "세션 맵은 null이 아니어야 합니다."); - } - - @Test - @Order(2) - @DisplayName("클라이언트 연결 테스트 - 접속 및 카운트 확인") - void testClientConnection() throws IOException, InterruptedException { - try (SocketChannel client = SocketChannel.open()) { - client.connect(new InetSocketAddress("127.0.0.1", TEST_PORT)); - client.configureBlocking(true); - - // 서버가 이벤트를 처리할 시간을 줌 - Thread.sleep(100); - - assertEquals(1, server.getConnectedClientCount(), "연결된 클라이언트 수는 1이어야 합니다."); - - // 싱글 세션 모드이므로 세션이 1개 생성되어야 함 - assertEquals(1, server.getActiveSessions().size(), "활성 세션이 1개 존재해야 합니다."); - } - - // 연결 종료 후 처리 대기 - Thread.sleep(100); - assertEquals(0, server.getConnectedClientCount(), "연결 종료 후 클라이언트 수는 0이어야 합니다."); - } - - @Test - @Order(3) - @DisplayName("보안 방어 테스트 - 악의적인 핸드셰이크 크기 전송") - void testMaliciousHandshakeDefense() throws IOException, InterruptedException { - try (SocketChannel client = SocketChannel.open()) { - client.connect(new InetSocketAddress("127.0.0.1", TEST_PORT)); - client.configureBlocking(true); - - // 악의적인 패킷 생성: [Type:ClientHello] + [Length: 20,000 (Max 초과)] - ByteBuffer maliciousPacket = ByteBuffer.allocate(5); - maliciousPacket.put(HandshakeMessage.CLIENT_HELLO); - maliciousPacket.putInt(20000); // MAX_HANDSHAKE_MSG_SIZE(16KB) 초과 - maliciousPacket.flip(); - - client.write(maliciousPacket); - - // 서버가 이를 감지하고 연결을 끊거나 예외를 처리했는지 확인 - // Server.java 로직상 예외 발생 시 closeParticipant()가 호출됨. - - // 약간의 지연 시간 (서버 처리 대기) - Thread.sleep(200); - - // 연결이 서버에 의해 끊겼는지 확인 (Read 시 -1 반환) - ByteBuffer buffer = ByteBuffer.allocate(10); - int bytesRead = client.read(buffer); - - // 참고: 서버 구현에 따라 즉시 소켓을 닫거나, 다음 읽기 시 닫을 수 있음. - // 여기서는 클라이언트 목록에서 제거되었는지를 확인 - assertEquals(0, server.getConnectedClientCount(), - "악의적인 패킷을 보낸 클라이언트는 강제 연결 종료되어야 합니다."); - } - } - - @Test - @Order(4) - @DisplayName("핸드셰이크 프로토콜 테스트 - Client Hello 전송 및 응답 수신") - void testHandshakeProtocol() throws IOException, InterruptedException { - // 주의: 이 테스트는 실제 PQC 암호화 라이브러리(Native)가 로드되어 있다고 가정합니다. - // 네이티브 라이브러리가 없다면 EntLibCryptoRegistry에서 예외가 발생할 수 있습니다. - - try (SocketChannel client = SocketChannel.open()) { - client.connect(new InetSocketAddress("127.0.0.1", TEST_PORT)); - client.configureBlocking(true); - - // 가짜 공개키 데이터 생성 (실제 키는 아니지만 길이 검증 통과용) - int fakeKeyLength = 100; - ByteBuffer clientHello = ByteBuffer.allocate(1 + 4 + fakeKeyLength); - - clientHello.put(HandshakeMessage.CLIENT_HELLO); - clientHello.putInt(fakeKeyLength); - for (int i = 0; i < fakeKeyLength; i++) { - clientHello.put((byte) i); - } - clientHello.flip(); - - // 전송 - client.write(clientHello); - - // 서버 응답 대기 (ServerHello) - // 응답 구조: [Type:1] + [ServerPkLen:4] + [ServerPk] + [EncapLen:4] + [Encap] - ByteBuffer responseBuffer = ByteBuffer.allocate(1024); - - // 넉넉하게 대기 - Thread.sleep(500); - - int bytesRead = client.read(responseBuffer); - - if (bytesRead > 0) { - responseBuffer.flip(); - byte msgType = responseBuffer.get(); - - assertEquals(HandshakeMessage.SERVER_HELLO, msgType, "서버 응답은 SERVER_HELLO 타입이어야 합니다."); - - int serverPkLen = responseBuffer.getInt(); - assertTrue(serverPkLen > 0, "서버 공개키 길이는 0보다 커야 합니다."); - - // 버퍼 포지션 이동 (공개키 스킵) - responseBuffer.position(responseBuffer.position() + serverPkLen); - - int encapLen = responseBuffer.getInt(); - assertTrue(encapLen > 0, "캡슐화 데이터 길이는 0보다 커야 합니다."); - - System.out.println("핸드셰이크 응답 수신 성공: " + bytesRead + " bytes"); - } else { - // 네이티브 라이브러리가 없어서 서버 내부 오류로 응답이 없을 수 있음. - // 이 경우 테스트 실패 처리가 맞으나, 환경에 따라 유연하게 로그만 남김. - System.err.println("경고: 서버로부터 응답을 받지 못했습니다. (네이티브 라이브러리 문제일 수 있음)"); - } - } catch (Exception e) { - // 암호화 관련 예외는 무시 (통신 흐름만 검증) - System.out.println("암호화 모듈 실행 중 예외 발생 (예상된 동작일 수 있음): " + e.getMessage()); - } - } - - // --- Helper Methods --- - - private static void awaitServerState(ServerState expectedState) { - long start = System.currentTimeMillis(); - while (System.currentTimeMillis() - start < 5000) { // 5초 대기 - if (server.getState() == expectedState) { - return; - } - try { - Thread.sleep(50); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - break; - } - } - throw new RuntimeException("서버가 " + expectedState + " 상태로 전환되지 않았습니다. 현재 상태: " + server.getState()); - } -} \ No newline at end of file diff --git a/src/test/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/AESStrategyTest.java b/src/test/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/AESStrategyTest.java deleted file mode 100644 index 53c72ad..0000000 --- a/src/test/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/AESStrategyTest.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.strategy.detail; - -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalArgumentException; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalStateException; -import space.qu4nt.entanglementlib.exception.secure.crypto.EntLibCryptoCipherProcessException; -import space.qu4nt.entanglementlib.security.crypto.CipherType; -import space.qu4nt.entanglementlib.security.crypto.EntLibCryptoRegistry; -import space.qu4nt.entanglementlib.security.crypto.Mode; -import space.qu4nt.entanglementlib.security.crypto.Padding; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.EntLibSymmetricKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.BlockCipherStrategy; - -import java.nio.charset.StandardCharsets; -import java.util.Objects; - -@Slf4j -class AESStrategyTest { - - @Test - @DisplayName("AES Test") - void test() throws EntLibSecureIllegalArgumentException, EntLibSecureIllegalStateException, EntLibCryptoCipherProcessException { - final SensitiveDataContainer key = EntLibCryptoRegistry.getKeyStrategy(CipherType.AES_256, EntLibSymmetricKeyStrategy.class).generateKey(); - - final SensitiveDataContainer iv = new SensitiveDataContainer(16); - BlockCipherStrategy aes = EntLibCryptoRegistry.getAlgStrategy(CipherType.AES_256, BlockCipherStrategy.class) - .setMode(Mode.CBC) - .setPadding(Padding.PKCS7); - // - aes.iv(iv); // 외부에서 IV 할당 - // - - SensitiveDataContainer enc = aes.encrypt(key, "This is Plain!".getBytes(StandardCharsets.UTF_8), false); - enc.exportData(); - log.info("ENC: {}", enc.getSegmentDataBase64()); - - SensitiveDataContainer dec = aes.decrypt(key, enc, false); - dec.exportData(); - log.info("DEC: {}", new String(Objects.requireNonNull(dec.getSegmentData()))); - } -} \ No newline at end of file diff --git a/src/test/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/ChaCha20StrategyTest.java b/src/test/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/ChaCha20StrategyTest.java deleted file mode 100644 index d6b3492..0000000 --- a/src/test/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/ChaCha20StrategyTest.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.strategy.detail; - -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalArgumentException; -import space.qu4nt.entanglementlib.exception.secure.EntLibSecureIllegalStateException; -import space.qu4nt.entanglementlib.exception.secure.crypto.EntLibCryptoCipherProcessException; -import space.qu4nt.entanglementlib.security.crypto.CipherType; -import space.qu4nt.entanglementlib.security.crypto.EntLibCryptoRegistry; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.EntLibSymmetricKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.StreamCipherStrategy; - -import java.nio.charset.StandardCharsets; -import java.util.Objects; - -@Slf4j -class ChaCha20StrategyTest { - - @Test - @DisplayName("ChaCha20 Test") - void test() throws EntLibSecureIllegalArgumentException, EntLibSecureIllegalStateException, EntLibCryptoCipherProcessException { - final SensitiveDataContainer key = EntLibCryptoRegistry.getKeyStrategy(CipherType.CHACHA20, EntLibSymmetricKeyStrategy.class).generateKey(); - - final SensitiveDataContainer iv = new SensitiveDataContainer(8); - StreamCipherStrategy chacha20 = EntLibCryptoRegistry.getAlgStrategy(CipherType.CHACHA20, StreamCipherStrategy.class); - // - chacha20.iv(iv); // 외부에서 IV 할당 - // - - SensitiveDataContainer enc = chacha20.encrypt(key, "This is Plain!".getBytes(StandardCharsets.UTF_8), false); - enc.exportData(); - log.info("ENC: {}", enc.getSegmentDataBase64()); - - SensitiveDataContainer dec = chacha20.decrypt(key, enc, false); - dec.exportData(); - log.info("DEC: {}", new String(Objects.requireNonNull(dec.getSegmentData()))); - } -} \ No newline at end of file diff --git a/src/test/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/MLKEMStrategyTest.java b/src/test/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/MLKEMStrategyTest.java deleted file mode 100644 index 01aa47e..0000000 --- a/src/test/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/MLKEMStrategyTest.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.strategy.detail; - -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.security.crypto.EntLibCryptoRegistry; -import space.qu4nt.entanglementlib.security.crypto.KEMType; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.EntLibAsymmetricKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.NativeKEMStrategy; -import space.qu4nt.entanglementlib.util.wrapper.Hex; - -/// 무지성테스트 -@Slf4j -class MLKEMStrategyTest { - - @Test - @DisplayName("ML-KEM Test") - void test() throws Throwable { - // given - EntLibAsymmetricKeyStrategy key = EntLibCryptoRegistry.getKeyStrategy(KEMType.ML_KEM_768, EntLibAsymmetricKeyStrategy.class); - NativeKEMStrategy mlkem = EntLibCryptoRegistry.getAlgStrategy(KEMType.ML_KEM_768, NativeKEMStrategy.class); - var pair = key.generateKeyPair(); - - // when then - pair.getFirst().exportData(); - log.info("PK: {}", pair.getFirst().getSegmentData().length); - SensitiveDataContainer capsule = mlkem.encapsulate(pair.getFirst()); - capsule.exportData(); - log.info("CAPSULE: {}", Hex.toHexString(capsule.getSegmentData())); - - SensitiveDataContainer sharedSecret = mlkem.decapsulate(pair.getSecond(), capsule.get(0).get()); - sharedSecret.exportData(); - log.info("SS: {}", Hex.toHexString(sharedSecret.getSegmentData())); - } -} \ No newline at end of file diff --git a/src/test/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/hybrid/X25519MLKEM768StrategyTest.java b/src/test/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/hybrid/X25519MLKEM768StrategyTest.java deleted file mode 100644 index d0be45b..0000000 --- a/src/test/java/space/qu4nt/entanglementlib/security/crypto/strategy/detail/hybrid/X25519MLKEM768StrategyTest.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright © 2025-2026 Quant. - * Under License "PolyForm Noncommercial License 1.0.0". - */ - -package space.qu4nt.entanglementlib.security.crypto.strategy.detail.hybrid; - -import lombok.extern.slf4j.Slf4j; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import space.qu4nt.entanglementlib.EntanglementLibBootstrap; -import space.qu4nt.entanglementlib.entlibnative.SensitiveDataContainer; -import space.qu4nt.entanglementlib.exception.secure.crypto.EntLibCryptoKEMProcessingException; -import space.qu4nt.entanglementlib.security.crypto.EntLibCryptoRegistry; -import space.qu4nt.entanglementlib.security.crypto.KEMType; -import space.qu4nt.entanglementlib.security.crypto.ParameterSizeDetail; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.EntLibAsymmetricKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.detail.MLKEMKeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.detail.X25519KeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.key.strategy.detail.X25519MLKEM768KeyStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.NativeKEMStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.MLKEMStrategy; -import space.qu4nt.entanglementlib.security.crypto.strategy.detail.X25519Strategy; -import space.qu4nt.entanglementlib.util.wrapper.Pair; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - -/// [X25519MLKEM768Strategy]의 통합 테스트입니다. -/// -/// 실제 하위 전략(X25519Strategy, MLKEMStrategy)을 사용하여 하이브리드 키 교환의 -/// 데이터 흐름과 컨테이너 구조(Binding)를 검증합니다. -/// -/// **주의:** 이 테스트는 네이티브 라이브러리가 로드된 환경을 가정합니다. -/// 또한, 임의로 생성된(Random) 더미 키를 사용하므로, 실제 암호학적 연산(Shared Secret 일치 여부)보다는 -/// 데이터의 규격(Size)과 입출력 구조의 정합성을 검증하는 데 목적이 있습니다. -/// -/// @author Q. T. Felix -@Slf4j -class X25519MLKEM768StrategyTest { - - private static NativeKEMStrategy x25519Strategy; - private static EntLibAsymmetricKeyStrategy x25519KeyStrategy; - private static NativeKEMStrategy mlkem768Strategy; - private static EntLibAsymmetricKeyStrategy mlkem768KeyStrategy; - - private static ParameterSizeDetail hybridDetail; - - @BeforeAll - static void setupParameterDetails() { - EntanglementLibBootstrap.registerEntanglementLib("X25519MLKEM768-TEST", true); - - x25519Strategy = EntLibCryptoRegistry.getAlgStrategy(KEMType.X25519, NativeKEMStrategy.class); - x25519KeyStrategy = EntLibCryptoRegistry.getKeyStrategy(KEMType.X25519, EntLibAsymmetricKeyStrategy.class); - mlkem768Strategy = EntLibCryptoRegistry.getAlgStrategy(KEMType.ML_KEM_768, NativeKEMStrategy.class); - mlkem768KeyStrategy = EntLibCryptoRegistry.getKeyStrategy(KEMType.ML_KEM_768, EntLibAsymmetricKeyStrategy.class); - - hybridDetail = KEMType.X25519MLKEM768.getParameterSizeDetail(); - - log.info("하이브리드 KEM (X25519 + ML-KEM-768) 테스트 초기화"); - log.info("하이브리드 PK 크기: {} 바이트", hybridDetail.getEncapsulationKeySize()); - log.info("하이브리드 CT 크기: {} 바이트", hybridDetail.getCiphertextSize()); - } - - @Test - @DisplayName("통합 테스트: 캡슐화 및 디캡슐화 수행 시 데이터 구조와 바인딩이 올바르게 유지되어야 함") - void testHybridEncapsulationFlow() throws Throwable { - // Given. 전략 인스턴스 생성 - final X25519MLKEM768Strategy strategy = EntLibCryptoRegistry.getAlgStrategy(KEMType.X25519MLKEM768, X25519MLKEM768Strategy.class); - strategy.setX25519Strategy((X25519Strategy) x25519Strategy); - strategy.setMlkemStrategy((MLKEMStrategy) mlkem768Strategy); - - // ECDHE 페어 생성 - final X25519MLKEM768KeyStrategy ecdhePairStrategy = - EntLibCryptoRegistry.getKeyStrategy(KEMType.X25519MLKEM768, X25519MLKEM768KeyStrategy.class); - ecdhePairStrategy.setX25519Key((X25519KeyStrategy) x25519KeyStrategy); - ecdhePairStrategy.setMlkem768Key((MLKEMKeyStrategy) mlkem768KeyStrategy); - final Pair ecdhePair = ecdhePairStrategy.generateKeyPair(); - - log.debug("더미 키 생성됨. 캡슐화 진행 중..."); - - // When & Then 1. 캡슐화 - try (SensitiveDataContainer ssResult = strategy.encapsulate(ecdhePair.getFirst())) { - - // 공유 비밀 사이즈 검증 - assertThat(ssResult.getMemorySegment().byteSize()) - .as("하이브리드 공유 비밀의 크기는 X25519와 ML-KEM의 합이어야 함") - .isEqualTo(hybridDetail.getSharedSecretKeySize()); - - // 암호문 바인딩 검증 - assertThat(ssResult.get(0)).isPresent(); - - @SuppressWarnings("OptionalGetWithoutIsPresent") SensitiveDataContainer ctResult = ssResult.get(0).get(); - assertThat(ctResult.getMemorySegment().byteSize()) - .as("하이브리드 암호문의 크기는 X25519와 ML-KEM의 합이어야 함") - .isEqualTo(hybridDetail.getCiphertextSize()); - - log.debug("캡슐화 성공. SS 크기: {}, CT 크기: {}", - ssResult.getMemorySegment().byteSize(), ctResult.getMemorySegment().byteSize()); - - // When & Then 2, 디캡슐화 - // 캡슐화로 생성된 CT와 더미 SK를 사용하여 디캡슐화 시도 - // 네이티브 라이브러리가 유효하지 않은 키에 대해 에러를 던질 수 있으므로 예외 처리 검증 포함 - try (SensitiveDataContainer recoveredSs = strategy.decapsulate(ecdhePair.getSecond(), ctResult)) { - - assertThat(recoveredSs.getMemorySegment().byteSize()) - .as("복원된 공유 비밀의 크기는 원본과 동일해야 함") - .isEqualTo(hybridDetail.getSharedSecretKeySize()); - - log.debug("디캡슐화 성공. 복원된 공유 비밀 크기: {}", - recoveredSs.getMemorySegment().byteSize()); - - } catch (IllegalArgumentException e) { - log.warn("더미 키로 인해 네이티브 암호화 작업 실패 (구조적 테스트에서 예상된 동작): {}", e.getMessage()); - } - } catch (Exception e) { - log.error("하이브리드 테스트 흐름 중 예상치 못한 오류 발생", e); - throw new RuntimeException(e); - } finally { - ecdhePair.getFirst().close(); - ecdhePair.getSecond().close(); - } - } - - @Test - @DisplayName("검증: 잘못된 크기의 키 입력 시 예외가 발생해야 함") - void testInvalidInputSize() { - final X25519MLKEM768Strategy strategy = EntLibCryptoRegistry.getAlgStrategy(KEMType.X25519MLKEM768, X25519MLKEM768Strategy.class); - strategy.setX25519Strategy((X25519Strategy) x25519Strategy); - strategy.setMlkemStrategy((MLKEMStrategy) mlkem768Strategy); - - // 규격보다 1바이트 작은 더미 키 - try (SensitiveDataContainer invalidPk = createDummyContainer(hybridDetail.getEncapsulationKeySize() - 1)) { - assertDoesNotThrow(() -> { - try { - strategy.encapsulate(invalidPk); - } catch (EntLibCryptoKEMProcessingException e) { - log.debug("잘못된 PK 크기에 대한 예상된 예외 포착: {}", e.getMessage()); - return; - } - throw new AssertionError("잘못된 PK 사이즈임에도 예외가 발생하지 않음"); - }); - } - } - - /// 지정된 크기의 랜덤 데이터로 채워진 컨테이너를 생성합니다. - private SensitiveDataContainer createDummyContainer(int size) { - byte[] randomBytes = SensitiveDataContainer.generateSafeRandomBytes(size); - return new SensitiveDataContainer(randomBytes, true); - } -} \ No newline at end of file diff --git a/src/test/resources/logback.xml b/src/test/resources/logback.xml deleted file mode 100644 index 6d80f41..0000000 --- a/src/test/resources/logback.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - [%d{HH:mm:ss.SSS}] %boldCyan(%-15.-15thread) %boldGreen(%-15.-30logger{0}) %highlight(%-6level) %msg%n - - - - - - - - \ No newline at end of file From 44b9d9130d6475730c0e12a93f31b50a6e0cc6a6 Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Wed, 4 Mar 2026 11:42:24 +0900 Subject: [PATCH 12/13] =?UTF-8?q?https://github.com/Quant-Off/entanglement?= =?UTF-8?q?lib/pull/13#issuecomment-3991816416=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 4 ++++ .../security/crypto/encode/Base64.java | 1 - .../entanglementlib/security/crypto/rng/RNG.java | 2 +- .../security/data/SensitiveDataContainer.java | 4 ++-- .../entlibnative/EntLibNativeManager.java | 5 +++-- .../security/crypto/Base64Test.java | 2 +- .../security/crypto/ChaCha20Test.java | 7 ++++--- .../entanglementlib/security/crypto/RNGTest.java | 11 +++++++---- .../security/crypto/SHA2HashTest.java | 11 ++++++----- .../security/crypto/SHA3HashTest.java | 15 ++++++++------- 10 files changed, 36 insertions(+), 26 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 043e846..ac994f7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -149,4 +149,8 @@ subprojects { tasks.withType { duplicatesStrategy = DuplicatesStrategy.INCLUDE } + + tasks.named("sourcesJar") { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + } } \ No newline at end of file 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 c1052cb..b982290 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 @@ -4,7 +4,6 @@ import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer; import space.qu4nt.entanglementlib.security.entlibnative.EntLibNativeManager; import space.qu4nt.entanglementlib.security.entlibnative.Function; -import sun.misc.Unsafe; /** * 네이티브 메모리(native memory) 기반의 안전한 Base64 인코딩 및 디코딩 유틸리티입니다. 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 8b102f4..e5e9473 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 @@ -1,8 +1,8 @@ package space.qu4nt.entanglementlib.security.crypto.rng; import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.core.exception.security.critical.ELIBSecurityNativeCritical; import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityProcessException; +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; 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 2cbe3a7..459f5f7 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 @@ -9,7 +9,6 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import space.qu4nt.entanglementlib.annotations.CallerResponsibility; import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityProcessException; import space.qu4nt.entanglementlib.security.entlibnative.EntLibNativeManager; @@ -21,7 +20,8 @@ import java.lang.foreign.ValueLayout; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; -import java.util.*; +import java.util.Arrays; +import java.util.Objects; /// `Rust`의 소유권(ownership) 개념처럼 이 클래스도 전달받은 민감 정보에 대한 소유권을 가집니다. /// 이 클래스에 저장된 데이터는 진행중인 세션에 종속되고, 세션이 종료됨에 따라 모든 데이터가 완벽하게 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 index a224d86..91763ef 100644 --- a/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/EntLibNativeManager.java +++ b/security/src/main/java/space/qu4nt/entanglementlib/security/entlibnative/EntLibNativeManager.java @@ -1,7 +1,6 @@ package space.qu4nt.entanglementlib.security.entlibnative; import org.jetbrains.annotations.NotNull; -import space.qu4nt.entanglementlib.annotations.CallerResponsibility; 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; @@ -9,7 +8,9 @@ import space.qu4nt.entanglementlib.security.data.SDCScopeContext; import space.qu4nt.entanglementlib.security.data.SensitiveDataContainer; -import java.lang.foreign.*; +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; 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 6fc7d79..c320453 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 @@ -19,7 +19,7 @@ class Base64Test { void test() { EntanglementLibSecurityFacade.initialize( EntanglementLibSecurityConfig.create( - new NativeSpecContext("/entlib-native/target/debug", "entlib_native_ffi", + 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, 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 40cd93b..518dfcc 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 @@ -7,6 +7,7 @@ import space.qu4nt.entanglementlib.security.EntanglementLibSecurityConfig; import space.qu4nt.entanglementlib.security.EntanglementLibSecurityFacade; import space.qu4nt.entanglementlib.security.data.HeuristicArenaFactory; +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; @@ -25,7 +26,7 @@ static void setUp() { // 테스트 클래스 로드 시 1회만 네이티브 라이브러리를 초기화하여 성능 최적화 EntanglementLibSecurityFacade.initialize( EntanglementLibSecurityConfig.create( - new NativeSpecContext("/Library/Quant/Repository/projects/entanglementlib/entlib-native/target/debug", "entlib_native_ffi", + 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, @@ -84,7 +85,7 @@ void chacha20Test() throws Throwable { SensitiveDataContainer result = ChaCha20.encrypt(context, key, nonce, aad, inputData); assertNotNull(result, "연산 결과는 null일 수 없습니다."); - resultSegmentAlias = result.getMemorySegment(); + resultSegmentAlias = InternalNativeBridge.unwrapMemorySegment(result); // [검증 A] 길이 검증 assertEquals(expected.length + 16, resultSegmentAlias.byteSize(), @@ -106,7 +107,7 @@ void chacha20Test() throws Throwable { // 복호화 // SensitiveDataContainer decryptResult = ChaCha20.decrypt(context, key, nonce, aad, result); - MemorySegment decResultOpt = decryptResult.getMemorySegment(); + MemorySegment decResultOpt = InternalNativeBridge.unwrapMemorySegment(decryptResult); assertNotEquals(MemorySegment.NULL, decResultOpt, "네이티브 측 복호화 결과가 null입니다."); assertNotEquals(0, decResultOpt.address(), "네이티브 측 복호화 결과가 유효하지 않습니다."); byte[] actualDecBytes = decResultOpt.toArray(ValueLayout.JAVA_BYTE); // 프로덕션에서 사용 권장 X 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 3a5c1ae..22b3476 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 @@ -1,6 +1,7 @@ package space.qu4nt.entanglementlib.security.crypto; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import space.qu4nt.entanglementlib.core.exception.security.checked.ELIBSecurityProcessException; @@ -9,6 +10,7 @@ import space.qu4nt.entanglementlib.security.EntanglementLibSecurityFacade; import space.qu4nt.entanglementlib.security.crypto.rng.RNG; import space.qu4nt.entanglementlib.security.data.HeuristicArenaFactory; +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; @@ -26,7 +28,7 @@ static void setUp() { // 테스트 클래스 로드 시 1회만 네이티브 라이브러리를 초기화하여 성능 최적화 EntanglementLibSecurityFacade.initialize( EntanglementLibSecurityConfig.create( - new NativeSpecContext("/Library/Quant/Repository/projects/entanglementlib/entlib-native/target/debug", "entlib_native_ffi", + new NativeSpecContext(System.getenv("ENTLIB_NATIVE_BIN"), "entlib_native_ffi", Function.chain( Function.withCalleeSecureBuffer(), Function.withCallerSecureBuffer(), @@ -46,10 +48,10 @@ void hardwareRngLifecycleAndZeroizeTest() throws ELIBSecurityProcessException { SensitiveDataContainer sdc = RNG.generateRNG(RNG.LOCAL_HARDWARE, scope, PQC_KEY_LENGTH); assertNotNull(sdc, "생성된 민감 데이터 컨테이너(sensitive data container)는 null이 아니어야 합니다."); - capturedSegment = sdc.getMemorySegment(); + capturedSegment = InternalNativeBridge.unwrapMemorySegment(sdc); // arena 및 메모리 세그먼트 유효성 검증 - assertTrue(sdc.getArena().scope().isAlive(), "스코프(scope) 내부에서는 Arena가 활성화 상태여야 합니다."); + assertTrue(InternalNativeBridge.unwrapArena(sdc).scope().isAlive(), "스코프(scope) 내부에서는 Arena가 활성화 상태여야 합니다."); assertEquals(PQC_KEY_LENGTH, capturedSegment.byteSize(), "생성된 난수의 크기가 요청한 길이와 일치해야 합니다."); // 엔트로피 데이터 존재 여부 검증 (모두 0인지 확인) @@ -74,6 +76,7 @@ void hardwareRngLifecycleAndZeroizeTest() throws ELIBSecurityProcessException { } @Test + @Disabled @DisplayName("양자 네트워크(quantum network) 기반 혼합 난수 생성 에러 핸들링 검증") void quantumNetworkRngErrorHandlingTest() throws ELIBSecurityProcessException { // 양자 네트워크 특성상 외부 통신 환경에 따라 실패할 수 있어서 생성 성공 여부 또는 @@ -82,7 +85,7 @@ void quantumNetworkRngErrorHandlingTest() throws ELIBSecurityProcessException { try { SensitiveDataContainer sdc = RNG.generateRNG(RNG.QUANTUM_NETWORK, scope, 64); assertNotNull(sdc); - assertTrue(sdc.getArena().scope().isAlive()); + assertTrue(InternalNativeBridge.unwrapArena(sdc).scope().isAlive()); } catch (ELIBSecurityNativeCritical e) { // 통신 실패 시 던져지는 예외 메시지가 우리가 정의한 규칙에 부합하는지 확인 String msg = e.getMessage(); 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 379453d..d092042 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 @@ -7,6 +7,7 @@ import space.qu4nt.entanglementlib.security.EntanglementLibSecurityFacade; import space.qu4nt.entanglementlib.security.crypto.hash.Hash; import space.qu4nt.entanglementlib.security.data.HeuristicArenaFactory; +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; @@ -26,7 +27,7 @@ static void setUp() { // 테스트 클래스 로드 시 1회만 네이티브 라이브러리를 초기화하여 성능 최적화 EntanglementLibSecurityFacade.initialize( EntanglementLibSecurityConfig.create( - new NativeSpecContext("/Library/Quant/Repository/projects/entanglementlib/entlib-native/target/debug", "entlib_native_ffi", + new NativeSpecContext(System.getenv("ENTLIB_NATIVE_BIN"), "entlib_native_ffi", Function.chain( Function.withCalleeSecureBuffer(), Function.withCallerSecureBuffer(), @@ -52,7 +53,7 @@ void sha224StrictTest() throws Throwable { SensitiveDataContainer result = Hash.sha2(224, context, inputData); assertNotNull(result, "해시 연산 결과는 null일 수 없습니다."); - resultSegmentAlias = result.getMemorySegment(); + resultSegmentAlias = InternalNativeBridge.unwrapMemorySegment(result); // [검증 A] 길이 검증 assertEquals(28, resultSegmentAlias.byteSize(), @@ -90,7 +91,7 @@ void sha256StrictTest() throws Throwable { SensitiveDataContainer result = Hash.sha2(256, context, inputData); assertNotNull(result, "해시 연산 결과는 null일 수 없습니다."); - resultSegmentAlias = result.getMemorySegment(); + resultSegmentAlias = InternalNativeBridge.unwrapMemorySegment(result); // [검증 A] 길이 검증 assertEquals(32, resultSegmentAlias.byteSize(), @@ -128,7 +129,7 @@ void sha384StrictTest() throws Throwable { SensitiveDataContainer result = Hash.sha2(384, context, inputData); assertNotNull(result, "해시 연산 결과는 null일 수 없습니다."); - resultSegmentAlias = result.getMemorySegment(); + resultSegmentAlias = InternalNativeBridge.unwrapMemorySegment(result); // [검증 A] 길이 검증 assertEquals(48, resultSegmentAlias.byteSize(), @@ -166,7 +167,7 @@ void sha512StrictTest() throws Throwable { SensitiveDataContainer result = Hash.sha2(512, context, inputData); assertNotNull(result, "해시 연산 결과는 null일 수 없습니다."); - resultSegmentAlias = result.getMemorySegment(); + resultSegmentAlias = InternalNativeBridge.unwrapMemorySegment(result); // [검증 A] 길이 검증 assertEquals(64, resultSegmentAlias.byteSize(), 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 d1458bf..ed32ec8 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 @@ -7,6 +7,7 @@ import space.qu4nt.entanglementlib.security.EntanglementLibSecurityFacade; import space.qu4nt.entanglementlib.security.data.HeuristicArenaFactory; import space.qu4nt.entanglementlib.security.crypto.hash.Hash; +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; @@ -26,7 +27,7 @@ static void setUp() { // 테스트 클래스 로드 시 1회만 네이티브 라이브러리를 초기화하여 성능 최적화 EntanglementLibSecurityFacade.initialize( EntanglementLibSecurityConfig.create( - new NativeSpecContext("/Library/Quant/Repository/projects/entanglementlib/entlib-native/target/debug", "entlib_native_ffi", + new NativeSpecContext(System.getenv("ENTLIB_NATIVE_BIN"), "entlib_native_ffi", Function.chain( Function.withCalleeSecureBuffer(), Function.withCallerSecureBuffer(), @@ -53,7 +54,7 @@ void sha3_224StrictTest() throws Throwable { SensitiveDataContainer result = Hash.sha3(224, context, inputData); assertNotNull(result, "해시 연산 결과는 null일 수 없습니다."); - resultSegmentAlias = result.getMemorySegment(); + resultSegmentAlias = InternalNativeBridge.unwrapMemorySegment(result); // [검증 A] 길이 검증 assertEquals(28, resultSegmentAlias.byteSize(), @@ -91,7 +92,7 @@ void sha3_256StrictTest() throws Throwable { SensitiveDataContainer result = Hash.sha3(256, context, inputData); assertNotNull(result, "해시 연산 결과는 null일 수 없습니다."); - resultSegmentAlias = result.getMemorySegment(); + resultSegmentAlias = InternalNativeBridge.unwrapMemorySegment(result); // [검증 A] 길이 검증 assertEquals(32, resultSegmentAlias.byteSize(), @@ -129,7 +130,7 @@ void sha3_384StrictTest() throws Throwable { SensitiveDataContainer result = Hash.sha3(384, context, inputData); assertNotNull(result, "해시 연산 결과는 null일 수 없습니다."); - resultSegmentAlias = result.getMemorySegment(); + resultSegmentAlias = InternalNativeBridge.unwrapMemorySegment(result); // [검증 A] 길이 검증 assertEquals(48, resultSegmentAlias.byteSize(), @@ -167,7 +168,7 @@ void sha3_512StrictTest() throws Throwable { SensitiveDataContainer result = Hash.sha3(512, context, inputData); assertNotNull(result, "해시 연산 결과는 null일 수 없습니다."); - resultSegmentAlias = result.getMemorySegment(); + resultSegmentAlias = InternalNativeBridge.unwrapMemorySegment(result); // [검증 A] 길이 검증 assertEquals(64, resultSegmentAlias.byteSize(), @@ -205,7 +206,7 @@ void sha3_shake128StrictTest() throws Throwable { SensitiveDataContainer result = Hash.sha3Shake(128, 32, context, inputData); assertNotNull(result, "해시 연산 결과는 null일 수 없습니다."); - resultSegmentAlias = result.getMemorySegment(); + resultSegmentAlias = InternalNativeBridge.unwrapMemorySegment(result); // [검증 A] 길이 검증 assertEquals(32, resultSegmentAlias.byteSize(), @@ -243,7 +244,7 @@ void sha3_shake256StrictTest() throws Throwable { SensitiveDataContainer result = Hash.sha3Shake(256, 64, context, inputData); assertNotNull(result, "해시 연산 결과는 null일 수 없습니다."); - resultSegmentAlias = result.getMemorySegment(); + resultSegmentAlias = InternalNativeBridge.unwrapMemorySegment(result); // [검증 A] 길이 검증 assertEquals(64, resultSegmentAlias.byteSize(), From b04f7c36c63864d6e350906efe7a726596dfbd7f Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Wed, 4 Mar 2026 13:52:46 +0900 Subject: [PATCH 13/13] =?UTF-8?q?Base64=20=EB=94=94=EC=BD=94=EB=94=A9=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/crypto/encode/Base64.java | 27 +++++++++--- .../security/entlibnative/NativeLoader.java | 41 +++++++++++++++---- .../security/crypto/Base64Test.java | 32 ++++++++++++--- 3 files changed, 81 insertions(+), 19 deletions(-) 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 b982290..72d0eb7 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 @@ -5,6 +5,8 @@ import space.qu4nt.entanglementlib.security.entlibnative.EntLibNativeManager; import space.qu4nt.entanglementlib.security.entlibnative.Function; +import java.lang.foreign.MemorySegment; + /** * 네이티브 메모리(native memory) 기반의 안전한 Base64 인코딩 및 디코딩 유틸리티입니다. * 모든 입출력은 {@link SensitiveDataContainer}를 통해 소유권(ownership)이 통제되며, @@ -68,24 +70,37 @@ public static SensitiveDataContainer decode(final SensitiveDataContainer input) final long inputLen = InternalNativeBridge.unwrapMemorySegment(input).byteSize(); final int maxRequired = (int) ((inputLen / 4 + 1) * 3); - SensitiveDataContainer output = new SensitiveDataContainer(maxRequired); + // 임시 컨테이너 생성 (try-with-resources로 자동 소거 보장) + try (SensitiveDataContainer tempOutput = new SensitiveDataContainer(maxRequired)) { - try { long result = (long) EntLibNativeManager.call(Function.Base64_decode).invokeExact( InternalNativeBridge.unwrapMemorySegment(input), inputLen, - InternalNativeBridge.unwrapMemorySegment(output), + InternalNativeBridge.unwrapMemorySegment(tempOutput), (long) maxRequired ); if (result < 0) { - output.close(); throw new RuntimeException("디코딩 중 네이티브 오류 발생 (error code): " + result); } - return output; + // 실제 크기에 맞는 반환용 컨테이너 생성 (소유권 이전용) + // 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()가 자동 호출 + } catch (Throwable t) { - output.close(); throw new RuntimeException("FFM API 디코딩 호출 실패", t); } } 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 dff14f6..c30d2fa 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 @@ -6,6 +6,7 @@ import space.qu4nt.entanglementlib.core.exception.security.critical.ELIBSecurityNativeCritical; import space.qu4nt.entanglementlib.security.EntanglementLibSecurityConfig; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.file.FileSystems; @@ -93,7 +94,6 @@ private static void extractAndLoadFromJar(String resourcePath, String fileName) } } - @SuppressWarnings("ResultOfMethodCallIgnored") private static Path createSecureTempFile(String fileName) throws IOException { int dotIndex = fileName.lastIndexOf('.'); String prefix = fileName.substring(0, dotIndex) + "-"; @@ -102,16 +102,41 @@ private static Path createSecureTempFile(String fileName) throws IOException { boolean isPosix = FileSystems.getDefault().supportedFileAttributeViews().contains("posix"); if (isPosix) { - // POSIX 호환 시스템 (linux, macos): 파일 권한 엄격 제한 + // POSIX 호환 시스템 (Linux, macOS) Set perms = PosixFilePermissions.fromString("rwx------"); return Files.createTempFile(prefix, suffix, PosixFilePermissions.asFileAttribute(perms)); } else { - // 윈도우 등 non POSIX 시스템: 기본 임시 파일 생성 후 읽기/쓰기/실행 권한 제어 - Path tempFile = Files.createTempFile(prefix, suffix); - tempFile.toFile().setReadable(true, true); - tempFile.toFile().setWritable(true, true); - tempFile.toFile().setExecutable(true, true); - return tempFile; + // Windows 등 non-POSIX 시스템 + Path tempPath = Files.createTempFile(prefix, suffix); + File tempFile = tempPath.toFile(); + + // 실행 권한 설정 (실패 시 즉시 중단) + if (!tempFile.setExecutable(true, true)) { + tryDelete(tempPath); + throw new IOException("임시 파일의 실행 권한(Owner Only)을 설정할 수 없습니다: " + tempPath); + } + + // 읽기 권한 설정 + if (!tempFile.setReadable(true, true)) { + tryDelete(tempPath); + throw new IOException("임시 파일의 읽기 권한(Owner Only)을 설정할 수 없습니다: " + tempPath); + } + + // 쓰기 권한 설정 + if (!tempFile.setWritable(true, true)) { + tryDelete(tempPath); + throw new IOException("임시 파일의 쓰기 권한(Owner Only)을 설정할 수 없습니다: " + tempPath); + } + + return tempPath; + } + } + + private static void tryDelete(Path path) { + try { + Files.deleteIfExists(path); + } catch (IOException e) { + log.error("임시 파일 제거 중 예외가 발생했습니다!", e); } } } 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 c320453..d37e76c 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 @@ -5,12 +5,21 @@ import org.junit.jupiter.api.Test; import space.qu4nt.entanglementlib.security.EntanglementLibSecurityConfig; import space.qu4nt.entanglementlib.security.EntanglementLibSecurityFacade; +import space.qu4nt.entanglementlib.security.crypto.encode.Base64; import space.qu4nt.entanglementlib.security.data.HeuristicArenaFactory; +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.NativeSpecContext; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; import java.nio.charset.StandardCharsets; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + @Slf4j class Base64Test { @@ -29,10 +38,23 @@ void test() { HeuristicArenaFactory.ArenaMode.CONFINED) ); - final byte[] input = "Hello, World!".getBytes(StandardCharsets.UTF_8); -// String result = Base64.encode(input); -// -// log.info("Encode: {}", result); -// log.info("Decode: {}", new String(Base64.decode(result))); + final byte[] plaintext = "Hello, World!".getBytes(StandardCharsets.UTF_8); + try (SDCScopeContext scope = new SDCScopeContext()) { + SensitiveDataContainer input = scope.allocate(plaintext, true); + SensitiveDataContainer result = Base64.encode(input); + final MemorySegment rms = InternalNativeBridge.unwrapMemorySegment(result); + + byte[] actualCipherBytes = rms.toArray(ValueLayout.JAVA_BYTE); // 프로덕션에서 사용 권장 X + byte[] newACBytes = new byte[actualCipherBytes.length]; + System.arraycopy(actualCipherBytes, 0, newACBytes, 0, actualCipherBytes.length); + log.info("Encoded: {}", new String(newACBytes, StandardCharsets.UTF_8)); + + SensitiveDataContainer decoded = Base64.decode(result); + MemorySegment decResultOpt = InternalNativeBridge.unwrapMemorySegment(decoded); + byte[] actualDecBytes = decResultOpt.toArray(ValueLayout.JAVA_BYTE); // 프로덕션에서 사용 권장 X + byte[] newADCBytes = new byte[actualDecBytes.length]; + System.arraycopy(actualDecBytes, 0, newADCBytes, 0, actualDecBytes.length); + log.info("Decoded: {}", new String(newADCBytes, StandardCharsets.UTF_8)); + } } } \ No newline at end of file