package com.eactive.eai.common.hsm; import static org.junit.jupiter.api.Assumptions.assumeTrue; import java.io.File; import java.io.InputStream; import java.security.KeyStore; import java.security.Provider; import java.security.Security; import java.util.Enumeration; import java.util.concurrent.TimeUnit; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; /** * SoftHSM2 마스터키 등록 유틸리티 테스트. * * 용도: CryptoModuleConfig.keyDerivParams 의 hsmKeyAlias 에 대응하는 * AES-256 마스터키를 SoftHSM2 토큰에 등록한다. * * 사전 조건: * C:\SoftHSM2 에 SoftHSM2 설치 * (토큰이 없으면 자동 초기화, 있으면 기존 토큰 재사용) * * 실행 후 확인: * softhsm2-util.exe --show-slots * 또는 pkcs11-tool --list-objects (OpenSC 설치 시) */ @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class HsmKeyRegisterUtil { private static final String SOFTHSM2_DLL = "C:/SoftHSM2/lib/softhsm2-x64.dll"; private static final String SOFTHSM2_UTIL = "C:/SoftHSM2/bin/softhsm2-util.exe"; private static final String TOKEN_LABEL = "eapim-test"; private static final String PIN = "1234"; /** DB CryptoModuleConfig.keyDerivParams.hsmKeyAlias 와 반드시 일치해야 한다. */ private static final String MASTER_KEY_ALIAS = "MASTER_KEY"; private static Provider provider; private static KeyStore keyStore; @BeforeAll static void setUp() throws Exception { System.setProperty("SOFTHSM2_CONF", "C:\\SoftHSM2\\etc\\softhsm2.conf"); assumeTrue(new File(SOFTHSM2_DLL).exists(), "SoftHSM2 미설치 (C:/SoftHSM2) - 테스트 건너뜀"); ensureTokenInitialized(); String cfgContent = "name = SoftHSM\n" + "library = " + SOFTHSM2_DLL + "\n" + "slotListIndex = 0\n"; provider = HsmManager.createProvider(cfgContent); Provider existing = Security.getProvider(provider.getName()); if (existing != null) Security.removeProvider(existing.getName()); Security.addProvider(provider); keyStore = KeyStore.getInstance("PKCS11", provider); keyStore.load(null, PIN.toCharArray()); } // ------------------------------------------------------------------ // 1. 현재 등록된 키 목록 출력 // ------------------------------------------------------------------ @Test @Order(1) void listKeys() throws Exception { System.out.println("=== HSM 키 목록 ==="); Enumeration aliases = keyStore.aliases(); if (!aliases.hasMoreElements()) { System.out.println(" (등록된 키 없음)"); } while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); String type = keyStore.isKeyEntry(alias) ? "KEY" : "CERT"; System.out.println(" [" + type + "] " + alias); } System.out.println("=================="); } // ------------------------------------------------------------------ // 2. 마스터키 등록 (없는 경우에만) // ------------------------------------------------------------------ @Test @Order(2) void registerMasterKey() throws Exception { if (keyStore.containsAlias(MASTER_KEY_ALIAS)) { System.out.println("[SKIP] 이미 등록됨: " + MASTER_KEY_ALIAS); return; } KeyGenerator kg = KeyGenerator.getInstance("AES", provider); kg.init(256); SecretKey sk = kg.generateKey(); keyStore.setKeyEntry(MASTER_KEY_ALIAS, sk, null, null); System.out.println("[OK] AES-256 마스터키 등록 완료: alias=" + MASTER_KEY_ALIAS); } // ------------------------------------------------------------------ // 3. 등록 후 조회 확인 // ------------------------------------------------------------------ @Test @Order(3) void verifyMasterKey() throws Exception { keyStore.load(null, PIN.toCharArray()); // 갱신 assert keyStore.containsAlias(MASTER_KEY_ALIAS) : "마스터키 등록 실패: " + MASTER_KEY_ALIAS; SecretKey sk = (SecretKey) keyStore.getKey(MASTER_KEY_ALIAS, null); assert sk != null : "키 조회 실패"; assert "AES".equals(sk.getAlgorithm()) : "알고리즘 불일치: " + sk.getAlgorithm(); System.out.println("[OK] 마스터키 조회 성공: alias=" + MASTER_KEY_ALIAS + " / algorithm=" + sk.getAlgorithm()); } // ------------------------------------------------------------------ // 헬퍼 // ------------------------------------------------------------------ private static void ensureTokenInitialized() throws Exception { ProcessBuilder pb = new ProcessBuilder( SOFTHSM2_UTIL, "--init-token", "--free", "--label", TOKEN_LABEL, "--pin", PIN, "--so-pin", PIN); pb.redirectErrorStream(true); Process p = pb.start(); String out = readOutput(p); p.waitFor(10, TimeUnit.SECONDS); System.out.println("[SoftHSM2] init-token: " + out.trim()); } private static String readOutput(Process p) throws Exception { InputStream is = p.getInputStream(); byte[] buf = new byte[4096]; StringBuilder sb = new StringBuilder(); int len; while ((len = is.read(buf)) != -1) { sb.append(new String(buf, 0, len, "UTF-8")); } return sb.toString(); } }