diff --git a/.clangd b/.clangd index 7a07116..8006528 100644 --- a/.clangd +++ b/.clangd @@ -6,4 +6,4 @@ CompileFlags: - "-I../nginx/objs" - "-I../nginx/src/os/unix" - "-I/opt/homebrew/Cellar/pcre2/10.47/include" - - "-I/opt/homebrew/Cellar/openssl@3/3.6.0/include" + - "-I/opt/homebrew/Cellar/openssl@3/3.6.1/include" diff --git a/Makefile b/Makefile index 081d20f..e0a3fec 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ build: ngx_http_pow.c .PHONY: configure configure: + cd ../nginx; \ ../nginx/auto/configure \ --prefix=/Users/jona/repos/ngx-pow/nginx/install \ --with-debug \ @@ -12,4 +13,4 @@ configure: .PHONY: run run: build - ../nginx/objs/nginx -c "$(PWD)/ngx_http_pow.conf" -g "daemon off;" + ../nginx/objs/nginx -c "$(PWD)/ngx_http_pow.conf" diff --git a/html/pow.html b/html/pow.html new file mode 100644 index 0000000..eb38c19 --- /dev/null +++ b/html/pow.html @@ -0,0 +1,95 @@ + + + + + + PoW Shield + + + diff --git a/ngx_http_pow.c b/ngx_http_pow.c index c7bd12a..c763fad 100644 --- a/ngx_http_pow.c +++ b/ngx_http_pow.c @@ -6,24 +6,24 @@ #define NGX_HTTP_POW_CHALLENGE_RANDOM_LENGTH 64 #define NGX_HTTP_POW_HMAC_KEY "thisisaverysecretkey" -#define NGX_HTTP_POW_CHALLENGE_HEADER_KEY "pow-challenge" -#define NGX_HTTP_POW_CHALLENGE_COOKIE_NAME "pow" +#define NGX_HTTP_POW_CHALLENGE_COOKIE_KEY "pow-challenge=" +#define NGX_HTTP_POW_CHALLENGE_COOKIE_ATTRIBUTES "; Path=/; SameSite=Lax" +#define NGX_HTTP_POW_COOKIE_NAME "pow" #define NGX_HTTP_POW_UNUSED __attribute__((unused)) static ngx_int_t ngx_http_pow_init(ngx_conf_t *cf); static ngx_int_t ngx_http_pow_handler(ngx_http_request_t *r); +static ngx_str_t *pow_html; + static ngx_http_module_t ngx_http_pow_module_ctx = { NULL, /* preconfiguration */ ngx_http_pow_init, /* postconfiguration */ - NULL, /* create main configuration */ NULL, /* init main configuration */ - NULL, /* create server configuration */ NULL, /* merge server configuration */ - NULL, /* create location configuration */ NULL /* merge location configuration */ }; @@ -43,8 +43,6 @@ ngx_module_t ngx_http_pow = { NGX_MODULE_V1_PADDING }; -#define NGX_HTTP_POW_CHALLENGE_SERIALIZED_LENGTH 256 - typedef struct __attribute__((packed)) ngx_http_pow_challenge_s { uint32_t version; uint32_t created; @@ -54,16 +52,23 @@ typedef struct __attribute__((packed)) ngx_http_pow_challenge_s { u_char digest[EVP_MAX_MD_SIZE]; } ngx_http_pow_challenge_t; +typedef struct __attribute__((packed)) ngx_http_pow_s { + ngx_http_pow_challenge_t challenge; + uint32_t nonce; + u_char hash[EVP_MAX_MD_SIZE]; +} ngx_http_pow_t; + static ngx_http_pow_challenge_t *ngx_http_pow_create_challenge(ngx_pool_t *pool) { ngx_http_pow_challenge_t *challenge; - challenge = ngx_palloc(pool, sizeof(*challenge)); + challenge = ngx_pcalloc(pool, sizeof(*challenge)); - challenge->version = 1; - challenge->created = ngx_time(); - challenge->ttl = 30; + challenge->version = 1; + challenge->created = ngx_time(); + challenge->ttl = 30; + challenge->difficulty = 3; RAND_bytes(challenge->random, NGX_HTTP_POW_CHALLENGE_RANDOM_LENGTH); @@ -87,8 +92,8 @@ static ngx_str_t challenge_string.len = sizeof(*challenge); encoded_length = ngx_base64_encoded_length(sizeof(*challenge)); - buf = ngx_palloc(pool, encoded_length); - marshalled = ngx_palloc(pool, sizeof(ngx_str_t)); + buf = ngx_pcalloc(pool, encoded_length); + marshalled = ngx_pcalloc(pool, sizeof(ngx_str_t)); marshalled->len = encoded_length; marshalled->data = buf; @@ -104,8 +109,8 @@ static ngx_http_pow_challenge_t NGX_HTTP_POW_UNUSED ngx_http_pow_challenge_t *challenge; ngx_str_t *unmarshalled; - challenge = ngx_palloc(pool, sizeof(*challenge)); - unmarshalled = ngx_palloc(pool, sizeof(ngx_str_t)); + challenge = ngx_pcalloc(pool, sizeof(*challenge)); + unmarshalled = ngx_pcalloc(pool, sizeof(ngx_str_t)); unmarshalled->data = (u_char *)challenge; unmarshalled->len = sizeof(*challenge); @@ -120,32 +125,58 @@ ngx_http_pow_require_pow(ngx_http_request_t *r) { ngx_http_pow_challenge_t *challenge; ngx_table_elt_t *header; + ngx_chain_t out; ngx_str_t *marshalled; + ngx_buf_t *body_buf; + u_char *p; challenge = ngx_http_pow_create_challenge(r->pool); marshalled = ngx_http_pow_challenge_marshal(challenge, r->pool); header = ngx_list_push(&r->headers_out.headers); - ngx_str_set(&header->key, NGX_HTTP_POW_CHALLENGE_HEADER_KEY); - header->value = *marshalled; - header->hash = 1; + ngx_str_set(&header->key, "Set-Cookie"); - r->headers_out.status = NGX_HTTP_UNAUTHORIZED; - r->headers_out.content_length = 0; + header->hash = 1; + + header->value.len = sizeof(NGX_HTTP_POW_CHALLENGE_COOKIE_KEY) - 1 + + marshalled->len + + sizeof(NGX_HTTP_POW_CHALLENGE_COOKIE_ATTRIBUTES) - 1; + + header->value.data = ngx_pcalloc(r->pool, header->value.len); + + p = header->value.data; + + p = ngx_cpymem(p, NGX_HTTP_POW_CHALLENGE_COOKIE_KEY, + sizeof(NGX_HTTP_POW_CHALLENGE_COOKIE_KEY) - 1); + p = ngx_cpymem(p, marshalled->data, marshalled->len); + p = ngx_cpymem(p, NGX_HTTP_POW_CHALLENGE_COOKIE_ATTRIBUTES, + sizeof(NGX_HTTP_POW_CHALLENGE_COOKIE_ATTRIBUTES) - 1); + + r->headers_out.status = NGX_HTTP_UNAUTHORIZED; + r->headers_out.content_length_n = pow_html->len; + + body_buf = ngx_calloc_buf(r->pool); + if (body_buf == NULL) { + /* We done fucked up */ + } + + body_buf->pos = pow_html->data; + body_buf->last = pow_html->data + pow_html->len; + body_buf->memory = 1; + body_buf->last_buf = 1; + body_buf->last_in_chain = 1; + body_buf->sync = 0; + + out.buf = body_buf; + out.next = NULL; ngx_http_send_header(r); + ngx_http_output_filter(r, &out); ngx_http_finalize_request(r, NGX_HTTP_UNAUTHORIZED); } -typedef struct __attribute__((packed)) ngx_http_pow_s { - ngx_http_pow_challenge_t challenge; - uint32_t nonce; - u_char hash[EVP_MAX_MD_SIZE]; -} ngx_http_pow_t; - static ngx_str_t NGX_HTTP_POW_UNUSED -*ngx_http_pow_marshal(ngx_http_pow_t *pow, - ngx_pool_t *pool) +*ngx_http_pow_marshal(ngx_http_pow_t *pow, ngx_pool_t *pool) { ngx_str_t pow_string; ngx_str_t *marshalled; @@ -156,8 +187,8 @@ static ngx_str_t NGX_HTTP_POW_UNUSED pow_string.len = sizeof(*pow); encoded_length = ngx_base64_encoded_length(sizeof(*pow)); - buf = ngx_palloc(pool, encoded_length); - marshalled = ngx_palloc(pool, sizeof(ngx_str_t)); + buf = ngx_pcalloc(pool, encoded_length); + marshalled = ngx_pcalloc(pool, sizeof(ngx_str_t)); marshalled->len = encoded_length; marshalled->data = buf; @@ -173,8 +204,8 @@ static ngx_http_pow_t ngx_http_pow_t *pow; ngx_str_t *unmarshalled; - pow = ngx_palloc(pool, sizeof(*pow)); - unmarshalled = ngx_palloc(pool, sizeof(ngx_str_t)); + pow = ngx_pcalloc(pool, sizeof(*pow)); + unmarshalled = ngx_pcalloc(pool, sizeof(ngx_str_t)); unmarshalled->data = (u_char *)pow; unmarshalled->len = sizeof(*pow); @@ -232,7 +263,7 @@ ngx_http_pow_check_pow(ngx_http_request_t *r) ngx_http_pow_t *pow; ngx_str_t pow_cookie_value; - ngx_str_t pow_cookie_name = ngx_string(NGX_HTTP_POW_CHALLENGE_COOKIE_NAME); + ngx_str_t pow_cookie_name = ngx_string(NGX_HTTP_POW_COOKIE_NAME); /* No PoW cookie is present */ if (!ngx_http_parse_multi_header_lines(r, r->headers_in.cookie, @@ -245,8 +276,8 @@ ngx_http_pow_check_pow(ngx_http_request_t *r) /* Check digest matches */ if (!ngx_http_pow_check_digest(challenge, - sizeof(*challenge) - sizeof(challenge->digest), - challenge->digest)) { + sizeof(*challenge) - sizeof(challenge->digest), + challenge->digest)) { return false; } @@ -256,12 +287,13 @@ ngx_http_pow_check_pow(ngx_http_request_t *r) } /* Check pow difficulty */ - ngx_http_pow_check_difficulty(pow, challenge->difficulty); + if (!ngx_http_pow_check_difficulty(pow, challenge->difficulty)) { + return false; + } /* Check pow is correct */ - if (!ngx_http_pow_check_digest(pow, - sizeof(*pow) - sizeof(pow->hash), - pow->hash)) { + if (!ngx_http_pow_check_digest(pow, sizeof(*pow) - sizeof(pow->hash), + pow->hash)) { return false; } @@ -281,15 +313,59 @@ ngx_http_pow_handler(ngx_http_request_t *r) return NGX_DECLINED; } +static ngx_str_t +*ngx_http_pow_load_js(ngx_pool_t *pool, ngx_str_t *filename, ngx_log_t *log) +{ + size_t filesize; + ngx_str_t *pow_html = NULL; + ngx_file_t file; + ngx_file_info_t file_info; + + file.name = *filename; + file.log = log; + file.fd = ngx_open_file(filename->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); + + if (file.fd == NGX_INVALID_FILE) { + goto cleanup; + } + + if (ngx_file_info(filename->data, &file_info) == NGX_FILE_ERROR) { + goto cleanup; + } + + filesize = ngx_file_size(&file_info); + + pow_html = ngx_pcalloc(pool, sizeof(ngx_str_t)); + pow_html->data = ngx_pcalloc(pool, filesize); + pow_html->len = ngx_read_file(&file, pow_html->data, filesize, 0); + + if (/* pow_html->len == NGX_ERROR || */ pow_html->len != filesize) { + pow_html = NULL; + } + +cleanup: + + ngx_close_file(file.fd); + + return pow_html; +} static ngx_int_t ngx_http_pow_init(ngx_conf_t *cf) { + ngx_str_t pow_html_path = + ngx_string("/Users/jona/repos/ngx-pow/ngx-http-pow/html/pow.html"); + ngx_http_handler_pt *h; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); + pow_html = ngx_http_pow_load_js(cf->pool, &pow_html_path, cf->log); + if (!pow_html) { + return NGX_ERROR; + } + h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers); if (h == NULL) { return NGX_ERROR; diff --git a/ngx_http_pow.conf b/ngx_http_pow.conf index c8100a4..77543a0 100644 --- a/ngx_http_pow.conf +++ b/ngx_http_pow.conf @@ -1,4 +1,6 @@ -worker_processes 1; +worker_processes 1; +daemon off; +master_process off; error_log stderr debug; pid /tmp/nginx.pid;