diff --git a/src/main/java/com/eactive/eai/adapter/http/client/impl/filter/HttpClient5AdapterFilterFactory.java b/src/main/java/com/eactive/eai/adapter/http/client/impl/filter/HttpClient5AdapterFilterFactory.java index f0947b2..4ac0c8a 100644 --- a/src/main/java/com/eactive/eai/adapter/http/client/impl/filter/HttpClient5AdapterFilterFactory.java +++ b/src/main/java/com/eactive/eai/adapter/http/client/impl/filter/HttpClient5AdapterFilterFactory.java @@ -6,7 +6,7 @@ import com.eactive.eai.common.util.Logger; public class HttpClient5AdapterFilterFactory { static Logger logger = Logger.getLogger(Logger.LOGGER_ADAPTER); - static String customBasePackage = "com.eactive.eai.custom.adapter.http.client.filter"; + static String customBasePackage = "com.eactive.eai.custom.adapter.http.client.impl.filter"; static String basePackage = "com.eactive.eai.adapter.http.client.impl.filter"; private static ConcurrentHashMap h = new ConcurrentHashMap<>(); diff --git a/src/main/java/com/eactive/eai/adapter/http/client/impl/filter/OutCryptoFilter.java b/src/main/java/com/eactive/eai/adapter/http/client/impl/filter/OutCryptoFilter.java index 8812c9f..3c7633e 100644 --- a/src/main/java/com/eactive/eai/adapter/http/client/impl/filter/OutCryptoFilter.java +++ b/src/main/java/com/eactive/eai/adapter/http/client/impl/filter/OutCryptoFilter.java @@ -19,14 +19,12 @@ import com.eactive.eai.adapter.http.filter.AbstractCryptoFilter; * STATIC 키 모듈을 사용하는 경우 빈 Map이 기본값이므로 오버라이드 불필요. * * 프로퍼티 설명은 {@link AbstractCryptoFilter} 참조. - * AAD 관련 추가 프로퍼티: - * CRYPTO_AAD_PROP tempProp에서 AAD 값을 조회할 키 이름. + * AAD 관련 프로퍼티: + * PROP_AAD_HEADER tempProp에서 AAD 값을 조회할 키 이름. * 미설정 또는 해당 키 없으면 aad=null → IV를 AAD 대체값으로 사용. */ public class OutCryptoFilter extends AbstractCryptoFilter implements HttpClientAdapterFilter { - public static final String PROP_AAD_PROP = "CRYPTO_AAD_PROP"; - /** * DYNAMIC 키 도출용 컨텍스트를 구성한다. * STATIC 키 모듈이면 빈 Map({@code new HashMap<>()})을 반환한다. @@ -41,7 +39,7 @@ public class OutCryptoFilter extends AbstractCryptoFilter implements HttpClientA * 미설정 또는 값 없으면 null을 반환하여 GCM 기본 동작(IV를 AAD 대체값으로 사용)을 따른다. */ protected byte[] buildAad(Properties prop, Properties tempProp) { - String propKey = prop.getProperty(PROP_AAD_PROP); + String propKey = prop.getProperty(PROP_AAD_HEADER); if (StringUtils.isBlank(propKey) || tempProp == null) { return null; } @@ -61,8 +59,8 @@ public class OutCryptoFilter extends AbstractCryptoFilter implements HttpClientA if (SCOPE_FIELD.equals(scope)) { return encryptField(body, moduleName, runtimeCtx, aad, - getProp(prop, PROP_ENC_FROM_PATH, PATH_ROOT), - getProp(prop, PROP_ENC_TO_PATH, PATH_ENCDATA)); + getProp(tempProp, PROP_ENC_FROM_PATH, PATH_ROOT), + getProp(tempProp, PROP_ENC_TO_PATH, PATH_ENCDATA)); } return encryptBody(body, moduleName, runtimeCtx, aad); } @@ -79,8 +77,8 @@ public class OutCryptoFilter extends AbstractCryptoFilter implements HttpClientA if (SCOPE_FIELD.equals(scope)) { return decryptField(body, moduleName, runtimeCtx, aad, - getProp(prop, PROP_DEC_FROM_PATH, PATH_ENCDATA), - getProp(prop, PROP_DEC_TO_PATH, PATH_ROOT)); + getProp(tempProp, PROP_DEC_FROM_PATH, PATH_ENCDATA), + getProp(tempProp, PROP_DEC_TO_PATH, PATH_ROOT)); } return decryptBody(body, moduleName, runtimeCtx, aad); } diff --git a/src/main/java/com/eactive/eai/common/hsm/HsmManager.java b/src/main/java/com/eactive/eai/common/hsm/HsmManager.java index ecad065..e692136 100644 --- a/src/main/java/com/eactive/eai/common/hsm/HsmManager.java +++ b/src/main/java/com/eactive/eai/common/hsm/HsmManager.java @@ -24,13 +24,17 @@ import com.eactive.eai.common.util.Logger; * SafeNet ProtectServer HSM 연동 관리자 (JDK 8 / SunPKCS11) * * PropManager 그룹 "HSM" 에서 읽는 키: - * PKCS11_CONFIG - pkcs11.cfg 파일 내용 (name/library/slot 설정을 DB에 직접 저장) + * PKCS11_CONFIG - pkcs11.cfg 파일 내용 * PIN - HSM 슬롯 PIN * * PKCS11_CONFIG 값 예시 (개행은 \n 으로 입력): * name = ProtectServer * library = /opt/safenet/protecttoolkit5/ptk/lib/libcryptoki.so * slot = 0 + * + * name = SoftHSM + * library = C:/SoftHSM2/lib/softhsm2-x64.dll + * slotListIndex = 0 */ @Component public class HsmManager implements Lifecycle { diff --git a/src/main/java/com/eactive/eai/common/security/keyderiv/KeyDerivationStrategy.java b/src/main/java/com/eactive/eai/common/security/keyderiv/KeyDerivationStrategy.java index 691b98f..e6914ad 100644 --- a/src/main/java/com/eactive/eai/common/security/keyderiv/KeyDerivationStrategy.java +++ b/src/main/java/com/eactive/eai/common/security/keyderiv/KeyDerivationStrategy.java @@ -3,7 +3,11 @@ package com.eactive.eai.common.security.keyderiv; import java.util.Map; public interface KeyDerivationStrategy { + + public static final String PARAM_HSM_KEY_ALIAS = "hsmKeyAlias"; + public static final String PARAM_CONTEXT_KEY = "contextKey"; + /** * 키를 도출한다. * diff --git a/src/main/java/com/eactive/eai/common/security/keyderiv/strategy/BaseHsmKeyDerivationStrategy.java b/src/main/java/com/eactive/eai/common/security/keyderiv/strategy/BaseHsmKeyDerivationStrategy.java index e0d9c58..7de4ab2 100644 --- a/src/main/java/com/eactive/eai/common/security/keyderiv/strategy/BaseHsmKeyDerivationStrategy.java +++ b/src/main/java/com/eactive/eai/common/security/keyderiv/strategy/BaseHsmKeyDerivationStrategy.java @@ -14,8 +14,6 @@ import com.eactive.eai.common.util.ApplicationContextProvider; */ public abstract class BaseHsmKeyDerivationStrategy implements KeyDerivationStrategy { - protected static final String PARAM_HSM_KEY_ALIAS = "hsmKeyAlias"; - protected byte[] getHsmKey(Map params) throws HsmException { String hsmKeyAlias = required(params, PARAM_HSM_KEY_ALIAS); return getHsmKey(hsmKeyAlias); diff --git a/src/main/java/com/eactive/eai/common/security/keyderiv/strategy/HsmContextSha256KeyDerivationStrategy.java b/src/main/java/com/eactive/eai/common/security/keyderiv/strategy/HsmContextSha256KeyDerivationStrategy.java index 4fe6a39..26bb696 100644 --- a/src/main/java/com/eactive/eai/common/security/keyderiv/strategy/HsmContextSha256KeyDerivationStrategy.java +++ b/src/main/java/com/eactive/eai/common/security/keyderiv/strategy/HsmContextSha256KeyDerivationStrategy.java @@ -23,9 +23,6 @@ import com.eactive.eai.common.util.ApplicationContextProvider; */ public class HsmContextSha256KeyDerivationStrategy implements KeyDerivationStrategy { - private static final String PARAM_HSM_KEY_ALIAS = "hsmKeyAlias"; - private static final String PARAM_CONTEXT_KEY = "contextKey"; - @Override public DerivedKey deriveKey(Map params, Map runtimeContext) throws Exception { String hsmKeyAlias = required(params, PARAM_HSM_KEY_ALIAS); diff --git a/src/main/java/com/eactive/eai/common/security/keyderiv/strategy/HsmContextXorKeyDerivationStrategy.java b/src/main/java/com/eactive/eai/common/security/keyderiv/strategy/HsmContextXorKeyDerivationStrategy.java index 550316b..d67e1dc 100644 --- a/src/main/java/com/eactive/eai/common/security/keyderiv/strategy/HsmContextXorKeyDerivationStrategy.java +++ b/src/main/java/com/eactive/eai/common/security/keyderiv/strategy/HsmContextXorKeyDerivationStrategy.java @@ -26,8 +26,6 @@ public class HsmContextXorKeyDerivationStrategy implements KeyDerivationStrategy /** DB key_deriv_strategy 컬럼에 사용하는 FQCN 참조용 상수. */ public static final String STRATEGY_CLASS = HsmContextXorKeyDerivationStrategy.class.getName(); - private static final String PARAM_HSM_KEY_ALIAS = "hsmKeyAlias"; - private static final String PARAM_CONTEXT_KEY = "contextKey"; private static final String PARAM_OFFSET = "offset"; private static final String PARAM_LENGTH = "length"; diff --git a/src/test/java/com/eactive/eai/adapter/http/client/impl/filter/OutCryptoFilterTest.java b/src/test/java/com/eactive/eai/adapter/http/client/impl/filter/OutCryptoFilterTest.java index 2f71cb0..8a0a7dc 100644 --- a/src/test/java/com/eactive/eai/adapter/http/client/impl/filter/OutCryptoFilterTest.java +++ b/src/test/java/com/eactive/eai/adapter/http/client/impl/filter/OutCryptoFilterTest.java @@ -85,7 +85,7 @@ class OutCryptoFilterTest { @DisplayName("1-2. buildAad — CRYPTO_AAD_PROP 설정, tempProp에 값 없음 → null") void testBuildAad_propSet_noTempValue_returnsNull() { Properties prop = new Properties(); - prop.setProperty(OutCryptoFilter.PROP_AAD_PROP, "aadKey"); + prop.setProperty(AbstractCryptoFilter.PROP_AAD_HEADER, "aadKey"); assertNull(filter.buildAad(prop, tempProp)); } @@ -95,7 +95,7 @@ class OutCryptoFilterTest { void testBuildAad_propSet_tempValuePresent_returnsByte() { String aadVal = "aad-value-xyz"; Properties prop = new Properties(); - prop.setProperty(OutCryptoFilter.PROP_AAD_PROP, "aadKey"); + prop.setProperty(AbstractCryptoFilter.PROP_AAD_HEADER, "aadKey"); tempProp.setProperty("aadKey", aadVal); assertArrayEquals(aadVal.getBytes(StandardCharsets.UTF_8), filter.buildAad(prop, tempProp)); @@ -118,7 +118,7 @@ class OutCryptoFilterTest { @DisplayName("2-2. FIELD scope, fromPath='/' — body 전체 암호화 → toPath 필드명으로 래핑") void testPreFilter_field_encryptFromRoot() throws Exception { String body = "{\"userId\":\"U001\"}"; - Properties prop = fieldEncryptProps("/", "/encryptedData"); + Properties prop = fieldEncryptProps(tempProp, "/", "/encryptedData"); Object result = filter.doPreFilter("g", "a", prop, body, tempProp); @@ -129,7 +129,7 @@ class OutCryptoFilterTest { @DisplayName("2-3. FIELD scope — fromPath 평문 암호화 → toPath Base64 반영") void testPreFilter_field_encryptSpecificPath() throws Exception { String body = "{\"data\":\"secret\",\"enc\":\"\"}"; - Properties prop = fieldEncryptProps("/data", "/enc"); + Properties prop = fieldEncryptProps(tempProp, "/data", "/enc"); Object result = filter.doPreFilter("g", "a", prop, body, tempProp); @@ -141,7 +141,7 @@ class OutCryptoFilterTest { @DisplayName("2-4. FIELD scope — fromPath 값 없음 → body 그대로 반환") void testPreFilter_field_missingFromPath_passThrough() throws Exception { String body = "{\"other\":\"value\"}"; - Properties prop = fieldEncryptProps("/data", "/enc"); + Properties prop = fieldEncryptProps(tempProp, "/data", "/enc"); Object result = filter.doPreFilter("g", "a", prop, body, tempProp); @@ -176,7 +176,7 @@ class OutCryptoFilterTest { @DisplayName("3-2. FIELD scope — fromPath 복호화 → toPath 반영") void testPostFilter_field_decryptToPath() throws Exception { String body = "{\"enc\":\"" + CIPHER_B64 + "\",\"result\":\"\"}"; - Properties prop = fieldDecryptProps("/enc", "/result"); + Properties prop = fieldDecryptProps(tempProp, "/enc", "/result"); Object result = filter.doPostFilter("g", "a", prop, body, tempProp); @@ -188,7 +188,7 @@ class OutCryptoFilterTest { @DisplayName("3-3. FIELD scope, toPath='/' — 복호화된 텍스트로 body 전체 교체") void testPostFilter_field_decryptToRoot() throws Exception { String body = "{\"enc\":\"" + CIPHER_B64 + "\"}"; - Properties prop = fieldDecryptProps("/enc", "/"); + Properties prop = fieldDecryptProps(tempProp, "/enc", "/"); Object result = filter.doPostFilter("g", "a", prop, body, tempProp); @@ -199,7 +199,7 @@ class OutCryptoFilterTest { @DisplayName("3-4. FIELD scope — fromPath 값 없음 → body 그대로 반환") void testPostFilter_field_missingFromPath_passThrough() throws Exception { String body = "{\"other\":\"value\"}"; - Properties prop = fieldDecryptProps("/enc", "/result"); + Properties prop = fieldDecryptProps(tempProp, "/enc", "/result"); Object result = filter.doPostFilter("g", "a", prop, body, tempProp); @@ -216,7 +216,7 @@ class OutCryptoFilterTest { void testPreFilter_body_aadFromTempProp() throws Exception { String aadVal = "out-aad-value"; Properties prop = bodyEncryptProps(); - prop.setProperty(OutCryptoFilter.PROP_AAD_PROP, "myAadKey"); + prop.setProperty(AbstractCryptoFilter.PROP_AAD_HEADER, "myAadKey"); tempProp.setProperty("myAadKey", aadVal); filter.doPreFilter("g", "a", prop, PLAIN_STR, tempProp); @@ -248,21 +248,21 @@ class OutCryptoFilterTest { return bodyEncryptProps(); } - private Properties fieldEncryptProps(String fromPath, String toPath) { + private Properties fieldEncryptProps(Properties tempProp, String fromPath, String toPath) { Properties p = new Properties(); p.setProperty(AbstractCryptoFilter.PROP_MODULE_NAME, MODULE); p.setProperty(AbstractCryptoFilter.PROP_SCOPE, "FIELD"); - p.setProperty(AbstractCryptoFilter.PROP_ENC_FROM_PATH, fromPath); - p.setProperty(AbstractCryptoFilter.PROP_ENC_TO_PATH, toPath); + tempProp.setProperty(AbstractCryptoFilter.PROP_ENC_FROM_PATH, fromPath); + tempProp.setProperty(AbstractCryptoFilter.PROP_ENC_TO_PATH, toPath); return p; } - private Properties fieldDecryptProps(String fromPath, String toPath) { + private Properties fieldDecryptProps(Properties tempProp, String fromPath, String toPath) { Properties p = new Properties(); p.setProperty(AbstractCryptoFilter.PROP_MODULE_NAME, MODULE); p.setProperty(AbstractCryptoFilter.PROP_SCOPE, "FIELD"); - p.setProperty(AbstractCryptoFilter.PROP_DEC_FROM_PATH, fromPath); - p.setProperty(AbstractCryptoFilter.PROP_DEC_TO_PATH, toPath); + tempProp.setProperty(AbstractCryptoFilter.PROP_DEC_FROM_PATH, fromPath); + tempProp.setProperty(AbstractCryptoFilter.PROP_DEC_TO_PATH, toPath); return p; } } diff --git a/src/test/java/com/eactive/eai/adapter/http/dynamic/filter/InCryptoFilterTest.java b/src/test/java/com/eactive/eai/adapter/http/dynamic/filter/InCryptoFilterTest.java index 66ba5de..ecd766e 100644 --- a/src/test/java/com/eactive/eai/adapter/http/dynamic/filter/InCryptoFilterTest.java +++ b/src/test/java/com/eactive/eai/adapter/http/dynamic/filter/InCryptoFilterTest.java @@ -6,12 +6,14 @@ import static org.mockito.Mockito.*; import java.nio.charset.StandardCharsets; import java.util.Base64; +import java.util.HashMap; import java.util.Map; import java.util.Properties; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.mockito.ArgumentCaptor; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -88,6 +90,47 @@ class InCryptoFilterTest { assertTrue(result.isEmpty()); } + @SuppressWarnings("unchecked") + @Test + @DisplayName("1-2. buildRuntimeContext 오버라이드 시 runtimeContext가 복호화 서비스에 전달된다") + void testBuildRuntimeContext_override_passedToDecrypt() throws Exception { + InCryptoFilter filterWithCtx = new InCryptoFilter() { + @Override + protected Map buildRuntimeContext(Properties p, HttpServletRequest req) { + Map ctx = new HashMap<>(); + ctx.put("groupSeq", "G001"); + return ctx; + } + }; + String body = "{\"enc\":\"" + CIPHER_B64 + "\",\"result\":\"\"}"; + + filterWithCtx.doPreFilter("g", "a", body, fieldDecryptProps("/enc", "/result"), mockRequest, mockResponse); + + ArgumentCaptor ctxCaptor = ArgumentCaptor.forClass(Map.class); + verify(mockService).decrypt(eq(MODULE), ctxCaptor.capture(), isNull(), eq(CIPHER)); + assertEquals("G001", ctxCaptor.getValue().get("groupSeq")); + } + + @SuppressWarnings("unchecked") + @Test + @DisplayName("1-3. buildRuntimeContext 오버라이드 시 runtimeContext가 암호화 서비스에 전달된다") + void testBuildRuntimeContext_override_passedToEncrypt() throws Exception { + InCryptoFilter filterWithCtx = new InCryptoFilter() { + @Override + protected Map buildRuntimeContext(Properties p, HttpServletRequest req) { + Map ctx = new HashMap<>(); + ctx.put("groupSeq", "G001"); + return ctx; + } + }; + + filterWithCtx.doPostFilter("g", "a", "plain-body", fieldEncryptProps("/", "/encryptedData"), mockRequest, mockResponse); + + ArgumentCaptor ctxCaptor = ArgumentCaptor.forClass(Map.class); + verify(mockService).encrypt(eq(MODULE), ctxCaptor.capture(), isNull(), any(byte[].class)); + assertEquals("G001", ctxCaptor.getValue().get("groupSeq")); + } + // ========================================================================= // 2. buildAad // ========================================================================= @@ -151,6 +194,19 @@ class InCryptoFilterTest { assertEquals(PLAIN_STR, result); } + @Test + @DisplayName("3-4. message가 String/byte[] 외 타입 — toString() 변환 후 처리") + void testPreFilter_body_nonStringMessage_usesToString() throws Exception { + Object nonStringMsg = new Object() { + @Override + public String toString() { return CIPHER_B64; } + }; + + Object result = filter.doPreFilter("g", "a", nonStringMsg, bodyDecryptProps(), mockRequest, mockResponse); + + assertEquals(PLAIN_STR, result); + } + // ========================================================================= // 4. doPreFilter — FIELD scope 복호화 // =========================================================================