CryptoModuleManager 기반 암호화모듈 진단/암복호화 테스트 관리 API 추가
/manage/crypto 하위에 등록된 암호화모듈 목록/단건 조회, DYNAMIC 키 전략(KeyDerivationStrategy) 로드 상태 확인, 동적 키 캐시 키 목록 조회, Base64 기반 암복호화 라운드트립 테스트 엔드포인트를 제공한다. 원본 암호키(encKeyHex/decKeyHex/ivHex)는 응답에 포함하지 않는다. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,391 @@
|
|||||||
|
{
|
||||||
|
"info": {
|
||||||
|
"_postman_id": "b7e2e1d0-3c1a-4b0a-9d2e-crypto-manage-0001",
|
||||||
|
"name": "CryptoModuleManage API (/manage/crypto)",
|
||||||
|
"description": "CryptoModuleManager 기반 암호화모듈 진단 및 암복호화 테스트 API 샘플 모음.\n\n사용 전 확인사항:\n1. Collection Variables 의 baseUrl 을 실제 서버 주소로 변경\n2. cryptoName / dynamicCryptoName 을 DB(crypto_module_config)에 실제 등록된 모듈명으로 변경\n3. DYNAMIC 모듈은 keyDerivParams 의 contextKey 값과 dynamicContextKey 변수가 일치해야 함 (예: X-Api-Enc-Key)\n\n주의: 이 API는 원본 암호키(encKeyHex/decKeyHex/ivHex)를 절대 응답에 포함하지 않는다. 진단 응답은 메타데이터와 키 도출 전략(KeyDerivationStrategy) 로드 상태만 노출한다.",
|
||||||
|
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||||
|
},
|
||||||
|
"variable": [
|
||||||
|
{ "key": "baseUrl", "value": "http://localhost:8080", "type": "string" },
|
||||||
|
{ "key": "cryptoName", "value": "SAMPLE_STATIC", "type": "string", "description": "STATIC 키 방식으로 등록된 실제 cryptoName으로 교체" },
|
||||||
|
{ "key": "dynamicCryptoName", "value": "SAMPLE_DYNAMIC", "type": "string", "description": "DYNAMIC 키 방식으로 등록된 실제 cryptoName으로 교체" },
|
||||||
|
{ "key": "dynamicContextKey", "value": "X-Api-Enc-Key", "type": "string", "description": "DYNAMIC 모듈의 key_deriv_params.contextKey 값과 일치해야 함" },
|
||||||
|
{ "key": "dynamicContextValue", "value": "clientKeyValue01", "type": "string" },
|
||||||
|
{ "key": "plainText", "value": "테스트 평문입니다", "type": "string" },
|
||||||
|
{ "key": "plainTextBase64", "value": "", "type": "string" },
|
||||||
|
{ "key": "staticCipherTextBase64", "value": "", "type": "string" },
|
||||||
|
{ "key": "dynamicCipherTextBase64", "value": "", "type": "string" }
|
||||||
|
],
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "1. 진단 (Diagnostics)",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "전체 암호화모듈 목록 조회",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/manage/crypto/list",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["manage", "crypto", "list"]
|
||||||
|
},
|
||||||
|
"description": "등록된 전체 암호화모듈의 진단정보(메타데이터)를 반환한다. 원본 키는 포함되지 않는다."
|
||||||
|
},
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"const json = pm.response.json();",
|
||||||
|
"pm.test('status 200', () => pm.response.to.have.status(200));",
|
||||||
|
"pm.test('success = true', () => pm.expect(json.success).to.eql(true));",
|
||||||
|
"pm.test('data는 배열', () => pm.expect(json.data).to.be.an('array'));",
|
||||||
|
"pm.test('원본 키 필드가 응답에 없어야 함', () => {",
|
||||||
|
" const body = pm.response.text();",
|
||||||
|
" pm.expect(body).to.not.include('encKeyHex');",
|
||||||
|
" pm.expect(body).to.not.include('decKeyHex');",
|
||||||
|
"});"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "단일 암호화모듈 조회",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/manage/crypto/{{cryptoName}}",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["manage", "crypto", "{{cryptoName}}"]
|
||||||
|
},
|
||||||
|
"description": "cryptoName 하나의 진단정보를 조회한다. STATIC 모듈은 keyDerivStrategy가 null, strategyLoaded가 false로 내려온다."
|
||||||
|
},
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"const json = pm.response.json();",
|
||||||
|
"pm.test('status 200', () => pm.response.to.have.status(200));",
|
||||||
|
"pm.test('success = true', () => pm.expect(json.success).to.eql(true));",
|
||||||
|
"pm.test('cryptoName 일치', () => pm.expect(json.data.cryptoName).to.eql(pm.collectionVariables.get('cryptoName')));"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "DYNAMIC 모듈 조회 (전략 로드 확인)",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/manage/crypto/{{dynamicCryptoName}}",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["manage", "crypto", "{{dynamicCryptoName}}"]
|
||||||
|
},
|
||||||
|
"description": "DYNAMIC 모듈은 resolveStrategy를 통해 키 도출 전략(KeyDerivationStrategy) 클래스가 정상 로드되는지 strategyLoaded/strategyClassName으로 확인할 수 있다."
|
||||||
|
},
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"const json = pm.response.json();",
|
||||||
|
"pm.test('success = true', () => pm.expect(json.success).to.eql(true));",
|
||||||
|
"pm.test('keySourceType = DYNAMIC', () => pm.expect(json.data.keySourceType).to.eql('DYNAMIC'));",
|
||||||
|
"pm.test('strategyLoaded = true', () => pm.expect(json.data.strategyLoaded).to.eql(true));",
|
||||||
|
"pm.test('strategyClassName 존재', () => pm.expect(json.data.strategyClassName).to.be.a('string'));"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "동적 키 캐시 목록 조회",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/manage/crypto/cache/dynamic-keys",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["manage", "crypto", "cache", "dynamic-keys"]
|
||||||
|
},
|
||||||
|
"description": "DYNAMIC 키 방식 모듈이 캐시한 키의 캐시-키 목록(예: 'cryptoName:contextValue')을 반환한다. 원본 키 값이 아니다. 아래 'DYNAMIC 키' 폴더의 암호화 요청을 먼저 실행한 뒤 호출하면 목록이 채워진다."
|
||||||
|
},
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"const json = pm.response.json();",
|
||||||
|
"pm.test('success = true', () => pm.expect(json.success).to.eql(true));",
|
||||||
|
"pm.test('data는 배열', () => pm.expect(json.data).to.be.an('array'));"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "2. STATIC 키 암복호화 테스트",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "암호화 (encrypt)",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [{ "key": "Content-Type", "value": "application/json" }],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"plainTextBase64\": \"{{plainTextBase64}}\"\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/manage/crypto/{{cryptoName}}/test/encrypt",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["manage", "crypto", "{{cryptoName}}", "test", "encrypt"]
|
||||||
|
},
|
||||||
|
"description": "STATIC 키 방식 모듈로 평문(Base64)을 암호화한다. runtimeContext 없이 호출한다."
|
||||||
|
},
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "prerequest",
|
||||||
|
"script": {
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"const plainText = pm.collectionVariables.get('plainText');",
|
||||||
|
"pm.collectionVariables.set('plainTextBase64', btoa(unescape(encodeURIComponent(plainText))));"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"const json = pm.response.json();",
|
||||||
|
"pm.test('status 200', () => pm.response.to.have.status(200));",
|
||||||
|
"pm.test('success = true', () => pm.expect(json.success).to.eql(true));",
|
||||||
|
"pm.test('cipherTextBase64 존재', () => pm.expect(json.data.cipherTextBase64).to.be.a('string'));",
|
||||||
|
"pm.test('평문 base64와 달라야 함', () => pm.expect(json.data.cipherTextBase64).to.not.eql(pm.collectionVariables.get('plainTextBase64')));",
|
||||||
|
"pm.collectionVariables.set('staticCipherTextBase64', json.data.cipherTextBase64);"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "복호화 (decrypt) — 위 암호화 결과 사용",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [{ "key": "Content-Type", "value": "application/json" }],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"cipherTextBase64\": \"{{staticCipherTextBase64}}\"\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/manage/crypto/{{cryptoName}}/test/decrypt",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["manage", "crypto", "{{cryptoName}}", "test", "decrypt"]
|
||||||
|
},
|
||||||
|
"description": "바로 위 '암호화 (encrypt)' 요청에서 저장한 staticCipherTextBase64를 복호화하여 원문과 일치하는지 확인한다. 반드시 암호화 요청을 먼저 실행할 것."
|
||||||
|
},
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"const json = pm.response.json();",
|
||||||
|
"pm.test('status 200', () => pm.response.to.have.status(200));",
|
||||||
|
"pm.test('success = true', () => pm.expect(json.success).to.eql(true));",
|
||||||
|
"const decoded = decodeURIComponent(escape(atob(json.data.plainTextBase64)));",
|
||||||
|
"pm.test('원문과 일치 (라운드트립)', () => pm.expect(decoded).to.eql(pm.collectionVariables.get('plainText')));"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "3. DYNAMIC 키 암복호화 테스트",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "암호화 (encrypt, runtimeContext 포함)",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [{ "key": "Content-Type", "value": "application/json" }],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"plainTextBase64\": \"{{plainTextBase64}}\",\n \"runtimeContext\": {\n \"{{dynamicContextKey}}\": \"{{dynamicContextValue}}\"\n }\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/manage/crypto/{{dynamicCryptoName}}/test/encrypt",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["manage", "crypto", "{{dynamicCryptoName}}", "test", "encrypt"]
|
||||||
|
},
|
||||||
|
"description": "DYNAMIC 키 방식 모듈로 runtimeContext(예: 헤더값)를 전달하여 키를 도출한 뒤 암호화한다. dynamicContextKey는 DB에 등록된 key_deriv_params.contextKey와 일치해야 한다."
|
||||||
|
},
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "prerequest",
|
||||||
|
"script": {
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"const plainText = pm.collectionVariables.get('plainText');",
|
||||||
|
"pm.collectionVariables.set('plainTextBase64', btoa(unescape(encodeURIComponent(plainText))));"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"const json = pm.response.json();",
|
||||||
|
"pm.test('status 200', () => pm.response.to.have.status(200));",
|
||||||
|
"pm.test('success = true', () => pm.expect(json.success).to.eql(true));",
|
||||||
|
"pm.test('cipherTextBase64 존재', () => pm.expect(json.data.cipherTextBase64).to.be.a('string'));",
|
||||||
|
"pm.collectionVariables.set('dynamicCipherTextBase64', json.data.cipherTextBase64);"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "복호화 (decrypt, 동일 runtimeContext 필요)",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [{ "key": "Content-Type", "value": "application/json" }],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"cipherTextBase64\": \"{{dynamicCipherTextBase64}}\",\n \"runtimeContext\": {\n \"{{dynamicContextKey}}\": \"{{dynamicContextValue}}\"\n }\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/manage/crypto/{{dynamicCryptoName}}/test/decrypt",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["manage", "crypto", "{{dynamicCryptoName}}", "test", "decrypt"]
|
||||||
|
},
|
||||||
|
"description": "암호화 시 사용한 것과 동일한 runtimeContext를 전달해야 동일한 키가 도출되어 복호화에 성공한다. dynamicContextValue를 바꿔서 보내면 키가 달라져 복호화가 실패(예외)하는 것도 확인해볼 수 있다."
|
||||||
|
},
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"const json = pm.response.json();",
|
||||||
|
"pm.test('status 200', () => pm.response.to.have.status(200));",
|
||||||
|
"pm.test('success = true', () => pm.expect(json.success).to.eql(true));",
|
||||||
|
"const decoded = decodeURIComponent(escape(atob(json.data.plainTextBase64)));",
|
||||||
|
"pm.test('원문과 일치 (라운드트립)', () => pm.expect(decoded).to.eql(pm.collectionVariables.get('plainText')));"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "4. 에러 케이스",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "미등록 cryptoName 조회 → success=false",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/manage/crypto/NOT_REGISTERED_MODULE",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["manage", "crypto", "NOT_REGISTERED_MODULE"]
|
||||||
|
},
|
||||||
|
"description": "존재하지 않는 cryptoName 조회 시 HTTP 200 + success=false + message에 사유가 담겨 반환되는지 확인한다 (예외가 그대로 500으로 노출되지 않음)."
|
||||||
|
},
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"const json = pm.response.json();",
|
||||||
|
"pm.test('status 200', () => pm.response.to.have.status(200));",
|
||||||
|
"pm.test('success = false', () => pm.expect(json.success).to.eql(false));",
|
||||||
|
"pm.test('message에 모듈명 포함', () => pm.expect(json.message).to.include('NOT_REGISTERED_MODULE'));"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "잘못된 Base64로 복호화 → success=false",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [{ "key": "Content-Type", "value": "application/json" }],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"cipherTextBase64\": \"not-valid-base64!!\"\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/manage/crypto/{{cryptoName}}/test/decrypt",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["manage", "crypto", "{{cryptoName}}", "test", "decrypt"]
|
||||||
|
},
|
||||||
|
"description": "Base64 형식이 아닌 문자열을 전달하면 success=false로 처리되고 서버가 500으로 죽지 않는지 확인한다."
|
||||||
|
},
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"const json = pm.response.json();",
|
||||||
|
"pm.test('status 200', () => pm.response.to.have.status(200));",
|
||||||
|
"pm.test('success = false', () => pm.expect(json.success).to.eql(false));",
|
||||||
|
"pm.test('message 존재', () => pm.expect(json.message).to.be.a('string'));"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "다른 runtimeContext로 DYNAMIC 복호화 시도 → 실패 확인",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [{ "key": "Content-Type", "value": "application/json" }],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"cipherTextBase64\": \"{{dynamicCipherTextBase64}}\",\n \"runtimeContext\": {\n \"{{dynamicContextKey}}\": \"differentContextValue\"\n }\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/manage/crypto/{{dynamicCryptoName}}/test/decrypt",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["manage", "crypto", "{{dynamicCryptoName}}", "test", "decrypt"]
|
||||||
|
},
|
||||||
|
"description": "'3. DYNAMIC 키' 폴더의 암호화를 먼저 실행해 dynamicCipherTextBase64를 채운 뒤, 암호화 때와 다른 runtimeContext 값으로 복호화를 시도한다. 키가 달라 padding/복호화 오류로 success=false가 되는 것을 확인한다 (패딩 모드에 따라 우연히 성공할 수도 있으니 참고용)."
|
||||||
|
},
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"const json = pm.response.json();",
|
||||||
|
"pm.test('status 200', () => pm.response.to.have.status(200));",
|
||||||
|
"console.log('다른 컨텍스트 복호화 결과:', JSON.stringify(json));"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.eactive.eai.common.security;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CryptoModuleManager 진단정보 DTO.
|
||||||
|
*
|
||||||
|
* 원본 암호키(encKey/decKey) 및 IV는 절대 포함하지 않는다. 메타데이터와
|
||||||
|
* 키 도출 전략(KeyDerivationStrategy) 로드 상태만 담는다.
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CryptoModuleDiagnosticDTO {
|
||||||
|
|
||||||
|
String cryptoName;
|
||||||
|
String cryptoDesc;
|
||||||
|
String algType;
|
||||||
|
String cipherMode;
|
||||||
|
String padding;
|
||||||
|
boolean hasIv;
|
||||||
|
String keySourceType;
|
||||||
|
String keyDerivStrategy;
|
||||||
|
boolean strategyLoaded;
|
||||||
|
String strategyClassName;
|
||||||
|
String strategyLoadError;
|
||||||
|
String cacheYn;
|
||||||
|
Integer cacheTtlSec;
|
||||||
|
String useYn;
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.eactive.eai.common.security;
|
package com.eactive.eai.common.security;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -153,6 +154,58 @@ public class CryptoModuleManager implements Lifecycle {
|
|||||||
return buildExtension(vo, iv, derivedKey.getEncKey(), derivedKey.getDecKey());
|
return buildExtension(vo, iv, derivedKey.getEncKey(), derivedKey.getDecKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 등록된 전체 암호화모듈의 진단정보를 반환한다.
|
||||||
|
* 원본 키(encKey/decKey/ivHex)는 포함하지 않는다.
|
||||||
|
*/
|
||||||
|
public List<CryptoModuleDiagnosticDTO> describeAll() {
|
||||||
|
List<CryptoModuleDiagnosticDTO> result = new ArrayList<>();
|
||||||
|
for (String cryptoName : configMap.keySet()) {
|
||||||
|
result.add(describe(cryptoName));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 단일 암호화모듈의 진단정보를 반환한다. DYNAMIC 키 방식인 경우 resolveStrategy를 통해
|
||||||
|
* 전략 클래스 로드 성공 여부를 함께 확인한다. 원본 키(encKey/decKey/ivHex)는 포함하지 않는다.
|
||||||
|
*/
|
||||||
|
public CryptoModuleDiagnosticDTO describe(String cryptoName) {
|
||||||
|
CryptoModuleConfigVO vo = getVO(cryptoName);
|
||||||
|
|
||||||
|
CryptoModuleDiagnosticDTO dto = new CryptoModuleDiagnosticDTO();
|
||||||
|
dto.setCryptoName(vo.getCryptoName());
|
||||||
|
dto.setCryptoDesc(vo.getCryptoDesc());
|
||||||
|
dto.setAlgType(vo.getAlgType());
|
||||||
|
dto.setCipherMode(vo.getCipherMode());
|
||||||
|
dto.setPadding(vo.getPadding());
|
||||||
|
dto.setHasIv(vo.getIvHex() != null);
|
||||||
|
dto.setKeySourceType(vo.getKeySourceType());
|
||||||
|
dto.setCacheYn(vo.getCacheYn());
|
||||||
|
dto.setCacheTtlSec(vo.getCacheTtlSec());
|
||||||
|
dto.setUseYn(vo.getUseYn());
|
||||||
|
|
||||||
|
if (!"STATIC".equalsIgnoreCase(vo.getKeySourceType()) && vo.getKeyDerivStrategy() != null) {
|
||||||
|
dto.setKeyDerivStrategy(vo.getKeyDerivStrategy());
|
||||||
|
try {
|
||||||
|
KeyDerivationStrategy strategy = resolveStrategy(vo.getKeyDerivStrategy());
|
||||||
|
dto.setStrategyLoaded(true);
|
||||||
|
dto.setStrategyClassName(strategy.getClass().getName());
|
||||||
|
} catch (Exception e) {
|
||||||
|
dto.setStrategyLoaded(false);
|
||||||
|
dto.setStrategyLoadError(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 동적 키 캐시에 존재하는 캐시 키 목록을 반환한다. (전략별 buildCacheKey 결과이며, 원본 키 값이 아니다)
|
||||||
|
*/
|
||||||
|
public List<String> listDynamicCacheKeys() {
|
||||||
|
return new ArrayList<>(dynamicKeyCache.keySet());
|
||||||
|
}
|
||||||
|
|
||||||
private CryptoModuleConfigVO getVO(String cryptoName) {
|
private CryptoModuleConfigVO getVO(String cryptoName) {
|
||||||
CryptoModuleConfigVO vo = configMap.get(cryptoName);
|
CryptoModuleConfigVO vo = configMap.get(cryptoName);
|
||||||
if (vo == null) {
|
if (vo == null) {
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package com.eactive.eai.manage.crypto;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import com.eactive.eai.common.security.CryptoModuleDiagnosticDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 암호화모듈(CryptoModuleManager) 진단 및 암복호화 테스트 API.
|
||||||
|
*
|
||||||
|
* 원본 암호키(encKey/decKey/ivHex)는 응답에 포함하지 않는다. 메타데이터와
|
||||||
|
* 키 도출 전략(KeyDerivationStrategy) 로드 상태만 노출한다.
|
||||||
|
*
|
||||||
|
* GET /manage/crypto/list → 등록된 전체 암호화모듈 진단정보
|
||||||
|
* GET /manage/crypto/{cryptoName} → 단일 암호화모듈 진단정보
|
||||||
|
* GET /manage/crypto/cache/dynamic-keys → 동적 키 캐시 키 목록 (원본 키 아님)
|
||||||
|
* POST /manage/crypto/{cryptoName}/test/encrypt → 테스트 암호화 (Base64 입출력)
|
||||||
|
* POST /manage/crypto/{cryptoName}/test/decrypt → 테스트 복호화 (Base64 입출력)
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/manage/crypto")
|
||||||
|
public class CryptoModuleManageController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CryptoModuleManageService cryptoModuleManageService;
|
||||||
|
|
||||||
|
@GetMapping("/list")
|
||||||
|
public ResponseEntity<?> listAll() {
|
||||||
|
List<CryptoModuleDiagnosticDTO> list = cryptoModuleManageService.listAll();
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("success", true);
|
||||||
|
result.put("data", list);
|
||||||
|
result.put("count", list.size());
|
||||||
|
return ResponseEntity.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{cryptoName}")
|
||||||
|
public ResponseEntity<?> get(@PathVariable String cryptoName) {
|
||||||
|
return respond(() -> cryptoModuleManageService.get(cryptoName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/cache/dynamic-keys")
|
||||||
|
public ResponseEntity<?> listDynamicCacheKeys() {
|
||||||
|
List<String> keys = cryptoModuleManageService.listDynamicCacheKeys();
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
result.put("success", true);
|
||||||
|
result.put("data", keys);
|
||||||
|
result.put("count", keys.size());
|
||||||
|
return ResponseEntity.ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{cryptoName}/test/encrypt")
|
||||||
|
public ResponseEntity<?> testEncrypt(@PathVariable String cryptoName, @RequestBody CryptoTestRequestDTO request) {
|
||||||
|
return respond(() -> {
|
||||||
|
String cipherTextBase64 = cryptoModuleManageService.testEncrypt(cryptoName,
|
||||||
|
request.getRuntimeContext(), request.getPlainTextBase64(), request.getAadBase64());
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("cipherTextBase64", cipherTextBase64);
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/{cryptoName}/test/decrypt")
|
||||||
|
public ResponseEntity<?> testDecrypt(@PathVariable String cryptoName, @RequestBody CryptoTestRequestDTO request) {
|
||||||
|
return respond(() -> {
|
||||||
|
String plainTextBase64 = cryptoModuleManageService.testDecrypt(cryptoName,
|
||||||
|
request.getRuntimeContext(), request.getCipherTextBase64(), request.getAadBase64());
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("plainTextBase64", plainTextBase64);
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResponseEntity<?> respond(Callable<Object> action) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
try {
|
||||||
|
result.put("success", true);
|
||||||
|
result.put("data", action.call());
|
||||||
|
} catch (Exception e) {
|
||||||
|
result.put("success", false);
|
||||||
|
result.put("message", e.getMessage());
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package com.eactive.eai.manage.crypto;
|
||||||
|
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.eactive.eai.common.security.CryptoModuleDiagnosticDTO;
|
||||||
|
import com.eactive.eai.common.security.CryptoModuleExtension;
|
||||||
|
import com.eactive.eai.common.security.CryptoModuleManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CryptoModuleManager 진단 및 암복호화 테스트를 위임하는 서비스.
|
||||||
|
*
|
||||||
|
* createExtension(cryptoName, runtimeContext) 는 STATIC 키 방식인 경우 내부에서
|
||||||
|
* createExtension(cryptoName) 으로 위임하므로, 테스트 시 runtimeContext 유무와 무관하게
|
||||||
|
* 동일 메서드를 사용한다.
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class CryptoModuleManageService {
|
||||||
|
|
||||||
|
public List<CryptoModuleDiagnosticDTO> listAll() {
|
||||||
|
return CryptoModuleManager.getInstance().describeAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CryptoModuleDiagnosticDTO get(String cryptoName) {
|
||||||
|
return CryptoModuleManager.getInstance().describe(cryptoName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> listDynamicCacheKeys() {
|
||||||
|
return CryptoModuleManager.getInstance().listDynamicCacheKeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String testEncrypt(String cryptoName, Map<String, String> runtimeContext,
|
||||||
|
String plainTextBase64, String aadBase64) throws Exception {
|
||||||
|
byte[] plain = Base64.getDecoder().decode(plainTextBase64);
|
||||||
|
CryptoModuleExtension ext = CryptoModuleManager.getInstance()
|
||||||
|
.createExtension(cryptoName, runtimeContext(runtimeContext));
|
||||||
|
byte[] cipher = aadBase64 != null
|
||||||
|
? ext.encrypt(plain, Base64.getDecoder().decode(aadBase64))
|
||||||
|
: ext.encrypt(plain);
|
||||||
|
return Base64.getEncoder().encodeToString(cipher);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String testDecrypt(String cryptoName, Map<String, String> runtimeContext,
|
||||||
|
String cipherTextBase64, String aadBase64) throws Exception {
|
||||||
|
byte[] cipherBytes = Base64.getDecoder().decode(cipherTextBase64);
|
||||||
|
CryptoModuleExtension ext = CryptoModuleManager.getInstance()
|
||||||
|
.createExtension(cryptoName, runtimeContext(runtimeContext));
|
||||||
|
byte[] plain = aadBase64 != null
|
||||||
|
? ext.decrypt(cipherBytes, Base64.getDecoder().decode(aadBase64))
|
||||||
|
: ext.decrypt(cipherBytes);
|
||||||
|
return Base64.getEncoder().encodeToString(plain);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> runtimeContext(Map<String, String> runtimeContext) {
|
||||||
|
return runtimeContext != null ? runtimeContext : Collections.emptyMap();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.eactive.eai.manage.crypto;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 암복호화 테스트 요청 DTO. 평문/암문은 Base64로 주고받는다.
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CryptoTestRequestDTO {
|
||||||
|
|
||||||
|
String plainTextBase64;
|
||||||
|
String cipherTextBase64;
|
||||||
|
String aadBase64;
|
||||||
|
Map<String, String> runtimeContext;
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import java.nio.charset.StandardCharsets;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@@ -295,6 +296,97 @@ class CryptoModuleManagerTest {
|
|||||||
"비활성화된 모듈은 configMap에서 제거되어야 한다");
|
"비활성화된 모듈은 configMap에서 제거되어야 한다");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// 5. 진단정보 — describe / describeAll / listDynamicCacheKeys
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("5-1. describe() STATIC — keyDerivStrategy 없음, 원본 키 필드 없음")
|
||||||
|
void testDescribe_static_noStrategyInfo() throws Exception {
|
||||||
|
CryptoModuleConfig config = buildStaticConfig("DESC_STATIC", "AES", "CBC", "PKCS5Padding");
|
||||||
|
when(mockLoader.findAll()).thenReturn(Arrays.asList(config));
|
||||||
|
reload();
|
||||||
|
|
||||||
|
CryptoModuleDiagnosticDTO dto = manager.describe("DESC_STATIC");
|
||||||
|
|
||||||
|
assertEquals("DESC_STATIC", dto.getCryptoName());
|
||||||
|
assertEquals("STATIC", dto.getKeySourceType());
|
||||||
|
assertTrue(dto.isHasIv());
|
||||||
|
assertNull(dto.getKeyDerivStrategy(), "STATIC 모듈은 keyDerivStrategy가 없어야 한다");
|
||||||
|
assertFalse(dto.isStrategyLoaded(), "STATIC 모듈은 전략을 로드하지 않는다");
|
||||||
|
assertFalse(dto.toString().contains(ENC_KEY_HEX), "원본 키(hex)가 진단정보에 노출되면 안 된다");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("5-2. describe() DYNAMIC 정상 FQCN — strategyLoaded=true, strategyClassName 존재")
|
||||||
|
void testDescribe_dynamic_validFqcn_strategyLoaded() throws Exception {
|
||||||
|
CryptoModuleConfig config = buildDynamicConfig("DESC_DYN", "AES", "CBC", "PKCS5Padding");
|
||||||
|
when(mockLoader.findAll()).thenReturn(Arrays.asList(config));
|
||||||
|
reload();
|
||||||
|
|
||||||
|
CryptoModuleDiagnosticDTO dto = manager.describe("DESC_DYN");
|
||||||
|
|
||||||
|
assertEquals("DYNAMIC", dto.getKeySourceType());
|
||||||
|
assertEquals("com.eactive.eai.common.security.keyderiv.strategy.HsmContextXorKeyDerivationStrategy",
|
||||||
|
dto.getKeyDerivStrategy());
|
||||||
|
assertTrue(dto.isStrategyLoaded());
|
||||||
|
assertEquals("com.eactive.eai.common.security.keyderiv.strategy.HsmContextXorKeyDerivationStrategy",
|
||||||
|
dto.getStrategyClassName());
|
||||||
|
assertNull(dto.getStrategyLoadError());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("5-3. describe() DYNAMIC 잘못된 FQCN — strategyLoaded=false, strategyLoadError 존재")
|
||||||
|
void testDescribe_dynamic_invalidFqcn_strategyLoadFailed() throws Exception {
|
||||||
|
CryptoModuleConfig config = buildDynamicConfig("DESC_BAD_FQCN", "AES", "CBC", "PKCS5Padding");
|
||||||
|
config.setKeyDerivStrategy("com.example.NonExistentStrategy");
|
||||||
|
when(mockLoader.findAll()).thenReturn(Arrays.asList(config));
|
||||||
|
reload();
|
||||||
|
|
||||||
|
CryptoModuleDiagnosticDTO dto = manager.describe("DESC_BAD_FQCN");
|
||||||
|
|
||||||
|
assertFalse(dto.isStrategyLoaded());
|
||||||
|
assertNotNull(dto.getStrategyLoadError());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("5-4. describe() 미등록 cryptoName — IllegalArgumentException")
|
||||||
|
void testDescribe_unknownName_throwsException() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> manager.describe("UNKNOWN_MODULE"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("5-5. describeAll() — 등록된 모듈 수만큼 반환")
|
||||||
|
void testDescribeAll_returnsAllRegisteredModules() throws Exception {
|
||||||
|
CryptoModuleConfig c1 = buildStaticConfig("DESC_ALL_1", "AES", "CBC", "PKCS5Padding");
|
||||||
|
CryptoModuleConfig c2 = buildDynamicConfig("DESC_ALL_2", "ARIA", "CBC", "PKCS5Padding");
|
||||||
|
when(mockLoader.findAll()).thenReturn(Arrays.asList(c1, c2));
|
||||||
|
reload();
|
||||||
|
|
||||||
|
List<CryptoModuleDiagnosticDTO> all = manager.describeAll();
|
||||||
|
|
||||||
|
assertEquals(2, all.size());
|
||||||
|
assertTrue(all.stream().anyMatch(d -> "DESC_ALL_1".equals(d.getCryptoName())));
|
||||||
|
assertTrue(all.stream().anyMatch(d -> "DESC_ALL_2".equals(d.getCryptoName())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("5-6. listDynamicCacheKeys() — DYNAMIC 키 생성 후 캐시키 노출 (원본 키 아님)")
|
||||||
|
void testListDynamicCacheKeys_afterCreateExtension_containsCacheKey() throws Exception {
|
||||||
|
CryptoModuleConfig config = buildDynamicConfig("DESC_CACHE_KEYS", "AES", "CBC", "PKCS5Padding");
|
||||||
|
when(mockLoader.findAll()).thenReturn(Arrays.asList(config));
|
||||||
|
reload();
|
||||||
|
|
||||||
|
Map<String, String> runtimeCtx = new HashMap<>();
|
||||||
|
runtimeCtx.put("X-Api-Enc-Key", "clientKeyValue01");
|
||||||
|
manager.createExtension("DESC_CACHE_KEYS", runtimeCtx);
|
||||||
|
|
||||||
|
List<String> cacheKeys = manager.listDynamicCacheKeys();
|
||||||
|
|
||||||
|
assertEquals(1, cacheKeys.size());
|
||||||
|
assertEquals("DESC_CACHE_KEYS:clientKeyValue01", cacheKeys.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// 헬퍼
|
// 헬퍼
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|||||||
@@ -0,0 +1,189 @@
|
|||||||
|
package com.eactive.eai.manage.crypto;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
|
import com.eactive.eai.common.security.CryptoModuleDiagnosticDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CryptoModuleManageController 단위 테스트.
|
||||||
|
*
|
||||||
|
* <p>Spring MVC를 기동하지 않고 컨트롤러를 직접 인스턴스화하여
|
||||||
|
* 응답 구조(success, data, count, message)를 검증한다.
|
||||||
|
*/
|
||||||
|
class CryptoModuleManageControllerTest {
|
||||||
|
|
||||||
|
private CryptoModuleManageController controller;
|
||||||
|
private CryptoModuleManageService mockService;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
controller = new CryptoModuleManageController();
|
||||||
|
mockService = mock(CryptoModuleManageService.class);
|
||||||
|
ReflectionTestUtils.setField(controller, "cryptoModuleManageService", mockService);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// Helper
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private CryptoModuleDiagnosticDTO makeDTO(String name, String keySourceType) {
|
||||||
|
CryptoModuleDiagnosticDTO dto = new CryptoModuleDiagnosticDTO();
|
||||||
|
dto.setCryptoName(name);
|
||||||
|
dto.setKeySourceType(keySourceType);
|
||||||
|
dto.setAlgType("AES");
|
||||||
|
dto.setCipherMode("CBC");
|
||||||
|
return dto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private Map<String, Object> body(ResponseEntity<?> response) {
|
||||||
|
return (Map<String, Object>) response.getBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// 목록 조회 — listAll
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("listAll() — 등록된 모듈 목록과 개수 반환")
|
||||||
|
void listAll_returnsListAndCount() {
|
||||||
|
List<CryptoModuleDiagnosticDTO> list = Arrays.asList(
|
||||||
|
makeDTO("MOD_A", "STATIC"),
|
||||||
|
makeDTO("MOD_B", "DYNAMIC")
|
||||||
|
);
|
||||||
|
when(mockService.listAll()).thenReturn(list);
|
||||||
|
|
||||||
|
Map<String, Object> body = body(controller.listAll());
|
||||||
|
|
||||||
|
assertEquals(Boolean.TRUE, body.get("success"));
|
||||||
|
assertSame(list, body.get("data"));
|
||||||
|
assertEquals(2, body.get("count"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("listAll() — 빈 목록이면 count=0")
|
||||||
|
void listAll_emptyList_countZero() {
|
||||||
|
when(mockService.listAll()).thenReturn(Collections.emptyList());
|
||||||
|
|
||||||
|
Map<String, Object> body = body(controller.listAll());
|
||||||
|
|
||||||
|
assertEquals(Boolean.TRUE, body.get("success"));
|
||||||
|
assertEquals(0, body.get("count"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// 단일 조회 — get
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("get() — 정상 조회 시 success=true, data 포함")
|
||||||
|
void get_found_successTrueAndDataPresent() {
|
||||||
|
CryptoModuleDiagnosticDTO dto = makeDTO("MOD_A", "STATIC");
|
||||||
|
when(mockService.get("MOD_A")).thenReturn(dto);
|
||||||
|
|
||||||
|
ResponseEntity<?> response = controller.get("MOD_A");
|
||||||
|
Map<String, Object> body = body(response);
|
||||||
|
|
||||||
|
assertEquals(200, response.getStatusCodeValue());
|
||||||
|
assertEquals(Boolean.TRUE, body.get("success"));
|
||||||
|
assertSame(dto, body.get("data"));
|
||||||
|
assertFalse(body.containsKey("message"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("get() — 미등록 cryptoName 예외 발생 시 success=false, message에 원인 포함")
|
||||||
|
void get_unknownName_successFalseWithMessage() {
|
||||||
|
when(mockService.get("GHOST")).thenThrow(new IllegalArgumentException("암호화모듈 미등록: GHOST"));
|
||||||
|
|
||||||
|
Map<String, Object> body = body(controller.get("GHOST"));
|
||||||
|
|
||||||
|
assertEquals(Boolean.FALSE, body.get("success"));
|
||||||
|
assertTrue(body.get("message").toString().contains("GHOST"));
|
||||||
|
assertFalse(body.containsKey("data"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// 동적 키 캐시 목록 — listDynamicCacheKeys
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("listDynamicCacheKeys() — 캐시키 목록과 개수 반환")
|
||||||
|
void listDynamicCacheKeys_returnsListAndCount() {
|
||||||
|
List<String> keys = Arrays.asList("MOD_A:ctx1", "MOD_A:ctx2");
|
||||||
|
when(mockService.listDynamicCacheKeys()).thenReturn(keys);
|
||||||
|
|
||||||
|
Map<String, Object> body = body(controller.listDynamicCacheKeys());
|
||||||
|
|
||||||
|
assertEquals(Boolean.TRUE, body.get("success"));
|
||||||
|
assertSame(keys, body.get("data"));
|
||||||
|
assertEquals(2, body.get("count"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// 암복호화 테스트 — testEncrypt / testDecrypt
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("testEncrypt() — 정상 암호화 시 success=true, cipherTextBase64 포함")
|
||||||
|
void testEncrypt_success_returnsCipherTextBase64() throws Exception {
|
||||||
|
CryptoTestRequestDTO request = new CryptoTestRequestDTO();
|
||||||
|
request.setPlainTextBase64("cGxhaW4=");
|
||||||
|
when(mockService.testEncrypt(eq("MOD_A"), isNull(), eq("cGxhaW4="), isNull()))
|
||||||
|
.thenReturn("Y2lwaGVy");
|
||||||
|
|
||||||
|
Map<String, Object> body = body(controller.testEncrypt("MOD_A", request));
|
||||||
|
|
||||||
|
assertEquals(Boolean.TRUE, body.get("success"));
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> data = (Map<String, Object>) body.get("data");
|
||||||
|
assertEquals("Y2lwaGVy", data.get("cipherTextBase64"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("testEncrypt() — 서비스 예외 발생 시 success=false, message 포함")
|
||||||
|
void testEncrypt_serviceThrows_successFalseWithMessage() throws Exception {
|
||||||
|
CryptoTestRequestDTO request = new CryptoTestRequestDTO();
|
||||||
|
request.setPlainTextBase64("not-valid-base64!!");
|
||||||
|
when(mockService.testEncrypt(anyString(), any(), anyString(), any()))
|
||||||
|
.thenThrow(new IllegalArgumentException("Illegal base64 character"));
|
||||||
|
|
||||||
|
Map<String, Object> body = body(controller.testEncrypt("MOD_A", request));
|
||||||
|
|
||||||
|
assertEquals(Boolean.FALSE, body.get("success"));
|
||||||
|
assertNotNull(body.get("message"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("testDecrypt() — runtimeContext 전달 시 서비스에 그대로 위임")
|
||||||
|
void testDecrypt_withRuntimeContext_delegatesToService() throws Exception {
|
||||||
|
Map<String, String> runtimeContext = new HashMap<>();
|
||||||
|
runtimeContext.put("X-Api-Enc-Key", "clientKeyValue01");
|
||||||
|
|
||||||
|
CryptoTestRequestDTO request = new CryptoTestRequestDTO();
|
||||||
|
request.setCipherTextBase64("Y2lwaGVy");
|
||||||
|
request.setRuntimeContext(runtimeContext);
|
||||||
|
when(mockService.testDecrypt("MOD_B", runtimeContext, "Y2lwaGVy", null))
|
||||||
|
.thenReturn("cGxhaW4=");
|
||||||
|
|
||||||
|
Map<String, Object> body = body(controller.testDecrypt("MOD_B", request));
|
||||||
|
|
||||||
|
assertEquals(Boolean.TRUE, body.get("success"));
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> data = (Map<String, Object>) body.get("data");
|
||||||
|
assertEquals("cGxhaW4=", data.get("plainTextBase64"));
|
||||||
|
verify(mockService).testDecrypt("MOD_B", runtimeContext, "Y2lwaGVy", null);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,291 @@
|
|||||||
|
package com.eactive.eai.manage.crypto;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||||
|
import org.springframework.context.support.GenericApplicationContext;
|
||||||
|
|
||||||
|
import com.eactive.eai.common.hsm.HsmCryptoService;
|
||||||
|
import com.eactive.eai.common.security.CryptoModuleConfigVO;
|
||||||
|
import com.eactive.eai.common.security.CryptoModuleDiagnosticDTO;
|
||||||
|
import com.eactive.eai.common.security.CryptoModuleManager;
|
||||||
|
import com.eactive.eai.common.security.loader.CryptoModuleConfigLoader;
|
||||||
|
import com.eactive.eai.common.security.mapper.CryptoModuleConfigMapper;
|
||||||
|
import com.eactive.eai.common.util.ApplicationContextProvider;
|
||||||
|
import com.eactive.eai.data.entity.onl.security.CryptoModuleConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CryptoModuleManageService 단위 테스트.
|
||||||
|
*
|
||||||
|
* <p>DB / 실 Spring 컨텍스트 없이 Mock으로 동작한다. CryptoModuleManager는 실제 인스턴스를
|
||||||
|
* 사용하며(싱글턴 getInstance() 경유), HSM 및 DB 로더만 Mock 처리한다.
|
||||||
|
*/
|
||||||
|
class CryptoModuleManageServiceTest {
|
||||||
|
|
||||||
|
private static final byte[] KEY_128 = "0123456789abcdef".getBytes(StandardCharsets.UTF_8);
|
||||||
|
private static final byte[] IV_16 = "abcdef0123456789".getBytes(StandardCharsets.UTF_8);
|
||||||
|
private static final String ENC_KEY_HEX = toHex(KEY_128);
|
||||||
|
private static final String IV_HEX = toHex(IV_16);
|
||||||
|
|
||||||
|
private static GenericApplicationContext ctx;
|
||||||
|
private static CryptoModuleConfigLoader mockLoader;
|
||||||
|
private static HsmCryptoService mockHsmCryptoService;
|
||||||
|
private static CryptoModuleManager manager;
|
||||||
|
|
||||||
|
private final CryptoModuleManageService service = new CryptoModuleManageService();
|
||||||
|
|
||||||
|
@BeforeAll
|
||||||
|
static void setUpClass() throws Exception {
|
||||||
|
mockLoader = mock(CryptoModuleConfigLoader.class);
|
||||||
|
mockHsmCryptoService = mock(HsmCryptoService.class);
|
||||||
|
|
||||||
|
SecretKey masterKey = new SecretKeySpec(KEY_128, "AES");
|
||||||
|
when(mockHsmCryptoService.getSecretKey(anyString())).thenReturn(masterKey);
|
||||||
|
when(mockLoader.findAll()).thenReturn(Collections.emptyList());
|
||||||
|
|
||||||
|
Constructor<CryptoModuleManager> ctor = CryptoModuleManager.class.getDeclaredConstructor();
|
||||||
|
ctor.setAccessible(true);
|
||||||
|
manager = ctor.newInstance();
|
||||||
|
|
||||||
|
ctx = new GenericApplicationContext();
|
||||||
|
ctx.getBeanFactory().registerSingleton("cryptoModuleConfigLoader", mockLoader);
|
||||||
|
ctx.getBeanFactory().registerSingleton("hsmCryptoService", mockHsmCryptoService);
|
||||||
|
ctx.getBeanFactory().registerSingleton("cryptoModuleManager", manager);
|
||||||
|
ctx.getBeanFactory().registerSingleton("cryptoModuleConfigMapper", testMapper());
|
||||||
|
ctx.registerBeanDefinition("applicationContextProvider",
|
||||||
|
BeanDefinitionBuilder.genericBeanDefinition(ApplicationContextProvider.class)
|
||||||
|
.getBeanDefinition());
|
||||||
|
ctx.refresh();
|
||||||
|
|
||||||
|
manager.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterAll
|
||||||
|
static void tearDownClass() {
|
||||||
|
if (ctx != null) ctx.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void resetMocks() throws Exception {
|
||||||
|
reset(mockLoader);
|
||||||
|
when(mockLoader.findAll()).thenReturn(Collections.emptyList());
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// 1. 진단정보 위임 — listAll / get
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("1-1. listAll() — CryptoModuleManager.describeAll() 위임")
|
||||||
|
void testListAll_delegatesToManager() throws Exception {
|
||||||
|
CryptoModuleConfig config = buildStaticConfig("SVC_LIST", "AES", "CBC", "PKCS5Padding");
|
||||||
|
when(mockLoader.findAll()).thenReturn(Arrays.asList(config));
|
||||||
|
reload();
|
||||||
|
|
||||||
|
List<CryptoModuleDiagnosticDTO> result = service.listAll();
|
||||||
|
|
||||||
|
assertEquals(1, result.size());
|
||||||
|
assertEquals("SVC_LIST", result.get(0).getCryptoName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("1-2. get() — 미등록 cryptoName은 IllegalArgumentException 전파")
|
||||||
|
void testGet_unknownName_throwsException() {
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> service.get("UNKNOWN_MODULE"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// 2. 동적 키 캐시 목록 — listDynamicCacheKeys
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("2-1. listDynamicCacheKeys() — DYNAMIC 암호화 테스트 후 캐시키 노출")
|
||||||
|
void testListDynamicCacheKeys_afterTestEncrypt_containsCacheKey() throws Exception {
|
||||||
|
CryptoModuleConfig config = buildDynamicConfig("SVC_CACHE_KEYS", "AES", "CBC", "PKCS5Padding");
|
||||||
|
when(mockLoader.findAll()).thenReturn(Arrays.asList(config));
|
||||||
|
reload();
|
||||||
|
|
||||||
|
Map<String, String> runtimeCtx = new HashMap<>();
|
||||||
|
runtimeCtx.put("X-Api-Enc-Key", "clientKeyValue01");
|
||||||
|
String plainB64 = Base64.getEncoder().encodeToString("hello".getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
service.testEncrypt("SVC_CACHE_KEYS", runtimeCtx, plainB64, null);
|
||||||
|
|
||||||
|
assertEquals(Collections.singletonList("SVC_CACHE_KEYS:clientKeyValue01"), service.listDynamicCacheKeys());
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// 3. 암복호화 테스트 — testEncrypt / testDecrypt
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("3-1. STATIC — 암호화 후 복호화 라운드트립 (Base64)")
|
||||||
|
void testEncryptDecrypt_static_roundTrip() throws Exception {
|
||||||
|
CryptoModuleConfig config = buildStaticConfig("SVC_STATIC_RT", "AES", "CBC", "PKCS5Padding");
|
||||||
|
when(mockLoader.findAll()).thenReturn(Arrays.asList(config));
|
||||||
|
reload();
|
||||||
|
|
||||||
|
String plainText = "테스트 평문";
|
||||||
|
String plainB64 = Base64.getEncoder().encodeToString(plainText.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
String cipherB64 = service.testEncrypt("SVC_STATIC_RT", null, plainB64, null);
|
||||||
|
assertNotNull(cipherB64);
|
||||||
|
assertNotEquals(plainB64, cipherB64);
|
||||||
|
|
||||||
|
String decryptedB64 = service.testDecrypt("SVC_STATIC_RT", null, cipherB64, null);
|
||||||
|
assertEquals(plainText, new String(Base64.getDecoder().decode(decryptedB64), StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("3-2. DYNAMIC — runtimeContext 전달 시 암복호화 라운드트립 (Base64)")
|
||||||
|
void testEncryptDecrypt_dynamic_roundTrip() throws Exception {
|
||||||
|
CryptoModuleConfig config = buildDynamicConfig("SVC_DYNAMIC_RT", "AES", "CBC", "PKCS5Padding");
|
||||||
|
when(mockLoader.findAll()).thenReturn(Arrays.asList(config));
|
||||||
|
reload();
|
||||||
|
|
||||||
|
Map<String, String> runtimeCtx = new HashMap<>();
|
||||||
|
runtimeCtx.put("X-Api-Enc-Key", "clientKeyValue01");
|
||||||
|
|
||||||
|
String plainText = "DYNAMIC 키 테스트";
|
||||||
|
String plainB64 = Base64.getEncoder().encodeToString(plainText.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
String cipherB64 = service.testEncrypt("SVC_DYNAMIC_RT", runtimeCtx, plainB64, null);
|
||||||
|
String decryptedB64 = service.testDecrypt("SVC_DYNAMIC_RT", runtimeCtx, cipherB64, null);
|
||||||
|
|
||||||
|
assertEquals(plainText, new String(Base64.getDecoder().decode(decryptedB64), StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("3-3. testDecrypt() — 잘못된 Base64 입력은 예외 전파")
|
||||||
|
void testDecrypt_invalidBase64_throwsException() throws Exception {
|
||||||
|
CryptoModuleConfig config = buildStaticConfig("SVC_BAD_B64", "AES", "CBC", "PKCS5Padding");
|
||||||
|
when(mockLoader.findAll()).thenReturn(Arrays.asList(config));
|
||||||
|
reload();
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> service.testDecrypt("SVC_BAD_B64", null, "not-valid-base64!!", null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("3-4. testEncrypt() — 미등록 cryptoName은 예외 전파")
|
||||||
|
void testEncrypt_unknownName_throwsException() {
|
||||||
|
String plainB64 = Base64.getEncoder().encodeToString("x".getBytes(StandardCharsets.UTF_8));
|
||||||
|
assertThrows(IllegalArgumentException.class,
|
||||||
|
() -> service.testEncrypt("UNKNOWN_MODULE", null, plainB64, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// 헬퍼
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
private void reload() throws Exception {
|
||||||
|
clearField("configMap");
|
||||||
|
clearField("dynamicKeyCache");
|
||||||
|
clearField("strategyCache");
|
||||||
|
|
||||||
|
Method loadAll = CryptoModuleManager.class.getDeclaredMethod("loadAll");
|
||||||
|
loadAll.setAccessible(true);
|
||||||
|
loadAll.invoke(manager);
|
||||||
|
|
||||||
|
reset(mockHsmCryptoService);
|
||||||
|
SecretKey masterKey = new SecretKeySpec(KEY_128, "AES");
|
||||||
|
when(mockHsmCryptoService.getSecretKey(anyString())).thenReturn(masterKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearField(String fieldName) throws Exception {
|
||||||
|
Field field = CryptoModuleManager.class.getDeclaredField(fieldName);
|
||||||
|
field.setAccessible(true);
|
||||||
|
((Map<?, ?>) field.get(manager)).clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private CryptoModuleConfig buildStaticConfig(String name, String alg, String mode, String padding) {
|
||||||
|
CryptoModuleConfig c = new CryptoModuleConfig();
|
||||||
|
c.setCryptoId(java.util.UUID.randomUUID().toString());
|
||||||
|
c.setCryptoName(name);
|
||||||
|
c.setAlgType(alg);
|
||||||
|
c.setCipherMode(mode);
|
||||||
|
c.setPadding(padding);
|
||||||
|
c.setIvHex(IV_HEX);
|
||||||
|
c.setKeySourceType("STATIC");
|
||||||
|
c.setEncKeyHex(ENC_KEY_HEX);
|
||||||
|
c.setDecKeyHex(ENC_KEY_HEX);
|
||||||
|
c.setCacheYn("Y");
|
||||||
|
c.setCacheTtlSec(300);
|
||||||
|
c.setUseYn("Y");
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CryptoModuleConfig buildDynamicConfig(String name, String alg, String mode, String padding) {
|
||||||
|
CryptoModuleConfig c = new CryptoModuleConfig();
|
||||||
|
c.setCryptoId(java.util.UUID.randomUUID().toString());
|
||||||
|
c.setCryptoName(name);
|
||||||
|
c.setAlgType(alg);
|
||||||
|
c.setCipherMode(mode);
|
||||||
|
c.setPadding(padding);
|
||||||
|
c.setIvHex(IV_HEX);
|
||||||
|
c.setKeySourceType("DYNAMIC");
|
||||||
|
c.setKeyDerivStrategy("com.eactive.eai.common.security.keyderiv.strategy.HsmContextXorKeyDerivationStrategy");
|
||||||
|
c.setKeyDerivParams("{\"hsmKeyAlias\":\"MASTER_KEY\",\"contextKey\":\"X-Api-Enc-Key\",\"offset\":\"0\",\"length\":\"16\"}");
|
||||||
|
c.setCacheYn("Y");
|
||||||
|
c.setCacheTtlSec(300);
|
||||||
|
c.setUseYn("Y");
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String toHex(byte[] bytes) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (byte b : bytes) sb.append(String.format("%02x", b));
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** MapStruct APT 없이도 동작하는 인라인 매퍼 구현체 */
|
||||||
|
private static CryptoModuleConfigMapper testMapper() {
|
||||||
|
return new CryptoModuleConfigMapper() {
|
||||||
|
@Override
|
||||||
|
public CryptoModuleConfigVO toVo(CryptoModuleConfig e) {
|
||||||
|
CryptoModuleConfigVO vo = new CryptoModuleConfigVO();
|
||||||
|
vo.setCryptoId(e.getCryptoId());
|
||||||
|
vo.setCryptoName(e.getCryptoName());
|
||||||
|
vo.setCryptoDesc(e.getCryptoDesc());
|
||||||
|
vo.setAlgType(e.getAlgType());
|
||||||
|
vo.setCipherMode(e.getCipherMode());
|
||||||
|
vo.setPadding(e.getPadding());
|
||||||
|
vo.setIvHex(e.getIvHex());
|
||||||
|
vo.setKeySourceType(e.getKeySourceType());
|
||||||
|
vo.setEncKeyHex(e.getEncKeyHex());
|
||||||
|
vo.setDecKeyHex(e.getDecKeyHex());
|
||||||
|
vo.setKeyDerivStrategy(e.getKeyDerivStrategy());
|
||||||
|
vo.setKeyDerivParams(e.getKeyDerivParams());
|
||||||
|
vo.setCacheYn(e.getCacheYn());
|
||||||
|
vo.setCacheTtlSec(e.getCacheTtlSec());
|
||||||
|
vo.setUseYn(e.getUseYn());
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public CryptoModuleConfig toEntity(CryptoModuleConfigVO vo) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user