327 lines
9.9 KiB
Markdown
327 lines
9.9 KiB
Markdown
# WebSocket Signing Server Architecture Plan
|
|
|
|
Based on my analysis of the existing Actix application structure, I've designed a comprehensive architecture for implementing a WebSocket server that handles signing operations. This server will integrate seamlessly with the existing hostbasket application.
|
|
|
|
## 1. Overview
|
|
|
|
The WebSocket Signing Server will:
|
|
- Accept WebSocket connections from clients
|
|
- Allow clients to identify themselves with a public key
|
|
- Provide a `send_to_sign()` function that takes a public key and a message
|
|
- Forward the message to the appropriate client for signing
|
|
- Wait for a signed response (with a 1-minute timeout)
|
|
- Verify the signature using the client's public key
|
|
- Return the response message and signature
|
|
|
|
## 2. Component Architecture
|
|
|
|
```mermaid
|
|
graph TD
|
|
A[Actix Web Server] --> B[WebSocket Manager]
|
|
B --> C[Connection Registry]
|
|
B --> D[Message Handler]
|
|
D --> E[Signature Verifier]
|
|
F[Client] <--> B
|
|
G[Controllers] --> H[SigningService]
|
|
H --> B
|
|
```
|
|
|
|
### Key Components:
|
|
|
|
1. **WebSocket Manager**
|
|
- Handles WebSocket connections
|
|
- Manages connection lifecycle
|
|
- Routes messages to appropriate handlers
|
|
|
|
2. **Connection Registry**
|
|
- Maps public keys to active WebSocket connections
|
|
- Handles connection tracking and cleanup
|
|
- Provides lookup functionality
|
|
|
|
3. **Message Handler**
|
|
- Processes incoming messages
|
|
- Implements the message protocol
|
|
- Manages timeouts for responses
|
|
|
|
4. **Signature Verifier**
|
|
- Verifies signatures using public keys
|
|
- Implements cryptographic operations
|
|
- Ensures security of the signing process
|
|
|
|
5. **Signing Service**
|
|
- Provides a clean API for controllers to use
|
|
- Abstracts WebSocket complexity from business logic
|
|
- Handles error cases and timeouts
|
|
|
|
## 3. Directory Structure
|
|
|
|
```
|
|
src/
|
|
├── websocket/
|
|
│ ├── mod.rs # Module exports
|
|
│ ├── manager.rs # WebSocket connection manager
|
|
│ ├── registry.rs # Connection registry
|
|
│ ├── handler.rs # Message handling logic
|
|
│ ├── protocol.rs # Message protocol definitions
|
|
│ ├── crypto.rs # Cryptographic operations
|
|
│ └── service.rs # Service API for controllers
|
|
├── controllers/
|
|
│ └── [existing controllers]
|
|
│ └── websocket.rs # WebSocket controller (if needed)
|
|
└── routes/
|
|
└── mod.rs # Updated to include WebSocket routes
|
|
```
|
|
|
|
## 4. Data Flow
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant Client
|
|
participant WebSocketManager
|
|
participant Registry
|
|
participant Controller
|
|
participant SigningService
|
|
|
|
Client->>WebSocketManager: Connect
|
|
Client->>WebSocketManager: Introduce(public_key)
|
|
WebSocketManager->>Registry: Register(connection, public_key)
|
|
|
|
Controller->>SigningService: send_to_sign(public_key, message)
|
|
SigningService->>Registry: Lookup(public_key)
|
|
Registry-->>SigningService: connection
|
|
SigningService->>WebSocketManager: Send message to connection
|
|
WebSocketManager->>Client: Message to sign
|
|
|
|
Client->>WebSocketManager: Signed response
|
|
WebSocketManager->>SigningService: Forward response
|
|
SigningService->>SigningService: Verify signature
|
|
SigningService-->>Controller: Return verified response
|
|
```
|
|
|
|
## 5. Message Protocol
|
|
|
|
We'll define a simple JSON-based protocol for communication:
|
|
|
|
```json
|
|
// Client introduction
|
|
{
|
|
"type": "introduction",
|
|
"public_key": "base64_encoded_public_key"
|
|
}
|
|
|
|
// Sign request
|
|
{
|
|
"type": "sign_request",
|
|
"message": "base64_encoded_message",
|
|
"request_id": "unique_request_id"
|
|
}
|
|
|
|
// Sign response
|
|
{
|
|
"type": "sign_response",
|
|
"request_id": "unique_request_id",
|
|
"message": "base64_encoded_message",
|
|
"signature": "base64_encoded_signature"
|
|
}
|
|
```
|
|
|
|
## 6. Required Dependencies
|
|
|
|
We'll need to add the following dependencies to the project:
|
|
|
|
```toml
|
|
# WebSocket support
|
|
actix-web-actors = "4.2.0"
|
|
|
|
# Cryptography
|
|
ed25519-dalek = "2.0.0" # For Ed25519 signatures
|
|
base64 = "0.21.0" # For encoding/decoding
|
|
rand = "0.8.5" # For generating random data
|
|
```
|
|
|
|
## 7. Implementation Details
|
|
|
|
### 7.1 WebSocket Manager
|
|
|
|
The WebSocket Manager will handle the lifecycle of WebSocket connections:
|
|
|
|
```rust
|
|
pub struct WebSocketManager {
|
|
registry: Arc<RwLock<ConnectionRegistry>>,
|
|
}
|
|
|
|
impl Actor for WebSocketManager {
|
|
type Context = ws::WebsocketContext<Self>;
|
|
|
|
fn started(&mut self, ctx: &mut Self::Context) {
|
|
// Handle connection start
|
|
}
|
|
|
|
fn stopped(&mut self, ctx: &mut Self::Context) {
|
|
// Handle connection close and cleanup
|
|
}
|
|
}
|
|
|
|
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WebSocketManager {
|
|
fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
|
|
// Handle different types of WebSocket messages
|
|
}
|
|
}
|
|
```
|
|
|
|
### 7.2 Connection Registry
|
|
|
|
The Connection Registry will maintain a mapping of public keys to active connections:
|
|
|
|
```rust
|
|
pub struct ConnectionRegistry {
|
|
connections: HashMap<String, Addr<WebSocketManager>>,
|
|
}
|
|
|
|
impl ConnectionRegistry {
|
|
pub fn register(&mut self, public_key: String, addr: Addr<WebSocketManager>) {
|
|
self.connections.insert(public_key, addr);
|
|
}
|
|
|
|
pub fn unregister(&mut self, public_key: &str) {
|
|
self.connections.remove(public_key);
|
|
}
|
|
|
|
pub fn get(&self, public_key: &str) -> Option<&Addr<WebSocketManager>> {
|
|
self.connections.get(public_key)
|
|
}
|
|
}
|
|
```
|
|
|
|
### 7.3 Signing Service
|
|
|
|
The Signing Service will provide a clean API for controllers:
|
|
|
|
```rust
|
|
pub struct SigningService {
|
|
registry: Arc<RwLock<ConnectionRegistry>>,
|
|
}
|
|
|
|
impl SigningService {
|
|
pub async fn send_to_sign(&self, public_key: &str, message: &[u8])
|
|
-> Result<(Vec<u8>, Vec<u8>), SigningError> {
|
|
|
|
// 1. Find the connection for the public key
|
|
let connection = self.registry.read().await.get(public_key).cloned();
|
|
|
|
if let Some(conn) = connection {
|
|
// 2. Generate a unique request ID
|
|
let request_id = Uuid::new_v4().to_string();
|
|
|
|
// 3. Create a response channel
|
|
let (tx, rx) = oneshot::channel();
|
|
|
|
// 4. Register the response channel
|
|
self.pending_requests.write().await.insert(request_id.clone(), tx);
|
|
|
|
// 5. Send the message to the client
|
|
let sign_request = SignRequest {
|
|
request_id: request_id.clone(),
|
|
message: message.to_vec(),
|
|
};
|
|
|
|
conn.do_send(sign_request);
|
|
|
|
// 6. Wait for the response with a timeout
|
|
match tokio::time::timeout(Duration::from_secs(60), rx).await {
|
|
Ok(Ok(response)) => {
|
|
// 7. Verify the signature
|
|
if self.verify_signature(&response.signature, message, public_key) {
|
|
Ok((response.message, response.signature))
|
|
} else {
|
|
Err(SigningError::InvalidSignature)
|
|
}
|
|
},
|
|
Ok(Err(_)) => Err(SigningError::ChannelClosed),
|
|
Err(_) => Err(SigningError::Timeout),
|
|
}
|
|
} else {
|
|
Err(SigningError::ConnectionNotFound)
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## 8. Error Handling
|
|
|
|
We'll define a comprehensive error type for the signing service:
|
|
|
|
```rust
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum SigningError {
|
|
#[error("Connection not found for the provided public key")]
|
|
ConnectionNotFound,
|
|
|
|
#[error("Timeout waiting for signature")]
|
|
Timeout,
|
|
|
|
#[error("Invalid signature")]
|
|
InvalidSignature,
|
|
|
|
#[error("Channel closed unexpectedly")]
|
|
ChannelClosed,
|
|
|
|
#[error("WebSocket error: {0}")]
|
|
WebSocketError(#[from] ws::ProtocolError),
|
|
|
|
#[error("Serialization error: {0}")]
|
|
SerializationError(#[from] serde_json::Error),
|
|
}
|
|
```
|
|
|
|
## 9. Security Considerations
|
|
|
|
1. **Public Key Validation**: Validate public keys upon connection to ensure they are properly formatted
|
|
2. **Message Authentication**: Consider adding a nonce or timestamp to prevent replay attacks
|
|
3. **Rate Limiting**: Implement rate limiting to prevent DoS attacks
|
|
4. **Connection Timeouts**: Automatically close inactive connections
|
|
5. **Error Logging**: Log errors but avoid exposing sensitive information
|
|
6. **Input Validation**: Validate all inputs to prevent injection attacks
|
|
|
|
## 10. Testing Strategy
|
|
|
|
1. **Unit Tests**: Test individual components in isolation
|
|
2. **Integration Tests**: Test the interaction between components
|
|
3. **End-to-End Tests**: Test the complete flow from client connection to signature verification
|
|
4. **Load Tests**: Test the system under high load to ensure stability
|
|
5. **Security Tests**: Test for common security vulnerabilities
|
|
|
|
## 11. Integration with Existing Controllers
|
|
|
|
Controllers can use the SigningService through dependency injection:
|
|
|
|
```rust
|
|
pub struct SomeController {
|
|
signing_service: Arc<SigningService>,
|
|
}
|
|
|
|
impl SomeController {
|
|
pub async fn some_action(&self, public_key: String, message: Vec<u8>) -> HttpResponse {
|
|
match self.signing_service.send_to_sign(&public_key, &message).await {
|
|
Ok((response, signature)) => {
|
|
HttpResponse::Ok().json(json!({
|
|
"response": base64::encode(response),
|
|
"signature": base64::encode(signature),
|
|
}))
|
|
},
|
|
Err(e) => {
|
|
HttpResponse::InternalServerError().json(json!({
|
|
"error": e.to_string(),
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## 12. Deployment Considerations
|
|
|
|
1. **Scalability**: The WebSocket server should be designed to scale horizontally
|
|
2. **Monitoring**: Implement metrics for connection count, message throughput, and error rates
|
|
3. **Logging**: Log important events for debugging and auditing
|
|
4. **Documentation**: Document the API and protocol for client implementers |