├── .gitignore ├── LICENSE ├── README.md ├── config └── src └── ngx_http_ocsp_proxy_module.c /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | 5 | # Libraries 6 | *.lib 7 | *.a 8 | 9 | # Shared objects (inc. Windows DLLs) 10 | *.dll 11 | *.so 12 | *.so.* 13 | *.dylib 14 | 15 | # Executables 16 | *.exe 17 | *.out 18 | *.app 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-2014, Eldar Zaitov 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | * Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Description 2 | =========== 3 | 4 | **nginx_ocsp_proxy-module** - module for OCSP request and response processing designed to allow response caching using 5 | [srcache-nginx-module](https://github.com/agentzh/srcache-nginx-module) and [memc-nginx-module](https://github.com/agentzh/memc-nginx-module). 6 | If you want to use standart caching mechanisms module could help you rewrite POST OCSP requests to GET. 7 | 8 | Status 9 | ====== 10 | 11 | Working alpha 12 | 13 | Note 14 | ==== 15 | 16 | If you're not an CA owner with a lot of legacy clients - forget about this module and use [OCSP stapling](http://en.wikipedia.org/wiki/OCSP_stapling) instead. 17 | 18 | Directives 19 | ========== 20 | 21 | ocsp_proxy 22 | ---------- 23 | **syntax:** *ocsp_proxy (on|off);* 24 | 25 | **default:** *off* 26 | 27 | **context:** *http, server, location, if* 28 | 29 | on - Enable module and GET/POST request processing 30 | 31 | off - Disable module 32 | 33 | ocsp_cache_timeout 34 | ------------------ 35 | **syntax:** *ocsp_cache_timeout 1h;* 36 | 37 | **default:** *24 hrs* 38 | 39 | **context:** *http, server, location, if* 40 | 41 | Maximum cache time for OCSP response. 42 | 43 | 44 | Variables 45 | ========= 46 | 47 | ocsp_request 48 | ------------ 49 | 50 | Base64 encoded OCSP request body. 51 | Can be used for POST to GET rewrite. 52 | 53 | ocsp_serial 54 | ----------- 55 | 56 | Certificate serial 57 | 58 | ocsp_skip_cache 59 | --------------- 60 | 61 | If set on request: 62 | 63 | 1 - no cached response should be sent 64 | 65 | Will be set to 1 in case of: 66 | 67 | * Nonce found in reqest 68 | 69 | If set on response: 70 | 71 | 0 - response can be cached 72 | 73 | 1 - response can not be cached 74 | 75 | Will be set to 1 in case of: 76 | 77 | * Nonce found in response 78 | 79 | * Service Locator extension found in request 80 | 81 | 82 | 83 | ocsp_response_cache_time 84 | ------------------------ 85 | 86 | Cache time for specific response. 87 | 88 | Can't be more than ocsp_cache_timeout. 89 | 90 | Will be set to 0 if ocsp_skip_cache set to 1. 91 | 0 means FOREVER in memcached, so don't forget to configure store_skip correctly! 92 | 93 | 94 | Example configuration 95 | ===================== 96 | 97 | upstream my_memcached { 98 | server 127.0.0.1:11211; 99 | } 100 | 101 | server { 102 | listen 80; 103 | server_name localhost; 104 | client_max_body_size 256k; 105 | 106 | location /memc { 107 | internal; 108 | 109 | set $memc_key $arg_key; 110 | set $memc_exptime $arg_exptime; 111 | 112 | memc_pass my_memcached; 113 | memc_ignore_client_abort on; 114 | } 115 | 116 | location / { 117 | ocsp_proxy on; 118 | set $key $ocsp_serial; 119 | 120 | srcache_methods GET POST; 121 | 122 | srcache_fetch GET /memc key=$key; 123 | srcache_fetch_skip $ocsp_skip_cache; 124 | 125 | srcache_store PUT /memc key=$key&exptime=$ocsp_response_cache_time; 126 | srcache_store_statuses 200; 127 | srcache_store_skip $ocsp_skip_cache; 128 | srcache_store_hide_header Date; 129 | 130 | proxy_pass http://ocsp.someca.com; 131 | proxy_ignore_client_abort on; 132 | } 133 | } 134 | 135 | 136 | Example configuration 2 137 | ======================= 138 | 139 | server { 140 | ... 141 | 142 | location / { 143 | ocsp_proxy on; 144 | 145 | if ($request_method = "POST") { 146 | rewrite ^(.*)$ $1$ocsp_request break; 147 | } 148 | 149 | proxy_method GET; 150 | proxy_pass_request_body off; 151 | 152 | proxy_set_header Content-Length ""; 153 | proxy_set_header Content-Type ""; 154 | 155 | proxy_cache_key "$proxy_host$ocsp_serial$request_uri"; 156 | 157 | ... 158 | 159 | proxy_pass http://ocsp.someca.com; 160 | proxy_ignore_client_abort on; 161 | } 162 | } 163 | 164 | Installation 165 | ============ 166 | 167 | Grab the nginx source code from [nginx.org](http://nginx.org/), for example, the version 1.5.8 (see nginx compatibility). 168 | Grab [srcache-nginx-module](https://github.com/agentzh/srcache-nginx-module) and [memc-nginx-module](https://github.com/agentzh/memc-nginx-module). 169 | Build the source with this module: 170 | 171 | wget 'http://nginx.org/download/nginx-1.5.8.tar.gz' 172 | tar -xzvf nginx-1.5.8.tar.gz 173 | cd nginx-1.5.8/ 174 | ./configure --with-debug --add-module=/path/to/srcache-nginx-module \ 175 | --add-module=/path/to/memc-nginx-module \ 176 | --add-module=/path/to/nginx_ocsp_proxy-module 177 | 178 | make 179 | make install 180 | 181 | Modules order does matter! 182 | 183 | Compatibility 184 | ============= 185 | 186 | Module was tested with nginx 1.5+, but should work with 1.4+. 187 | 188 | 189 | Sources 190 | ======= 191 | 192 | Available on github at [/kyprizel/nginx_ocsp_proxy-module](https://github.com/kyprizel/nginx_ocsp_proxy-module). 193 | Most POST processing code was borrowed from [form-input-nginx-module](https://github.com/calio/form-input-nginx-module). 194 | 195 | [![Analytics](https://ga-beacon.appspot.com/UA-559211-28/nginx_ocsp_proxy-module/README)](https://github.com/igrigorik/ga-beacon) 196 | 197 | TODO 198 | ==== 199 | 200 | * Code review 201 | * Testing 202 | * Load testing 203 | * OCSP Response validation with CA cert 204 | 205 | Bugs 206 | ==== 207 | 208 | Feel free to report bugs and send patches to eldar@kyprizel.net 209 | or using [github's issue tracker](https://github.com/kyprizel/nginx_ocsp_proxy-module/issues). 210 | 211 | Copyright & License 212 | =================== 213 | 214 | Copyright (c) 2013-2014, Eldar Zaitov 215 | All rights reserved. 216 | 217 | Redistribution and use in source and binary forms, with or without modification, 218 | are permitted provided that the following conditions are met: 219 | 220 | * Redistributions of source code must retain the above copyright notice, this 221 | list of conditions and the following disclaimer. 222 | 223 | * Redistributions in binary form must reproduce the above copyright notice, this 224 | list of conditions and the following disclaimer in the documentation and/or 225 | other materials provided with the distribution. 226 | 227 | * Neither the name of the {organization} nor the names of its 228 | contributors may be used to endorse or promote products derived from 229 | this software without specific prior written permission. 230 | 231 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 232 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 233 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 234 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 235 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 236 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 237 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 238 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 239 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 240 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 241 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_http_ocsp_proxy_filter_module 2 | HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES ngx_http_ocsp_proxy_filter_module" 3 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/src/ngx_http_ocsp_proxy_module.c" 4 | NGX_ADDON_DEPS="$NGX_ADDON_DEPS" 5 | #CFLAGS="$CFLAGS" 6 | CORE_LIBS="$CORE_LIBS -lssl" 7 | -------------------------------------------------------------------------------- /src/ngx_http_ocsp_proxy_module.c: -------------------------------------------------------------------------------- 1 | /* 2 | v0.07 3 | 4 | Copyright (C) 2013-2014 Eldar Zaitov (eldar@kyprizel.net). 5 | All rights reserved. 6 | This module is licenced under the terms of BSD license. 7 | 8 | */ 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #define ocsp_request_content_type "application/ocsp-request" 17 | #define ocsp_request_content_type_len (sizeof(ocsp_request_content_type) - 1) 18 | 19 | #define DDEBUG 0 20 | 21 | /* response valid lag buffer time */ 22 | #define TIME_BUF 300 23 | 24 | typedef struct { 25 | ngx_flag_t enable; 26 | time_t max_cache_time; 27 | } ngx_http_ocsp_proxy_conf_t; 28 | 29 | 30 | typedef struct { 31 | OCSP_CERTID *cid; 32 | ngx_str_t serial; 33 | ngx_str_t ocsp_request; 34 | unsigned valid; 35 | time_t delta; 36 | unsigned done:1; 37 | unsigned waiting_more_body:1; 38 | unsigned skip_caching; 39 | unsigned state; 40 | } ngx_http_ocsp_proxy_ctx_t; 41 | 42 | 43 | static ngx_int_t ngx_http_ocsp_proxy_handler(ngx_http_request_t *r); 44 | static void ngx_http_ocsp_proxy_handle_body(ngx_http_request_t *r); 45 | static ngx_int_t ngx_http_ocsp_proxy_handle_response(ngx_http_request_t *r, ngx_chain_t *in); 46 | 47 | static ngx_int_t 48 | copy_ocsp_certid(ngx_http_request_t *r, OCSP_CERTID *dst, OCSP_CERTID *src); 49 | static ngx_int_t 50 | process_ocsp_request(ngx_http_request_t *r, u_char *buf, size_t len); 51 | static ngx_int_t 52 | ngx_http_ocsp_proxy_process_post_body(ngx_http_request_t *r); 53 | 54 | static ngx_int_t ngx_http_ocsp_proxy_init(ngx_conf_t *cf); 55 | static ngx_int_t ngx_http_ocsp_proxy_add_variables(ngx_conf_t *cf); 56 | 57 | static ngx_int_t ngx_http_ocsp_request_get_serial_variable(ngx_http_request_t *r, 58 | ngx_http_variable_value_t *v, uintptr_t data); 59 | 60 | static ngx_int_t ngx_http_ocsp_request_get_skip_caching_variable(ngx_http_request_t *r, 61 | ngx_http_variable_value_t *v, uintptr_t data); 62 | 63 | static ngx_int_t ngx_http_ocsp_request_get_delta_variable(ngx_http_request_t *r, 64 | ngx_http_variable_value_t *v, uintptr_t data); 65 | 66 | static void *ngx_http_ocsp_proxy_create_conf(ngx_conf_t *cf); 67 | static char *ngx_http_ocsp_proxy_merge_conf(ngx_conf_t *cf, void *parent, void *child); 68 | 69 | static ngx_http_output_body_filter_pt ngx_http_next_body_filter; 70 | 71 | static ngx_str_t ngx_http_ocsp_serial = ngx_string("ocsp_serial"); 72 | static ngx_str_t ngx_http_ocsp_skip_caching = ngx_string("ocsp_skip_cache"); 73 | static ngx_str_t ngx_http_ocsp_delta = ngx_string("ocsp_response_cache_time"); 74 | static ngx_str_t ngx_http_ocsp_request = ngx_string("ocsp_request"); 75 | 76 | static ngx_command_t ngx_http_ocsp_proxy_filter_commands[] = { 77 | { ngx_string("ocsp_proxy"), 78 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 79 | ngx_conf_set_flag_slot, 80 | NGX_HTTP_LOC_CONF_OFFSET, 81 | offsetof(ngx_http_ocsp_proxy_conf_t, enable), 82 | NULL }, 83 | 84 | 85 | { ngx_string("ocsp_cache_timeout"), 86 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, 87 | ngx_conf_set_sec_slot, 88 | NGX_HTTP_LOC_CONF_OFFSET, 89 | offsetof(ngx_http_ocsp_proxy_conf_t, max_cache_time), 90 | NULL }, 91 | 92 | ngx_null_command 93 | }; 94 | 95 | 96 | static ngx_http_module_t ngx_http_ocsp_proxy_filter_module_ctx = { 97 | ngx_http_ocsp_proxy_add_variables, /* preconfiguration */ 98 | ngx_http_ocsp_proxy_init, /* postconfiguration */ 99 | 100 | NULL, /* create main configuration */ 101 | NULL, /* init main configuration */ 102 | 103 | NULL, /* create server configuration */ 104 | NULL, /* merge server configuration */ 105 | 106 | ngx_http_ocsp_proxy_create_conf, /* create location configration */ 107 | ngx_http_ocsp_proxy_merge_conf /* merge location configration */ 108 | }; 109 | 110 | 111 | ngx_module_t ngx_http_ocsp_proxy_filter_module = { 112 | NGX_MODULE_V1, 113 | &ngx_http_ocsp_proxy_filter_module_ctx, /* module context */ 114 | ngx_http_ocsp_proxy_filter_commands, /* module directives */ 115 | NGX_HTTP_MODULE, /* module type */ 116 | NULL, /* init master */ 117 | NULL, /* init module */ 118 | NULL, /* init process */ 119 | NULL, /* init thread */ 120 | NULL, /* exit thread */ 121 | NULL, /* exit process */ 122 | NULL, /* exit master */ 123 | NGX_MODULE_V1_PADDING 124 | }; 125 | 126 | 127 | static ngx_int_t 128 | ngx_http_ocsp_proxy_handler(ngx_http_request_t *r) 129 | { 130 | ngx_http_ocsp_proxy_conf_t *conf; 131 | ngx_http_ocsp_proxy_ctx_t *ctx; 132 | u_char *p, *last, *start, *dst, *src; 133 | ngx_str_t value; 134 | ngx_int_t rc = NGX_HTTP_BAD_REQUEST; 135 | ngx_str_t b64req; 136 | ngx_str_t rreq; 137 | size_t b64len, len; 138 | 139 | conf = ngx_http_get_module_loc_conf(r->main, ngx_http_ocsp_proxy_filter_module); 140 | if (!conf->enable || r->internal) { 141 | return NGX_DECLINED; 142 | } 143 | 144 | if (r->method != NGX_HTTP_POST && r->method != NGX_HTTP_GET) { 145 | return rc; 146 | } 147 | 148 | ctx = ngx_http_get_module_ctx(r, ngx_http_ocsp_proxy_filter_module); 149 | if (ctx != NULL) { 150 | if (ctx->done) { 151 | return NGX_DECLINED; 152 | } 153 | /* wtf? */ 154 | return NGX_DECLINED; 155 | } 156 | 157 | /* 158 | ctx->state = 0 159 | */ 160 | ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_ocsp_proxy_ctx_t)); 161 | if (ctx == NULL) { 162 | return NGX_ERROR; 163 | } 164 | 165 | ngx_http_set_ctx(r, ctx, ngx_http_ocsp_proxy_filter_module); 166 | 167 | if (r->method == NGX_HTTP_GET) { 168 | /* Some browsers using MS CryptoAPI (IE, Chromium, Opera) use GET */ 169 | p = start = &r->unparsed_uri.data[0]; 170 | last = r->unparsed_uri.data + r->unparsed_uri.len; 171 | 172 | while (p < last) { 173 | if (*p++ == '/') { 174 | start = p; 175 | } 176 | } 177 | 178 | b64len = last - start; 179 | if (b64len <= 0) { 180 | return rc; 181 | } 182 | 183 | src = start; 184 | 185 | b64req.data = (u_char *) ngx_pcalloc(r->pool, b64len); 186 | if (b64req.data == NULL) { 187 | return NGX_ERROR; 188 | } 189 | 190 | dst = b64req.data; 191 | ngx_unescape_uri(&dst, &src, b64len, NGX_UNESCAPE_URI); 192 | b64req.len = b64len; 193 | 194 | len = ngx_base64_decoded_length(b64len); 195 | if (len <= 0) { 196 | return rc; 197 | } 198 | 199 | rreq.data = (u_char *) ngx_pcalloc(r->pool, len); 200 | if (rreq.data == NULL) { 201 | return NGX_ERROR; 202 | } 203 | 204 | if (ngx_decode_base64(&rreq, &b64req) != NGX_OK) { 205 | return rc; 206 | } 207 | 208 | ctx->ocsp_request.data = (u_char *) ngx_pcalloc(r->pool, rreq.len); 209 | ngx_memcpy(ctx->ocsp_request.data, rreq.data, rreq.len); 210 | ctx->ocsp_request.len = rreq.len; 211 | 212 | ctx->state = 1; 213 | 214 | /* parse OCSP request here */ 215 | if (process_ocsp_request(r, rreq.data, rreq.len) != NGX_OK) { 216 | return rc; 217 | } 218 | ctx->done = 1; 219 | return NGX_DECLINED; 220 | } 221 | 222 | 223 | if (r->headers_in.content_type == NULL 224 | || r->headers_in.content_type->value.data == NULL) 225 | { 226 | return rc; 227 | } 228 | 229 | value = r->headers_in.content_type->value; 230 | 231 | if (value.len < ocsp_request_content_type_len 232 | || ngx_strncasecmp(value.data, (u_char *) ocsp_request_content_type, 233 | ocsp_request_content_type_len) != 0) 234 | { 235 | return rc; 236 | } 237 | 238 | 239 | rc = ngx_http_read_client_request_body(r, ngx_http_ocsp_proxy_handle_body); 240 | 241 | if (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE) { 242 | return rc; 243 | } 244 | 245 | if (rc == NGX_AGAIN) { 246 | ctx->waiting_more_body = 1; 247 | return NGX_DONE; 248 | } 249 | 250 | return NGX_DECLINED; 251 | } 252 | 253 | 254 | static void 255 | ngx_http_ocsp_proxy_handle_body(ngx_http_request_t *r) 256 | { 257 | ngx_http_ocsp_proxy_ctx_t *ctx; 258 | 259 | ctx = ngx_http_get_module_ctx(r, ngx_http_ocsp_proxy_filter_module); 260 | 261 | r->read_event_handler = ngx_http_request_empty_handler; 262 | 263 | ctx->done = 1; 264 | 265 | #if defined(nginx_version) && nginx_version >= 8011 266 | r->main->count--; 267 | #endif 268 | 269 | if (ctx->waiting_more_body) { 270 | ctx->waiting_more_body = 0; 271 | 272 | ngx_http_core_run_phases(r); 273 | } 274 | } 275 | 276 | static ngx_int_t 277 | ngx_http_ocsp_request_get_skip_caching_variable(ngx_http_request_t *r, 278 | ngx_http_variable_value_t *v, uintptr_t data) 279 | { 280 | ngx_http_ocsp_proxy_ctx_t *ctx; 281 | ngx_http_ocsp_proxy_conf_t *conf; 282 | 283 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ngx_http_ocsp_request_get_skip_caching_variable"); 284 | 285 | conf = ngx_http_get_module_loc_conf(r->main, ngx_http_ocsp_proxy_filter_module); 286 | if (!conf->enable) { 287 | v->not_found = 1; 288 | return NGX_OK; 289 | } 290 | 291 | ctx = ngx_http_get_module_ctx(r, ngx_http_ocsp_proxy_filter_module); 292 | if (ctx == NULL) { 293 | v->not_found = 1; 294 | return NGX_OK; 295 | } 296 | 297 | #if DDEBUG 298 | 299 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ctx->skip_caching: %d, ctx->valid: %d", ctx->skip_caching, ctx->valid); 300 | 301 | #endif 302 | 303 | switch (ctx->state) { 304 | case 1: 305 | v->data = (u_char *) ngx_pcalloc(r->pool, 1); 306 | if (v->data == NULL) { 307 | return NGX_ERROR; 308 | } 309 | 310 | if (ctx->skip_caching == 1) { 311 | ngx_memcpy(v->data, "1", 1); 312 | } else { 313 | ngx_memcpy(v->data, "0", 1); 314 | } 315 | v->len = 1; 316 | break; 317 | case 2: 318 | v->data = (u_char *) ngx_pcalloc(r->pool, 1); 319 | if (v->data == NULL) { 320 | return NGX_ERROR; 321 | } 322 | 323 | if (ctx->valid == 1 && ctx->skip_caching == 0) { 324 | ngx_memcpy(v->data, "0", 1); 325 | } else { 326 | ngx_memcpy(v->data, "1", 1); 327 | } 328 | v->len = 1; 329 | break; 330 | default: 331 | /* 332 | wtf? unknown state? 333 | invalid OCSP request 334 | */ 335 | v->not_found = 1; 336 | return NGX_OK; 337 | } 338 | 339 | v->valid = 1; 340 | v->no_cacheable = 1; 341 | v->not_found = 0; 342 | 343 | return NGX_OK; 344 | } 345 | 346 | 347 | static ngx_int_t 348 | ngx_http_ocsp_request_get_delta_variable(ngx_http_request_t *r, 349 | ngx_http_variable_value_t *v, uintptr_t data) 350 | { 351 | ngx_http_ocsp_proxy_ctx_t *ctx; 352 | ngx_http_ocsp_proxy_conf_t *conf; 353 | 354 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ngx_http_ocsp_request_get_delta_variable"); 355 | 356 | conf = ngx_http_get_module_loc_conf(r->main, ngx_http_ocsp_proxy_filter_module); 357 | if (!conf->enable) { 358 | v->not_found = 1; 359 | return NGX_OK; 360 | } 361 | 362 | ctx = ngx_http_get_module_ctx(r, ngx_http_ocsp_proxy_filter_module); 363 | if (ctx == NULL) { 364 | v->not_found = 1; 365 | return NGX_OK; 366 | } 367 | 368 | v->data = ngx_pnalloc(r->pool, NGX_TIME_T_LEN); 369 | if (v->data == NULL) { 370 | return NGX_ERROR; 371 | } 372 | 373 | if (ctx->skip_caching == 1 || ctx->delta == 0) { 374 | ngx_memcpy(v->data, "0", 1); 375 | v->len = 1; 376 | goto complete; 377 | } 378 | 379 | if (ctx->delta > conf->max_cache_time) { 380 | v->len = ngx_sprintf(v->data, "%T", conf->max_cache_time) - v->data; 381 | } else { 382 | v->len = ngx_sprintf(v->data, "%T", ctx->delta) - v->data; 383 | } 384 | 385 | complete: 386 | 387 | v->valid = 1; 388 | v->no_cacheable = 1; 389 | v->not_found = 0; 390 | 391 | return NGX_OK; 392 | } 393 | 394 | static ngx_int_t 395 | copy_ocsp_certid(ngx_http_request_t *r, OCSP_CERTID *dst, OCSP_CERTID *src) 396 | { 397 | u_char *data1; 398 | char *data2; 399 | 400 | /* required */ 401 | if (!src->hashAlgorithm || !src->hashAlgorithm->algorithm 402 | || src->hashAlgorithm->algorithm->length <= 0) 403 | { 404 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "OCSP no hash algo specified"); 405 | return NGX_ERROR; 406 | } 407 | 408 | /* required */ 409 | if (!src->issuerNameHash || src->issuerNameHash->length <= 0) { 410 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "OCSP no issuer name hash specified"); 411 | return NGX_ERROR; 412 | } 413 | 414 | if (!src->serialNumber || src->serialNumber->length <= 0) { 415 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "OCSP no serial specified"); 416 | return NGX_ERROR; 417 | } 418 | 419 | dst->hashAlgorithm = (X509_ALGOR *) ngx_pcalloc(r->pool, sizeof(X509_ALGOR)); 420 | if (dst->hashAlgorithm == NULL) { 421 | return NGX_ERROR; 422 | } 423 | ngx_memcpy(dst->hashAlgorithm, dst->hashAlgorithm, sizeof(ASN1_INTEGER)); 424 | 425 | dst->hashAlgorithm->algorithm = (ASN1_OBJECT *) ngx_pcalloc(r->pool, sizeof(ASN1_OBJECT)); 426 | if (dst->hashAlgorithm->algorithm == NULL) { 427 | return NGX_ERROR; 428 | } 429 | ngx_memcpy(dst->hashAlgorithm->algorithm, src->hashAlgorithm->algorithm, sizeof(ASN1_OBJECT)); 430 | 431 | data1 = (u_char *) ngx_pcalloc(r->pool, src->hashAlgorithm->algorithm->length); 432 | if (data1 == NULL) { 433 | return NGX_ERROR; 434 | } 435 | ngx_memcpy(data1, src->hashAlgorithm->algorithm->data, src->hashAlgorithm->algorithm->length); 436 | dst->hashAlgorithm->algorithm->data = (const u_char *)data1; 437 | 438 | if (src->hashAlgorithm->algorithm->sn && ngx_strlen(src->hashAlgorithm->algorithm->sn) > 0) { 439 | data2 = (char *) ngx_pcalloc(r->pool, ngx_strlen(src->hashAlgorithm->algorithm->sn) + 1); 440 | if (data2 == NULL) { 441 | return NGX_ERROR; 442 | } 443 | 444 | ngx_memcpy(data2, src->hashAlgorithm->algorithm->sn, ngx_strlen(src->hashAlgorithm->algorithm->sn)); 445 | dst->hashAlgorithm->algorithm->sn = (const char *)data2; 446 | } 447 | 448 | if (src->hashAlgorithm->algorithm->ln && ngx_strlen(src->hashAlgorithm->algorithm->ln) > 0) { 449 | data2 = (char *) ngx_pcalloc(r->pool, ngx_strlen(src->hashAlgorithm->algorithm->ln) + 1); 450 | if (data2 == NULL) { 451 | return NGX_ERROR; 452 | } 453 | 454 | ngx_memcpy(data2, src->hashAlgorithm->algorithm->ln, ngx_strlen(src->hashAlgorithm->algorithm->ln)); 455 | dst->hashAlgorithm->algorithm->ln = (const char *)data2; 456 | } 457 | 458 | dst->issuerNameHash = (ASN1_OCTET_STRING *) ngx_pcalloc(r->pool, sizeof(ASN1_OCTET_STRING)); 459 | if (dst->issuerNameHash == NULL) { 460 | return NGX_ERROR; 461 | } 462 | ngx_memcpy(dst->issuerNameHash, src->issuerNameHash, sizeof(ASN1_OCTET_STRING)); 463 | 464 | if (src->issuerNameHash->length > 0) { 465 | dst->issuerNameHash->data = (u_char *) ngx_pcalloc(r->pool, src->issuerNameHash->length); 466 | if (dst->issuerNameHash->data == NULL) { 467 | return NGX_ERROR; 468 | } 469 | ngx_memcpy(dst->issuerNameHash->data, src->issuerNameHash->data, src->issuerNameHash->length); 470 | } 471 | 472 | dst->issuerKeyHash = (ASN1_OCTET_STRING *) ngx_pcalloc(r->pool, sizeof(ASN1_OCTET_STRING)); 473 | if (dst->issuerKeyHash == NULL) { 474 | return NGX_ERROR; 475 | } 476 | 477 | ngx_memcpy(dst->issuerKeyHash, src->issuerKeyHash, sizeof(ASN1_OCTET_STRING)); 478 | 479 | if (src->issuerKeyHash->length > 0) { 480 | dst->issuerKeyHash->data = (u_char *) ngx_pcalloc(r->pool, src->issuerKeyHash->length); 481 | if (dst->issuerKeyHash->data == NULL) { 482 | return NGX_ERROR; 483 | } 484 | 485 | ngx_memcpy(dst->issuerKeyHash->data, src->issuerKeyHash->data, src->issuerKeyHash->length); 486 | } 487 | 488 | dst->serialNumber = (ASN1_INTEGER *) ngx_pcalloc(r->pool, sizeof(ASN1_INTEGER)); 489 | if (dst->serialNumber == NULL) { 490 | return NGX_ERROR; 491 | } 492 | ngx_memcpy(dst->serialNumber, src->serialNumber, sizeof(ASN1_INTEGER)); 493 | 494 | dst->serialNumber->data = (u_char *) ngx_pcalloc(r->pool, src->serialNumber->length); 495 | if (dst->serialNumber->data == NULL) { 496 | return NGX_ERROR; 497 | } 498 | ngx_memcpy(dst->serialNumber->data, src->serialNumber->data, src->serialNumber->length); 499 | 500 | return NGX_OK; 501 | } 502 | 503 | static ngx_int_t 504 | process_ocsp_request(ngx_http_request_t *r, u_char *buf, size_t len) 505 | { 506 | #if OPENSSL_VERSION_NUMBER >= 0x0090707fL 507 | const 508 | #endif 509 | u_char *d; 510 | ngx_http_ocsp_proxy_ctx_t *ctx; 511 | OCSP_REQUEST *ocsp = NULL; 512 | OCSP_REQINFO *inf = NULL; 513 | OCSP_ONEREQ *one = NULL; 514 | OCSP_CERTID *cid = NULL; 515 | BIGNUM *bnser = NULL; 516 | char *serial = NULL; 517 | size_t slen; 518 | int n; 519 | 520 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "process_ocsp_request"); 521 | 522 | ctx = ngx_http_get_module_ctx(r, ngx_http_ocsp_proxy_filter_module); 523 | if (ctx == NULL) { 524 | return NGX_ERROR; 525 | } 526 | 527 | if (ctx->serial.len > 0) { 528 | /* request was already processed */ 529 | return NGX_OK; 530 | } 531 | 532 | d = buf; 533 | 534 | ocsp = d2i_OCSP_REQUEST(NULL, &d, len); 535 | if (ocsp == NULL) { 536 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 537 | "d2i_OCSP_REQUEST() failed"); 538 | return NGX_ERROR; 539 | } 540 | 541 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "process_ocsp_request 1"); 542 | 543 | if (ocsp->tbsRequest == NULL) { 544 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 545 | "OCSP request format error"); 546 | goto error; 547 | } 548 | 549 | /* Check if there is service locator ext in the request */ 550 | n = OCSP_REQUEST_get_ext_by_NID(ocsp, NID_id_pkix_OCSP_serviceLocator, -1); 551 | if (n >= 0) { 552 | /* If there is service locator extension - we should not cache response on this request */ 553 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 554 | "got OCSP request with service locator extension"); 555 | ctx->skip_caching = 1; 556 | } 557 | 558 | inf = ocsp->tbsRequest; 559 | 560 | /* we process only one request */ 561 | if (sk_OCSP_ONEREQ_num(inf->requestList) != 1) { 562 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 563 | "OCSP request format error, != 1"); 564 | goto error; 565 | } 566 | 567 | one = sk_OCSP_ONEREQ_value(inf->requestList, 0); 568 | if (one == NULL) { 569 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 570 | "OCSP request format error, no valid requests found"); 571 | goto error; 572 | } 573 | 574 | cid = one->reqCert; 575 | if (cid == NULL) { 576 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 577 | "OCSP request format error, no valid certificate id"); 578 | goto error; 579 | } 580 | 581 | ctx->cid = (OCSP_CERTID *) ngx_pcalloc(r->pool, sizeof(OCSP_CERTID)); 582 | if (ctx->cid == NULL) { 583 | goto error; 584 | } 585 | 586 | if (copy_ocsp_certid(r, ctx->cid, cid) != NGX_OK) { 587 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 588 | "Error while copying OCSP_CERTID"); 589 | goto error; 590 | } 591 | 592 | bnser = ASN1_INTEGER_to_BN(cid->serialNumber, NULL); 593 | if (bnser == NULL) { 594 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 595 | "OCSP request format error, serial bignum err"); 596 | goto error; 597 | } 598 | 599 | slen = BN_num_bytes(bnser) * 2; 600 | if (slen <= 0) { 601 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 602 | "OCSP request format error, serial len <= 0"); 603 | goto error; 604 | } 605 | 606 | serial = BN_bn2hex(bnser); 607 | if (serial == NULL) { 608 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 609 | "OCSP request error, BN2hex alloc failed"); 610 | goto error; 611 | } 612 | 613 | ctx->serial.data = (u_char *) ngx_pcalloc(r->pool, slen); 614 | if (ctx->serial.data == NULL) { 615 | goto error; 616 | } 617 | ngx_memcpy(ctx->serial.data, serial, slen); 618 | ctx->serial.len = slen; 619 | 620 | BN_free(bnser); 621 | OPENSSL_free(serial); 622 | OCSP_REQUEST_free(ocsp); 623 | 624 | return NGX_OK; 625 | 626 | error: 627 | 628 | if (ctx->ocsp_request.len > 0) { 629 | /* will nginx free the buf? */ 630 | ctx->ocsp_request.len = 0; 631 | } 632 | 633 | if (bnser) { 634 | BN_free(bnser); 635 | } 636 | if (serial) { 637 | OPENSSL_free(serial); 638 | } 639 | if (ocsp) { 640 | OCSP_REQUEST_free(ocsp); 641 | } 642 | 643 | return NGX_ERROR; 644 | } 645 | 646 | static ngx_int_t 647 | ngx_http_ocsp_proxy_process_post_body(ngx_http_request_t *r) { 648 | ngx_http_ocsp_proxy_ctx_t *ctx; 649 | ngx_buf_t *b; 650 | u_char *p, *buf, *last; 651 | size_t len; 652 | ngx_chain_t *cl; 653 | 654 | ctx = ngx_http_get_module_ctx(r, ngx_http_ocsp_proxy_filter_module); 655 | if (ctx == NULL) { 656 | return NGX_ERROR; 657 | } 658 | 659 | if (r->method != NGX_HTTP_POST || ctx->state != 0) { 660 | return NGX_ERROR; 661 | } 662 | 663 | if (r->request_body == NULL || r->request_body->bufs == NULL) { 664 | return NGX_ERROR; 665 | } 666 | 667 | ctx->state = 1; 668 | 669 | if (r->request_body->bufs->next != NULL) { 670 | /* more than one buffer...we should copy the data out... */ 671 | len = 0; 672 | for (cl = r->request_body->bufs; cl; cl = cl->next) { 673 | b = cl->buf; 674 | 675 | if (b->in_file) { 676 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 677 | "OCSP proxy: in-file buffer found. aborted. " 678 | "Too big OCSP request found."); 679 | return NGX_ERROR; 680 | } 681 | 682 | len += b->last - b->pos; 683 | } 684 | 685 | if (len == 0) { 686 | return NGX_ERROR; 687 | } 688 | 689 | buf = ngx_palloc(r->pool, len); 690 | if (buf == NULL) { 691 | return NGX_ERROR; 692 | } 693 | 694 | p = buf; 695 | last = p + len; 696 | 697 | for (cl = r->request_body->bufs; cl; cl = cl->next) { 698 | p = ngx_copy(p, cl->buf->pos, cl->buf->last - cl->buf->pos); 699 | } 700 | } else { 701 | b = r->request_body->bufs->buf; 702 | if (ngx_buf_size(b) == 0) { 703 | return NGX_ERROR; 704 | } 705 | 706 | buf = b->pos; 707 | last = b->last; 708 | } 709 | 710 | len = last-buf; 711 | 712 | ctx->ocsp_request.data = (u_char *) ngx_pcalloc(r->pool, len); 713 | if (ctx->ocsp_request.data == NULL) { 714 | return NGX_ERROR; 715 | } 716 | 717 | ngx_memcpy(ctx->ocsp_request.data, buf, len); 718 | ctx->ocsp_request.len = len; 719 | 720 | return NGX_OK; 721 | } 722 | 723 | static ngx_int_t 724 | ngx_http_ocsp_request_get_b64encoded_variable(ngx_http_request_t *r, 725 | ngx_http_variable_value_t *v, uintptr_t data) 726 | { 727 | ngx_http_ocsp_proxy_ctx_t *ctx; 728 | ngx_http_ocsp_proxy_conf_t *conf; 729 | ngx_str_t rreq; 730 | size_t b64len; 731 | #if 0 732 | uintptr_t escape; 733 | u_char *p; 734 | #endif 735 | 736 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ngx_http_ocsp_request_get_b64encoded_variable"); 737 | 738 | conf = ngx_http_get_module_loc_conf(r->main, ngx_http_ocsp_proxy_filter_module); 739 | if (!conf->enable) { 740 | v->not_found = 1; 741 | return NGX_OK; 742 | } 743 | 744 | ctx = ngx_http_get_module_ctx(r, ngx_http_ocsp_proxy_filter_module); 745 | if (ctx == NULL) { 746 | v->not_found = 1; 747 | return NGX_OK; 748 | } 749 | 750 | if (ctx->ocsp_request.len > 0) { 751 | goto complete; 752 | } 753 | 754 | /* process POST body only once */ 755 | if (ctx->state == 0) { 756 | if (ngx_http_ocsp_proxy_process_post_body(r) != NGX_OK) { 757 | v->not_found = 1; 758 | return NGX_OK; 759 | } 760 | } 761 | 762 | if (!ctx->cid || ctx->serial.len == 0) { 763 | if (process_ocsp_request(r, ctx->ocsp_request.data, ctx->ocsp_request.len) != NGX_OK) { 764 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 765 | "OCSP proxy: request processing error"); 766 | v->not_found = 1; 767 | return NGX_OK; 768 | } 769 | } 770 | 771 | complete: 772 | 773 | b64len = ngx_base64_encoded_length(ctx->ocsp_request.len); 774 | if (b64len <= 0 || b64len < ctx->ocsp_request.len) { 775 | return NGX_ERROR; 776 | } 777 | 778 | rreq.data = (u_char *) ngx_pcalloc(r->pool, b64len); 779 | if (rreq.data == NULL) { 780 | return NGX_ERROR; 781 | } 782 | 783 | ngx_encode_base64(&rreq, &ctx->ocsp_request); 784 | #if 0 785 | escape = 2 * ngx_escape_uri(NULL, &rreq, rreq.len, NGX_ESCAPE_URI_COMPONENT); 786 | 787 | b64len = rreq.len + escape; 788 | #endif 789 | v->data = (u_char *) ngx_pcalloc(r->pool, b64len); 790 | if (v->data == NULL) { 791 | return NGX_ERROR; 792 | } 793 | 794 | v->valid = 1; 795 | v->no_cacheable = 0; 796 | v->not_found = 0; 797 | #if 0 798 | if (escape == 0) { 799 | p = (u_char *) ngx_cpymem(v->data, rreq.data, rreq.len); 800 | } else { 801 | p = (u_char *) ngx_escape_uri(v->data, rreq.data, rreq.len, NGX_ESCAPE_URI_COMPONENT); 802 | } 803 | v->len = p - v->data; 804 | #else 805 | ngx_memcpy(v->data, rreq.data, rreq.len); 806 | v->len = rreq.len; 807 | #endif 808 | 809 | return NGX_OK; 810 | } 811 | 812 | 813 | 814 | static ngx_int_t 815 | ngx_http_ocsp_request_get_serial_variable(ngx_http_request_t *r, 816 | ngx_http_variable_value_t *v, uintptr_t data) 817 | { 818 | ngx_http_ocsp_proxy_ctx_t *ctx; 819 | ngx_http_ocsp_proxy_conf_t *conf; 820 | 821 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ngx_http_ocsp_request_get_serial_variable"); 822 | 823 | conf = ngx_http_get_module_loc_conf(r->main, ngx_http_ocsp_proxy_filter_module); 824 | if (!conf->enable) { 825 | v->not_found = 1; 826 | return NGX_OK; 827 | } 828 | 829 | ctx = ngx_http_get_module_ctx(r, ngx_http_ocsp_proxy_filter_module); 830 | if (ctx == NULL) { 831 | v->not_found = 1; 832 | return NGX_OK; 833 | } 834 | 835 | /* was serial already set in this ctx? */ 836 | if (ctx->serial.len > 0) { 837 | goto complete; 838 | } 839 | 840 | /* process POST body only once */ 841 | if (ctx->state == 0) { 842 | if (ngx_http_ocsp_proxy_process_post_body(r) != NGX_OK) { 843 | v->not_found = 1; 844 | return NGX_OK; 845 | } 846 | } 847 | 848 | if (process_ocsp_request(r, ctx->ocsp_request.data, ctx->ocsp_request.len) != NGX_OK) { 849 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 850 | "OCSP proxy: request processing error"); 851 | v->not_found = 1; 852 | return NGX_OK; 853 | } 854 | 855 | complete: 856 | 857 | v->data = (u_char *) ngx_pcalloc(r->pool, ctx->serial.len); 858 | if (v->data == NULL) { 859 | return NGX_ERROR; 860 | } 861 | 862 | v->valid = 1; 863 | v->no_cacheable = 0; 864 | v->not_found = 0; 865 | 866 | ngx_memcpy(v->data, ctx->serial.data, ctx->serial.len); 867 | v->len = ctx->serial.len; 868 | 869 | return NGX_OK; 870 | } 871 | 872 | /* XXX: make it nice! */ 873 | static time_t 874 | ASN1_GetTimeT(ASN1_TIME* time) 875 | { 876 | struct tm t; 877 | const char* str = (const char*) time->data; 878 | 879 | memset(&t, 0, sizeof(t)); 880 | 881 | if (time->type == V_ASN1_GENERALIZEDTIME && time->length < 14) { 882 | goto complete; 883 | } 884 | 885 | if (time->type == V_ASN1_UTCTIME && time->length < 12) { 886 | goto complete; 887 | } 888 | 889 | if (time->type == V_ASN1_UTCTIME) { /* two digit year */ 890 | t.tm_year = (str[0] - '0') * 10 + (str[1] - '0'); 891 | if (t.tm_year < 70) 892 | t.tm_year += 100; 893 | } else if (time->type == V_ASN1_GENERALIZEDTIME) { /* four digit year */ 894 | t.tm_year = (str[0] - '0') * 1000 + (str[1] - '0') * 100 + (str[2] - '0') * 10 + (str[3] - '0'); 895 | t.tm_year -= 1900; 896 | } 897 | t.tm_mon = ((str[4] - '0') * 10 + (str[5] - '0')) - 1; // -1 since January is 0 not 1. 898 | t.tm_mday = (str[6] - '0') * 10 + (str[7] - '0'); 899 | t.tm_hour = (str[8] - '0') * 10 + (str[9] - '0'); 900 | t.tm_min = (str[10] - '0') * 10 + (str[11] - '0'); 901 | t.tm_sec = (str[12] - '0') * 10 + (str[13] - '0'); 902 | 903 | complete: 904 | return mktime(&t); 905 | } 906 | 907 | static ngx_int_t 908 | ngx_http_ocsp_proxy_handle_response(ngx_http_request_t *r, ngx_chain_t *in) 909 | { 910 | 911 | #if OPENSSL_VERSION_NUMBER >= 0x0090707fL 912 | const 913 | #endif 914 | u_char *d; 915 | ngx_http_ocsp_proxy_conf_t *conf; 916 | ngx_http_ocsp_proxy_ctx_t *ctx; 917 | u_char *p, *buf, *last; 918 | size_t len; 919 | ngx_chain_t *cl; 920 | ngx_buf_t *b; 921 | int n, delta; 922 | OCSP_RESPONSE *ocsp = NULL; 923 | OCSP_BASICRESP *basic = NULL; 924 | ASN1_GENERALIZEDTIME *thisupdate = NULL; 925 | ASN1_GENERALIZEDTIME *nextupdate = NULL; 926 | time_t now, t_tmp, timedelta; 927 | 928 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ngx_http_ocsp_proxy_handle_response"); 929 | 930 | conf = ngx_http_get_module_loc_conf(r->main, ngx_http_ocsp_proxy_filter_module); 931 | if (!conf->enable) { 932 | return ngx_http_next_body_filter(r, in); 933 | } 934 | 935 | if (in == NULL || r->header_only) { 936 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ngx_http_ocsp_proxy_handle_response: no body or header only"); 937 | return ngx_http_next_body_filter(r, in); 938 | } 939 | 940 | ctx = ngx_http_get_module_ctx(r, ngx_http_ocsp_proxy_filter_module); 941 | if (ctx == NULL) { 942 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ngx_http_ocsp_proxy_handle_response ctx not set"); 943 | return ngx_http_next_body_filter(r, in); 944 | } 945 | 946 | if (ctx->cid == NULL) { 947 | /* wtf? */ 948 | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ngx_http_ocsp_proxy_handle_response invalid request"); 949 | return ngx_http_next_body_filter(r, in); 950 | } 951 | 952 | if (in->next != NULL) { 953 | len = 0; 954 | for (cl = in; cl; cl = cl->next) { 955 | b = cl->buf; 956 | len += b->last - b->pos; 957 | } 958 | 959 | if (len == 0) { 960 | return NGX_ERROR; 961 | } 962 | 963 | buf = ngx_palloc(r->pool, len); 964 | if (buf == NULL) { 965 | return NGX_ERROR; 966 | } 967 | 968 | p = buf; 969 | last = p + len; 970 | 971 | for (cl = in; cl; cl = cl->next) { 972 | p = ngx_copy(p, cl->buf->pos, cl->buf->last - cl->buf->pos); 973 | } 974 | } else { 975 | b = in->buf; 976 | if (ngx_buf_size(b) == 0) { 977 | return ngx_http_next_body_filter(r, in); 978 | } 979 | 980 | buf = b->pos; 981 | last = b->last; 982 | } 983 | 984 | len = last-buf; 985 | d = buf; 986 | 987 | ctx->state = 2; 988 | 989 | ocsp = d2i_OCSP_RESPONSE(NULL, &d, len); 990 | if (ocsp == NULL) { 991 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 992 | "d2i_OCSP_RESPONSE() failed"); 993 | goto error; 994 | } 995 | 996 | n = OCSP_response_status(ocsp); 997 | if (n != OCSP_RESPONSE_STATUS_SUCCESSFUL) { 998 | ctx->valid = 0; 999 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 1000 | "OCSP response not successful (%d: %s)", 1001 | n, OCSP_response_status_str(n)); 1002 | goto error; 1003 | } 1004 | 1005 | 1006 | basic = OCSP_response_get1_basic(ocsp); 1007 | if (basic == NULL) { 1008 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 1009 | "OCSP_response_get1_basic() failed"); 1010 | goto error; 1011 | } 1012 | 1013 | /* Check for nonce in response */ 1014 | n = OCSP_BASICRESP_get_ext_by_NID(basic, NID_id_pkix_OCSP_Nonce, -1); 1015 | if (n >= 0) { 1016 | /* If there is nonce - we should not cache the response */ 1017 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 1018 | "got OCSP response with nonce"); 1019 | 1020 | ctx->skip_caching = 1; 1021 | } 1022 | 1023 | if (OCSP_resp_find_status(basic, ctx->cid, &n, NULL, NULL, 1024 | &thisupdate, &nextupdate) 1025 | != 1) 1026 | { 1027 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 1028 | "certificate status not found in the OCSP response"); 1029 | goto error; 1030 | } 1031 | 1032 | 1033 | if (n != V_OCSP_CERTSTATUS_GOOD) { 1034 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 1035 | "certificate status \"%s\" in the OCSP response", 1036 | OCSP_cert_status_str(n)); 1037 | goto error; 1038 | } 1039 | 1040 | 1041 | if (OCSP_check_validity(thisupdate, nextupdate, TIME_BUF, -1) != 1) { 1042 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 1043 | "OCSP_check_validity() failed"); 1044 | goto error; 1045 | } 1046 | 1047 | if (nextupdate) { 1048 | now = ngx_time(); 1049 | t_tmp = ASN1_GetTimeT(nextupdate); 1050 | delta = difftime(now, t_tmp); 1051 | 1052 | /* store response until nextupdate - TIME_BUF */ 1053 | if (delta > 0 || (delta + TIME_BUF) >= 0) { 1054 | /* wtf? */ 1055 | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, 1056 | "OCSP response exp datetime in the past or format error"); 1057 | goto error; 1058 | } 1059 | 1060 | timedelta = (time_t) (delta * -1); 1061 | if (timedelta > conf->max_cache_time) { 1062 | timedelta = conf->max_cache_time; 1063 | } 1064 | ctx->delta = timedelta; 1065 | } else { 1066 | ctx->delta = conf->max_cache_time; 1067 | } 1068 | 1069 | #if DDEBUG 1070 | ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "delta: %d, serial: %v", ctx->delta, &ctx->serial); 1071 | #endif 1072 | 1073 | ctx->valid = 1; 1074 | 1075 | OCSP_BASICRESP_free(basic); 1076 | 1077 | OCSP_RESPONSE_free(ocsp); 1078 | 1079 | return ngx_http_next_body_filter(r, in); 1080 | 1081 | error: 1082 | 1083 | if (basic) { 1084 | OCSP_BASICRESP_free(basic); 1085 | } 1086 | 1087 | if (ocsp) { 1088 | OCSP_RESPONSE_free(ocsp); 1089 | } 1090 | 1091 | return ngx_http_next_body_filter(r, in); 1092 | } 1093 | 1094 | 1095 | static ngx_int_t 1096 | ngx_http_ocsp_proxy_add_variables(ngx_conf_t *cf) 1097 | { 1098 | ngx_http_variable_t *var; 1099 | 1100 | var = ngx_http_add_variable(cf, &ngx_http_ocsp_serial, NGX_HTTP_VAR_NOHASH); 1101 | if (var == NULL) { 1102 | return NGX_ERROR; 1103 | } 1104 | var->get_handler = ngx_http_ocsp_request_get_serial_variable; 1105 | 1106 | var = ngx_http_add_variable(cf, &ngx_http_ocsp_skip_caching, NGX_HTTP_VAR_NOHASH); 1107 | if (var == NULL) { 1108 | return NGX_ERROR; 1109 | } 1110 | var->get_handler = ngx_http_ocsp_request_get_skip_caching_variable; 1111 | 1112 | var = ngx_http_add_variable(cf, &ngx_http_ocsp_delta, NGX_HTTP_VAR_NOHASH); 1113 | if (var == NULL) { 1114 | return NGX_ERROR; 1115 | } 1116 | var->get_handler = ngx_http_ocsp_request_get_delta_variable; 1117 | 1118 | var = ngx_http_add_variable(cf, &ngx_http_ocsp_request, NGX_HTTP_VAR_NOHASH); 1119 | if (var == NULL) { 1120 | return NGX_ERROR; 1121 | } 1122 | var->get_handler = ngx_http_ocsp_request_get_b64encoded_variable; 1123 | 1124 | return NGX_OK; 1125 | } 1126 | 1127 | static void * 1128 | ngx_http_ocsp_proxy_create_conf(ngx_conf_t *cf) 1129 | { 1130 | ngx_http_ocsp_proxy_conf_t *conf; 1131 | 1132 | conf = (ngx_http_ocsp_proxy_conf_t *) ngx_pcalloc(cf->pool, sizeof(ngx_http_ocsp_proxy_conf_t)); 1133 | if (conf == NULL) { 1134 | return NGX_CONF_ERROR; 1135 | } 1136 | 1137 | /* 1138 | * set by ngx_pcalloc(): 1139 | * 1140 | */ 1141 | 1142 | conf->enable = NGX_CONF_UNSET; 1143 | conf->max_cache_time = NGX_CONF_UNSET; 1144 | 1145 | return conf; 1146 | } 1147 | 1148 | 1149 | static char * 1150 | ngx_http_ocsp_proxy_merge_conf(ngx_conf_t *cf, void *parent, void *child) 1151 | { 1152 | ngx_http_ocsp_proxy_conf_t *prev = parent; 1153 | ngx_http_ocsp_proxy_conf_t *conf = child; 1154 | 1155 | ngx_conf_merge_value(conf->enable, prev->enable, 0); 1156 | 1157 | /* default max_cache_time = 3600 * 24 */ 1158 | ngx_conf_merge_value(conf->max_cache_time, 1159 | prev->max_cache_time, 86400); 1160 | 1161 | return NGX_CONF_OK; 1162 | } 1163 | 1164 | static ngx_int_t 1165 | ngx_http_ocsp_proxy_init(ngx_conf_t *cf) 1166 | { 1167 | ngx_http_handler_pt *h; 1168 | ngx_http_core_main_conf_t *cmcf; 1169 | 1170 | cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); 1171 | h = ngx_array_push(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers); 1172 | if (NULL == h) { 1173 | return NGX_ERROR; 1174 | } 1175 | *h = ngx_http_ocsp_proxy_handler; 1176 | 1177 | ngx_http_next_body_filter = ngx_http_top_body_filter; 1178 | ngx_http_top_body_filter = ngx_http_ocsp_proxy_handle_response; 1179 | 1180 | return NGX_OK; 1181 | } 1182 | --------------------------------------------------------------------------------