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.
- 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
- Go 1.21 or higher
- UKey hardware accessible via WebSocket
Using Make:
make build # Build the binary
make build-all # Build for all platforms (Linux, Windows, macOS)
make install # Install to $GOPATH/bin
Manual build:
go mod download
go build -o chinaport-proxy
Create a config.json
file:
{
"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
}
}
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 |
Using Make:
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:
# With default config.json
./chinaport-proxy
# With custom config file
./chinaport-proxy -config /path/to/config.json
The server exposes a single RPC endpoint:
POST /rpc
For methods without parameters:
{
"_method": "cus-sec_SpcGetCertNo"
}
For methods with parameters:
{
"_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
Success response:
{
"_method": "cus-sec_SpcGetCertNo",
"_status": "00",
"_args": {
"Result": true,
"Data": ["0177f045"],
"Error": []
}
}
Error response (UKey operation failed):
{
"_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.
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 PEMcus-sec_SpcGetSignCertAsPEM
- Get signing certificate as PEMcus-sec_SpcSHA1DigestAsPEM
- Calculate SHA1 digest (requiresszInfo
)cus-sec_SpcSignDataAsPEM
- Sign data with hash (requiresinData
)cus-sec_SpcSignDataNoHashAsPEM
- Sign data without hash (requiresinData
)
Get enterprise name:
curl -H "Content-Type: application/json" \
-d '{"_method":"cus-sec_SpcGetEntName"}' \
http://127.0.0.1:8780/rpc
Get certificate number:
curl -H "Content-Type: application/json" \
-d '{"_method":"cus-sec_SpcGetCertNo"}' \
http://127.0.0.1:8780/rpc
Sign data:
curl -H "Content-Type: application/json" \
-d '{"_method":"cus-sec_SpcSignDataAsPEM","args":{"inData":"Hello World"}}' \
http://127.0.0.1:8780/rpc
Example request with nested JSON objects (no stringified sub-fields):
curl -X POST http://localhost:8780/api/customs179 \
-H "Content-Type: application/json" \
-d '{
"sessionId": "123456",
"payExchangeInfoHead": {"guid":"IYUU4A6D0D78893C739E004DA89918FC8DBB","initalRequest":"<xml><openid><![CDATA[o9dVx7XBtxqRvfQEuaUkoSFiHdto]]></openid><out_trade_no><![CDATA[cp477912536700682240]]></out_trade_no><total_fee>14364</total_fee><attach><![CDATA[product]]></attach><body><![CDATA[ISG--ISG BioSkin 赋活奇....]]></body><trade_type><![CDATA[JSAPI]]></trade_type><notify_url><![CDATA[https://isg.tw.cn/api/pay/notify/routine]]></notify_url><spbill_create_ip><![CDATA[27.148.85.253]]></spbill_create_ip><appid><![CDATA[wxff7ddeae4a59f857]]></appid><mch_id>1695916432</mch_id><nonce_str><![CDATA[6899cbbe0592c]]></nonce_str><sign><![CDATA[CA932F8245FBC44195A2C89B98039F9A]]></sign></xml>","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
}'
When auth.app_secret
is configured, all requests must include authentication headers:
X-Timestamp
: Current timestamp in millisecondsX-Signature
: MD5 signature of the request
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 headerBODY
: Raw request bodyAPP_SECRET
: Configured secret key
Requests must be within 3 seconds of server time to prevent replay attacks.
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)
});
}
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
Triggered when UKey operations fail or connection issues occur.
Payload:
{
"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 failedconnection_failure
: WebSocket connection losthealth_check_failure
: Health check failed
Triggered for all requests (success or failure) for auditing purposes.
Payload:
{
"event": "api_audit",
"timestamp": 1723809415000,
"request_id": "uuid-here",
"method": "cus-sec_SpcGetCertNo",
"request": {},
"response": {},
"success": true,
"error": "",
"duration_ms": 125
}
Webhooks use the same authentication mechanism as incoming requests:
X-Timestamp
header with current timestampX-Signature
header with MD5 signature
To verify webhooks are from the proxy server, implement the same signature validation:
// 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' });
});
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
- Normal mode: INFO level and above
- Verbose mode: DEBUG level with detailed request/response logging
All passwords in logs are automatically masked as ***MASKED***
for security.
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
200 OK
: Successful UKey operation400 Bad Request
: UKey operation failed or invalid request format401 Unauthorized
: Authentication failed503 Service Unavailable
: UKey connection issues
- Exponential backoff starting at 1 second
- Maximum backoff: 8 seconds
- Continues indefinitely until connection restored
The server handles SIGTERM and SIGINT signals:
- Stops accepting new requests
- Waits for active requests to complete (up to 10 seconds)
- Closes WebSocket connection
- Exits cleanly
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
Using Make:
make fmt # Format code
make vet # Vet code
make check # Run fmt and vet
Using Make:
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:
# Linux
GOOS=linux GOARCH=amd64 go build -o chinaport-proxy
# Windows
GOOS=windows GOARCH=amd64 go build -o chinaport-proxy.exe
make clean # Remove build artifacts
make fmt # Format code
make vet # Vet code
make deps # Download dependencies
make tidy # Tidy dependencies