Ticket #776: ngx_http_aes_module.c

File ngx_http_aes_module.c, 15.1 KB (added by tguyard.wordpress.com, 11 years ago)

The module I'm currently developping

Line 
1#include <nginx.h>
2#include <ngx_config.h>
3#include <ngx_core.h>
4#include <ngx_http.h>
5
6#include <openssl/evp.h>
7#include <openssl/aes.h>
8
9static const u_int32_t BLOCK_SIZE = 16;
10
11static ngx_int_t ngx_http_aes_filter_init(ngx_conf_t *cf);
12static void * ngx_http_aes_create_conf(ngx_conf_t *cf);
13static char * ngx_http_aes_merge_conf(ngx_conf_t *cf, void *parent, void *child);
14
15typedef struct {
16 ngx_uint_t bufs;
17
18 u_int16_t range_read;
19
20 ngx_chain_t* free;
21 ngx_chain_t* busy;
22 ngx_chain_t* out;
23 ngx_chain_t* in;
24
25 unsigned char ivec[16];
26 unsigned int num;
27 unsigned char ecount[16];
28 AES_KEY aes_key;
29} ngx_http_aes_ctx_t;
30
31// get http_range data from the http_range module
32extern ngx_module_t ngx_http_range_body_filter_module;
33typedef struct {
34 off_t start;
35 off_t end;
36 ngx_str_t content_range;
37} ngx_http_range_t;
38typedef struct {
39 off_t offset;
40 ngx_str_t boundary_header;
41 ngx_array_t ranges;
42} ngx_http_range_filter_ctx_t;
43
44typedef struct {
45 ngx_flag_t enable;
46 ngx_bufs_t bufs;
47} ngx_http_aes_conf_t;
48
49static ngx_command_t ngx_http_aes_filter_commands[] = {
50 { ngx_string("aes"),
51 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
52 ngx_conf_set_flag_slot,
53 NGX_HTTP_LOC_CONF_OFFSET,
54 offsetof(ngx_http_aes_conf_t, enable),
55 NULL },
56 { ngx_string("aes_buffers"),
57 NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
58 ngx_conf_set_bufs_slot,
59 NGX_HTTP_LOC_CONF_OFFSET,
60 offsetof(ngx_http_aes_conf_t, bufs),
61 NULL },
62 ngx_null_command
63};
64
65static ngx_http_module_t ngx_http_aes_module_ctx = {
66 NULL, /* preconfiguration */
67 ngx_http_aes_filter_init, /* postconfiguration */
68 NULL, /* create main configuration */
69 NULL, /* init main configuration */
70 NULL, /* create server configuration */
71 NULL, /* merge server configuration */
72 ngx_http_aes_create_conf, /* create location configuration */
73 ngx_http_aes_merge_conf /* merge location configuration */
74};
75
76ngx_module_t ngx_http_aes_module = {
77 NGX_MODULE_V1,
78 &ngx_http_aes_module_ctx, /* module context */
79 ngx_http_aes_filter_commands, /* module directives */
80 NGX_HTTP_MODULE, /* module type */
81 NULL, /* init master */
82 NULL, /* init module */
83 NULL, /* init process */
84 NULL, /* init thread */
85 NULL, /* exit thread */
86 NULL, /* exit process */
87 NULL, /* exit master */
88 NGX_MODULE_V1_PADDING
89};
90
91static ngx_http_output_header_filter_pt ngx_http_next_header_filter;
92static ngx_http_output_body_filter_pt ngx_http_next_body_filter;
93
94static ngx_table_elt_t *
95search_headers(ngx_http_upstream_headers_in_t *headers, u_char *name, size_t len) {
96 ngx_list_part_t *part;
97 ngx_table_elt_t *h;
98 ngx_uint_t i;
99
100 part = &headers->headers.part;
101 h = part->elts;
102
103 for (i = 0; /* void */; i++) {
104 if (i >= part->nelts) {
105 if (part->next == NULL) {
106 break;
107 }
108 part = part->next;
109 h = part->elts;
110 i = 0;
111 }
112 if (len != h[i].key.len || ngx_strcasecmp(name, h[i].key.data) != 0) {
113 continue;
114 }
115 return &h[i];
116 }
117 return NULL;
118}
119
120ngx_http_aes_ctx_t*
121init_ctx (ngx_http_request_t* r)
122{
123 ngx_http_aes_ctx_t* ctx = ngx_pcalloc (r->pool, sizeof(ngx_http_aes_ctx_t));
124 if (ctx == NULL) {
125 return ctx;
126 }
127
128 ctx->bufs = 0;
129 ctx->range_read = 0;
130
131 ctx->free = NULL;
132 ctx->busy = NULL;
133 ctx->out = NULL;
134
135// ctx->ivec = ;
136 ctx->num = 0;
137// ctx->ecount =;
138// ctx->aes_key =;
139
140 return ctx;
141}
142
143static ngx_int_t
144ngx_http_aes_header_filter(ngx_http_request_t *r)
145{
146 ngx_http_aes_conf_t* conf = ngx_http_get_module_loc_conf(r, ngx_http_aes_module);
147 if (!conf->enable) {
148 return ngx_http_next_header_filter(r);
149 }
150
151 ngx_table_elt_t* header_val = search_headers(&r->upstream->headers_in, "x-accel-aes", strlen("x-accel-aes"));
152 if (header_val == NULL) {
153 return ngx_http_next_header_filter(r);
154 }
155
156 ngx_http_aes_ctx_t* ctx = init_ctx(r);
157 if (ctx == NULL) {
158 return NGX_ERROR;
159 }
160 ngx_http_set_ctx(r, ctx, ngx_http_aes_module);
161
162 ngx_str_t raw_key = header_val->value;
163
164 u_char* key = ngx_pcalloc(r->pool, BLOCK_SIZE);
165 PKCS5_PBKDF2_HMAC(raw_key.data, raw_key.len,
166 "8&AqGhV#nz sXVG1N+]e@>c +=c#D,wx", 32, 2,
167 EVP_sha512(),
168 BLOCK_SIZE, key);
169
170 u_char* iv = ngx_pcalloc(r->pool, BLOCK_SIZE);
171 PKCS5_PBKDF2_HMAC(raw_key.data, raw_key.len,
172 "Vr '.J9ipG(]=Z!=YPy\\i5l3G(l8JG^3", 33, 2,
173 EVP_sha512(),
174 BLOCK_SIZE, iv);
175
176 ctx->num = 0;
177 memset(ctx->ecount, 0, BLOCK_SIZE);
178 memcpy(ctx->ivec, iv, BLOCK_SIZE);
179
180 int result = AES_set_encrypt_key(key, 128, &ctx->aes_key);
181 if (result < 0)
182 {
183 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Error trying to set the AES key.");
184 return NGX_ERROR;
185 }
186
187 return ngx_http_next_header_filter(r);
188}
189
190ngx_int_t
191get_chain_elm(ngx_http_request_t* r, ngx_http_aes_ctx_t* ctx, size_t size, ngx_chain_t** new_elm)
192{
193 ngx_chain_t* chain = ngx_chain_get_free_buf(r->pool, &ctx->free);
194 if (chain == NULL)
195 {
196 return NGX_ERROR;
197 }
198
199 *new_elm = chain;
200 if (chain->buf->start == NULL)
201 {
202 ngx_http_aes_conf_t* conf = ngx_http_get_module_loc_conf(r, ngx_http_aes_module);
203 if (ctx->bufs < conf->bufs.num) {
204 chain->buf->start = ngx_palloc(r->pool, size);
205 if (chain->buf->start == NULL)
206 {
207 return NGX_ERROR;
208 }
209 chain->buf->pos = chain->buf->start;
210 chain->buf->last = chain->buf->start;
211 chain->buf->end = chain->buf->start + size;
212 chain->buf->memory = 1;
213 //chain->buf->recycled = 1;
214// chain->buf->flush = 1;
215 chain->buf->tag = (ngx_buf_tag_t) &ngx_http_aes_module;
216 ctx->bufs++;
217 } else {
218 return NGX_DECLINED;
219 }
220 }
221 return NGX_OK;
222}
223
224static void ctr128_inc(unsigned char *counter)
225{
226 uint32_t n = 16;
227 unsigned char c;
228
229 do {
230 --n;
231 c = counter[n];
232 ++c;
233 counter[n] = c;
234 if (c)
235 return;
236 } while (n);
237}
238
239static ngx_int_t
240ngx_http_aes_next_filter_update_chains(ngx_http_request_t *r, ngx_http_aes_ctx_t* ctx, ngx_chain_t *in)
241{
242 ngx_int_t rc, rc2;
243 rc = ngx_http_next_body_filter(r, ctx->out);
244 ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &ctx->out,
245 (ngx_buf_tag_t) &ngx_http_aes_module);
246
247 if (rc == NGX_AGAIN && in != NULL && in->buf != NULL && ngx_buf_size(in->buf) > 0) {
248
249 // save the current in buffer for further use
250 ctx->in = NULL;
251 rc2 = ngx_chain_add_copy(r->pool, &ctx->in, in);
252
253 if (rc2 != NGX_OK) {
254 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Cannot allocate new chain");
255 return NGX_ERROR;
256 }
257 }
258
259 return rc;
260}
261
262static ngx_int_t logg(ngx_http_request_t *r, int line, ngx_int_t a) {
263// ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "return %i, line=%i", a, line);
264 return a;
265}
266
267static ngx_int_t ngx_http_aes_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
268{
269 ngx_int_t rc;
270 ngx_http_aes_ctx_t* ctx = ngx_http_get_module_ctx(r, ngx_http_aes_module);
271 if (ctx == NULL) {
272 return logg(r, __LINE__, ngx_http_next_body_filter(r, in));
273 }
274
275 // If we do not start at the beginning of the file
276 // we need to initialize the AES_crt_128 algorithm
277 if (!ctx->range_read) {
278 ctx->range_read = 1;
279 ngx_http_range_filter_ctx_t* ctx_range = ngx_http_get_module_ctx(r, ngx_http_range_body_filter_module);
280 if (ctx_range != NULL) {
281 off_t start = 0;
282 if (ctx_range->ranges.nelts == 1 && (start = ((ngx_http_range_t*)ctx_range->ranges.elts)->start) > 0) {
283 ctx->num = start % BLOCK_SIZE;
284 uint32_t n = start / BLOCK_SIZE + 1;
285
286 // openssl uses num to find if it's the first iteration
287 if (ctx->num == 0) {
288 n--;
289 }
290 while (n > 0) {
291 AES_encrypt(ctx->ivec, ctx->ecount, &ctx->aes_key);
292 ctr128_inc(ctx->ivec);
293 n--;
294 }
295
296 } else if (ctx_range->ranges.nelts > 1) {
297 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Cannot aes decrypt multi-range request");
298 return NGX_ERROR;
299 }
300 }
301 }
302
303 if (in == NULL) {
304 rc = ngx_http_next_body_filter(r, in);
305
306 if (rc != NGX_OK || ctx->in == NULL) {
307 return logg(r, __LINE__, rc);
308 }
309
310 // 'in' was null, but we have some data not sent yet
311 // ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Uses old IN");
312 in = ctx->in;
313 }
314
315 ngx_chain_t *cl_in = in;
316 ngx_chain_t *curr_out = NULL;
317 ngx_buf_t* input = NULL;
318 ngx_http_aes_conf_t* conf = ngx_http_get_module_loc_conf(r, ngx_http_aes_module);
319 ngx_int_t retry = 0;
320
321 for ( ; ; ) {
322 size_t size = ngx_buf_size(cl_in->buf);
323
324 // Has long has this cl_in is empty, try the next one.
325 if (size == 0) {
326 // Everything is finished
327 if (cl_in->buf->last_buf) {
328
329 // curr_out does not exist yet => we just got an empty 'in'
330 if (curr_out == NULL) {
331 return ngx_http_next_body_filter(r, NULL);
332 }
333
334 curr_out->buf->last_buf = 1;
335 curr_out->buf->last_in_chain = 1;
336 curr_out->buf->flush = 1;
337 return logg(r, __LINE__, ngx_http_aes_next_filter_update_chains(r, ctx, cl_in));
338 }
339
340 // Still some data waiting somewhere
341 cl_in = cl_in->next;
342
343 if (cl_in == NULL) {
344 // No more buffer in this chain
345 if (curr_out != NULL) {
346 curr_out->buf->last_in_chain = 1;
347 curr_out->buf->flush = 1;
348 }
349 return logg(r, __LINE__, ngx_http_aes_next_filter_update_chains(r, ctx, cl_in));
350 }
351
352 // RESTART with the new input buffer
353 continue;
354 }
355
356 // if output buffer is full/non-existing => create new one
357 if (ctx->out == NULL || curr_out == NULL || curr_out->buf->end == curr_out->buf->last) {
358 ngx_chain_t* chain;
359 rc = get_chain_elm(r, ctx, conf->bufs.size, &chain);
360 if (rc == NGX_ERROR) {
361 return NGX_ERROR;
362 }
363
364 // No buffer available ...
365 if (rc == NGX_DECLINED) {
366
367 // ... empty the ones we have ...
368 if (ctx->out != NULL) {
369 ctx->out->buf->flush = 1;
370 }
371 if (curr_out != NULL) {
372 curr_out->buf->flush = 1;
373 }
374 rc = logg(r, __LINE__, ngx_http_aes_next_filter_update_chains(r, ctx, cl_in));
375 if (rc == NGX_AGAIN && retry-- > 0) {
376 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Retry=%i", retry);
377 continue;
378 }
379 if (rc != NGX_OK) {
380 return rc;
381 }
382
383 // ... RETRY with more buffers available
384 continue;
385 }
386
387 // update current output
388 if (ctx->out == NULL) {
389 ctx->out = chain;
390 curr_out = chain;
391 } else {
392 curr_out->next = chain;
393 curr_out = chain;
394 }
395 }
396
397 // If input has been read entirely or is null
398 if (input == NULL || input->pos == input->last) {
399 if (!ngx_buf_in_memory(cl_in->buf))
400 {
401 input = ngx_calloc_buf(r->pool);
402 if (input == NULL) {
403 return NGX_ERROR;
404 }
405 input->start = curr_out->buf->start;
406 input->end = curr_out->buf->end;
407 input->pos = curr_out->buf->last;
408 input->last = curr_out->buf->last;
409 input->memory = 1;
410 input->tag = (ngx_buf_tag_t) &ngx_http_aes_module;
411
412 size_t read_size = ngx_min(size, input->end - input->pos);
413 ssize_t n = ngx_read_file(cl_in->buf->file,
414 input->pos,
415 read_size,
416 cl_in->buf->file_pos);
417 if (n == NGX_ERROR) {
418 return NGX_ERROR;
419 }
420
421 if (n != read_size) {
422 ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
423 ngx_read_file_n " returned "
424 "only %z bytes instead of %z, %z, %z",
425 n, read_size, size, input->end - input->pos);
426 }
427
428 input->last = input->pos + n;
429 input->last_buf = cl_in->buf->last_buf;
430 input->last_in_chain = cl_in->buf->last_in_chain;
431 cl_in->buf->file_pos += n;
432 } else {
433 input = cl_in->buf;
434 }
435 }
436
437 size_t usable_size = ngx_min(size, (curr_out->buf->end - curr_out->buf->last));
438
439 AES_ctr128_encrypt(input->pos,
440 curr_out->buf->last,
441 usable_size,
442 &ctx->aes_key,
443 ctx->ivec,
444 ctx->ecount,
445 &ctx->num);
446
447 input->pos += usable_size;
448 curr_out->buf->last += usable_size;
449 }
450}
451
452static void *
453ngx_http_aes_create_conf(ngx_conf_t *cf)
454{
455 ngx_http_aes_conf_t *conf;
456
457 conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_aes_conf_t));
458 if (conf == NULL) {
459 return NULL;
460 }
461
462 conf->enable = NGX_CONF_UNSET;
463 return conf;
464}
465
466static char *
467ngx_http_aes_merge_conf(ngx_conf_t *cf, void *parent, void *child)
468{
469 ngx_http_aes_conf_t *prev = parent;
470 ngx_http_aes_conf_t *conf = child;
471
472 ngx_conf_merge_value(conf->enable, prev->enable, 0);
473
474 ngx_conf_merge_bufs_value(conf->bufs, prev->bufs,
475 (128 * 1024) / ngx_pagesize, ngx_pagesize);
476
477 return NGX_CONF_OK;
478}
479
480static ngx_int_t
481ngx_http_aes_filter_init(ngx_conf_t *cf)
482{
483 ngx_http_next_header_filter = ngx_http_top_header_filter;
484 ngx_http_top_header_filter = ngx_http_aes_header_filter;
485
486 ngx_http_next_body_filter = ngx_http_top_body_filter;
487 ngx_http_top_body_filter = ngx_http_aes_body_filter;
488
489 return NGX_OK;
490}