-
-
Notifications
You must be signed in to change notification settings - Fork 808
/
Copy pathmain.rs
164 lines (129 loc) · 4.84 KB
/
main.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
use actix_web::{
body::{self, MessageBody},
dev::{self, ServiceResponse},
middleware::Logger,
web::{self, Data, Json},
App, Error, HttpServer, Responder,
};
use actix_web_lab::middleware::{from_fn, Next};
use aes_gcm_siv::{aead::Aead as _, Aes256GcmSiv, KeyInit as _, Nonce};
use base64::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct Req {
id: u64,
data: String,
#[serde(skip_serializing_if = "Option::is_none")]
nonce: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
struct Res {
data: String,
#[serde(skip_serializing_if = "Option::is_none")]
nonce: Option<String>,
}
async fn make_encrypted(
cipher: Data<Aes256GcmSiv>,
Json(Req { id, data, .. }): Json<Req>,
) -> impl Responder {
log::info!("creating encrypted sample request for ID = {id:?}");
// this nonce should actually be unique per message in a production environment
let nonce = Nonce::from_slice(b"unique nonce");
let nonce_b64 = Some(BASE64_STANDARD.encode(nonce));
let data_enc = cipher.encrypt(nonce, data.as_bytes()).unwrap();
let data_enc = BASE64_STANDARD.encode(data_enc);
web::Json(Req {
id,
nonce: nonce_b64,
data: data_enc,
})
}
async fn reverse_data(Json(Req { id, data, .. }): Json<Req>) -> impl Responder {
log::info!("request {id:?} with data: {data}");
let data_rev = data.chars().rev().collect();
log::info!("response {id:?} with new data: {data_rev}");
Json(Res {
data: data_rev,
nonce: None,
})
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
log::info!("starting HTTP server at http://localhost:8080");
// initialize cipher outside HttpServer closure
let cipher = Aes256GcmSiv::new_from_slice(&[0; 32]).unwrap();
HttpServer::new(move || {
App::new()
.app_data(Data::new(cipher.clone()))
.service(web::resource("/encrypt").route(web::post().to(make_encrypted)))
.service(
web::resource("/reverse")
.route(web::post().to(reverse_data))
.wrap(from_fn(encrypt_payloads)),
)
.wrap(Logger::default())
})
.bind(("127.0.0.1", 8080))?
.workers(1)
.run()
.await
}
async fn encrypt_payloads(
mut req: dev::ServiceRequest,
next: Next<impl MessageBody>,
) -> Result<dev::ServiceResponse<impl MessageBody>, Error> {
// get cipher from app data
let cipher = req.extract::<web::Data<Aes256GcmSiv>>().await.unwrap();
// extract JSON with encrypted+encoded data field
let Json(Req { id, nonce, data }) = req.extract::<Json<Req>>().await?;
log::info!("decrypting request {id:?}");
// decode nonce from payload
let nonce = BASE64_STANDARD.decode(nonce.unwrap()).unwrap();
let nonce = Nonce::from_slice(&nonce);
// decode and decrypt data field
let data_enc = BASE64_STANDARD.decode(&data).unwrap();
let data = cipher.decrypt(nonce, data_enc.as_slice()).unwrap();
// construct request body format with plaintext data
let req_body = Req {
id,
nonce: None,
data: String::from_utf8(data).unwrap(),
};
// encode request body as JSON
let req_body = serde_json::to_vec(&req_body).unwrap();
// re-insert request body
req.set_payload(bytes_to_payload(web::Bytes::from(req_body)));
// call next service
let res = next.call(req).await?;
log::info!("encrypting response {id:?}");
// deconstruct response into parts
let (req, res) = res.into_parts();
let (res, body) = res.into_parts();
// Read all bytes out of response stream. Only use `to_bytes` if you can guarantee all handlers
// wrapped by this middleware return complete responses or bounded streams.
let body = body::to_bytes(body).await.ok().unwrap();
// parse JSON from response body
let Res { data, .. } = serde_json::from_slice(&body).unwrap();
// generate and encode nonce for later
let nonce = Nonce::from_slice(b"unique nonce");
let nonce_b64 = Some(BASE64_STANDARD.encode(nonce));
// encrypt and encode data field
let data_enc = cipher.encrypt(nonce, data.as_bytes()).unwrap();
let data_enc = BASE64_STANDARD.encode(data_enc);
// re-pack response into JSON format
let res_body = Res {
data: data_enc,
nonce: nonce_b64,
};
let res_body_enc = serde_json::to_string(&res_body).unwrap();
// set response body as new JSON payload and re-combine response object
let res = res.set_body(res_body_enc);
let res = ServiceResponse::new(req, res);
Ok(res)
}
fn bytes_to_payload(buf: web::Bytes) -> dev::Payload {
let (_, mut pl) = actix_http::h1::Payload::create(true);
pl.unread_data(buf);
dev::Payload::from(pl)
}