diff options
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | receiver.cpp | 303 |
2 files changed, 305 insertions, 2 deletions
@@ -3,8 +3,8 @@ all: sender receiver | |||
3 | sender: sender.c | 3 | sender: sender.c |
4 | $(CC) -o sender sender.c -lmbedtls -lmbedcrypto | 4 | $(CC) -o sender sender.c -lmbedtls -lmbedcrypto |
5 | 5 | ||
6 | receiver: receiver.c | 6 | receiver: receiver.cpp |
7 | $(CC) -o receiver receiver.c -lmbedtls -lmbedcrypto | 7 | $(CC) -std=c++17 -o receiver receiver.cpp -lmbedtls -lmbedcrypto -lc++ |
8 | 8 | ||
9 | clean: | 9 | clean: |
10 | rm -f sender receiver | 10 | rm -f sender receiver |
diff --git a/receiver.cpp b/receiver.cpp new file mode 100644 index 0000000..fdeee05 --- /dev/null +++ b/receiver.cpp | |||
@@ -0,0 +1,303 @@ | |||
1 | #include <regex.h> | ||
2 | #include <string.h> | ||
3 | #include <dirent.h> | ||
4 | #include <err.h> | ||
5 | #include <stdio.h> | ||
6 | #include <inttypes.h> | ||
7 | #include <fcntl.h> | ||
8 | #include <unistd.h> | ||
9 | #include <sys/socket.h> | ||
10 | #include <netinet/in.h> | ||
11 | |||
12 | #include <mbedtls/pk.h> | ||
13 | #include <mbedtls/entropy.h> | ||
14 | #include "mbedtls/ctr_drbg.h" | ||
15 | #include "mbedtls/gcm.h" | ||
16 | |||
17 | #include <string> | ||
18 | #include <map> | ||
19 | #include <iostream> | ||
20 | |||
21 | const unsigned short PORT = 58132; | ||
22 | |||
23 | static const unsigned char privkey[] = | ||
24 | "-----BEGIN RSA PRIVATE KEY-----\n" | ||
25 | "MIIEpAIBAAKCAQEAwWlNmLHOOzZpdrfp+EAANwqab0FhQwCyZ/u+ySBW5XxPf6mo\n" | ||
26 | "bySvtJrLdsWzdwnup/UfwZiEhJk/4wpD4Qf/2+syuJi3Rf7L+Jfh//Qf9uXAS80+\n" | ||
27 | "LYad7dW0c1r5nt+F9Can5fBn7futnd8n672T+y8QpHRwX9GtaILvYQe5GQac8cfq\n" | ||
28 | "2rGUd5iYj5KSdcaIZnJ4YgnjLHg2PMbtEJwqcV+2oAkcOPzTAJoNE7XjLZTwXmLl\n" | ||
29 | "FgL/2cN4uJZBDnwv3RZSAhpdYF4KOJmE2GFs2jdvRUrYO7WSl8fM16vRH4vz5MNN\n" | ||
30 | "caprg2MlXheVTPQa+WMdcz7dyQx8s9kRVPPUSwIDAQABAoIBAH1KD8A4flYRO2Ry\n" | ||
31 | "YxgzrW/6aGxlt/HFg8ykYcS8NE5Yps8WQkwtQb0HAYKhM06LmpQm0DmC6WVUOPSE\n" | ||
32 | "c9BUdEQsKiE2nJK1KcCR8w7xP7uavWTdQcgQCkJFS63mYwmt1oKAgAcOIuUhQiig\n" | ||
33 | "pKWrmy7+IBPIcftAQssO9q6uaBNy+ONu6KU/FYd4UAoEt07MzuIAl5rybROOWCrA\n" | ||
34 | "cjuOKK830Q2Mi2ESwwlO0+3Vyz9VhSha5wW2WwFv9zx8YQblaVXxfXC6O5XcXb0j\n" | ||
35 | "O2rTpgHMmOVik3Zrg3XoRXsNZCvFQqbIevwGhRNEFQTnzakh/5g0VDa54RAK/APC\n" | ||
36 | "t3ABEAECgYEA+rnqDqmXQc/xMCLOhJduxRFvZbT6EbUVJG5+l6XtATTa4LWzqx1B\n" | ||
37 | "QDXS/Dixxc9FA1vSH2rAW//6wbr8KXihSeI7YxgIfWyrSIxbS3Dd1qwddvniYIpx\n" | ||
38 | "ms9vYpQKAewv2p310nf7fyuURES8YikhpSuff3DhzXBEi7s3Y7gIIEsCgYEAxXrD\n" | ||
39 | "6xjgLSgbbdyqxKeNk8ADMifbj8ZoiNIHkJ6sShFaTEbyHweOJ4RY7OmTBUDhgzUY\n" | ||
40 | "1oynztilADFaq8KhsiMqzI3DgZ3/2OElGYReEAbXljvgudL3tmiBWc5j9S7XNLBe\n" | ||
41 | "u9f3WYAYDu7/BVqmQWa/QtD9JquoZ1xgV2D6nAECgYEA29w9t9/VSJvM9xX+nNyi\n" | ||
42 | "AON6GOjrRK3TPWA7WEXjH+S2bshHJi0ANAs+2Xfpw/kunnRdPLmCtuowfMO4LbGf\n" | ||
43 | "VcexpgLEJyAszvBteikeDwpcyCD19wxP9J4kIYCJiggQKpfLoWUfP/P6DydrPnSt\n" | ||
44 | "EUbAlaNqDpl9Mj7YonQVhCMCgYEAv0c9M5+hrDuX7d76zYaZvI4UymUO54E/yZ7e\n" | ||
45 | "UvdOXGPYed+SL/oKeD5aQAeyLzl79bHdgBs3g0QW9kvXzly0cC5eC0oZH5hhs7nI\n" | ||
46 | "TKII1i86bLtM3dD5vQYWnF0sNtWK/+8Bo6L5ZAiNxRE7lP0L4ndaNKbnPaixcoRo\n" | ||
47 | "kNpPhAECgYAg7jmNlw+7VVurzR36LKKE+d6veraF5ltpJiboDb3j38RGe3982LLq\n" | ||
48 | "WaBKm1gkHfXgBjkNzS4r2kyRijw0GQ9JgmWooR7BXFH30HkPNl4gFTSsrOG5zGLi\n" | ||
49 | "0aexkDpXQuKsgBzqU0Wn94GZMM+RhuOYec/56JFT+8U5Tcntb26wwA==\n" | ||
50 | "-----END RSA PRIVATE KEY-----\n"; | ||
51 | |||
52 | static const unsigned char pp[] = "IJUHZGFDXTZKHJKHGFDHZLUÖDRTFGHHJGHH"; | ||
53 | |||
54 | // Close files after 20 minutes | ||
55 | #define ACCESS_TIMEOUT (20*60) | ||
56 | |||
57 | /* | ||
58 | 1 byte type: 0 allocate session, 1 log to session, rest: discard | ||
59 | 8 bytes session id | ||
60 | type 0: | ||
61 | 256 bytes rsa 2048 data yielding 32 byte AES session key | ||
62 | type 1: | ||
63 | 16 bytes iv | ||
64 | 16 bytes tag | ||
65 | rest cipher text | ||
66 | |||
67 | File name format: 2020-10-13-23-42-05-SSSSSSSSSSSSSSSS-KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK.log | ||
68 | |||
69 | */ | ||
70 | |||
71 | // Time helper | ||
72 | static time_t now() { | ||
73 | struct timespec tp; | ||
74 | clock_gettime(CLOCK_MONOTONIC, &tp); | ||
75 | return tp.tv_sec; | ||
76 | } | ||
77 | |||
78 | // Constants | ||
79 | enum { SESSION_ID_LENGTH = 8, AES_KEY_LENGTH = 16, GCM_IV_LENGTH = 16, GCM_TAG_LENGTH = 16, MIN_PACKET_SIZE = 40 }; | ||
80 | enum { FILENAME_LENGTH = 73, SIDOFFS = 20, KEYOFFS = 37 }; | ||
81 | |||
82 | class Session { | ||
83 | public: | ||
84 | Session(uint64_t session_id, uint8_t key[AES_KEY_LENGTH], const std::string &filename) : | ||
85 | _session_id(session_id), _filename(filename) { | ||
86 | memcpy(_key, key, AES_KEY_LENGTH); | ||
87 | mbedtls_gcm_init(&_ctx); | ||
88 | mbedtls_gcm_setkey(&_ctx, MBEDTLS_CIPHER_ID_AES, _key, 8 * AES_KEY_LENGTH); | ||
89 | } | ||
90 | |||
91 | Session(uint64_t session_id, uint8_t key[AES_KEY_LENGTH]) : _session_id(session_id) { | ||
92 | memcpy(_key, key, AES_KEY_LENGTH); | ||
93 | mbedtls_gcm_init(&_ctx); | ||
94 | mbedtls_gcm_setkey(&_ctx, MBEDTLS_CIPHER_ID_AES, _key, 8 * AES_KEY_LENGTH); | ||
95 | |||
96 | // Create timestamp | ||
97 | char tprefix[32]; | ||
98 | time_t t = time(NULL); | ||
99 | struct tm * jetzt = localtime(&t); | ||
100 | size_t nlen = strftime(tprefix, sizeof(tprefix), "%F-%H-%M-%S", jetzt); | ||
101 | |||
102 | // Dump key | ||
103 | char hexkey[2*AES_KEY_LENGTH + 1]; | ||
104 | for (int i=0; i<AES_KEY_LENGTH; ++i) | ||
105 | sprintf(hexkey + 2 * i, "%02x", _key[i]); | ||
106 | |||
107 | // Glue together serialisation | ||
108 | char filename[FILENAME_LENGTH + 1]; | ||
109 | snprintf(filename, sizeof(filename), "%s-%016" PRIx64 "-%s.log", tprefix, _session_id, hexkey); | ||
110 | |||
111 | // Touch file to save session_id and key | ||
112 | close(open(filename, O_WRONLY|O_CREAT, 0755)); | ||
113 | |||
114 | _filename = std::string(filename, filename + FILENAME_LENGTH); | ||
115 | } | ||
116 | |||
117 | ~Session() { | ||
118 | mbedtls_gcm_free(&_ctx); | ||
119 | } | ||
120 | |||
121 | void write_log(const uint8_t *packet, size_t len) { | ||
122 | // First check if the packet holds enough space for session id, iv and at least one gcm block | ||
123 | if (len < GCM_IV_LENGTH + GCM_TAG_LENGTH) { | ||
124 | std::cerr << "Error: Short packet, size " << len << std::endl; | ||
125 | return; | ||
126 | } | ||
127 | |||
128 | const uint8_t *iv = packet; | ||
129 | const uint8_t *tag = packet + GCM_IV_LENGTH; | ||
130 | const uint8_t *payload = packet + GCM_IV_LENGTH + GCM_TAG_LENGTH; | ||
131 | len -= GCM_IV_LENGTH + GCM_TAG_LENGTH; | ||
132 | |||
133 | // Create output file if it doesn't exist | ||
134 | if (_fd < 0) | ||
135 | _fd = ::open(_filename.c_str(), O_WRONLY | O_APPEND | O_CREAT, 0755); | ||
136 | if (_fd < 0) { | ||
137 | std::cerr << "Error: Can't create file " << _filename << " for session " << std::hex << _session_id; | ||
138 | return; | ||
139 | } | ||
140 | |||
141 | // Prepare output scratch space | ||
142 | uint8_t *output = static_cast<uint8_t*>(alloca(len)); | ||
143 | |||
144 | // This should fail on invalid input sizes | ||
145 | switch (mbedtls_gcm_auth_decrypt(&_ctx, len, iv, GCM_IV_LENGTH, (uint8_t*)&_session_id, SESSION_ID_LENGTH, tag, GCM_TAG_LENGTH, payload, output)) | ||
146 | { | ||
147 | case 0: | ||
148 | write(_fd, output, len); | ||
149 | _last_access = now(); | ||
150 | break; | ||
151 | case MBEDTLS_ERR_GCM_BAD_INPUT: | ||
152 | std::cerr << "Error: Invalid log data" << std::endl; | ||
153 | break; | ||
154 | case MBEDTLS_ERR_GCM_AUTH_FAILED : | ||
155 | std::cerr << "Error: Can't decrypt" << std::endl; | ||
156 | break; | ||
157 | default: | ||
158 | std::cerr << "Error: Unknown gcm error" << std::endl; | ||
159 | } | ||
160 | } | ||
161 | |||
162 | void check_timeout(time_t when) { | ||
163 | if (_last_access && _last_access < when && _fd != -1) { | ||
164 | ::close(_fd); | ||
165 | _fd = -1; | ||
166 | } | ||
167 | } | ||
168 | |||
169 | private: | ||
170 | uint64_t _session_id; | ||
171 | uint8_t _key[AES_KEY_LENGTH]; | ||
172 | int _fd = -1; | ||
173 | time_t _last_access = 0; | ||
174 | std::string _filename; | ||
175 | mbedtls_gcm_context _ctx; | ||
176 | }; | ||
177 | |||
178 | std::map<uint64_t, std::unique_ptr<Session>> g_sessions; | ||
179 | |||
180 | static uint8_t hex2nyble(char c) | ||
181 | { | ||
182 | return (c>='0'&&c<='9') ? (uint8_t)(c-'0') | ||
183 | : (c>='a'&&c<='f') ? (uint8_t)(c-'a'+10) | ||
184 | : (c>='A'&&c<='F') ? (uint8_t)(c-'A'+10) | ||
185 | : 0; | ||
186 | } | ||
187 | |||
188 | static void import_sessions(const char *dirname) { | ||
189 | DIR * dirp = opendir(dirname); | ||
190 | if (!dirp) | ||
191 | errx(-1, "Fatal: Can't open dir %s\n", dirname); | ||
192 | |||
193 | regex_t regex; | ||
194 | if (regcomp(®ex, "^[[:digit:]]{4}-[[:digit:]][[:digit:]]-[[:digit:]][[:digit:]]-[[:digit:]][[:digit:]]-[[:digit:]][[:digit:]]-" | ||
195 | "[[:digit:]][[:digit:]]-[[:xdigit:]]{16}-[[:xdigit:]]{32}.log$", REG_EXTENDED)) | ||
196 | errx(-1, "Fatal: Can't compile re"); | ||
197 | |||
198 | struct dirent * entry; | ||
199 | while ((entry = readdir(dirp)) != NULL) { | ||
200 | // We expect a very specific format | ||
201 | if (entry->d_type != DT_REG || entry->d_namlen != FILENAME_LENGTH) | ||
202 | continue; | ||
203 | |||
204 | std::string filename(entry->d_name, entry->d_name + entry->d_namlen); | ||
205 | uint64_t session_id; | ||
206 | uint8_t aeskey[AES_KEY_LENGTH]; | ||
207 | |||
208 | if (regexec(®ex, filename.c_str(), (size_t) 0, NULL, 0) || | ||
209 | sscanf(filename.c_str() + SIDOFFS, "%" SCNx64, &session_id) != 1) { | ||
210 | std::cerr << "Skipping non-re-matching file: " << filename << std::endl; | ||
211 | continue; | ||
212 | } | ||
213 | |||
214 | const char * hexkey = filename.c_str() + KEYOFFS; | ||
215 | for (int i=0; i<16; ++i) | ||
216 | aeskey[i] = (hex2nyble(hexkey[2*i]) << 4 ) | hex2nyble(hexkey[2*i+1]); | ||
217 | g_sessions[session_id] = std::make_unique<Session>(session_id, aeskey, filename); | ||
218 | } | ||
219 | closedir(dirp); | ||
220 | regfree(®ex); | ||
221 | } | ||
222 | |||
223 | int main() { | ||
224 | mbedtls_ctr_drbg_context ctr_drbg; | ||
225 | mbedtls_entropy_context entropy; | ||
226 | mbedtls_pk_context pk; | ||
227 | int ret = 0; | ||
228 | unsigned char result[256]; | ||
229 | unsigned char input[256]; | ||
230 | size_t inputlen = 0; | ||
231 | |||
232 | chdir("sessions"); | ||
233 | |||
234 | mbedtls_pk_init( &pk ); | ||
235 | mbedtls_entropy_init( &entropy ); | ||
236 | mbedtls_ctr_drbg_init( &ctr_drbg ); | ||
237 | mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, pp, sizeof(pp)); | ||
238 | |||
239 | if ((ret = mbedtls_pk_parse_key(&pk, privkey, sizeof(privkey), NULL, 0) ) != 0 ) | ||
240 | errx(-1, "Fatal: mbedtls_pk_parse_key returned -0x%04x\n", -ret ); | ||
241 | |||
242 | int sock = socket(AF_INET, SOCK_DGRAM, 0); | ||
243 | if (sock < 0) | ||
244 | errx(-1, "Fatal: Can not make socket\n"); | ||
245 | |||
246 | struct sockaddr_in servaddr, peer; | ||
247 | servaddr.sin_family = AF_INET; | ||
248 | servaddr.sin_addr.s_addr = INADDR_ANY; | ||
249 | servaddr.sin_port = htons(PORT); | ||
250 | if (bind(sock, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) | ||
251 | errx(-1, "Fatal: Can't bind to port %d\n", PORT); | ||
252 | |||
253 | import_sessions("."); | ||
254 | |||
255 | time_t last_close_check = now(); | ||
256 | while(1) { | ||
257 | uint8_t packet[16*1024]; | ||
258 | uint8_t rsa_plain_text[AES_KEY_LENGTH]; | ||
259 | size_t olen; | ||
260 | socklen_t peer_len = sizeof(peer); | ||
261 | |||
262 | ssize_t len = recvfrom(sock, packet, sizeof(packet), MSG_WAITALL, (struct sockaddr *) &peer, &peer_len); | ||
263 | if (len <= 0) | ||
264 | errx(-1, "Fatal: recvfrom yields %zd\n", len); | ||
265 | |||
266 | if (len < MIN_PACKET_SIZE) { | ||
267 | std::cerr << "Skipping short packet" << std::endl; | ||
268 | continue; | ||
269 | } | ||
270 | |||
271 | uint64_t session_id = *(uint64_t*)(packet + 1); | ||
272 | auto session = g_sessions.find(session_id); | ||
273 | |||
274 | switch(packet[0]) { | ||
275 | case 0: // Session setup | ||
276 | ret = mbedtls_pk_decrypt(&pk, packet + 1 + SESSION_ID_LENGTH, len - 1 - SESSION_ID_LENGTH, | ||
277 | rsa_plain_text, &olen, sizeof(rsa_plain_text), mbedtls_ctr_drbg_random, &ctr_drbg); | ||
278 | if (ret < 0) { | ||
279 | std::cerr << "Error: Failed to decrypt key (error " << -ret << ") for session " << std::hex << session_id; | ||
280 | break; | ||
281 | } | ||
282 | |||
283 | if (session == g_sessions.end()) | ||
284 | g_sessions[session_id] = std::make_unique<Session>(session_id, rsa_plain_text); | ||
285 | break; | ||
286 | case 1: | ||
287 | if (session != g_sessions.end()) | ||
288 | session->second->write_log(packet + 1 + SESSION_ID_LENGTH, len - 1 - SESSION_ID_LENGTH); | ||
289 | else | ||
290 | std::cerr << "Error: Can't log to unknown session " << std::hex << session_id << std::endl; | ||
291 | break; | ||
292 | default: | ||
293 | break; | ||
294 | } | ||
295 | |||
296 | time_t jetzt = now(); | ||
297 | if (jetzt > last_close_check + 60) { | ||
298 | for (auto &&session: g_sessions) | ||
299 | session.second->check_timeout(jetzt - ACCESS_TIMEOUT); | ||
300 | last_close_check = jetzt; | ||
301 | } | ||
302 | } | ||
303 | } | ||