598 lines
20 KiB
Text
598 lines
20 KiB
Text
<!-- task=tls_support plan=0 tag=TLS -->
|
|
|
|
# Python·TypeScript·Kotlin TLS 지원 추가
|
|
|
|
## 이 파일을 읽는 구현 에이전트에게
|
|
|
|
각 항목의 체크리스트를 완료 표시하고, 중간 검증 명령을 실행한 뒤 출력을 CODE_REVIEW.md의 `검증 결과` 섹션에 붙여 넣는다. 최종 검증까지 완료한 후 CODE_REVIEW.md의 각 항목을 `[x]`로 체크한다. **의존 관계 및 구현 순서** 섹션에 따라 항목 순서를 지킨다.
|
|
|
|
## 배경
|
|
|
|
Go와 Dart는 TLS TCP와 WSS를 모두 지원하며 crosstest에서 검증된다. Python과 TypeScript는 구현 완료(`Available`) 상태이지만 TLS 코드가 전혀 없다. Kotlin은 WSS 클라이언트(`dialWss`)만 지원하고 TCP TLS가 없다. 이번 작업으로 세 언어에 TLS 지원을 추가해 Go/Dart와 동등한 수준의 TLS 커버리지를 갖춘다.
|
|
|
|
**테스트 인증서 전략:** `dart/test/certs/server.crt`(CN=localhost, SAN IP:127.0.0.1)와 `server.key`(PKCS#8 RSA)가 이미 존재한다. 각 언어의 테스트 디렉토리에 복사해 사용한다.
|
|
|
|
---
|
|
|
|
## [TLS-1] Python TLS TCP
|
|
|
|
### 문제
|
|
|
|
`python/toki_socket/tcp_client.py` — TLS 연결 함수 없음.
|
|
`python/toki_socket/tcp_server.py` — `TcpServer.__init__`와 `start()`에 `ssl` 파라미터 없음.
|
|
|
|
### 해결 방법
|
|
|
|
**`tcp_client.py`:** `connect_tcp_tls()` 함수 추가. `asyncio.open_connection(ssl=ssl_context)` 사용.
|
|
|
|
```python
|
|
# 추가 import
|
|
import ssl
|
|
|
|
async def connect_tcp_tls(
|
|
host: str,
|
|
port: int,
|
|
ssl_context: ssl.SSLContext,
|
|
interval_sec: int,
|
|
wait_sec: int,
|
|
parser_map: ParserMap,
|
|
) -> TcpClient:
|
|
reader, writer = await asyncio.open_connection(host, port, ssl=ssl_context)
|
|
return TcpClient(reader, writer, interval_sec, wait_sec, parser_map)
|
|
```
|
|
|
|
**`tcp_server.py`:** `TcpServer.__init__`에 `ssl_context: ssl.SSLContext | None = None` 파라미터 추가, `start()`에서 `asyncio.start_server(ssl=self._ssl_context)` 전달.
|
|
|
|
**Before (`tcp_server.py:13-18`):**
|
|
```python
|
|
class TcpServer:
|
|
def __init__(
|
|
self,
|
|
host: str,
|
|
port: int,
|
|
new_client: Callable[[asyncio.StreamReader, asyncio.StreamWriter], TcpClient] | None = None,
|
|
interval_sec: int = 0,
|
|
wait_sec: int = 0,
|
|
parser_map: ParserMap | None = None,
|
|
) -> None:
|
|
```
|
|
|
|
**After:**
|
|
```python
|
|
class TcpServer:
|
|
def __init__(
|
|
self,
|
|
host: str,
|
|
port: int,
|
|
new_client: Callable[[asyncio.StreamReader, asyncio.StreamWriter], TcpClient] | None = None,
|
|
interval_sec: int = 0,
|
|
wait_sec: int = 0,
|
|
parser_map: ParserMap | None = None,
|
|
ssl_context: ssl.SSLContext | None = None,
|
|
) -> None:
|
|
```
|
|
|
|
그리고 `__init__` 바디에 `self._ssl_context = ssl_context` 추가.
|
|
|
|
**Before (`tcp_server.py` `start()`):**
|
|
```python
|
|
async def start(self) -> None:
|
|
self._server = await asyncio.start_server(self._handle_connection, self._host, self._port)
|
|
```
|
|
|
|
**After:**
|
|
```python
|
|
async def start(self) -> None:
|
|
self._server = await asyncio.start_server(
|
|
self._handle_connection, self._host, self._port, ssl=self._ssl_context
|
|
)
|
|
```
|
|
|
|
### 수정 파일 및 체크리스트
|
|
|
|
- `python/toki_socket/tcp_client.py`
|
|
- [x] `import ssl` 추가
|
|
- [x] `connect_tcp_tls()` 함수 추가
|
|
- `python/toki_socket/tcp_server.py`
|
|
- [x] `import ssl` 추가
|
|
- [x] `__init__` 파라미터에 `ssl_context: ssl.SSLContext | None = None` 추가
|
|
- [x] `__init__` 바디에 `self._ssl_context = ssl_context` 추가
|
|
- [x] `start()` 바디에 `ssl=self._ssl_context` 전달
|
|
|
|
### 테스트 작성
|
|
|
|
파일: `python/test/test_tcp.py`
|
|
- 테스트 이름: `test_tcp_tls_send_receive`, `test_tcp_tls_request_response`
|
|
- 인증서: `python/test/certs/server.crt`, `python/test/certs/server.key`를 복사 (dart/test/certs/ 에서)
|
|
|
|
```python
|
|
def _make_server_ssl_context() -> ssl.SSLContext:
|
|
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
|
certs_dir = Path(__file__).parent / "certs"
|
|
ctx.load_cert_chain(certs_dir / "server.crt", certs_dir / "server.key")
|
|
return ctx
|
|
|
|
def _make_client_ssl_context() -> ssl.SSLContext:
|
|
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
|
certs_dir = Path(__file__).parent / "certs"
|
|
ctx.load_verify_locations(certs_dir / "server.crt")
|
|
ctx.check_hostname = False
|
|
return ctx
|
|
```
|
|
|
|
### 중간 검증
|
|
|
|
```
|
|
$ cd python && python3 -m py_compile toki_socket/tcp_client.py toki_socket/tcp_server.py
|
|
(exit 0)
|
|
|
|
$ cd python && python3 -m pytest test/test_tcp.py -q
|
|
(전체 PASS)
|
|
```
|
|
|
|
---
|
|
|
|
## [TLS-2] Python WSS
|
|
|
|
### 문제
|
|
|
|
`python/toki_socket/ws_client.py` — WSS 연결 함수 없음.
|
|
`python/toki_socket/ws_server.py` — `WsServer.__init__`와 `start()`에 `ssl` 파라미터 없음.
|
|
|
|
### 해결 방법
|
|
|
|
**`ws_client.py`:** `connect_wss()` 함수 추가. `ws_connect("wss://...", ssl=ssl_context)`.
|
|
|
|
```python
|
|
import ssl
|
|
|
|
async def connect_wss(
|
|
host: str,
|
|
port: int,
|
|
path: str,
|
|
ssl_context: ssl.SSLContext,
|
|
interval_sec: int,
|
|
wait_sec: int,
|
|
parser_map: ParserMap,
|
|
) -> WsClient:
|
|
uri = f"wss://{host}:{port}{path}"
|
|
ws = await ws_connect(uri, ssl=ssl_context)
|
|
return WsClient(ws, interval_sec, wait_sec, parser_map)
|
|
```
|
|
|
|
**`ws_server.py`:** `WsServer.__init__`에 `ssl_context: ssl.SSLContext | None = None` 파라미터 추가, `start()`에서 `ws_serve(handler, ssl=self._ssl_context)` 전달.
|
|
|
|
기존 `start()`의 `await ws_serve(handler, self._host, self._port)` 호출을 `await ws_serve(handler, self._host, self._port, ssl=self._ssl_context)`로 변경. 두 API 경로(`_NEW_WEBSOCKETS_API`, legacy) 모두 변경.
|
|
|
|
### 수정 파일 및 체크리스트
|
|
|
|
- `python/toki_socket/ws_client.py`
|
|
- [x] `import ssl` 추가
|
|
- [x] `connect_wss()` 함수 추가
|
|
- `python/toki_socket/ws_server.py`
|
|
- [x] `import ssl` 추가
|
|
- [x] `__init__` 파라미터에 `ssl_context: ssl.SSLContext | None = None` 추가
|
|
- [x] `__init__` 바디에 `self._ssl_context = ssl_context` 추가
|
|
- [x] `start()` — `ws_serve()` 호출에 `ssl=self._ssl_context` 추가 (new API + legacy API 두 경로 모두)
|
|
|
|
### 테스트 작성
|
|
|
|
파일: `python/test/test_ws.py`
|
|
- 테스트 이름: `test_ws_tls_send_receive`, `test_ws_tls_request_response`
|
|
- TLS-1에서 만든 헬퍼 함수 재사용 (같은 certs 디렉토리)
|
|
|
|
### 중간 검증
|
|
|
|
```
|
|
$ cd python && python3 -m py_compile toki_socket/ws_client.py toki_socket/ws_server.py
|
|
(exit 0)
|
|
|
|
$ cd python && python3 -m pytest test/test_ws.py -q
|
|
(전체 PASS)
|
|
```
|
|
|
|
---
|
|
|
|
## [TLS-3] TypeScript TLS TCP
|
|
|
|
### 문제
|
|
|
|
`typescript/src/tcp_client.ts` — TLS 연결 함수 없음.
|
|
`typescript/src/tcp_server.ts` — `TcpServer`에 TLS 옵션 없음.
|
|
|
|
### 해결 방법
|
|
|
|
**`tcp_client.ts`:** `connectTcpTls()` 추가. `tls.TLSSocket extends net.Socket`이므로 `TcpClient` 생성자에 직접 전달 가능.
|
|
|
|
```typescript
|
|
import * as tls from "node:tls";
|
|
|
|
export async function connectTcpTls(
|
|
host: string,
|
|
port: number,
|
|
tlsOptions: tls.ConnectionOptions,
|
|
intervalSec: number,
|
|
waitSec: number,
|
|
parserMap: ParserMap,
|
|
): Promise<TcpClient> {
|
|
return new Promise<TcpClient>((resolve, reject) => {
|
|
const socket = tls.connect({ ...tlsOptions, host, port });
|
|
const onSecureConnect = () => {
|
|
cleanup();
|
|
resolve(new TcpClient(socket, intervalSec, waitSec, parserMap));
|
|
};
|
|
const onError = (err: Error) => {
|
|
cleanup();
|
|
reject(err);
|
|
};
|
|
const cleanup = () => {
|
|
socket.off("secureConnect", onSecureConnect);
|
|
socket.off("error", onError);
|
|
};
|
|
socket.once("secureConnect", onSecureConnect);
|
|
socket.once("error", onError);
|
|
});
|
|
}
|
|
```
|
|
|
|
**`tcp_server.ts`:** `TcpServer` 생성자에 선택적 `tlsOptions?: tls.TlsOptions` 파라미터 추가. `server` 필드 타입을 `net.Server | tls.Server`로 변경하고, 생성자에서 `tlsOptions` 유무로 분기.
|
|
|
|
**Before (`tcp_server.ts`):**
|
|
```typescript
|
|
import * as net from "node:net";
|
|
// ...
|
|
export class TcpServer {
|
|
private readonly server: net.Server;
|
|
// ...
|
|
constructor(
|
|
private readonly host: string,
|
|
private readonly listenPort: number,
|
|
private readonly newClient: (socket: net.Socket) => TcpClient,
|
|
) {
|
|
this.server = net.createServer((socket) => {
|
|
const client = this.newClient(socket);
|
|
// ...
|
|
});
|
|
}
|
|
```
|
|
|
|
**After:**
|
|
```typescript
|
|
import * as net from "node:net";
|
|
import * as tls from "node:tls";
|
|
// ...
|
|
export class TcpServer {
|
|
private readonly server: net.Server;
|
|
// ...
|
|
constructor(
|
|
private readonly host: string,
|
|
private readonly listenPort: number,
|
|
private readonly newClient: (socket: net.Socket) => TcpClient,
|
|
tlsOptions?: tls.TlsOptions,
|
|
) {
|
|
const handleSocket = (socket: net.Socket): void => {
|
|
const client = this.newClient(socket);
|
|
this.clients.add(client);
|
|
client.addDisconnectListener(() => { this.removeClient(client); });
|
|
this.onClientConnected(client);
|
|
};
|
|
this.server = tlsOptions
|
|
? tls.createServer(tlsOptions, handleSocket)
|
|
: net.createServer(handleSocket);
|
|
}
|
|
```
|
|
|
|
기존 `net.createServer` 콜백 바디를 `handleSocket`으로 추출해서 중복 없이 두 경로에 사용한다.
|
|
|
|
**`index.ts`:** `connectTcpTls` export 추가.
|
|
|
|
### 수정 파일 및 체크리스트
|
|
|
|
- `typescript/src/tcp_client.ts`
|
|
- [x] `import * as tls from "node:tls"` 추가
|
|
- [x] `connectTcpTls()` 함수 추가
|
|
- `typescript/src/tcp_server.ts`
|
|
- [x] `import * as tls from "node:tls"` 추가
|
|
- [x] `server` 필드 타입 `net.Server | tls.Server`로 변경
|
|
- [x] 생성자에 `tlsOptions?: tls.TlsOptions` 파라미터 추가
|
|
- [x] 생성자 바디: `handleSocket` 헬퍼 추출 후 분기 처리
|
|
- `typescript/src/index.ts`
|
|
- [x] `connectTcpTls` 포함 (tcp_client.ts re-export로 자동 포함되는지 확인)
|
|
|
|
### 테스트 작성
|
|
|
|
파일: `typescript/test/tcp.test.ts`
|
|
- 테스트 이름: `"connectTcpTls sends and receives TestData"`, `"TLS sendRequest/response roundtrip"`
|
|
- 인증서: `typescript/test/certs/server.crt`, `server.key`(dart/test/certs/ 에서 복사)
|
|
- 헬퍼:
|
|
```typescript
|
|
import * as fs from "node:fs";
|
|
import * as path from "node:path";
|
|
const certsDir = path.join(import.meta.dirname, "certs");
|
|
const cert = fs.readFileSync(path.join(certsDir, "server.crt"));
|
|
const key = fs.readFileSync(path.join(certsDir, "server.key"));
|
|
```
|
|
- `TcpServer` TLS 옵션: `{ cert, key }`
|
|
- `connectTcpTls` 옵션: `{ ca: cert, servername: "localhost" }`
|
|
|
|
### 중간 검증
|
|
|
|
```
|
|
$ cd typescript && npx tsc --noEmit
|
|
(exit 0)
|
|
|
|
$ cd typescript && npx vitest run test/tcp.test.ts
|
|
(전체 PASS)
|
|
```
|
|
|
|
---
|
|
|
|
## [TLS-4] TypeScript WSS
|
|
|
|
### 문제
|
|
|
|
`typescript/src/ws_client.ts` — WSS 연결 함수 없음.
|
|
`typescript/src/ws_server.ts` — `WsServer`에 TLS 옵션 없음.
|
|
|
|
### 해결 방법
|
|
|
|
**`ws_client.ts`:** `connectWss()` 추가. `WebSocket.ClientOptions`를 사용해 CA 인증서 등을 전달.
|
|
|
|
```typescript
|
|
export async function connectWss(
|
|
host: string,
|
|
port: number,
|
|
path: string,
|
|
wsOptions: WebSocket.ClientOptions,
|
|
intervalSec: number,
|
|
waitSec: number,
|
|
parserMap: ParserMap,
|
|
): Promise<WsClient> {
|
|
return new Promise<WsClient>((resolve, reject) => {
|
|
const ws = new WebSocket(`wss://${host}:${port}${path}`, wsOptions);
|
|
const onOpen = () => { cleanup(); resolve(new WsClient(ws, intervalSec, waitSec, parserMap)); };
|
|
const onError = (err: Error) => { cleanup(); reject(err); };
|
|
const cleanup = () => { ws.off("open", onOpen); ws.off("error", onError); };
|
|
ws.once("open", onOpen);
|
|
ws.once("error", onError);
|
|
});
|
|
}
|
|
```
|
|
|
|
**`ws_server.ts`:** `WsServer`에 `tlsOptions?: https.ServerOptions` 파라미터 추가. `tlsOptions` 유무로 `ws` vs `wss` 경로를 분기. `httpsServer`를 인스턴스 필드로 보관해 `stop()`에서 함께 종료.
|
|
|
|
**추가 import:**
|
|
```typescript
|
|
import * as https from "node:https";
|
|
```
|
|
|
|
**생성자 시그니처:**
|
|
```typescript
|
|
constructor(
|
|
private readonly host: string,
|
|
private readonly listenPort: number,
|
|
private readonly path: string,
|
|
private readonly newClient: (ws: WebSocket) => WsClient,
|
|
private readonly tlsOptions?: https.ServerOptions,
|
|
) {}
|
|
```
|
|
|
|
**`start()` 변경 포인트:**
|
|
- `tlsOptions`가 있으면: `httpsServer = https.createServer(this.tlsOptions)`, `wss = new WebSocketServer({ server: httpsServer, path })`, `httpsServer.listen(port, host)` 후 `httpsServer`의 `listening` 이벤트 대기
|
|
- 없으면: 기존 `WebSocketServer({ host, port, path })` 경로 유지
|
|
|
|
**`stop()` 변경 포인트:**
|
|
- TLS 모드: `await closeServer(wss)` 후 `httpsServer.close()`도 닫는다
|
|
- plain 모드: 변경 없음
|
|
|
|
**`index.ts`:** `connectWss` re-export 확인 (ws_client.ts export가 자동 포함되는지 확인).
|
|
|
|
### 수정 파일 및 체크리스트
|
|
|
|
- `typescript/src/ws_client.ts`
|
|
- [x] `connectWss()` 함수 추가
|
|
- `typescript/src/ws_server.ts`
|
|
- [x] `import * as https from "node:https"` 추가
|
|
- [x] `httpsServer: https.Server | null = null` 인스턴스 필드 추가
|
|
- [x] 생성자에 `tlsOptions?: https.ServerOptions` 파라미터 추가
|
|
- [x] `start()` 분기 처리 (TLS vs plain)
|
|
- [x] `stop()` — TLS 모드 시 `httpsServer.close()` 호출 추가
|
|
|
|
### 테스트 작성
|
|
|
|
파일: `typescript/test/ws.test.ts`
|
|
- 테스트 이름: `"connectWss sends and receives TestData"`, `"WSS sendRequest/response roundtrip"`
|
|
- TLS-3에서 만든 `cert`, `key` 재사용
|
|
- `WsServer` TLS 옵션: `{ cert, key }`
|
|
- `connectWss` 옵션: `{ ca: cert }` (rejectUnauthorized 기본값 true 유지)
|
|
|
|
### 중간 검증
|
|
|
|
```
|
|
$ cd typescript && npx tsc --noEmit
|
|
(exit 0)
|
|
|
|
$ cd typescript && npx vitest run test/ws.test.ts
|
|
(전체 PASS)
|
|
```
|
|
|
|
---
|
|
|
|
## [TLS-5] Kotlin TLS TCP
|
|
|
|
### 문제
|
|
|
|
`kotlin/src/main/kotlin/.../TcpClient.kt` — `dialTcpTls()` top-level 함수 없음.
|
|
`kotlin/src/main/kotlin/.../TcpServer.kt` — TLS `ServerSocket` 생성 경로 없음.
|
|
|
|
### 해결 방법
|
|
|
|
**`TcpClient.kt`:** `dialTcpTls()` top-level 함수 추가. `SSLContext.socketFactory.createSocket()` 사용.
|
|
|
|
```kotlin
|
|
import javax.net.ssl.SSLContext
|
|
|
|
fun dialTcpTls(
|
|
host: String,
|
|
port: Int,
|
|
sslContext: SSLContext,
|
|
intervalSec: Int = 30,
|
|
waitSec: Int = 10,
|
|
parserMap: ParserMap,
|
|
): TcpClient {
|
|
val socket = sslContext.socketFactory.createSocket(host, port)
|
|
return TcpClient.fromSocket(socket, intervalSec, waitSec, parserMap)
|
|
}
|
|
```
|
|
|
|
**`TcpServer.kt`:** 생성자에 `sslContext: SSLContext? = null` 파라미터 추가. `start()` 내 `ServerSocket()` 생성 부분을 분기.
|
|
|
|
**Before (`TcpServer.kt` `start()`):**
|
|
```kotlin
|
|
val server = ServerSocket()
|
|
server.reuseAddress = true
|
|
server.bind(InetSocketAddress(host, port))
|
|
serverSocket = server
|
|
```
|
|
|
|
**After:**
|
|
```kotlin
|
|
val server: ServerSocket = if (sslContext != null) {
|
|
sslContext.serverSocketFactory.createServerSocket()
|
|
} else {
|
|
ServerSocket()
|
|
}
|
|
server.reuseAddress = true
|
|
server.bind(InetSocketAddress(host, port))
|
|
serverSocket = server
|
|
```
|
|
|
|
`TcpServer` 생성자 파라미터 추가:
|
|
```kotlin
|
|
class TcpServer(
|
|
private val host: String,
|
|
private val port: Int,
|
|
private val newClient: (java.net.Socket) -> TcpClient,
|
|
private val sslContext: SSLContext? = null,
|
|
) {
|
|
```
|
|
|
|
**`port()` getter 추가** (테스트에서 port=0 사용 시 필요):
|
|
```kotlin
|
|
fun port(): Int = serverSocket?.localPort ?: port
|
|
```
|
|
|
|
### 수정 파일 및 체크리스트
|
|
|
|
- `kotlin/src/main/kotlin/com/tokilabs/toki_socket/TcpClient.kt`
|
|
- [x] `import javax.net.ssl.SSLContext` 추가 (중복 확인)
|
|
- [x] `dialTcpTls()` top-level 함수 추가
|
|
- `kotlin/src/main/kotlin/com/tokilabs/toki_socket/TcpServer.kt`
|
|
- [x] `import javax.net.ssl.SSLContext` 추가
|
|
- [x] 생성자 파라미터에 `sslContext: SSLContext? = null` 추가
|
|
- [x] `start()` 분기 처리 (TLS `SSLServerSocket` vs 일반 `ServerSocket`)
|
|
- [x] `fun port(): Int` getter 추가 (필요 시)
|
|
|
|
### 테스트 작성
|
|
|
|
파일: `kotlin/src/test/kotlin/com/tokilabs/toki_socket/TlsTcpTest.kt` (신규)
|
|
- 인증서: `kotlin/src/test/resources/server.crt`, `server.key` (dart/test/certs/ 에서 복사)
|
|
- `TestHelpers.kt`에 `createTestSslContexts(certPath, keyPath): Pair<SSLContext, SSLContext>` 추가:
|
|
```kotlin
|
|
fun createTestSslContexts(certPath: String, keyPath: String): Pair<SSLContext, SSLContext> {
|
|
val certFactory = java.security.cert.CertificateFactory.getInstance("X.509")
|
|
val cert = java.io.File(certPath).inputStream().use {
|
|
certFactory.generateCertificate(it) as java.security.cert.X509Certificate
|
|
}
|
|
val keyPem = java.io.File(keyPath).readText()
|
|
.replace("-----BEGIN PRIVATE KEY-----", "")
|
|
.replace("-----END PRIVATE KEY-----", "")
|
|
.replace("\\s+".toRegex(), "")
|
|
val keyBytes = java.util.Base64.getDecoder().decode(keyPem)
|
|
val privateKey = java.security.KeyFactory.getInstance("RSA")
|
|
.generatePrivate(java.security.spec.PKCS8EncodedKeySpec(keyBytes))
|
|
val ks = java.security.KeyStore.getInstance("PKCS12")
|
|
ks.load(null, null)
|
|
ks.setKeyEntry("toki", privateKey, CharArray(0), arrayOf(cert))
|
|
val kmf = javax.net.ssl.KeyManagerFactory.getInstance(
|
|
javax.net.ssl.KeyManagerFactory.getDefaultAlgorithm()
|
|
)
|
|
kmf.init(ks, CharArray(0))
|
|
val serverCtx = SSLContext.getInstance("TLS")
|
|
serverCtx.init(kmf.keyManagers, null, null)
|
|
val ts = java.security.KeyStore.getInstance("PKCS12")
|
|
ts.load(null, null)
|
|
ts.setCertificateEntry("toki", cert)
|
|
val tmf = javax.net.ssl.TrustManagerFactory.getInstance(
|
|
javax.net.ssl.TrustManagerFactory.getDefaultAlgorithm()
|
|
)
|
|
tmf.init(ts)
|
|
val clientCtx = SSLContext.getInstance("TLS")
|
|
clientCtx.init(null, tmf.trustManagers, null)
|
|
return Pair(serverCtx, clientCtx)
|
|
}
|
|
```
|
|
- 테스트: `testTlsTcpSendReceive`, `testTlsTcpRequestResponse`
|
|
- 인증서 경로 로드: `Thread.currentThread().contextClassLoader.getResource("server.crt")!!.path`
|
|
|
|
### 중간 검증
|
|
|
|
```
|
|
$ cd kotlin && env JAVA_HOME=/config/opt/jdk/jdk-17.0.10+7 GRADLE_USER_HOME=/tmp/gradle ./gradlew test --tests "*.TlsTcpTest"
|
|
BUILD SUCCESSFUL
|
|
```
|
|
|
|
---
|
|
|
|
## 수정 파일 요약
|
|
|
|
| 파일 | 항목 |
|
|
|------|------|
|
|
| `python/toki_socket/tcp_client.py` | TLS-1 |
|
|
| `python/toki_socket/tcp_server.py` | TLS-1 |
|
|
| `python/toki_socket/ws_client.py` | TLS-2 |
|
|
| `python/toki_socket/ws_server.py` | TLS-2 |
|
|
| `python/test/test_tcp.py` | TLS-1 |
|
|
| `python/test/test_ws.py` | TLS-2 |
|
|
| `python/test/certs/server.crt` | TLS-1, TLS-2 (신규, 복사) |
|
|
| `python/test/certs/server.key` | TLS-1, TLS-2 (신규, 복사) |
|
|
| `typescript/src/tcp_client.ts` | TLS-3 |
|
|
| `typescript/src/tcp_server.ts` | TLS-3 |
|
|
| `typescript/src/ws_client.ts` | TLS-4 |
|
|
| `typescript/src/ws_server.ts` | TLS-4 |
|
|
| `typescript/test/tcp.test.ts` | TLS-3 |
|
|
| `typescript/test/ws.test.ts` | TLS-4 |
|
|
| `typescript/test/certs/server.crt` | TLS-3, TLS-4 (신규, 복사) |
|
|
| `typescript/test/certs/server.key` | TLS-3, TLS-4 (신규, 복사) |
|
|
| `kotlin/src/main/kotlin/.../TcpClient.kt` | TLS-5 |
|
|
| `kotlin/src/main/kotlin/.../TcpServer.kt` | TLS-5 |
|
|
| `kotlin/src/test/kotlin/.../TlsTcpTest.kt` | TLS-5 (신규) |
|
|
| `kotlin/src/test/kotlin/.../TestHelpers.kt` | TLS-5 |
|
|
| `kotlin/src/test/resources/server.crt` | TLS-5 (신규, 복사) |
|
|
| `kotlin/src/test/resources/server.key` | TLS-5 (신규, 복사) |
|
|
|
|
## 의존 관계 및 구현 순서
|
|
|
|
- TLS-1 → TLS-2 (Python 소스 파일 순서)
|
|
- TLS-3 → TLS-4 (TypeScript 소스 파일 순서)
|
|
- TLS-5 독립 (Kotlin)
|
|
- TLS-1/TLS-2와 TLS-3/TLS-4는 병렬 가능
|
|
|
|
## 최종 검증
|
|
|
|
```
|
|
$ cd python && python3 -m py_compile toki_socket/tcp_client.py toki_socket/tcp_server.py toki_socket/ws_client.py toki_socket/ws_server.py
|
|
(exit 0)
|
|
|
|
$ cd python && python3 -m pytest test/ -q
|
|
(전체 PASS)
|
|
|
|
$ cd typescript && npx tsc --noEmit
|
|
(exit 0)
|
|
|
|
$ cd typescript && npx vitest run
|
|
(전체 PASS)
|
|
|
|
$ cd kotlin && env JAVA_HOME=/config/opt/jdk/jdk-17.0.10+7 GRADLE_USER_HOME=/tmp/gradle ./gradlew test
|
|
BUILD SUCCESSFUL
|
|
```
|