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:
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
Configuration
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
}
}
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:
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
API Endpoint
The server exposes a single RPC endpoint:
POST /rpc
Request Format
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
Response Format
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.
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 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
)
Example API Calls
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
Authentication
When auth.app_secret
is configured, all requests must include authentication headers:
Required Headers
X-Timestamp
: Current timestamp in millisecondsX-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 headerBODY
: Raw request bodyAPP_SECRET
: Configured secret key
Timestamp Validation
Requests must be within 3 seconds of server time to prevent replay attacks.
Example (Node.js)
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:
{
"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
Audit Webhook
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
}
Webhook Authentication
Webhooks use the same authentication mechanism as incoming requests:
X-Timestamp
header with current timestampX-Signature
header with MD5 signature
Verifying Webhook Signatures
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' });
});
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 operation400 Bad Request
: UKey operation failed or invalid request format401 Unauthorized
: Authentication failed503 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:
- Stops accepting new requests
- Waits for active requests to complete (up to 10 seconds)
- Closes WebSocket connection
- 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:
make fmt # Format code
make vet # Vet code
make check # Run fmt and vet
Building for Production
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
Other Make Commands
make clean # Remove build artifacts
make fmt # Format code
make vet # Vet code
make deps # Download dependencies
make tidy # Tidy dependencies