# iyuunet **Repository Path**: ledc/iyuunet ## Basic Information - **Project Name**: iyuunet - **Description**: IYUU.net 是一个使用 go 语言开发的高性能代理服务器,通过将 HTTP/JSON-RPC 请求桥接到中国电子口岸 UKey 硬件,提供 179加签、CEB报文加签、身份验证、请求跟踪 和 Webhook通知。 - **Primary Language**: Go - **License**: Not specified - **Default Branch**: main - **Homepage**: https://iyuu.net - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-08-17 - **Last Updated**: 2025-09-25 ## Categories & Tags **Categories**: networklib **Tags**: None ## README # China E-Port UKey Proxy Server A high-performance Go proxy server that bridges HTTP/JSON-RPC requests to China E-Port UKey hardware via WebSocket, providing authentication, request tracking, and webhook notifications. ## Features - **Persistent WebSocket Connection**: Maintains a stable connection to UKey hardware with automatic reconnection - **Request Authentication**: MD5-based signature validation for incoming requests - **Request Tracking**: UUID v4 request IDs for complete request/response tracing - **Webhook Notifications**: - Failure notifications for UKey malfunctions - Audit logging for all requests and responses - **Health Monitoring**: Automatic health checks every 30 seconds - **Password Management**: Automatic password injection from configuration - **Verbose Logging**: Detailed debug logging with password masking ## Installation ### Prerequisites - Go 1.21 or higher - UKey hardware accessible via WebSocket ### Build Using Make: ```bash make build # Build the binary make build-all # Build for all platforms (Linux, Windows, macOS) make install # Install to $GOPATH/bin ``` Manual build: ```bash go mod download go build -o chinaport-proxy ``` ## Configuration Create a `config.json` file: ```json { "server": { "port": "8780" }, "ukey": { "url": "ws://127.0.0.1:61232", "password": "88888888" }, "auth": { "app_secret": "your-secret-key" }, "webhook": { "failure_url": "https://your-webhook.com/failure", "audit_url": "https://your-webhook.com/audit" }, "log": { "verbose": false } } ``` ### Configuration Options | Field | Description | Default | |-------|-------------|---------| | `server.port` | HTTP server port | `8780` | | `ukey.url` | UKey WebSocket URL | `ws://127.0.0.1:61232` | | `ukey.password` | UKey password (auto-injected) | `88888888` | | `auth.app_secret` | Secret for signature validation (empty = disabled) | - | | `webhook.failure_url` | URL for failure notifications | - | | `webhook.audit_url` | URL for audit logging | - | | `log.verbose` | Enable debug logging | `false` | ## Usage ### Starting the Server Using Make: ```bash make run # Build and run with default config.json CONFIG=prod.json make run-config # Build and run with custom config file make dev # Run with race detector (development) ``` Direct execution: ```bash # With default config.json ./chinaport-proxy # With custom config file ./chinaport-proxy -config /path/to/config.json ``` ### API Endpoint The server exposes a single RPC endpoint: ``` POST /rpc ``` ### Request Format For methods without parameters: ```json { "_method": "cus-sec_SpcGetCertNo" } ``` For methods with parameters: ```json { "_method": "cus-sec_SpcSignDataAsPEM", "args": { "inData": "Hello World" } } ``` **Note**: - The `passwd` field is automatically injected from configuration and should not be sent by clients - The `args` field can be omitted for methods that don't require parameters ### Response Format Success response: ```json { "_method": "cus-sec_SpcGetCertNo", "_status": "00", "_args": { "Result": true, "Data": ["0177f045"], "Error": [] } } ``` Error response (UKey operation failed): ```json { "_method": "cus-sec_SpcSignDataAsPEM", "_status": "00", "_args": { "Result": false, "Data": [], "Error": ["参数检查失败,传入的args中必须包含必要的非空白参数:inData", "Err:Base50000"] } } ``` Note: The `_status` field indicates the protocol status ("00" = success), while `_args.Result` indicates whether the UKey operation succeeded. ### Supported Methods - `cus-sec_SpcGetCertNo` - Get certificate number (no args required) - `cus-sec_SpcGetEntName` - Get enterprise name (no args required) - `cus-sec_SpcGetEnvCertAsPEM` - Get envelope certificate as PEM - `cus-sec_SpcGetSignCertAsPEM` - Get signing certificate as PEM - `cus-sec_SpcSHA1DigestAsPEM` - Calculate SHA1 digest (requires `szInfo`) - `cus-sec_SpcSignDataAsPEM` - Sign data with hash (requires `inData`) - `cus-sec_SpcSignDataNoHashAsPEM` - Sign data without hash (requires `inData`) ### Example API Calls Get enterprise name: ```bash curl -H "Content-Type: application/json" \ -d '{"_method":"cus-sec_SpcGetEntName"}' \ http://127.0.0.1:8780/rpc ``` Get certificate number: ```bash curl -H "Content-Type: application/json" \ -d '{"_method":"cus-sec_SpcGetCertNo"}' \ http://127.0.0.1:8780/rpc ``` Sign data: ```bash curl -H "Content-Type: application/json" \ -d '{"_method":"cus-sec_SpcSignDataAsPEM","args":{"inData":"Hello World"}}' \ http://127.0.0.1:8780/rpc ``` #### Customs 179 Data Upload Example request with nested JSON objects (no stringified sub-fields): ```bash curl -X POST http://localhost:8780/api/customs179 \ -H "Content-Type: application/json" \ -d '{ "sessionId": "123456", "payExchangeInfoHead": {"guid":"IYUU4A6D0D78893C739E004DA89918FC8DBB","initalRequest":"143641695916432","initalResponse":"ok","ebpCode":"3506960ABA","payCode":"4403169D3W","payTransactionId":"4200002754202508110844258623","totalAmount":143.64,"currency":"142","verDept":"3","payType":"4","tradingTime":"20250811185359","note":"备注"}, "payExchangeInfoLists": [{"orderNo":"cp477912536700681230","goodsInfo":[{"gname":"ISG BioSkin 赋活奇肌全方位洗卸慕斯150mL","itemLink":"https://isg.tw.cn/pages/goods_details/index?id=8"}],"recpAccount":"612755288","recpCode":"91350603MADY5YXCX2","recpName":"漳州伊诗嘉跨境电子商务有限公司"}], "serviceTime": 1756571640000, "realTimeDataUpload": true }' ``` ## Authentication When `auth.app_secret` is configured, all requests must include authentication headers: ### Required Headers - `X-Timestamp`: Current timestamp in milliseconds - `X-Signature`: MD5 signature of the request ### Signature Algorithm ``` signature = MD5(METHOD + PATH + TIMESTAMP + BODY + APP_SECRET) ``` Where: - `METHOD`: HTTP method in uppercase (e.g., "POST") - `PATH`: Request path (e.g., "/rpc") - `TIMESTAMP`: Same as X-Timestamp header - `BODY`: Raw request body - `APP_SECRET`: Configured secret key ### Timestamp Validation Requests must be within 3 seconds of server time to prevent replay attacks. ### Example (Node.js) ```javascript const crypto = require('crypto'); function makeAuthenticatedRequest(body) { const timestamp = Date.now(); const method = 'POST'; const path = '/rpc'; const appSecret = 'your-secret-key'; const plaintext = method + path + timestamp + JSON.stringify(body) + appSecret; const signature = crypto.createHash('md5').update(plaintext).digest('hex'); return fetch('http://localhost:8780/rpc', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Timestamp': timestamp.toString(), 'X-Signature': signature }, body: JSON.stringify(body) }); } ``` ## Request Tracking Every request is assigned a UUID v4 request ID for tracing: - Request ID is returned in the `X-Request-ID` response header - All logs related to the request include this ID - Request ID is included in webhook notifications ## Webhooks ### Failure Webhook Triggered when UKey operations fail or connection issues occur. Payload: ```json { "event": "ukey_malfunction", "timestamp": 1723809415000, "error": "Error description", "details": { "type": "operation_failure", "method": "cus-sec_SpcGetCertNo", "errors": ["error1", "error2"] } } ``` Failure types: - `operation_failure`: UKey method execution failed - `connection_failure`: WebSocket connection lost - `health_check_failure`: Health check failed ### Audit Webhook Triggered for all requests (success or failure) for auditing purposes. Payload: ```json { "event": "api_audit", "timestamp": 1723809415000, "request_id": "uuid-here", "method": "cus-sec_SpcGetCertNo", "request": {}, "response": {}, "success": true, "error": "", "duration_ms": 125 } ``` ### Webhook Authentication Webhooks use the same authentication mechanism as incoming requests: - `X-Timestamp` header with current timestamp - `X-Signature` header with MD5 signature #### Verifying Webhook Signatures To verify webhooks are from the proxy server, implement the same signature validation: ```javascript // Node.js example const crypto = require('crypto'); function verifyWebhookSignature(req, appSecret) { const timestamp = req.headers['x-timestamp']; const signature = req.headers['x-signature']; const method = req.method; const path = req.path; // e.g., '/webhook/audit' const body = req.rawBody; // Raw request body string // Check timestamp (within 3 seconds) const now = Date.now(); const timeDiff = Math.abs(now - parseInt(timestamp)); if (timeDiff > 3000) { return false; // Timestamp expired } // Calculate expected signature const plaintext = method.toUpperCase() + path + timestamp + body + appSecret; const expectedSignature = crypto.createHash('md5').update(plaintext).digest('hex'); return expectedSignature === signature; } // Express.js middleware example app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf.toString('utf8'); } })); app.post('/webhook/audit', (req, res) => { if (!verifyWebhookSignature(req, 'your-secret-key')) { return res.status(401).json({ error: 'Invalid signature' }); } // Process webhook console.log('Audit event:', req.body); res.json({ status: 'received' }); }); ``` ## Health Monitoring The server performs automatic health checks: - Interval: 30 seconds - Method: Calls `cus-sec_SpcGetCertNo` to verify UKey connectivity - On failure: Triggers reconnection and sends failure webhook ## Logging ### Log Levels - **Normal mode**: INFO level and above - **Verbose mode**: DEBUG level with detailed request/response logging ### Password Masking All passwords in logs are automatically masked as `***MASKED***` for security. ### Log Examples ``` 2024/01/15 10:30:00 INFO Starting China E-Port proxy server address=:8780 2024/01/15 10:30:01 INFO WebSocket connected 2024/01/15 10:30:15 INFO Received RPC request method=cus-sec_SpcGetCertNo request_id=abc-123 2024/01/15 10:30:15 INFO Request completed successfully method=cus-sec_SpcGetCertNo request_id=abc-123 ``` ## Error Handling ### HTTP Status Codes - `200 OK`: Successful UKey operation - `400 Bad Request`: UKey operation failed or invalid request format - `401 Unauthorized`: Authentication failed - `503 Service Unavailable`: UKey connection issues ### Automatic Reconnection - Exponential backoff starting at 1 second - Maximum backoff: 8 seconds - Continues indefinitely until connection restored ## Signal Handling The server handles SIGTERM and SIGINT signals: 1. Stops accepting new requests 2. Waits for active requests to complete (up to 10 seconds) 3. Closes WebSocket connection 4. Exits cleanly ## Development ### Project Structure ``` chinaport-proxy/ ├── main.go # Application entry point ├── config.json # Configuration file ├── go.mod # Go module definition ├── config/ │ └── config.go # Configuration structures ├── client/ │ └── ukey.go # WebSocket client implementation ├── handlers/ │ └── proxy.go # HTTP request handlers ├── services/ │ └── webhook.go # Webhook service ├── middleware/ │ └── auth.go # Authentication middleware ├── auth/ │ └── signature.go # Signature generation/validation ├── protocol/ │ └── types.go # Protocol type definitions └── utils/ └── logging.go # Logging utilities ``` ### Code Quality Using Make: ```bash make fmt # Format code make vet # Vet code make check # Run fmt and vet ``` ### Building for Production Using Make: ```bash make build-linux # Linux (amd64, arm64) make build-windows # Windows (amd64) make build-darwin # macOS (amd64, arm64) make build-all # All platforms ``` Manual cross-compilation: ```bash # Linux GOOS=linux GOARCH=amd64 go build -o chinaport-proxy # Windows GOOS=windows GOARCH=amd64 go build -o chinaport-proxy.exe ``` ### Other Make Commands ```bash make clean # Remove build artifacts make fmt # Format code make vet # Vet code make deps # Download dependencies make tidy # Tidy dependencies ```