From bfbe0794d3fce1880b78271a12b26d5a86fe3384 Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:31:40 +0900 Subject: [PATCH 1/6] =?UTF-8?q?=ED=81=AC=EB=A0=88=EC=9D=B4=ED=8A=B8/?= =?UTF-8?q?=EB=AA=A8=EB=93=88=20=EC=84=B8=EB=B6=84=ED=99=94=20=EB=B0=8F=20?= =?UTF-8?q?=ED=95=B5=EC=8B=AC=20=EB=B3=B4=EC=95=88=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/copyright/profiles_settings.xml | 3 - .idea/entlib-native.iml | 24 +- core/ffi/benches/ffi_base64_bench.rs | 154 ------------ core/ffi/benches/ffi_wipe_bench.rs | 37 --- core/ffi/src/base64_ffi.rs | 182 --------------- core/ffi/src/secure_buffer.rs | 80 ------- core/helper/Cargo.toml | 32 --- core/helper/benches/secure_buffer_bench.rs | 42 ---- core/helper/src/lib.rs | 14 -- core/helper/src/secure_buffer.rs | 29 --- .../base64}/benches/base64_bench.rs | 2 +- {core/helper => crypto/base64}/src/base64.rs | 6 +- .../base64}/tests/base64_test.rs | 2 +- .../constant-time}/benches/ct_ops_security.rs | 24 +- .../benches/ct_ops_throughput.rs | 24 +- .../constant-time}/constant_time.md | 0 .../constant-time}/constant_time_asm.md | 0 .../constant-time}/src/constant_time.rs | 24 +- .../constant-time}/src/constant_time_asm.rs | 26 ++- .../tests/constant_time_asm_test.rs | 24 +- .../tests/constant_time_test.rs | 2 +- .../tests/ct_cross_validation.rs | 2 +- .../benches/secure_buffer_bench.rs | 64 +++++ crypto/core-secure/src/secure_buffer.rs | 63 +++++ crypto/rng/Cargo.toml | 12 +- crypto/rng/benches/anu_qrng_bench.rs | 98 ++++++++ crypto/rng/src/anu_qrng.rs | 77 ++++++ crypto/rng/src/base_rng.rs | 6 +- crypto/rng/src/lib.rs | 1 + crypto/rng/src/mixed.rs | 32 ++- crypto/rng/tests/anu_qrng_test.rs | 88 +++++++ crypto/rng/tests/hw_based_rng_test.rs | 2 +- crypto/rng/tests/mixed_rng_test.rs | 176 ++++++++------ crypto/sha2/Cargo.toml | 2 +- crypto/sha2/benches/sha2_bench.rs | 2 +- crypto/sha3/Cargo.toml | 2 +- crypto/sha3/benches/sha3_bench.rs | 2 +- {core => internal}/ffi/Cargo.toml | 25 +- internal/ffi/src/base64_ffi.rs | 219 ++++++++++++++++++ {core => internal}/ffi/src/lib.rs | 7 +- internal/ffi/src/rng_ffi.rs | 158 +++++++++++++ internal/ffi/src/secure_buffer_ffi.rs | 98 ++++++++ .../sha2.rs => internal/ffi/src/sha2_ffi.rs | 2 +- 43 files changed, 1176 insertions(+), 693 deletions(-) delete mode 100644 .idea/copyright/profiles_settings.xml delete mode 100644 core/ffi/benches/ffi_base64_bench.rs delete mode 100644 core/ffi/benches/ffi_wipe_bench.rs delete mode 100644 core/ffi/src/base64_ffi.rs delete mode 100644 core/ffi/src/secure_buffer.rs delete mode 100644 core/helper/Cargo.toml delete mode 100644 core/helper/benches/secure_buffer_bench.rs delete mode 100644 core/helper/src/lib.rs delete mode 100644 core/helper/src/secure_buffer.rs rename {core/helper => crypto/base64}/benches/base64_bench.rs (98%) rename {core/helper => crypto/base64}/src/base64.rs (89%) rename {core/helper => crypto/base64}/tests/base64_test.rs (98%) rename {core/helper => crypto/constant-time}/benches/ct_ops_security.rs (90%) rename {core/helper => crypto/constant-time}/benches/ct_ops_throughput.rs (78%) rename {core/helper => crypto/constant-time}/constant_time.md (100%) rename {core/helper => crypto/constant-time}/constant_time_asm.md (100%) rename {core/helper => crypto/constant-time}/src/constant_time.rs (76%) rename {core/helper => crypto/constant-time}/src/constant_time_asm.rs (93%) rename {core/helper => crypto/constant-time}/tests/constant_time_asm_test.rs (66%) rename {core/helper => crypto/constant-time}/tests/constant_time_test.rs (96%) rename {core/helper => crypto/constant-time}/tests/ct_cross_validation.rs (98%) create mode 100644 crypto/core-secure/benches/secure_buffer_bench.rs create mode 100644 crypto/core-secure/src/secure_buffer.rs create mode 100644 crypto/rng/benches/anu_qrng_bench.rs create mode 100644 crypto/rng/src/anu_qrng.rs create mode 100644 crypto/rng/tests/anu_qrng_test.rs rename {core => internal}/ffi/Cargo.toml (61%) create mode 100644 internal/ffi/src/base64_ffi.rs rename {core => internal}/ffi/src/lib.rs (90%) create mode 100644 internal/ffi/src/rng_ffi.rs create mode 100644 internal/ffi/src/secure_buffer_ffi.rs rename core/ffi/src/sha2.rs => internal/ffi/src/sha2_ffi.rs (98%) diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml deleted file mode 100644 index c3ba54a..0000000 --- a/.idea/copyright/profiles_settings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/.idea/entlib-native.iml b/.idea/entlib-native.iml index d541518..fdae6f1 100644 --- a/.idea/entlib-native.iml +++ b/.idea/entlib-native.iml @@ -3,14 +3,7 @@ - - - - - - - @@ -20,6 +13,23 @@ + + + + + + + + + + + + + + + + + diff --git a/core/ffi/benches/ffi_base64_bench.rs b/core/ffi/benches/ffi_base64_bench.rs deleted file mode 100644 index 52650f4..0000000 --- a/core/ffi/benches/ffi_base64_bench.rs +++ /dev/null @@ -1,154 +0,0 @@ -use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; -use std::time::Duration; - -use entlib_native_ffi::base64_ffi::{ - entlib_b64_decode_secure, entlib_b64_encode_secure, entlib_free_secure_buffer, - entlib_secure_buffer_get_ptr, -}; - -const ENCODE_SIZES: &[usize] = &[3, 32, 256, 1_024, 4_096, 65_536, 1_048_576]; - -// -// 처리량 — encode: 입력 크기별 전체 lifecycle (encode → get_ptr → free) -// - -fn ffi_encode_throughput(c: &mut Criterion) { - let mut group = c.benchmark_group("throughput/ffi_b64_encode"); - - for &size in ENCODE_SIZES { - let input = vec![0x42u8; size]; - group.throughput(Throughput::Bytes(size as u64)); - - group.bench_with_input(BenchmarkId::from_parameter(size), &input, |b, input| { - b.iter(|| { - let mut out_len: usize = 0; - let ptr = - unsafe { entlib_b64_encode_secure(input.as_ptr(), input.len(), &mut out_len) }; - let _ = unsafe { entlib_secure_buffer_get_ptr(ptr) }; - entlib_free_secure_buffer(ptr); - }) - }); - } - - group.finish(); -} - -// -// 처리량 — decode: 사전 인코딩된 입력으로 decode lifecycle -// - -fn ffi_decode_throughput(c: &mut Criterion) { - let mut group = c.benchmark_group("throughput/ffi_b64_decode"); - - for &size in ENCODE_SIZES { - // 사전 인코딩: 원본 크기 size → Base64 인코딩된 데이터 생성 - let plain = vec![0x42u8; size]; - let mut enc_len: usize = 0; - let enc_ptr = - unsafe { entlib_b64_encode_secure(plain.as_ptr(), plain.len(), &mut enc_len) }; - let enc_data_ptr = unsafe { entlib_secure_buffer_get_ptr(enc_ptr) }; - let encoded: Vec = - unsafe { std::slice::from_raw_parts(enc_data_ptr, enc_len) }.to_vec(); - entlib_free_secure_buffer(enc_ptr); - - group.throughput(Throughput::Bytes(encoded.len() as u64)); - - group.bench_with_input(BenchmarkId::from_parameter(size), &encoded, |b, encoded| { - b.iter(|| { - let mut out_len: usize = 0; - let mut err_flag: u8 = 0; - let ptr = unsafe { - entlib_b64_decode_secure( - encoded.as_ptr(), - encoded.len(), - &mut out_len, - &mut err_flag, - ) - }; - entlib_free_secure_buffer(ptr); - }) - }); - } - - group.finish(); -} - -// -// 보안성 — decode: 유효 입력 vs 무효 문자 주입 (1KB) -// - -fn ffi_decode_security(c: &mut Criterion) { - let mut group = c.benchmark_group("security/ffi_b64_decode"); - group.measurement_time(Duration::from_secs(5)); - group.sample_size(1000); - - // 유효 입력: 1KB 평문을 인코딩한 결과 - let valid_plain = vec![0x42u8; 1024]; - let mut enc_len: usize = 0; - let enc_ptr = - unsafe { entlib_b64_encode_secure(valid_plain.as_ptr(), valid_plain.len(), &mut enc_len) }; - let enc_data_ptr = unsafe { entlib_secure_buffer_get_ptr(enc_ptr) }; - let valid_encoded: Vec = - unsafe { std::slice::from_raw_parts(enc_data_ptr, enc_len) }.to_vec(); - entlib_free_secure_buffer(enc_ptr); - - // 무효 입력: 같은 길이의 데이터에 무효 문자(0x00) 주입 - let mut invalid_encoded = valid_encoded.clone(); - for i in (0..invalid_encoded.len()).step_by(4) { - invalid_encoded[i] = 0x00; // 무효 바이트 주입 (매 4번째) - } - - group.bench_with_input( - BenchmarkId::new("valid_1kb", ""), - &valid_encoded, - |b, input| { - b.iter(|| { - let mut out_len: usize = 0; - let mut err_flag: u8 = 0; - let ptr = unsafe { - entlib_b64_decode_secure( - input.as_ptr(), - input.len(), - &mut out_len, - &mut err_flag, - ) - }; - entlib_free_secure_buffer(ptr); - }) - }, - ); - - group.bench_with_input( - BenchmarkId::new("invalid_1kb", ""), - &invalid_encoded, - |b, input| { - b.iter(|| { - let mut out_len: usize = 0; - let mut err_flag: u8 = 0; - let ptr = unsafe { - entlib_b64_decode_secure( - input.as_ptr(), - input.len(), - &mut out_len, - &mut err_flag, - ) - }; - entlib_free_secure_buffer(ptr); - }) - }, - ); - - group.finish(); -} - -// -// Criterion 설정 -// - -criterion_group!( - benches, - ffi_encode_throughput, - ffi_decode_throughput, - ffi_decode_security, -); -criterion_main!(benches); diff --git a/core/ffi/benches/ffi_wipe_bench.rs b/core/ffi/benches/ffi_wipe_bench.rs deleted file mode 100644 index b6bc443..0000000 --- a/core/ffi/benches/ffi_wipe_bench.rs +++ /dev/null @@ -1,37 +0,0 @@ -use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; - -use entlib_native_ffi::secure_buffer::entanglement_secure_wipe; - -const SIZES: &[usize] = &[64, 256, 1_024, 4_096, 65_536, 1_048_576]; - -// -// 처리량 — secure wipe (각 반복마다 0xAA 재충전으로 반복 zero-wipe 최적화 방지) -// - -fn ffi_wipe_throughput(c: &mut Criterion) { - let mut group = c.benchmark_group("throughput/ffi_secure_wipe"); - - for &size in SIZES { - group.throughput(Throughput::Bytes(size as u64)); - - group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| { - let mut buffer = vec![0xAAu8; size]; - b.iter(|| { - // 0xAA로 재충전 — 반복 zero-wipe 최적화 방지 - buffer.fill(0xAA); - unsafe { - entanglement_secure_wipe(buffer.as_mut_ptr(), buffer.len()); - } - }) - }); - } - - group.finish(); -} - -// -// Criterion 설정 -// - -criterion_group!(benches, ffi_wipe_throughput); -criterion_main!(benches); diff --git a/core/ffi/src/base64_ffi.rs b/core/ffi/src/base64_ffi.rs deleted file mode 100644 index 5fa7f81..0000000 --- a/core/ffi/src/base64_ffi.rs +++ /dev/null @@ -1,182 +0,0 @@ -use std::boxed::Box; -use std::ffi::c_void; -use std::slice; -use std::vec::Vec; - -use entlib_native_helper::base64::{ct_b64_to_bin_u8, ct_bin_to_b64_u8}; -use entlib_native_helper::constant_time::ConstantTimeOps; -use entlib_native_helper::secure_buffer::SecureBuffer; - -/// Java측에 노출되는 Base64 인코딩 함수 (encode endpoint) -/// -/// # Arguments -/// * `input_ptr` - 인코딩할 평문 바이트 배열의 포인터 (memory segment address) -/// * `input_len` - 평문 배열의 길이 -/// * `out_len` - (출력 매개변수) 생성된 base64 문자열의 길이를 반환할 포인터 -/// -/// # Returns -/// 인코딩된 데이터를 담고 있는 `SecureBuffer`의 불투명 포인터 (opaque pointer) -/// -/// # Safety -/// * `input_ptr`은 `input_len` 바이트만큼 유효한 메모리를 가리켜야 합니다. -/// * `out_len`은 유효한 `usize` 메모리 공간을 가리켜야 합니다. -/// * 반환된 포인터는 사용 후 반드시 `entlib_free_secure_buffer`로 해제해야 합니다. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn entlib_b64_encode_secure( - input_ptr: *const u8, - input_len: usize, - out_len: *mut usize, -) -> *mut c_void { - // null pointer 검증 - if input_ptr.is_null() || out_len.is_null() { - return core::ptr::null_mut(); - } - - // 입력 길이 검증 및 최대 용량 산출 - let capacity = match input_len.checked_add(2) { - Some(val) => (val / 3).checked_mul(4), - None => None, - }; - - let capacity = match capacity { - Some(c) => c, - None => return core::ptr::null_mut(), // 오버플로우 방지 (overflow protection) - }; - - let input = unsafe { slice::from_raw_parts(input_ptr, input_len) }; - let mut out = Vec::with_capacity(capacity); - - // 상수 시간 인코딩 루프 - // 보안을 위해 분기 처리 대신 비트 연산 기반의 패딩 마스킹을 사용해야 하지만 - // 공간 제약상 기본적인 3-to-4 블록 변환 구조를 명시 - let mut i = 0; - while i < input_len { - let b0 = input[i]; - let b1 = if i + 1 < input_len { input[i + 1] } else { 0 }; - let b2 = if i + 2 < input_len { input[i + 2] } else { 0 }; - - let e0 = b0 >> 2; - let e1 = ((b0 & 0x03) << 4) | (b1 >> 4); - let e2 = ((b1 & 0x0F) << 2) | (b2 >> 6); - let e3 = b2 & 0x3F; - - out.push(ct_bin_to_b64_u8(e0)); - out.push(ct_bin_to_b64_u8(e1)); - - // 패딩 처리 로직은 별도의 상수 시간 마스킹으로 치환 가능 - out.push(if i + 1 < input_len { - ct_bin_to_b64_u8(e2) - } else { - b'=' - }); - out.push(if i + 2 < input_len { - ct_bin_to_b64_u8(e3) - } else { - b'=' - }); - - i += 3; - } - - unsafe { *out_len = out.len() }; - - let secure_buf = SecureBuffer { inner: out }; - Box::into_raw(Box::new(secure_buf)) as *mut c_void -} - -/// Java 측에 노출되는 Base64 디코딩 함수 (decode endpoint) -/// -/// # Arguments -/// * `input_ptr` - 디코딩할 base64 문자열의 포인터 -/// * `input_len` - 문자열의 길이 -/// * `out_len` - (출력 매개변수) 복원된 평문의 길이를 반환할 포인터 -/// * `err_flag` - (출력 매개변수) 디코딩 중 에러(잘못된 문자 등) 발생 시 1을 반환, 정상 시 0 -/// -/// # Safety -/// * `input_ptr`은 `input_len` 바이트만큼 유효한 메모리를 가리켜야 합니다. -/// * `out_len`과 `err_flag`는 유효한 메모리 공간을 가리켜야 합니다. -/// * 반환된 포인터는 사용 후 반드시 `entlib_free_secure_buffer`로 해제해야 합니다. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn entlib_b64_decode_secure( - input_ptr: *const u8, - input_len: usize, - out_len: *mut usize, - err_flag: *mut u8, -) -> *mut c_void { - if input_ptr.is_null() || out_len.is_null() || err_flag.is_null() { - return core::ptr::null_mut(); - } - - let input = unsafe { slice::from_raw_parts(input_ptr, input_len) }; - let capacity = (input_len / 4) * 3; - let mut out = Vec::with_capacity(capacity); - - let mut error_accum = 0u8; - let mut buf = [0u8; 4]; - let mut buf_idx = 0; - - for &byte in input { - let decoded = ct_b64_to_bin_u8(byte); - - // 에러 마스크 누적 (error accumulation) - 분기 없는 상수 시간 로직 - let is_invalid = decoded.ct_eq(0xFF); - error_accum |= is_invalid & 0x01; - - // 공백 문자(0x80) 무시 및 유효 바이트 버퍼링 - let is_valid_char = !decoded.ct_eq(0x80) & !decoded.ct_eq(0x81) & !is_invalid; - - // note: 완벽한 상수 시간 구동을 위해 버퍼 인덱스 증가도 분기 없이 처리하는 것이 이상적임 - if is_valid_char == 0xFF { - buf[buf_idx] = decoded; - buf_idx = (buf_idx + 1) & 0x03; // modulo 4 - - if buf_idx == 0 { - out.push((buf[0] << 2) | (buf[1] >> 4)); - out.push((buf[1] << 4) | (buf[2] >> 2)); - out.push((buf[2] << 6) | buf[3]); - } - } - } - - // 잔여 버퍼 처리가 없으면 패딩 문자로 인해 4-문자 블록이 완성되지 않은 잔여 버퍼 데이터가 그대로 버려짐 - // 해당 문제 해결을 위해 잔여 버퍼를 처리함 - if buf_idx == 2 { - out.push((buf[0] << 2) | (buf[1] >> 4)); - } else if buf_idx == 3 { - out.push((buf[0] << 2) | (buf[1] >> 4)); - out.push((buf[1] << 4) | (buf[2] >> 2)); - } - - unsafe { - *out_len = out.len(); - *err_flag = error_accum; - } - - let secure_buf = SecureBuffer { inner: out }; - Box::into_raw(Box::new(secure_buf)) as *mut c_void -} - -/// `SecureBuffer` 메모리 데이터 추출 함수 (buffer read endpoint) -/// -/// # Safety -/// * `ptr`은 `entlib_b64_encode_secure` 혹은 `entlib_b64_decode_secure`로부터 반환된 유효한 `SecureBuffer` 포인터여야 합니다. -/// * `ptr`이 가리키는 메모리는 해제되지 않은 상태여야 합니다. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn entlib_secure_buffer_get_ptr(ptr: *mut c_void) -> *const u8 { - if ptr.is_null() { - return core::ptr::null(); - } - let buf = unsafe { &*(ptr as *const SecureBuffer) }; - buf.inner.as_ptr() -} - -/// Java 힙(heap)으로 복사 완료 후 반드시 호출해야 하는 네이티브 메모리 소거 및 해제 함수 -#[unsafe(no_mangle)] -pub extern "C" fn entlib_free_secure_buffer(ptr: *mut c_void) { - if !ptr.is_null() { - unsafe { - // 박스 소유권 반환을 통한 drop 호출 및 zeroize 수행 - let _ = Box::from_raw(ptr as *mut SecureBuffer); - } - } -} diff --git a/core/ffi/src/secure_buffer.rs b/core/ffi/src/secure_buffer.rs deleted file mode 100644 index ba8a56a..0000000 --- a/core/ffi/src/secure_buffer.rs +++ /dev/null @@ -1,80 +0,0 @@ -use core::ptr::{self, write_volatile}; -use core::slice; -use core::sync::atomic::{Ordering, compiler_fence}; -use entlib_native_helper::secure_buffer::SecureBuffer; - -/// 보안 버퍼 내 실제 데이터의 메모리 주소 반환 (get immutable data pointer) -/// -/// # Safety -/// - 반환된 원시 포인터(raw pointer)는 `SecureBuffer`가 `entlib_secure_buffer_free`를 통해 -/// 해제되기 전까지만 유효합니다. 해제 후 역참조 시 미정의 동작(undefined behavior)이 발생합니다. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn entlib_secure_buffer_data(buf: *const SecureBuffer) -> *const u8 { - if buf.is_null() { - return ptr::null(); - } - let buffer = unsafe { &*buf }; - buffer.inner.as_ptr() -} - -/// 보안 버퍼 내 데이터의 바이트 길이 반환 (get length of data) -/// -/// # Safety -/// - `buf`가 null이 아닌 경우, 유효한 `SecureBuffer` 인스턴스를 가리켜야 합니다. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn entlib_secure_buffer_len(buf: *const SecureBuffer) -> usize { - if buf.is_null() { - return 0; - } - let buffer = unsafe { &*buf }; - buffer.inner.len() -} - -/// 보안 버퍼 메모리 해제 및 데이터 소거 (free and zeroize) -/// -/// # Safety -/// - 호출 즉시 `SecureBuffer`의 `Drop` 트레이트가 실행되어 `write_volatile` 및 -/// `compiler_fence`를 통해 메모리가 안전하게 소거됩니다. -/// - 자바 링커(java linker api)를 통해 반드시 한 번만 호출되어야 합니다 (double-free 방지). -#[unsafe(no_mangle)] -pub unsafe extern "C" fn entlib_secure_buffer_free(buf: *mut SecureBuffer) { - if !buf.is_null() { - unsafe { - // Box::from_raw를 통해 소유권을 가져오며, 즉시 스코프를 벗어나 Drop 실행 - drop(Box::from_raw(buf)); - } - } -} - -/// 자바 측 `SensitiveDataContainer`가 소유한 네이티브 메모리 세그먼트(memory segment)를 -/// 안전하게 소거(zeroize)하는 ffi 엔드포인트입니다. -/// -/// # Arguments -/// * `ptr` - 소거할 메모리 영역의 시작 포인터 (*mut u8) -/// * `len` - 소거할 메모리의 바이트 크기 (usize) -/// -/// # Safety -/// * `ptr`은 `len` 바이트만큼 할당된 유효한 메모리 영역을 가리켜야 합니다. -/// * 자바의 제한된 아레나(confined arena) 수명 주기에 의해 유효성이 검증된 상태에서만 호출되어야 합니다. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn entanglement_secure_wipe(ptr: *mut u8, len: usize) { - // null pointer 및 0바이트 길이 방어 코드 - if ptr.is_null() || len == 0 { - return; - } - - // 원시 포인터로부터 가변 슬라이스 생성 - let buffer = unsafe { slice::from_raw_parts_mut(ptr, len) }; - - // secure_buffer 구조체와 동일한 수준의 컴파일러 최적화 방어 소거 - for byte in buffer.iter_mut() { - unsafe { - // 휘발성 쓰기(volatile write)를 통해 dce 최적화 강제 회피 - write_volatile(byte, 0); - } - } - - // 메모리 배리어를 통해 소거 작업이 후속 메모리 해제(arena#close) 연산보다 - // 반드시 먼저 완료되도록 순서(sequential consistency) 보장 - compiler_fence(Ordering::SeqCst); -} diff --git a/core/helper/Cargo.toml b/core/helper/Cargo.toml deleted file mode 100644 index b74f65a..0000000 --- a/core/helper/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ -[package] -name = "entlib-native-helper" -version.workspace = true -edition.workspace = true -authors.workspace = true -license.workspace = true - -[features] -ct-tests = [] - -[dependencies] - -[dev-dependencies] -subtle = "2.6.1" -proptest = "1.10.0" -criterion = { version = "0.8.2", features = ["html_reports"] } - -[[bench]] -name = "ct_ops_security" -harness = false - -[[bench]] -name = "ct_ops_throughput" -harness = false - -[[bench]] -name = "base64_bench" -harness = false - -[[bench]] -name = "secure_buffer_bench" -harness = false \ No newline at end of file diff --git a/core/helper/benches/secure_buffer_bench.rs b/core/helper/benches/secure_buffer_bench.rs deleted file mode 100644 index eb59e35..0000000 --- a/core/helper/benches/secure_buffer_bench.rs +++ /dev/null @@ -1,42 +0,0 @@ -use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; - -use entlib_native_helper::secure_buffer::SecureBuffer; - -const SIZES: &[usize] = &[64, 256, 1_024, 4_096, 65_536, 1_048_576]; - -// -// SecureBuffer drop(wipe) 처리량 -// - -fn secure_buffer_wipe_throughput(c: &mut Criterion) { - let mut group = c.benchmark_group("throughput/secure_buffer_wipe"); - - for &size in SIZES { - group.throughput(Throughput::Bytes(size as u64)); - - let batch_size = if size >= 65_536 { - BatchSize::LargeInput - } else { - BatchSize::SmallInput - }; - - group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| { - b.iter_batched( - || SecureBuffer { - inner: vec![0xAA; size], - }, - drop, // |buf| drop(buf) - batch_size, - ) - }); - } - - group.finish(); -} - -// -// Criterion 설정 -// - -criterion_group!(benches, secure_buffer_wipe_throughput); -criterion_main!(benches); diff --git a/core/helper/src/lib.rs b/core/helper/src/lib.rs deleted file mode 100644 index a4ed6f8..0000000 --- a/core/helper/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -#![no_std] -extern crate alloc; - -pub mod constant_time; - -#[cfg(feature = "ct-tests")] -#[doc(hidden)] -pub mod constant_time_asm; - -#[cfg(not(feature = "ct-tests"))] -pub(crate) mod constant_time_asm; - -pub mod base64; -pub mod secure_buffer; diff --git a/core/helper/src/secure_buffer.rs b/core/helper/src/secure_buffer.rs deleted file mode 100644 index e555e6c..0000000 --- a/core/helper/src/secure_buffer.rs +++ /dev/null @@ -1,29 +0,0 @@ -use alloc::vec::Vec; -use core::ptr::write_volatile; -use core::sync::atomic::{Ordering, compiler_fence}; - -/// 메모리 소거를 보장하는 보안 버퍼 구조체입니다. -/// -/// 민감한 데이터를 사용한 연산 후 그 결과를 Java Heap으로 전달하는 경우, -/// 가비지 컬렉터의 생명주기에 종속되어 위험에 다시 노출되는 딜레마를 가지게 -/// 됩니다. 이 문제를 해결하기 위해 단순히 `byte[]`와 같은 Java 데이터로 -/// 직렬화(serialize)하지 않고, 이 구조체에 저장합니다. -/// -/// Java 측으로는 오직 해당 메모리의 원시 포인만이 전달되기 떄문에 안전한 -/// 데이터 관리가 가능합니다. -pub struct SecureBuffer { - pub inner: Vec, -} - -impl Drop for SecureBuffer { - fn drop(&mut self) { - for byte in self.inner.iter_mut() { - // volatile write -> 컴파일러 dce 최적화 방지함 - unsafe { - write_volatile(byte, 0); - } - } - // 메모리 배리어로 소거 순서 보장 - compiler_fence(Ordering::SeqCst); - } -} diff --git a/core/helper/benches/base64_bench.rs b/crypto/base64/benches/base64_bench.rs similarity index 98% rename from core/helper/benches/base64_bench.rs rename to crypto/base64/benches/base64_bench.rs index 5baaa18..f793cde 100644 --- a/core/helper/benches/base64_bench.rs +++ b/crypto/base64/benches/base64_bench.rs @@ -1,7 +1,7 @@ use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; use std::time::Duration; -use entlib_native_helper::base64::{ct_b64_to_bin_u8, ct_bin_to_b64_u8}; +use entlib_native_base64::base64::{ct_b64_to_bin_u8, ct_bin_to_b64_u8}; // // 보안성 — encode: 문자 클래스별 타이밍 비교 diff --git a/core/helper/src/base64.rs b/crypto/base64/src/base64.rs similarity index 89% rename from core/helper/src/base64.rs rename to crypto/base64/src/base64.rs index b0a8ea8..0199ce9 100644 --- a/core/helper/src/base64.rs +++ b/crypto/base64/src/base64.rs @@ -1,6 +1,6 @@ -use crate::constant_time::ConstantTimeOps; +use entlib_native_constant_time::constant_time::ConstantTimeOps; -/// 단일 바이트 상수-시간 Base64 인코딩을 수행하는 함수입니다. +/// 단일 바이트 상수-시간 `Base64` 인코딩을 수행하는 함수입니다. #[inline(always)] pub fn ct_bin_to_b64_u8(c: u8) -> u8 { // 0 <= c < 26 @@ -29,7 +29,7 @@ pub fn ct_bin_to_b64_u8(c: u8) -> u8 { res } -/// 단일 바이트 상수-시간 Base64 디코딩을 수행하는 함수입니다. +/// 단일 바이트 상수-시간 `Base64` 디코딩을 수행하는 함수입니다. #[inline(always)] pub fn ct_b64_to_bin_u8(b: u8) -> u8 { // 범위 검사 diff --git a/core/helper/tests/base64_test.rs b/crypto/base64/tests/base64_test.rs similarity index 98% rename from core/helper/tests/base64_test.rs rename to crypto/base64/tests/base64_test.rs index 01977cf..2cd5fed 100644 --- a/core/helper/tests/base64_test.rs +++ b/crypto/base64/tests/base64_test.rs @@ -1,4 +1,4 @@ -use entlib_native_helper::base64::{ct_b64_to_bin_u8, ct_bin_to_b64_u8}; +use entlib_native_base64::base64::{ct_b64_to_bin_u8, ct_bin_to_b64_u8}; #[cfg(test)] mod base64_constant_time_tests { diff --git a/core/helper/benches/ct_ops_security.rs b/crypto/constant-time/benches/ct_ops_security.rs similarity index 90% rename from core/helper/benches/ct_ops_security.rs rename to crypto/constant-time/benches/ct_ops_security.rs index 27148a8..67cfc74 100644 --- a/core/helper/benches/ct_ops_security.rs +++ b/crypto/constant-time/benches/ct_ops_security.rs @@ -1,7 +1,29 @@ +/* + * Copyright (c) 2025-2026 Quant + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the “Software”), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main}; use std::time::Duration; -use entlib_native_helper::constant_time::ConstantTimeOps; +use entlib_native_constant_time::constant_time::ConstantTimeOps; // // Macros — 반복 제거를 위한 보안성 벤치마크 매크로 diff --git a/core/helper/benches/ct_ops_throughput.rs b/crypto/constant-time/benches/ct_ops_throughput.rs similarity index 78% rename from core/helper/benches/ct_ops_throughput.rs rename to crypto/constant-time/benches/ct_ops_throughput.rs index 73ef388..a6a8cae 100644 --- a/core/helper/benches/ct_ops_throughput.rs +++ b/crypto/constant-time/benches/ct_ops_throughput.rs @@ -1,6 +1,28 @@ +/* + * Copyright (c) 2025-2026 Quant + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the “Software”), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + use criterion::{Criterion, Throughput, criterion_group, criterion_main}; -use entlib_native_helper::constant_time::ConstantTimeOps; +use entlib_native_constant_time::constant_time::ConstantTimeOps; const BATCH: u64 = 4096; diff --git a/core/helper/constant_time.md b/crypto/constant-time/constant_time.md similarity index 100% rename from core/helper/constant_time.md rename to crypto/constant-time/constant_time.md diff --git a/core/helper/constant_time_asm.md b/crypto/constant-time/constant_time_asm.md similarity index 100% rename from core/helper/constant_time_asm.md rename to crypto/constant-time/constant_time_asm.md diff --git a/core/helper/src/constant_time.rs b/crypto/constant-time/src/constant_time.rs similarity index 76% rename from core/helper/src/constant_time.rs rename to crypto/constant-time/src/constant_time.rs index 9d738d5..aaaa71a 100644 --- a/core/helper/src/constant_time.rs +++ b/crypto/constant-time/src/constant_time.rs @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2025-2026 Quant + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the “Software”), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + //! 상수-시간(Constant-Time) 연산을 위한 통합 트레이트 및 구현체 //! //! 이 모듈은 민감 데이터를 다룰 때 타이밍 공격(timing attack)을 방지하기 위해 @@ -9,7 +31,7 @@ //! # Security Warning //! 이 코드는 `entlib-native`의 일부로, 컴파일러 최적화 레벨이나 타겟 아키텍처에 따라 //! 안전성이 달라질 수 있습니다. `x86_64` 및 `aarch64`에서는 인라인 어셈블리를 사용하여 -//! 컴파일러 최적화를 원천 차단하며, 기타 아키텍처에서는 `core::hint::black_box`로 폴백합니다. +//! 컴파일러 최적화를 원천 차단하며, 기타 아키텍처에서는 `internal::hint::black_box`로 폴백합니다. //! 최종 바이너리에 대한 어셈블리 검증이 권장됩니다. //! //! 이 모듈은 단순한 개념 증명(PoC) 기능을 가지지 않습니다. diff --git a/core/helper/src/constant_time_asm.rs b/crypto/constant-time/src/constant_time_asm.rs similarity index 93% rename from core/helper/src/constant_time_asm.rs rename to crypto/constant-time/src/constant_time_asm.rs index 3b88017..f1fd7d9 100644 --- a/core/helper/src/constant_time_asm.rs +++ b/crypto/constant-time/src/constant_time_asm.rs @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2025-2026 Quant + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the “Software”), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + //! 아키텍처별 인라인 어셈블리 기반 상수-시간 프리미티브 //! //! # Author @@ -17,7 +39,7 @@ /// /// # Usage /// ```rust -/// use entlib_native_helper::constant_time_asm::CtPrimitive; +/// use entlib_native_constant_time::constant_time_asm::CtPrimitive; /// /// let a = 10u32; /// let b = 20u32; @@ -33,7 +55,7 @@ /// ``` /// /// # Safety -/// 이 트레이트의 구현체들은 컴파일러 최적화를 방지하기 위해 `core::hint::black_box` 또는 +/// 이 트레이트의 구현체들은 컴파일러 최적화를 방지하기 위해 `internal::hint::black_box` 또는 /// 인라인 어셈블리(`asm!`)를 사용합니다. 하지만 특정 아키텍처나 컴파일러 버전에서는 /// 의도치 않은 최적화가 발생할 수 있으므로, 중요한 보안 로직에 사용할 경우 /// 생성된 어셈블리 코드를 검증하는 것이 좋습니다. diff --git a/core/helper/tests/constant_time_asm_test.rs b/crypto/constant-time/tests/constant_time_asm_test.rs similarity index 66% rename from core/helper/tests/constant_time_asm_test.rs rename to crypto/constant-time/tests/constant_time_asm_test.rs index add0e48..a013eda 100644 --- a/core/helper/tests/constant_time_asm_test.rs +++ b/crypto/constant-time/tests/constant_time_asm_test.rs @@ -1,5 +1,27 @@ +/* + * Copyright (c) 2025-2026 Quant + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the “Software”), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + #![cfg(feature = "ct-tests")] -use entlib_native_helper::constant_time_asm::CtPrimitive; +use entlib_native_constant_time::constant_time_asm::CtPrimitive; macro_rules! test_ct_primitive { ($test_name:ident, $t:ty, $zero:expr, $one:expr, $neg:expr) => { diff --git a/core/helper/tests/constant_time_test.rs b/crypto/constant-time/tests/constant_time_test.rs similarity index 96% rename from core/helper/tests/constant_time_test.rs rename to crypto/constant-time/tests/constant_time_test.rs index a7557bb..c5a7b77 100644 --- a/core/helper/tests/constant_time_test.rs +++ b/crypto/constant-time/tests/constant_time_test.rs @@ -1,4 +1,4 @@ -use entlib_native_helper::constant_time::ConstantTimeOps; +use entlib_native_constant_time::constant_time::ConstantTimeOps; #[test] fn test_ct_eq_u32() { diff --git a/core/helper/tests/ct_cross_validation.rs b/crypto/constant-time/tests/ct_cross_validation.rs similarity index 98% rename from core/helper/tests/ct_cross_validation.rs rename to crypto/constant-time/tests/ct_cross_validation.rs index 7093f8c..3da648c 100644 --- a/core/helper/tests/ct_cross_validation.rs +++ b/crypto/constant-time/tests/ct_cross_validation.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod tests { - use entlib_native_helper::constant_time::ConstantTimeOps; + use entlib_native_constant_time::constant_time::ConstantTimeOps; use proptest::prelude::*; use subtle::Choice; diff --git a/crypto/core-secure/benches/secure_buffer_bench.rs b/crypto/core-secure/benches/secure_buffer_bench.rs new file mode 100644 index 0000000..6922b08 --- /dev/null +++ b/crypto/core-secure/benches/secure_buffer_bench.rs @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2025-2026 Quant + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the “Software”), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; + +use entlib_native_core_secure::secure_buffer::SecureBuffer; + +const SIZES: &[usize] = &[64, 256, 1_024, 4_096, 65_536, 1_048_576]; + +// +// SecureBuffer drop(wipe) 처리량 +// + +fn secure_buffer_wipe_throughput(c: &mut Criterion) { + let mut group = c.benchmark_group("throughput/secure_buffer_wipe"); + + for &size in SIZES { + group.throughput(Throughput::Bytes(size as u64)); + + let batch_size = if size >= 65_536 { + BatchSize::LargeInput + } else { + BatchSize::SmallInput + }; + + group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, &size| { + b.iter_batched( + || SecureBuffer { + inner: vec![0xAA; size], + }, + drop, // |buf| drop(buf) + batch_size, + ) + }); + } + + group.finish(); +} + +// +// Criterion 설정 +// + +criterion_group!(benches, secure_buffer_wipe_throughput); +criterion_main!(benches); diff --git a/crypto/core-secure/src/secure_buffer.rs b/crypto/core-secure/src/secure_buffer.rs new file mode 100644 index 0000000..2e2c562 --- /dev/null +++ b/crypto/core-secure/src/secure_buffer.rs @@ -0,0 +1,63 @@ +use core::ptr::write_volatile; +use core::sync::atomic::{Ordering, compiler_fence}; + +/// 메모리 소거를 보장하는 보안 버퍼 구조체입니다. +/// +/// 이 구조체는 쉽게 말해 Rust가 할당하고 소유하는 메모리입니다. +/// 이 네이티브 코드상에서 연산의 '결과물'을 새로 생성할 때 사용됩니다. +/// `Base64` 디코딩 결과, 암호화된 사이퍼텍스트 생성 등의 상황을 예로 +/// 들 수 있습니다. +/// +/// 민감한 데이터를 사용한 연산 후 그 결과를 Java Heap으로 전달하는 경우, +/// 가비지 컬렉터의 생명주기에 종속되어 위험에 다시 노출되는 딜레마를 가지게 +/// 됩니다. 이 문제를 해결하기 위해 단순히 `byte[]`와 같은 Java 데이터로 +/// 직렬화(serialize)하지 않고, 이 구조체에 저장합니다. +/// +/// Java 측으로는 오직 해당 메모리의 원시 포인만이 전달되기 떄문에 안전한 +/// 데이터 관리가 가능합니다. +pub struct SecureBuffer { + pub inner: Vec, +} + +impl Drop for SecureBuffer { + fn drop(&mut self) { + for byte in self.inner.iter_mut() { + // volatile write -> 컴파일러 dce 최적화 방지함 + unsafe { + write_volatile(byte, 0); + } + } + // 메모리 배리어로 소거 순서 보장 + compiler_fence(Ordering::SeqCst); + } +} + +/// 메모리 소거를 보장하며, Java 측에서 사용되는 보안 버퍼 구조체입니다. +/// +/// 이 구조체는 Java 측에서 민감 데이터를 이미 Off-Heap 영역에 할당하여 +/// 들고 있을 때 사용됩니다. +/// +/// 민감한 데이터를 사용한 연산 후 그 결과를 Java Heap으로 전달하는 경우, +/// 가비지 컬렉터의 생명주기에 종속되어 위험에 다시 노출되는 딜레마를 가지게 +/// 됩니다. 이 문제를 해결하기 위해 단순히 `byte[]`와 같은 Java 데이터로 +/// 직렬화(serialize)하지 않고, 이 구조체에 저장합니다. +#[repr(C)] +pub struct FFIExternalSecureBuffer { + pub inner: *mut u8, + pub len: usize, +} + +impl Drop for FFIExternalSecureBuffer { + fn drop(&mut self) { + if self.inner.is_null() || self.len == 0 { + return; + } + unsafe { + let slice = core::slice::from_raw_parts_mut(self.inner, self.len); + for byte in slice.iter_mut() { + write_volatile(byte, 0); + } + } + compiler_fence(Ordering::SeqCst); + } +} diff --git a/crypto/rng/Cargo.toml b/crypto/rng/Cargo.toml index 71a9b1f..a5ebc73 100644 --- a/crypto/rng/Cargo.toml +++ b/crypto/rng/Cargo.toml @@ -5,10 +5,12 @@ edition.workspace = true authors.workspace = true license.workspace = true -#[lib] -#crate-type = ["cdylib", "rlib"] - [dependencies] -entlib-native-helper.workspace = true +entlib-native-core-secure.workspace = true + +[dev-dependencies] +criterion = { version = "0.8.2", features = ["html_reports"] } -[dev-dependencies] \ No newline at end of file +[[bench]] +name = "anu_qrng_bench" +harness = false \ No newline at end of file diff --git a/crypto/rng/benches/anu_qrng_bench.rs b/crypto/rng/benches/anu_qrng_bench.rs new file mode 100644 index 0000000..7b16857 --- /dev/null +++ b/crypto/rng/benches/anu_qrng_bench.rs @@ -0,0 +1,98 @@ +//! 베이스가 TLS 통신 처리라 기본적으로 그닥 성능이 좋지 못함 +//! 하지만 난수가 Java로 전달되기 전의 지연율 상한성을 파악할 수 있어서 +//! 혼합 rng 모듈이 양자 난수를 최초 1회만 받아야 하는 수학적 근거가 됌. + +use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main}; +use entlib_native_rng::anu_qrng::AnuQrngClient; +use std::hint::black_box; // Q. T. Felix NOTE: std::hint blackbox +use std::time::Duration; + +/// 섀넌 엔트로피(shannon entropy) 계산 함수 +/// +/// 주어진 바이트 시퀀스의 정보량을 측정합니다. +/// 대규모 및 군사급 보안 시스템에서는 난수 스트림의 엔트로피가 8.0에 수렴하는지 실시간으로 +/// 평가(security evaluation)해야해서 해당 연산의 처리량(throughput) 측정은 필수적입니다. +fn compute_shannon_entropy(data: &[u8]) -> f64 { + let mut counts = [0usize; 256]; + for &byte in data { + counts[byte as usize] += 1; + } + + let mut entropy = 0.0; + let len = data.len() as f64; + + for &count in &counts { + if count > 0 { + let probability = count as f64 / len; + entropy -= probability * probability.log2(); + } + } + + entropy +} + +/// 양자 난수 추출 처리량(throughput) 벤치마크 +/// +/// 외부 네트워크(curl) 호출 및 무의존성(zero-dependency) json 파서의 병목을 분석합니다. +fn bench_qrng_throughput(c: &mut Criterion) { + let mut group = c.benchmark_group("QRNG_Throughput"); + + // 네트워크 지연(network latency)으로 인한 벤치마크 타임아웃을 방지하기 위해 + // 샘플 크기(sample size)를 축소하고 측정 시간을 연장합니다. + group.sample_size(10); + group.measurement_time(Duration::from_secs(20)); + + // 전략 패턴(strategy pattern)의 초기 상태 구성에 필요한 32바이트(키) 및 최대 허용치 1024바이트 + let lengths = [32usize, 256, 1024]; + + for &len in &lengths { + group.throughput(Throughput::Bytes(len as u64)); + group.bench_with_input( + BenchmarkId::new("fetch_secure_bytes", len), + &len, + |b, &size| { + b.iter(|| { + // black_box를 통해 컴파일러의 데드 코드 제거(dead code elimination) 최적화 방지 + let result = AnuQrngClient::fetch_secure_bytes(black_box(size)); + + // 네트워크 실패 시 벤치마크 패닉을 방지하고 에러 코드를 반환하도록 처리 + if let Ok(buffer) = result { + black_box(buffer); + } + }); + }, + ); + } + + group.finish(); +} + +/// 실시간 보안성 평가(security evaluation) 연산 오버헤드 벤치마크 +fn bench_qrng_security_evaluation(c: &mut Criterion) { + let mut group = c.benchmark_group("QRNG_Security_Evaluation"); + + let sizes = [32usize, 256, 1024]; + + for &size in &sizes { + // 실제 API 호출로 인한 네트워크 병목을 배제하고, 순수 엔트로피 연산의 + // CPU 처리량(cpu throughput)만을 정밀하게 평가하기 위해 더미(dummy) 데이터를 구성합니다. + let mock_data: Vec = (0..size).map(|i| (i % 256) as u8).collect(); + + group.throughput(Throughput::Bytes(size as u64)); + group.bench_with_input(BenchmarkId::new("shannon_entropy", size), &size, |b, _| { + b.iter(|| { + let entropy = compute_shannon_entropy(black_box(&mock_data)); + black_box(entropy); + }); + }); + } + + group.finish(); +} + +criterion_group!( + benches, + bench_qrng_throughput, + bench_qrng_security_evaluation +); +criterion_main!(benches); diff --git a/crypto/rng/src/anu_qrng.rs b/crypto/rng/src/anu_qrng.rs new file mode 100644 index 0000000..5bf0bd9 --- /dev/null +++ b/crypto/rng/src/anu_qrng.rs @@ -0,0 +1,77 @@ +//! ANU QRNG API(Streaming) +//! +//! TLS통신을 통해 작업을 수행하기 떄문에 +//! 네트워크 지연 등의 문제가 있습니다. +//! +//! # Author +//! Q. T. Felix + +use crate::base_rng::RngError; +use entlib_native_core_secure::secure_buffer::SecureBuffer; +use std::process::Command; +use std::str; +use std::vec::Vec; + +pub struct AnuQrngClient; + +impl AnuQrngClient { + pub fn fetch_secure_bytes(length: usize) -> Result { + if !(1..=1024).contains(&length) { + return Err(RngError::ParseError); + } + + let url = format!( + "https://qrng.anu.edu.au/API/jsonI.php?length={}&type=uint8", + length + ); + + // -f (HTTP 오류 시 실패), -L (리다이렉트 추적) + let output = Command::new("curl") + .arg("-s") + .arg("-f") + .arg("-L") + .arg(url) + .output() + .map_err(|_| RngError::NetworkFailure)?; + + if !output.status.success() { + return Err(RngError::NetworkFailure); + } + + let response_str = str::from_utf8(&output.stdout).map_err(|_| RngError::ParseError)?; + + let bytes = Self::parse_json_data(response_str)?; + + Ok(SecureBuffer { inner: bytes }) + } + + /// 공백 및 포맷 변경에 강건하게 대응하는 개선된 슬라이싱 파서 + pub fn parse_json_data(json: &str) -> Result, RngError> { + let data_key = "\"data\""; + + // "data" 키의 시작 위치 탐색 + let key_idx = json.find(data_key).ok_or(RngError::ParseError)?; + + // 키 이후부터 첫 번째 '[' 탐색 + let start_bracket = json[key_idx..].find('[').ok_or(RngError::ParseError)? + key_idx + 1; + + // '[' 이후부터 첫 번째 ']' 탐색 + let end_bracket = json[start_bracket..] + .find(']') + .ok_or(RngError::ParseError)? + + start_bracket; + + let data_part = &json[start_bracket..end_bracket]; + let mut bytes = Vec::new(); + + for val_str in data_part.split(',') { + let trimmed = val_str.trim(); + if !trimmed.is_empty() { + let val = trimmed.parse::().map_err(|_| RngError::ParseError)?; + bytes.push(val); + } + } + + Ok(bytes) + } +} diff --git a/crypto/rng/src/base_rng.rs b/crypto/rng/src/base_rng.rs index c599db7..2da5f69 100644 --- a/crypto/rng/src/base_rng.rs +++ b/crypto/rng/src/base_rng.rs @@ -12,7 +12,7 @@ use core::arch::asm; use core::ptr::{copy_nonoverlapping, write_unaligned, write_volatile}; use core::sync::atomic::compiler_fence; -use entlib_native_helper::secure_buffer::SecureBuffer; +use entlib_native_core_secure::secure_buffer::SecureBuffer; use std::sync::atomic::Ordering; use std::vec::Vec; @@ -23,6 +23,10 @@ pub enum RngError { UnsupportedHardware, /// 엔트로피 풀 고갈 또는 하드웨어 응답에 실패했습니다. EntropyDepletion, + /// 양자 난수 네트워크 요청(curl)이 실패했습니다. + NetworkFailure, + /// 양자 난수 API 응답 데이터 파싱에 실패했습니다. + ParseError, } /// 요청한 길이만큼의 진난수를 포함하는 보안 버퍼를 반환합니다. diff --git a/crypto/rng/src/lib.rs b/crypto/rng/src/lib.rs index 5179aa5..4bd6aa2 100644 --- a/crypto/rng/src/lib.rs +++ b/crypto/rng/src/lib.rs @@ -1,2 +1,3 @@ +pub mod anu_qrng; pub mod base_rng; pub mod mixed; diff --git a/crypto/rng/src/mixed.rs b/crypto/rng/src/mixed.rs index d9edc21..b3b23d9 100644 --- a/crypto/rng/src/mixed.rs +++ b/crypto/rng/src/mixed.rs @@ -13,10 +13,11 @@ //! # Note //! 이 기능은 곧 `chacha20` 모듈로 차별화됩니다. +use crate::anu_qrng::AnuQrngClient; use crate::base_rng::{RngError, generate_hardware_random_bytes}; use core::ptr::{copy_nonoverlapping, write_volatile}; use core::sync::atomic::{Ordering, compiler_fence}; -use entlib_native_helper::secure_buffer::SecureBuffer; +use entlib_native_core_secure::secure_buffer::SecureBuffer; /// 상수 시간(constant-time) 연산을 보장하는 chacha20 쿼터 라운드(quarter round) macro_rules! quarter_round { @@ -36,6 +37,14 @@ macro_rules! quarter_round { }; } +/// 엔트로피 소스(entropy source)를 결정하는 전략(strategy) 열거형 +pub enum EntropyStrategy { + /// 로컬 프로세서의 하드웨어 난수 생성기(`rdseed`, `rndr` 등)를 사용합니다. + LocalHardware, + /// ANU 양자 난수 API를 통해 진공 양자 요동(quantum vacuum fluctuations) 데이터를 가져옵니다. + QuantumNetwork, +} + /// 혼합 난수 생성기(mixed rng) 구조체 /// /// 하드웨어 진난수를 시드(seed)로 사용하여 512-비트(bit) 상태를 초기화합니다. @@ -44,11 +53,19 @@ pub struct MixedRng { } impl MixedRng { - /// 하드웨어 명령어를 호출하여 새로운 혼합 난수 생성기를 인스턴스화합니다. - pub fn new() -> Result { - // 32바이트 키(key) 및 12바이트 논스(nonce) 추출 - let hw_key = generate_hardware_random_bytes(32)?; - let hw_nonce = generate_hardware_random_bytes(12)?; + /// 지정된 엔트로피 전략을 사용하여 새로운 혼합 난수 생성기를 인스턴스화합니다. + pub fn new(strategy: EntropyStrategy) -> Result { + // 전략 패턴(strategy pattern)에 따른 시드(seed) 추출 분기 + let (hw_key, hw_nonce) = match strategy { + EntropyStrategy::LocalHardware => ( + generate_hardware_random_bytes(32)?, + generate_hardware_random_bytes(12)?, + ), + EntropyStrategy::QuantumNetwork => ( + AnuQrngClient::fetch_secure_bytes(32)?, + AnuQrngClient::fetch_secure_bytes(12)?, + ), + }; let mut state = [0u32; 16]; @@ -73,6 +90,9 @@ impl MixedRng { // 블록 카운터(block counter) state[12] = 0; + // hw_key와 hw_nonce는 SecureBuffer의 Drop 트레이트 구현에 의해 + // 스코프를 벗어날 때 자동으로 데이터 소거(zeroize)가 수행됨 + Ok(Self { state }) } diff --git a/crypto/rng/tests/anu_qrng_test.rs b/crypto/rng/tests/anu_qrng_test.rs new file mode 100644 index 0000000..71d726e --- /dev/null +++ b/crypto/rng/tests/anu_qrng_test.rs @@ -0,0 +1,88 @@ +#[cfg(test)] +mod tests { + use entlib_native_rng::anu_qrng::*; + use entlib_native_rng::base_rng::RngError; + use std::collections::HashSet; + + #[test] + fn test_fetch_secure_bytes_valid() { + // 정상적인 길이의 난수 요청이 보안 버퍼(secure buffer)에 올바르게 적재되는지 검증 + let target_length = 32; + + match AnuQrngClient::fetch_secure_bytes(target_length) { + Ok(buffer) => { + assert_eq!( + buffer.inner.len(), + target_length, + "요청한 길이와 반환된 버퍼의 크기가 일치하지 않습니다." + ); + } + Err(RngError::NetworkFailure) | Err(RngError::ParseError) => { + // 폐쇄망 환경이거나 ANU 서버의 정책 변경으로 인한 통신 불가 시 테스트를 안전하게 우회 + println!( + "Skipping network test: ANU QRNG service is currently unreachable or format changed." + ); + } + Err(e) => { + panic!( + "예기치 않은 보안 모듈 에러(unexpected security module error): {:?}", + e + ); + } + } + } + + #[test] + fn test_fetch_secure_bytes_invalid_bounds() { + // 버퍼 오버플로 및 비정상적 메모리 할당을 방지하기 위한 경계값 검증 + // api가 허용하는 1~1024 범위를 벗어난 요청 시 명시적 에러반환 + assert!(AnuQrngClient::fetch_secure_bytes(0).is_err()); + assert!(AnuQrngClient::fetch_secure_bytes(1025).is_err()); + } + + #[test] + fn test_parse_json_data_stability() { + // 외부 라이브러리 없이 구현된 슬라이싱(slicing) 기반 파서의 정상 작동 검증 + let mock_json = + r#"{"type":"uint8","length":5,"data":[12, 255, 0, 128, 42],"success":true}"#; + let result = AnuQrngClient::parse_json_data(mock_json); + + assert!(result.is_ok()); + let bytes = result.unwrap(); + assert_eq!(bytes, vec![12, 255, 0, 128, 42]); + } + + #[test] + fn test_parse_json_data_malformed() { + // 손상된 페이로드(malformed payload) 주입 시 시스템이 패닉(panic)에 빠지지 않고 + // 안전하게 에러(error)를 반환하는지 검증 + let missing_data_json = r#"{"type":"uint8","length":5,"success":true}"#; + assert!(AnuQrngClient::parse_json_data(missing_data_json).is_err()); + + let unclosed_bracket_json = r#"{"type":"uint8","length":5,"data":[12, 255, 0"#; + assert!(AnuQrngClient::parse_json_data(unclosed_bracket_json).is_err()); + + let invalid_type_json = r#"{"type":"uint8","length":1,"data":[NaN],"success":false}"#; + assert!(AnuQrngClient::parse_json_data(invalid_type_json).is_err()); + } + + #[test] + fn test_randomness_basic_entropy() { + // 생성된 양자 난수(quantum random number)의 기본적인 무작위성(randomness)을 평가 + // 추출된 바이트 시퀀스 내의 고유값(unique values) 개수를 확인하여, 통신 오류로 인해 + // 0으로만 채워진 배열(zeroed array)이 반환되는 치명적 보안 결함을 방지함 + let len = 100; + if let Ok(buffer) = AnuQrngClient::fetch_secure_bytes(len) { + let mut unique_values = HashSet::new(); + for &byte in &buffer.inner { + unique_values.insert(byte); + } + + // 100바이트 표본에서 이론적 엔트로피 $H(X)$가 0에 수렴하는 경우(동일한 값만 반복)를 실패로 간주합니다. + assert!( + unique_values.len() > 10, + "추출된 난수의 엔트로피가 보안 요구사항을 충족하지 못합니다." + ); + } + } +} diff --git a/crypto/rng/tests/hw_based_rng_test.rs b/crypto/rng/tests/hw_based_rng_test.rs index 7440255..e0b1a3e 100644 --- a/crypto/rng/tests/hw_based_rng_test.rs +++ b/crypto/rng/tests/hw_based_rng_test.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod tests { - use entlib_native_helper::secure_buffer::SecureBuffer; + use entlib_native_core_secure::secure_buffer::SecureBuffer; use entlib_native_rng::base_rng::{RngError, generate_hardware_random_bytes, next_generate}; #[test] diff --git a/crypto/rng/tests/mixed_rng_test.rs b/crypto/rng/tests/mixed_rng_test.rs index 8b6fa92..265bfd2 100644 --- a/crypto/rng/tests/mixed_rng_test.rs +++ b/crypto/rng/tests/mixed_rng_test.rs @@ -1,38 +1,59 @@ #[cfg(test)] mod tests { use entlib_native_rng::base_rng::RngError; - use entlib_native_rng::mixed::MixedRng; + use entlib_native_rng::mixed::{EntropyStrategy, MixedRng}; + + /// 등록된 모든 엔트로피 전략(entropy strategy)에 대해 동일한 테스트를 수행하기 위한 헬퍼 함수입니다. + fn run_with_strategies(test_logic: F) + where + F: Fn(EntropyStrategy, &str), + { + // 로컬 프로세서 하드웨어 난수 기반(trng) + test_logic(EntropyStrategy::LocalHardware, "LocalHardware"); + // 외부 양자 난수 네트워크 통신 기반(qrng) + test_logic(EntropyStrategy::QuantumNetwork, "QuantumNetwork"); + } #[test] fn test_mixed_rng_initialization() { - // MixedRng 초기화 테스트 - match MixedRng::new() { - Ok(rng) => { - // 초기화 성공 시, 내부 상태가 0이 아닌지 확인 (간접적 확인) - // state 필드는 private이므로 직접 접근 불가하지만, - // generate 호출을 통해 동작 여부 확인 가능 - let mut rng = rng; - let result = rng.generate(32); - assert!( - result.is_ok(), - "Failed to generate random bytes after initialization" - ); - } - Err(RngError::UnsupportedHardware) => { - println!("Skipping initialization test: Hardware RNG not supported"); - } - Err(e) => { - panic!("Failed to initialize MixedRng: {:?}", e); + // MixedRng 초기화 및 의존성 주입 테스트 + run_with_strategies(|strategy, strategy_name| { + match MixedRng::new(strategy) { + Ok(mut rng) => { + // 초기화 성공 시, 32바이트(byte) 난수 생성을 통한 내부 상태 무결성 확인 + let result = rng.generate(32); + assert!( + result.is_ok(), + "[{}] Failed to generate random bytes after initialization", + strategy_name + ); + } + Err(RngError::UnsupportedHardware) => { + println!( + "[{}] Skipping initialization test: Hardware RNG not supported", + strategy_name + ); + } + Err(RngError::NetworkFailure) | Err(RngError::ParseError) => { + // 오프라인 환경 또는 방화벽에 의한 네트워크 차단 시 테스트 우회(skip) + println!( + "[{}] Skipping initialization test: Network or parsing unavailable", + strategy_name + ); + } + Err(e) => { + panic!("[{}] Failed to initialize MixedRng: {:?}", strategy_name, e); + } } - } + }); } #[test] fn test_mixed_rng_generation_length() { - // 다양한 길이의 난수 생성 테스트 + // 다양한 길이의 블록(block) 및 잔여 바이트 생성 테스트 let lengths = [0, 1, 16, 64, 128, 1024]; - match MixedRng::new() { + run_with_strategies(|strategy, strategy_name| match MixedRng::new(strategy) { Ok(mut rng) => { for &len in &lengths { match rng.generate(len) { @@ -40,76 +61,97 @@ mod tests { assert_eq!( buffer.inner.len(), len, - "Buffer length mismatch for requested length: {}", + "[{}] Buffer length mismatch for requested length: {}", + strategy_name, len ); } Err(e) => { panic!( - "Failed to generate random bytes for length {}: {:?}", - len, e + "[{}] Failed to generate random bytes for length {}: {:?}", + strategy_name, len, e ); } } } } - Err(RngError::UnsupportedHardware) => { - println!("Skipping length test: Hardware RNG not supported"); + Err(RngError::UnsupportedHardware) + | Err(RngError::NetworkFailure) + | Err(RngError::ParseError) => { + println!( + "[{}] Skipping length test: Dependency unavailable", + strategy_name + ); } Err(e) => { - panic!("Failed to initialize MixedRng: {:?}", e); + panic!("[{}] Failed to initialize MixedRng: {:?}", strategy_name, e); } - } + }); } #[test] fn test_mixed_rng_randomness() { - // 생성된 난수의 무작위성 (기본적인 중복 검사) - match MixedRng::new() { - Ok(mut rng) => { - let len = 32; - let buf1 = rng.generate(len).expect("First generation failed"); - let buf2 = rng.generate(len).expect("Second generation failed"); + // 생성된 난수 스트림(random stream)의 멱등성(idempotence) 및 중복 검사 + run_with_strategies(|strategy, strategy_name| { + match MixedRng::new(strategy) { + Ok(mut rng) => { + let len = 32; + let buf1 = rng.generate(len).expect("First generation failed"); + let buf2 = rng.generate(len).expect("Second generation failed"); - // 연속 호출 시 동일한 값이 나오지 않는지 확인 - assert_ne!( - buf1.inner, buf2.inner, - "Two consecutive random generations produced identical output" - ); - } - Err(RngError::UnsupportedHardware) => { - println!("Skipping randomness test: Hardware RNG not supported"); - } - Err(e) => { - panic!("Failed to initialize MixedRng: {:?}", e); + // 연속 호출 시 동일한 값이 출력되지 않음을 증명(내부 상태가 정상 갱신됨을 의미) + assert_ne!( + buf1.inner, buf2.inner, + "[{}] Two consecutive random generations produced identical output", + strategy_name + ); + } + Err(RngError::UnsupportedHardware) + | Err(RngError::NetworkFailure) + | Err(RngError::ParseError) => { + println!( + "[{}] Skipping randomness test: Dependency unavailable", + strategy_name + ); + } + Err(e) => { + panic!("[{}] Failed to initialize MixedRng: {:?}", strategy_name, e); + } } - } + }); } #[test] fn test_mixed_rng_block_counter_increment() { - // 블록 카운터 증가에 따른 출력 변화 확인 - // 64바이트(1블록) 이상 생성 시 내부 카운터가 증가하여 다음 블록이 생성되어야 함 - match MixedRng::new() { - Ok(mut rng) => { - // 128바이트 생성 (2개 블록) - let len = 128; - let buffer = rng.generate(len).expect("Generation failed"); + // chacha20 코어(core) 블록 카운터(block counter) 증가에 따른 출력 전이 검증 + run_with_strategies(|strategy, strategy_name| { + match MixedRng::new(strategy) { + Ok(mut rng) => { + // 128바이트 생성 (64바이트 단위 2개 블록) + let len = 128; + let buffer = rng.generate(len).expect("Generation failed"); - let (first_block, second_block) = buffer.inner.split_at(64); + let (first_block, second_block) = buffer.inner.split_at(64); - // 첫 번째 블록과 두 번째 블록이 달라야 함 - assert_ne!( - first_block, second_block, - "Consecutive blocks are identical, counter might not be incrementing" - ); - } - Err(RngError::UnsupportedHardware) => { - println!("Skipping block counter test: Hardware RNG not supported"); - } - Err(e) => { - panic!("Failed to initialize MixedRng: {:?}", e); + // 블록 카운터가 올바르게 증가햇다면 두 블록의 암호학적 출력은 독립적이어야 함 + assert_ne!( + first_block, second_block, + "[{}] Consecutive blocks are identical, counter might not be incrementing", + strategy_name + ); + } + Err(RngError::UnsupportedHardware) + | Err(RngError::NetworkFailure) + | Err(RngError::ParseError) => { + println!( + "[{}] Skipping block counter test: Dependency unavailable", + strategy_name + ); + } + Err(e) => { + panic!("[{}] Failed to initialize MixedRng: {:?}", strategy_name, e); + } } - } + }); } } diff --git a/crypto/sha2/Cargo.toml b/crypto/sha2/Cargo.toml index 0553ee6..02e7a2b 100644 --- a/crypto/sha2/Cargo.toml +++ b/crypto/sha2/Cargo.toml @@ -9,10 +9,10 @@ license.workspace = true crate-type = ["cdylib", "rlib"] [dependencies] -entlib-native-helper.workspace = true [dev-dependencies] criterion = { version = "0.8.2", features = ["html_reports"] } +entlib-native-core-secure.workspace = true entlib-native-rng.workspace = true [[bench]] diff --git a/crypto/sha2/benches/sha2_bench.rs b/crypto/sha2/benches/sha2_bench.rs index d5e10ee..7e6d2a5 100644 --- a/crypto/sha2/benches/sha2_bench.rs +++ b/crypto/sha2/benches/sha2_bench.rs @@ -1,5 +1,5 @@ use criterion::{Criterion, Throughput, criterion_group, criterion_main}; -use entlib_native_helper::secure_buffer::SecureBuffer; +use entlib_native_core_secure::secure_buffer::SecureBuffer; use entlib_native_rng::base_rng::next_generate; use entlib_native_sha2::api::*; use std::hint::black_box; diff --git a/crypto/sha3/Cargo.toml b/crypto/sha3/Cargo.toml index ddf9c10..b169460 100644 --- a/crypto/sha3/Cargo.toml +++ b/crypto/sha3/Cargo.toml @@ -9,10 +9,10 @@ license.workspace = true crate-type = ["cdylib", "rlib"] [dependencies] -entlib-native-helper.workspace = true [dev-dependencies] criterion = { version = "0.8.2", features = ["html_reports"] } +entlib-native-core-secure.workspace = true entlib-native-rng.workspace = true [[bench]] diff --git a/crypto/sha3/benches/sha3_bench.rs b/crypto/sha3/benches/sha3_bench.rs index 8303b9b..9f611da 100644 --- a/crypto/sha3/benches/sha3_bench.rs +++ b/crypto/sha3/benches/sha3_bench.rs @@ -1,5 +1,5 @@ use criterion::{Criterion, Throughput, criterion_group, criterion_main}; -use entlib_native_helper::secure_buffer::SecureBuffer; +use entlib_native_core_secure::secure_buffer::SecureBuffer; use entlib_native_rng::base_rng::next_generate; use entlib_native_sha3::api::*; use std::hint::black_box; diff --git a/core/ffi/Cargo.toml b/internal/ffi/Cargo.toml similarity index 61% rename from core/ffi/Cargo.toml rename to internal/ffi/Cargo.toml index 62bd654..dbdbb67 100644 --- a/core/ffi/Cargo.toml +++ b/internal/ffi/Cargo.toml @@ -20,19 +20,20 @@ strip = true panic = "abort" [dependencies] -entlib-native-helper.workspace = true +entlib-native-base64.workspace = true +entlib-native-constant-time.workspace = true +entlib-native-core-secure.workspace = true +entlib-native-rng.workspace = true entlib-native-sha2.workspace = true entlib-native-sha3.workspace = true -#libc = "0.2.182" -#libc_alloc = "1.0.7" [dev-dependencies] -criterion = { version = "0.8.2", features = ["html_reports"] } - -[[bench]] -name = "ffi_base64_bench" -harness = false - -[[bench]] -name = "ffi_wipe_bench" -harness = false \ No newline at end of file +#criterion = { version = "0.8.2", features = ["html_reports"] } +# +#[[bench]] +#name = "ffi_base64_bench" +#harness = false +# +#[[bench]] +#name = "ffi_wipe_bench" +#harness = false \ No newline at end of file diff --git a/internal/ffi/src/base64_ffi.rs b/internal/ffi/src/base64_ffi.rs new file mode 100644 index 0000000..4d4f18e --- /dev/null +++ b/internal/ffi/src/base64_ffi.rs @@ -0,0 +1,219 @@ +//! 상수-시간(constant-time) Base64 인코딩·디코딩 FFI 모듈 +//! +//! Java/Kotlin 측에서 호출자 할당(caller-alloc) 메모리에 직접 기록합니다. +//! 모든 연산은 분기 없는 상수 시간으로 side-channel 공격에 저항합니다. +//! +//! # Author +//! Q. T. Felix + +use entlib_native_base64::base64::{ct_b64_to_bin_u8, ct_bin_to_b64_u8}; +use entlib_native_constant_time::constant_time::ConstantTimeOps; +use std::slice; + +/// Java 측에서 할당한 메모리에 `Base64` 인코딩 결과를 직접 기록하는 함수입니다. +/// +/// # Arguments +/// * `input_ptr` - 인코딩할 평문 배열의 포인터 +/// * `input_len` - 평문 배열의 길이 +/// * `out_ptr` - (호출자 할당) 인코딩된 결과를 기록할 메모리의 시작 포인터 +/// * `out_capacity` - 할당된 출력 메모리의 최대 바이트 크기 +/// +/// # Returns +/// * `>= 0`: 성공 시 인코딩된 결과의 실제 바이트 길이 반환 +/// * `< 0`: 에러 코드 반환 (-1: `Null`, -2: `Capacity` 부족) +/// +/// # Safety +/// 이 함수는 raw pointer를 직접 다루므로 unsafe입니다. 호출자는 다음을 **반드시** 보장해야 합니다. +/// +/// - `input_ptr`은 null이 아니며, `input_len` 바이트만큼 **읽기 유효**한 메모리를 가리켜야 합니다 +/// (정렬 요구사항 없음, u8 기준). +/// - `out_ptr`은 null이 아니며, `out_capacity` 바이트만큼 **쓰기 유효**한 메모리를 가리켜야 합니다. +/// - `input_ptr`과 `out_ptr`이 가리키는 메모리 영역은 서로 겹치지 않아야 합니다 +/// (aliasing violation → UB). +/// - `out_capacity`는 내부에서 계산된 `required_capacity` 이상이어야 합니다 +/// (함수가 -2를 반환하지만, 호출 전 미리 확인 권장). +/// - 호출 기간 동안 두 메모리 영역이 해제되거나 재할당되지 않아야 합니다. +/// - 단일 스레드에서 호출되며, concurrent 접근이 없어야 합니다. +/// +/// 함수 내부는 `write_volatile`과 constant-time 연산만 사용하므로 +/// timing attack 및 메모리 잔여 데이터 유출에 대한 군사급 보호를 제공합니다. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn entlib_b64_encode_caller_alloc( + input_ptr: *const u8, + input_len: usize, + out_ptr: *mut u8, + out_capacity: usize, +) -> isize { + // 포인터 유효성 검증 + if input_ptr.is_null() || out_ptr.is_null() { + return -1; + } + + // 필요 버퍼 크기 산출 및 오버플로우 방지 + let required_capacity = match input_len.checked_add(2) { + Some(val) => (val / 3).checked_mul(4), + None => return -1, + }; + + // 호출자가 할당한 용량(capacity) 검증 + if out_capacity < required_capacity.expect("entlib-native-ffi ERROR: overflow") { + return -2; + } + + let input = unsafe { slice::from_raw_parts(input_ptr, input_len) }; + let mut out_idx = 0; + let mut i = 0; + + // 상수 시간 인코딩 쓰기 루프 + while i < input_len { + let b0 = input[i]; + let b1 = if i + 1 < input_len { input[i + 1] } else { 0 }; + let b2 = if i + 2 < input_len { input[i + 2] } else { 0 }; + + let e0 = b0 >> 2; + let e1 = ((b0 & 0x03) << 4) | (b1 >> 4); + let e2 = ((b1 & 0x0F) << 2) | (b2 >> 6); + let e3 = b2 & 0x3F; + + unsafe { + core::ptr::write_volatile(out_ptr.add(out_idx), ct_bin_to_b64_u8(e0)); + core::ptr::write_volatile(out_ptr.add(out_idx + 1), ct_bin_to_b64_u8(e1)); + + // 패딩 처리 (상수 시간 마스킹으로 치환 권장) + let pad2 = if i + 1 < input_len { + ct_bin_to_b64_u8(e2) + } else { + b'=' + }; + let pad3 = if i + 2 < input_len { + ct_bin_to_b64_u8(e3) + } else { + b'=' + }; + + core::ptr::write_volatile(out_ptr.add(out_idx + 2), pad2); + core::ptr::write_volatile(out_ptr.add(out_idx + 3), pad3); + } + + out_idx += 4; + i += 3; + } + + out_idx as isize +} + +/// Java 측에서 할당한 메모리에 분기 없는 상수-시간(constant-time)으로 `Base64` 디코딩을 수행합니다. +/// +/// # Arguments +/// * `input_ptr` - 디코딩할 `Base64` 문자열의 포인터 +/// * `input_len` - 문자열의 바이트 길이 +/// * `out_ptr` - (호출자 할당) 복원된 평문을 기록할 메모리의 시작 포인터 +/// * `out_capacity` - 할당된 출력 메모리의 최대 바이트 크기 +/// +/// # Returns +/// * `>= 0`: 성공 시 디코딩된 평문의 실제 바이트 길이 반환 +/// * `< 0`: 에러 코드 반환 (-1: `Null`, -2: `Capacity` 부족, -3: 디코딩 중 유효하지 않은 문자열 감지) +/// +/// # Safety +/// 이 함수는 raw pointer를 직접 다루므로 unsafe입니다. 호출자는 다음을 **반드시** 보장해야 합니다. +/// +/// - `input_ptr`은 null이 아니며, `input_len` 바이트만큼 **읽기 유효**한 메모리를 가리켜야 합니다. +/// - `out_ptr`은 null이 아니며, `out_capacity` 바이트만큼 **쓰기 유효**한 메모리를 가리켜야 합니다. +/// - `input_ptr`과 `out_ptr`이 가리키는 메모리 영역은 서로 겹치지 않아야 합니다 (aliasing UB 방지). +/// - `out_capacity`는 `(input_len / 4 + 1) * 3` 이상이어야 합니다 (함수가 -2 반환). +/// - 호출 기간 동안 메모리 영역이 유효해야 합니다. +/// - 단일 스레드 호출, concurrent 접근 금지. +/// +/// 함수는 `ct_b64_to_bin_u8`와 [ConstantTimeOps::ct_select] 연산만 사용하며, +/// `write_volatile`로 메모리 잔여 데이터 유출을 방지합니다. +/// 잘못된 Base64 문자는 -3 에러로 안전하게 처리되므로 side-channel 누출이 없습니다. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn entlib_b64_decode_caller_alloc( + input_ptr: *const u8, + input_len: usize, + out_ptr: *mut u8, + out_capacity: usize, +) -> isize { + // 포인터 유효성 검증 + if input_ptr.is_null() || out_ptr.is_null() { + return -1; + } + + // 최대 필요 버퍼 크기 산출 (여유 공간 3바이트 포함) + let max_required_capacity = (input_len / 4 + 1) * 3; + + // 호출자가 할당한 용량 검증 + if out_capacity < max_required_capacity { + return -2; + } + + let input = unsafe { slice::from_raw_parts(input_ptr, input_len) }; + + let mut error_accum = 0u8; + let mut acc = 0u32; + let mut buf_idx = 0usize; + let mut out_idx = 0usize; + + for &byte in input { + let decoded = ct_b64_to_bin_u8(byte); + + let is_err = decoded.ct_eq(0xFF); + let is_pad = decoded.ct_eq(0x81); + let is_ws = decoded.ct_eq(0x80); + + let is_valid = !is_err & !is_pad & !is_ws; + error_accum |= is_err & 0x01; + + let valid_mask_u32 = (is_valid as i8 as i32) as u32; + let valid_mask_usize = (is_valid as i8 as isize) as usize; + + let next_acc = (acc << 6) | (decoded as u32 & 0x3F); + acc = next_acc.ct_select(acc, valid_mask_u32); + + let next_buf_idx = buf_idx + 1; + buf_idx = next_buf_idx.ct_select(buf_idx, valid_mask_usize); + + let is_full = buf_idx.ct_eq(4); + + let b0 = (acc >> 16) as u8; + let b1 = (acc >> 8) as u8; + let b2 = acc as u8; + + unsafe { + core::ptr::write_volatile(out_ptr.add(out_idx), b0); + core::ptr::write_volatile(out_ptr.add(out_idx + 1), b1); + core::ptr::write_volatile(out_ptr.add(out_idx + 2), b2); + } + + let next_out_idx = out_idx + 3; + out_idx = next_out_idx.ct_select(out_idx, is_full); + + acc = 0u32.ct_select(acc, is_full as u32); + buf_idx = 0usize.ct_select(buf_idx, is_full); + } + + let is_two = buf_idx.ct_eq(2); + let is_three = buf_idx.ct_eq(3); + + let b0_2 = (acc >> 4) as u8; + let b0_3 = (acc >> 10) as u8; + let b1_3 = (acc >> 2) as u8; + + let final_b0 = b0_2.ct_select(0, is_two as u8) | b0_3.ct_select(0, is_three as u8); + let final_b1 = b1_3.ct_select(0, is_three as u8); + + unsafe { + core::ptr::write_volatile(out_ptr.add(out_idx), final_b0); + core::ptr::write_volatile(out_ptr.add(out_idx + 1), final_b1); + } + + let add_len = 1usize.ct_select(0, is_two) | 2usize.ct_select(0, is_three); + out_idx += add_len; + + // 에러 플래그 누적 여부 확인 후 분기 반환 + if error_accum != 0 { + return -3; + } + + out_idx as isize +} diff --git a/core/ffi/src/lib.rs b/internal/ffi/src/lib.rs similarity index 90% rename from core/ffi/src/lib.rs rename to internal/ffi/src/lib.rs index 46243bc..6edac8e 100644 --- a/core/ffi/src/lib.rs +++ b/internal/ffi/src/lib.rs @@ -1,6 +1,7 @@ pub mod base64_ffi; -pub mod secure_buffer; -pub mod sha2; +mod rng_ffi; +pub mod secure_buffer_ffi; +pub mod sha2_ffi; /// ffi 작업 중 발생할 수 있는 상태 코드 (status code) #[repr(C)] @@ -28,7 +29,7 @@ pub enum FFIStatus { // // #[cfg(not(test))] // #[panic_handler] -// fn panic(_info: &core::panic::PanicInfo) -> ! { +// fn panic(_info: &internal::panic::PanicInfo) -> ! { // // 민감 데이터 유출 방지를 위해 즉시 해제 // unsafe { // libc::abort(); diff --git a/internal/ffi/src/rng_ffi.rs b/internal/ffi/src/rng_ffi.rs new file mode 100644 index 0000000..9e3a418 --- /dev/null +++ b/internal/ffi/src/rng_ffi.rs @@ -0,0 +1,158 @@ +//! 난수 생성기 FFI 브릿지 모듈 +//! +//! `base_rng` (하드웨어 TRNG)와 `mixed` (ChaCha20 혼합 RNG)를 Java/Kotlin 네이티브 환경으로 +//! 안전하게 노출합니다. ANU QRNG 네트워크 엔트로피도 전략 선택으로 사용 가능. +//! +//! # Security +//! - 모든 민감 상태는 `MixedRng::Drop` + `SecureBuffer::Drop`에 의해 강제 zeroize +//! - FFI 경계에서 철저한 null 체크 + 에러 코드 매핑 +//! - Rust 2024 에디션 완벽 호환 (unsafe-op-in-unsafe-fn 해결) +//! +//! # Author +//! Q. T. Felix + +use core::ptr; +use entlib_native_core_secure::secure_buffer::SecureBuffer; +use entlib_native_rng::base_rng::{RngError, generate_hardware_random_bytes, next_generate}; +use entlib_native_rng::mixed::{EntropyStrategy, MixedRng}; +use std::boxed::Box; + +/// FFI 에러 코드 매핑 (모든 RngError variant + FFI 전용 코드 커버) +#[inline(always)] +fn map_rng_error(err: RngError) -> u8 { + match err { + RngError::UnsupportedHardware => 1, + RngError::EntropyDepletion => 2, + RngError::NetworkFailure => 4, + RngError::ParseError => 5, + } +} + +// ================================================ +// 하드웨어 진난수 생성기 (Hardware TRNG) FFI +// ================================================ + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn entlib_rng_hw_generate( + len: usize, + err_flag: *mut u8, +) -> *mut SecureBuffer { + if !err_flag.is_null() { + unsafe { + *err_flag = 0; + } + } + + match generate_hardware_random_bytes(len) { + Ok(buffer) => Box::into_raw(Box::new(buffer)), + Err(e) => { + if !err_flag.is_null() { + unsafe { + *err_flag = map_rng_error(e); + } + } + ptr::null_mut() + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn entlib_rng_hw_next_generate(buf: *mut SecureBuffer) -> u8 { + if buf.is_null() { + return 3; // Invalid pointer + } + + let buffer = unsafe { &mut *buf }; + match next_generate(buffer) { + Ok(_) => 0, + Err(e) => map_rng_error(e), + } +} + +// ================================================ +// 혼합 난수 생성기 (Mixed RNG with ChaCha20) FFI +// ================================================ + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn entlib_rng_mixed_new_with_strategy( + strategy: u8, + err_flag: *mut u8, +) -> *mut MixedRng { + if !err_flag.is_null() { + unsafe { + *err_flag = 0; + } + } + + let entropy_strategy = match strategy { + 0 => EntropyStrategy::LocalHardware, + 1 => EntropyStrategy::QuantumNetwork, + _ => { + if !err_flag.is_null() { + unsafe { + *err_flag = 3; + } // Invalid strategy + } + return ptr::null_mut(); + } + }; + + match MixedRng::new(entropy_strategy) { + Ok(rng) => Box::into_raw(Box::new(rng)), + Err(e) => { + if !err_flag.is_null() { + unsafe { + *err_flag = map_rng_error(e); + } + } + ptr::null_mut() + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn entlib_rng_mixed_new(err_flag: *mut u8) -> *mut MixedRng { + unsafe { entlib_rng_mixed_new_with_strategy(0, err_flag) } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn entlib_rng_mixed_generate( + rng_ptr: *mut MixedRng, + len: usize, + err_flag: *mut u8, +) -> *mut SecureBuffer { + if !err_flag.is_null() { + unsafe { + *err_flag = 0; + } + } + + if rng_ptr.is_null() { + if !err_flag.is_null() { + unsafe { + *err_flag = 3; + } + } + return ptr::null_mut(); + } + + let rng = unsafe { &mut *rng_ptr }; + match rng.generate(len) { + Ok(buffer) => Box::into_raw(Box::new(buffer)), + Err(e) => { + if !err_flag.is_null() { + unsafe { + *err_flag = map_rng_error(e); + } + } + ptr::null_mut() + } + } +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn entlib_rng_mixed_free(rng_ptr: *mut MixedRng) { + if !rng_ptr.is_null() { + let _ = unsafe { Box::from_raw(rng_ptr) }; // Drop → zeroize 보장 + } +} diff --git a/internal/ffi/src/secure_buffer_ffi.rs b/internal/ffi/src/secure_buffer_ffi.rs new file mode 100644 index 0000000..86e9ed8 --- /dev/null +++ b/internal/ffi/src/secure_buffer_ffi.rs @@ -0,0 +1,98 @@ +//! 외부 함수 인터페이스(foreign function interface) 환경에서 민감 데이터의 안전한 교환 및 +//! 메모리 소거(zeroize)를 완벽하게 보장하기 위한 통제 모듈입니다. +//! +//! 얽힘 라이브러리의 Java 런타임과 네이티브 환경 간의 브리지 역할을 수행하며, +//! 가비지 컬렉터(garbage collector)에 의한 메모리 누수 및 잔류 데이터 노출을 원천 차단합니다. +//! 본 모듈은 대규모 엔터프라이즈 및 군사급 보안 요구사항을 충족하기 위해, 다음과 같이 +//! 두 가지 독립적이고 엄격한 메모리 소유권(memory ownership) 모델을 지원합니다. +//! +//! # 피호출자 할당 패턴 (callee-allocated, rust-owned memory) +//! 네이티브 환경에서 연산 결과의 크기를 Java 측이 사전에 알 수 없는 경우(가변 길이의 암호문 생성 등) +//! 사용되는 패턴입니다. Rust가 동적으로 할당한 [SecureBuffer]의 불투명 포인터(opaque pointer)가 +//! Java로 반환됩니다. +//! +//! Java 측은 획득한 포인터를 통해 다음 함수들을 순차적으로 호출해야 합니다. +//! - [entlib_secure_buffer_data]: 실제 데이터의 메모리 주소 매핑 +//! - [entlib_secure_buffer_len]: 데이터의 바이트 길이 확인 +//! - [entlib_secure_buffer_free]: 사용 완료 후 즉각적인 데이터 소거 및 메모리 할당 해제(deallocation) 지시 +//! +//! # 호출자 할당 패턴 (caller-allocated, java-owned memory) +//! Java 측의 보안 데이터 컨테이너(`SensitiveDataContainer`)가 `off-heap` 영역에 메모리를 +//! 선제적으로 확보하여 제공하는 경우 사용되는 패턴입니다. +//! +//! Java 스코프 컨텍스트(`SDCScopeContext`)가 종료될 때 호출되며, 네이티브는 데이터의 덮어쓰기만 수행합니다. +//! - [entanglement_secure_wipe]: Java가 소유한 메모리 영역을 임시로 `FFIExternalSecureBuffer`에 +//! 매핑하여 소거 로직을 실행하되, 할당 해제는 수행하지 않음 (해제 권한은 Java에 위임됨) +//! +//! # Authors +//! Q. T. Felix + +use core::ptr; +use entlib_native_core_secure::secure_buffer::{FFIExternalSecureBuffer, SecureBuffer}; + +/// 보안 버퍼 내 실제 데이터의 메모리 주소 반환 (get immutable data pointer) +/// +/// # Safety +/// - 반환된 원시 포인터(raw pointer)는 `SecureBuffer`가 `entlib_secure_buffer_free`를 통해 +/// 해제되기 전까지만 유효합니다. 해제 후 역참조 시 미정의 동작(undefined behavior)이 발생합니다. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn entlib_secure_buffer_data(buf: *const SecureBuffer) -> *const u8 { + if buf.is_null() { + return ptr::null(); + } + let buffer = unsafe { &*buf }; + buffer.inner.as_ptr() +} + +/// 보안 버퍼 내 데이터의 바이트 길이 반환 (get length of data) +/// +/// # Safety +/// - `buf`가 null이 아닌 경우, 유효한 `SecureBuffer` 인스턴스를 가리켜야 합니다. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn entlib_secure_buffer_len(buf: *const SecureBuffer) -> usize { + if buf.is_null() { + return 0; + } + let buffer = unsafe { &*buf }; + buffer.inner.len() +} + +/// 보안 버퍼 메모리 해제 및 데이터 소거 (free and zeroize) +/// +/// # Safety +/// - 호출 즉시 `SecureBuffer`의 `Drop` 트레이트가 실행되어 `write_volatile` 및 +/// `compiler_fence`를 통해 메모리가 안전하게 소거됩니다. +/// - Java 링커(java linker api)를 통해 반드시 한 번만 호출되어야 합니다 (double-free 방지). +#[unsafe(no_mangle)] +pub unsafe extern "C" fn entlib_secure_buffer_free(buf: *mut SecureBuffer) { + if !buf.is_null() { + unsafe { + // Box::from_raw를 통해 소유권을 가져오며, 즉시 스코프를 벗어나 Drop 실행 + drop(Box::from_raw(buf)); + } + } +} + +/// Java 측 `SensitiveDataContainer`가 소유한 네이티브 메모리 세그먼트(memory segment)를 +/// 안전하게 소거(zeroize)하는 ffi 엔드포인트입니다. +/// +/// # Arguments +/// * `ptr` - 소거할 메모리 영역의 시작 포인터 (*mut u8) +/// * `len` - 소거할 메모리의 바이트 크기 (usize) +/// +/// # Safety +/// - `ptr`은 `len` 바이트만큼 할당된 유효한 메모리 영역을 가리켜야 합니다. +/// - Java의 제한된 아레나(confined arena) 수명 주기에 의해 유효성이 검증된 상태에서만 호출되어야 합니다. +/// - 이 함수는 호출자 할당 패턴으로, Java 측에서 할당 해제를 수행해야 합니다. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn entanglement_secure_wipe(ptr: *mut u8, len: usize) { + // null 포인터 및 길이 검증 + if ptr.is_null() || len == 0 { + return; + } + + // 명시적 drop 호출로 RAII 소멸자를 즉시 실행 + // Q. T. Felix NOTE: FFIExternalSecureBuffer의 Drop 구현은 오직 write_volatile을 통한 소거만 수행해야 하며, + // 메모리 할당 해제(deallocate)는 Java 측에서 수행되어야 합니당 + drop(FFIExternalSecureBuffer { inner: ptr, len }); +} diff --git a/core/ffi/src/sha2.rs b/internal/ffi/src/sha2_ffi.rs similarity index 98% rename from core/ffi/src/sha2.rs rename to internal/ffi/src/sha2_ffi.rs index a40bf3e..9e71c76 100644 --- a/core/ffi/src/sha2.rs +++ b/internal/ffi/src/sha2_ffi.rs @@ -1,7 +1,7 @@ use crate::FFIStatus; use core::ptr; use core::slice; -use entlib_native_helper::secure_buffer::SecureBuffer; +use entlib_native_core_secure::secure_buffer::SecureBuffer; use entlib_native_sha2::api::{SHA224, SHA256, SHA384, SHA512}; macro_rules! generate_sha2_ffi { From 4dfeb3164cf102d601a48d500bdbf26e86e74489 Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:32:04 +0900 Subject: [PATCH 2/6] =?UTF-8?q?=ED=81=AC=EB=A0=88=EC=9D=B4=ED=8A=B8=20?= =?UTF-8?q?=EC=84=B8=EB=B6=84=ED=99=94=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=EC=83=81=EC=88=98-=EC=8B=9C=EA=B0=84=20=EC=9D=B8=EB=9D=BC?= =?UTF-8?q?=EC=9D=B8=20=EC=96=B4=EC=85=88=EB=B8=94=EB=A6=AC=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/verify_ct_asm.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/verify_ct_asm.sh b/scripts/verify_ct_asm.sh index b0bdb5d..f15bfb6 100755 --- a/scripts/verify_ct_asm.sh +++ b/scripts/verify_ct_asm.sh @@ -8,7 +8,7 @@ if [ -z "$1" ]; then fi TARGET=$1 -CRATE_NAME="entlib_native" +CRATE_NAME="entlib_native_constant_time" MODULE_PATH="constant_time_asm" EXIT_CODE=0 @@ -30,7 +30,7 @@ echo "[$TARGET] 상수-시간 어셈블리 검증 시작..." for TYPE in "${TYPES[@]}"; do for METHOD in "${METHODS[@]}"; do - # Rust 심볼 경로 조합 (::ct_mux 등) + # Rust 심볼 경로 조합 (::ct_mux 등) SYMBOL="<$TYPE as ${CRATE_NAME}::${MODULE_PATH}::CtPrimitive>::${METHOD}" echo "검사 중: $SYMBOL" From ef6f4ec0f585a67002ac2fc8b09fe79a1108f55f Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:32:16 +0900 Subject: [PATCH 3/6] =?UTF-8?q?=EB=AC=B8=EC=84=9C=20=EB=82=B4=EC=9A=A9=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 --- INTRODUCTION.md | 22 +--------------------- INTRODUCTION_EN.md | 22 +--------------------- README.md | 2 +- README_EN.md | 2 +- 4 files changed, 4 insertions(+), 44 deletions(-) diff --git a/INTRODUCTION.md b/INTRODUCTION.md index e41747e..de16c49 100644 --- a/INTRODUCTION.md +++ b/INTRODUCTION.md @@ -64,24 +64,4 @@ # 구성 -네이티브는 가상 매니페스트(virtual manifest) 기반의 워크스페이스로 구성되며, 각 크레이트는 명확한 책임 경계를 가집니다. - -## core/helper - -`no_std` 환경에서 동작하는 보안 원시 연산 모듈입니다. 상수 시간 비교 및 선택 연산(`ConstantTimeOps` 트레이트), 아키텍처별 인라인 어셈블리 구현(`CtPrimitive` 트레이트), 상수 시간 `Base64` 인코딩/디코딩, 그리고 `SecureBuffer`를 제공합니다. 네이티브의 다른 모든 크레이트가 의존하는 기반 계층입니다. - -## core/ffi - -Java 측 FFM API와 직접 맞닿는 C ABI 브릿지 계층입니다. `Base64` 인/디코딩 FFI 엔드포인트(`entlib_b64_encode_secure`, `entlib_b64_decode_secure`)와 메모리 소거 엔드포인트(`entanglement_secure_wipe`), `SecureBuffer`의 포인터 추출 및 해제 함수를 제공합니다. 모든 진입점에서 `null` 포인터 검증과 오버플로 보호를 수행합니다. - -## crypto/rng - -하드웨어 기반 난수 생성 모듈입니다. CPU의 `rdseed`/`rndr` 명령어를 직접 호출하는 기본 생성기(`base_rng`)와, 하드웨어 엔트로피를 ChaCha20 코어 블록으로 비선형 혼합하는 확장 생성기(`MixedRng`)를 제공합니다. - -## crypto/sha2 - -SHA-2 계열 해시 함수 구현입니다. `SHA-224`, `SHA-256`, `SHA-384`, `SHA-512` 네 가지 변형을 제공하며, 내부 상태 구조체(`Sha256State`, `Sha512State`)는 `Drop` 트레이트를 통해 연산 완료 시 자동 소거됩니다. 메시지 스케줄 등 연산 중간의 임시 데이터 역시 휘발성 소거 후 메모리 배리어가 적용됩니다. - -## crypto/sha3 - -SHA-3 계열 해시 함수 및 XOF(확장 출력 함수) 구현입니다. `Keccak` 스펀지 구조 위에 `SHA3-224`, `SHA3-256`, `SHA3-384`, `SHA3-512` 고정 출력 변형과 `SHAKE128`, `SHAKE256` 가변 출력 변형을 제공합니다. 내부 상태(`KeccakState`)의 25개 64비트 레인과 200바이트 버퍼는 `Drop` 트레이트에 의해 자동 소거되며, Keccak-f[1600] 순열의 임시 상태 역시 매 호출마다 휘발성 소거됩니다. +네이티브는 가상 매니페스트(virtual manifest) 기반의 워크스페이스로 구성되며, 각 크레이트는 세분화되어 명확한 책임 경계를 가집니다. `crypto/` 하위에는 `Base64`, `Hash` 또는 알고리즘 연산을 수행하기 위한 크레이트가 위치해 있고, `internal/` 하위에는 `ffi` 연동 및 양자 관련 유틸리티 크레이트가 포함되어 있습니다. \ No newline at end of file diff --git a/INTRODUCTION_EN.md b/INTRODUCTION_EN.md index c04a12c..38200e3 100644 --- a/INTRODUCTION_EN.md +++ b/INTRODUCTION_EN.md @@ -64,24 +64,4 @@ The reason this design is necessary is clear. Java's Garbage Collector freely co # Composition -Native is composed of a workspace based on a virtual manifest, and each crate has clear responsibility boundaries. - -## core/helper - -A security primitive operation module operating in a `no_std` environment. It provides constant time comparison and selection operations (`ConstantTimeOps` trait), inline assembly implementations per architecture (`CtPrimitive` trait), constant time `Base64` encoding/decoding, and `SecureBuffer`. It is the base layer on which all other crates of Native depend. - -## core/ffi - -A C ABI bridge layer directly touching the Java side FFM API. It provides `Base64` encoding/decoding FFI endpoints (`entlib_b64_encode_secure`, `entlib_b64_decode_secure`), memory erasure endpoint (`entanglement_secure_wipe`), and pointer extraction and release functions of `SecureBuffer`. It performs null pointer verification and overflow protection at all entry points. - -## crypto/rng - -A hardware-based random number generation module. It provides a basic generator (`base_rng`) that directly calls the CPU's `rdseed`/`rndr` instructions, and an extended generator (`MixedRng`) that non-linearly mixes hardware entropy with the ChaCha20 core block. - -## crypto/sha2 - -Implementation of SHA-2 family hash functions. It provides four variants: `SHA-224`, `SHA-256`, `SHA-384`, `SHA-512`, and internal state structures (`Sha256State`, `Sha512State`) are automatically erased upon operation completion via the `Drop` trait. Temporary data during operations such as message schedules are also volatilely erased, and then memory barriers are applied. - -## crypto/sha3 - -Implementation of SHA-3 family hash functions and XOF (Extendable Output Functions). It provides `SHA3-224`, `SHA3-256`, `SHA3-384`, `SHA3-512` fixed output variants and `SHAKE128`, `SHAKE256` variable output variants on top of the `Keccak` sponge structure. The 25 64-bit lanes and 200-byte buffer of the internal state (`KeccakState`) are automatically erased by the `Drop` trait, and the temporary state of the Keccak-f[1600] permutation is also volatilely erased at every call. \ No newline at end of file +Native is composed of a virtual manifest-based workspace, and each crate is granularized to have clear responsibility boundaries. Under `crypto/`, crates for performing `Base64`, `Hash`, or algorithm operations are located, and under `internal/`, crates for `ffi` integration and quantum-related utilities are included. \ No newline at end of file diff --git a/README.md b/README.md index 65547b6..611eca3 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ # Alpha 버전 -이 네이티브 라이브러리는 `1.1.0` 릴리즈 출시에 대한 준비를 마쳐도 곧바로 출시되진 않습니다. 이에 세밀하고 또 정밀하게 코드를 검토하고, 그 환경을 원활히 구축하기 위해 지금 이 시점에서 알파 버전으로 우선 공개하겠습니다. +이 네이티브 라이브러리는 `1.1.x` 릴리즈 출시에 대한 준비를 마쳐도 곧바로 출시되진 않습니다. 이에 세밀하고 또 정밀하게 코드를 검토하고, 그 환경을 원활히 구축하기 위해 지금 이 시점에서 알파 버전으로 우선 공개하겠습니다. # 벤치마킹 diff --git a/README_EN.md b/README_EN.md index f45b43d..053f664 100644 --- a/README_EN.md +++ b/README_EN.md @@ -19,7 +19,7 @@ Just in time, the respected security organization `Legion of the BouncyCastle In # Alpha Version -This native library will not be released immediately even if preparations for the `1.1.0` release are completed. Therefore, to review the code in detail and precisely, and to smoothly build that environment, I will first release it as an alpha version at this point. +This native library will not be released immediately even if preparations for the `1.1.x` release are completed. Therefore, to review the code in detail and precisely, and to smoothly build that environment, I will first release it as an alpha version at this point. # Benchmarking From 09d65abccbc34a59ef1e6c7f88acd1eccff26021 Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:32:51 +0900 Subject: [PATCH 4/6] =?UTF-8?q?=EC=95=94=ED=98=B8=ED=99=94=20=ED=81=AC?= =?UTF-8?q?=EB=A0=88=EC=9D=B4=ED=8A=B8=20=EC=84=B8=EB=B6=84=ED=99=94=20?= =?UTF-8?q?=EB=B0=8F=20=EB=B3=B4=EC=95=88=20=EA=B0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crypto/base64/Cargo.toml | 16 ++++++++++++++++ crypto/base64/src/lib.rs | 1 + crypto/constant-time/Cargo.toml | 24 ++++++++++++++++++++++++ crypto/constant-time/src/lib.rs | 8 ++++++++ crypto/core-secure/Cargo.toml | 15 +++++++++++++++ crypto/core-secure/src/lib.rs | 1 + 6 files changed, 65 insertions(+) create mode 100644 crypto/base64/Cargo.toml create mode 100644 crypto/base64/src/lib.rs create mode 100644 crypto/constant-time/Cargo.toml create mode 100644 crypto/constant-time/src/lib.rs create mode 100644 crypto/core-secure/Cargo.toml create mode 100644 crypto/core-secure/src/lib.rs diff --git a/crypto/base64/Cargo.toml b/crypto/base64/Cargo.toml new file mode 100644 index 0000000..7bc30a1 --- /dev/null +++ b/crypto/base64/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "entlib-native-base64" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true + +[dependencies] +entlib-native-constant-time.workspace = true + +[dev-dependencies] +criterion = { version = "0.8.2", features = ["html_reports"] } + +[[bench]] +name = "base64_bench" +harness = false \ No newline at end of file diff --git a/crypto/base64/src/lib.rs b/crypto/base64/src/lib.rs new file mode 100644 index 0000000..39abdce --- /dev/null +++ b/crypto/base64/src/lib.rs @@ -0,0 +1 @@ +pub mod base64; diff --git a/crypto/constant-time/Cargo.toml b/crypto/constant-time/Cargo.toml new file mode 100644 index 0000000..14c54c3 --- /dev/null +++ b/crypto/constant-time/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "entlib-native-constant-time" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true + +[features] +ct-tests = [] + +[dependencies] + +[dev-dependencies] +subtle = "2.6.1" +proptest = "1.10.0" +criterion = { version = "0.8.2", features = ["html_reports"] } + +[[bench]] +name = "ct_ops_security" +harness = false + +[[bench]] +name = "ct_ops_throughput" +harness = false \ No newline at end of file diff --git a/crypto/constant-time/src/lib.rs b/crypto/constant-time/src/lib.rs new file mode 100644 index 0000000..be1cdae --- /dev/null +++ b/crypto/constant-time/src/lib.rs @@ -0,0 +1,8 @@ +pub mod constant_time; + +#[cfg(feature = "ct-tests")] +#[doc(hidden)] +pub mod constant_time_asm; + +#[cfg(not(feature = "ct-tests"))] +pub(crate) mod constant_time_asm; diff --git a/crypto/core-secure/Cargo.toml b/crypto/core-secure/Cargo.toml new file mode 100644 index 0000000..6f473db --- /dev/null +++ b/crypto/core-secure/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "entlib-native-core-secure" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true + +[dependencies] + +[dev-dependencies] +criterion = { version = "0.8.2", features = ["html_reports"] } + +[[bench]] +name = "secure_buffer_bench" +harness = false \ No newline at end of file diff --git a/crypto/core-secure/src/lib.rs b/crypto/core-secure/src/lib.rs new file mode 100644 index 0000000..0da0cd6 --- /dev/null +++ b/crypto/core-secure/src/lib.rs @@ -0,0 +1 @@ +pub mod secure_buffer; From dd1d65f46366fffe92824e0990bd0aeb73f7da2a Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:33:07 +0900 Subject: [PATCH 5/6] =?UTF-8?q?=EC=96=91=EC=9E=90=20=EA=B8=B0=EC=88=A0=20?= =?UTF-8?q?=EC=9C=A0=ED=8B=B8=EB=A6=AC=ED=8B=B0=20=ED=81=AC=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/quantum-util/Cargo.toml | 8 ++++++++ internal/quantum-util/src/lib.rs | 14 ++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 internal/quantum-util/Cargo.toml create mode 100644 internal/quantum-util/src/lib.rs diff --git a/internal/quantum-util/Cargo.toml b/internal/quantum-util/Cargo.toml new file mode 100644 index 0000000..6b74391 --- /dev/null +++ b/internal/quantum-util/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "entlib-native-quantum-util" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true + +[dependencies] diff --git a/internal/quantum-util/src/lib.rs b/internal/quantum-util/src/lib.rs new file mode 100644 index 0000000..b93cf3f --- /dev/null +++ b/internal/quantum-util/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: u64, right: u64) -> u64 { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} From e43ca209b7365a5fe30f805fa466c4e83ca5ecbe Mon Sep 17 00:00:00 2001 From: "Q. T. Felix" <53819958+Quant-TheodoreFelix@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:33:38 +0900 Subject: [PATCH 6/6] =?UTF-8?q?=EB=A7=8E=EC=9D=80=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EB=B2=84=EC=A0=84=20=EC=97=85?= =?UTF-8?q?=20->=20`1.1.2-Alpha`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 87a9978..62eaea7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [workspace] -members = ["core/*", "crypto/*"] +members = ["internal/*", "crypto/*"] resolver = "2" [workspace.package] -version = "1.1.1-Alpha3" +version = "1.1.2-Alpha" edition = "2024" authors = ["Q. T. Felix "] license = "MIT LICENSE" @@ -12,8 +12,13 @@ license = "MIT LICENSE" strip = true [workspace.dependencies] -entlib-native-helper = { path = "./core/helper", version = "1.1.1-Alpha3" } -entlib-native-ffi = { path = "./core/ffi", version = "1.1.1-Alpha3" } -entlib-native-sha2 = { path = "./crypto/sha2", version = "1.1.1-Alpha3" } -entlib-native-sha3 = { path = "./crypto/sha3", version = "1.1.1-Alpha3" } -entlib-native-rng = { path = "./crypto/rng", version = "1.1.1-Alpha3" } \ No newline at end of file +### INTERNAL CORE DEPENDENCIES ### +entlib-native-ffi = { path = "./internal/ffi", version = "1.1.2-Alpha" } +entlib-native-quantum-util = { path = "./internal/quantum-util", version = "1.1.2-Alpha" } +### INTERNAL CRYPTO DEPENDENCIES ### +entlib-native-base64 = { path = "./crypto/base64", version = "1.1.2-Alpha" } +entlib-native-constant-time = { path = "./crypto/constant-time", version = "1.1.2-Alpha" } +entlib-native-core-secure = { path = "./crypto/core-secure", version = "1.1.2-Alpha" } +entlib-native-rng = { path = "./crypto/rng", version = "1.1.2-Alpha" } +entlib-native-sha2 = { path = "./crypto/sha2", version = "1.1.2-Alpha" } +entlib-native-sha3 = { path = "./crypto/sha3", version = "1.1.2-Alpha" }