Implement PoW core functionality
This commit is contained in:
+249
-14
@@ -1,22 +1,31 @@
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_http.h>
|
||||
#include <openssl/hmac.h>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
#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_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_http_module_t ngx_http_pow_module_ctx = {
|
||||
NULL, /* preconfiguration */
|
||||
ngx_http_pow_init, /* postconfiguration */
|
||||
NULL, /* preconfiguration */
|
||||
ngx_http_pow_init, /* postconfiguration */
|
||||
|
||||
NULL, /* create main configuration */
|
||||
NULL, /* init main configuration */
|
||||
NULL, /* create main configuration */
|
||||
NULL, /* init main configuration */
|
||||
|
||||
NULL, /* create server configuration */
|
||||
NULL, /* merge server configuration */
|
||||
NULL, /* create server configuration */
|
||||
NULL, /* merge server configuration */
|
||||
|
||||
NULL, /* create location configuration */
|
||||
NULL /* merge location configuration */
|
||||
NULL, /* create location configuration */
|
||||
NULL /* merge location configuration */
|
||||
};
|
||||
|
||||
ngx_module_t ngx_http_pow = {
|
||||
@@ -34,13 +43,242 @@ 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;
|
||||
uint32_t ttl;
|
||||
uint32_t difficulty;
|
||||
u_char random[NGX_HTTP_POW_CHALLENGE_RANDOM_LENGTH];
|
||||
u_char digest[EVP_MAX_MD_SIZE];
|
||||
} ngx_http_pow_challenge_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->version = 1;
|
||||
challenge->created = ngx_time();
|
||||
challenge->ttl = 30;
|
||||
|
||||
RAND_bytes(challenge->random, NGX_HTTP_POW_CHALLENGE_RANDOM_LENGTH);
|
||||
|
||||
HMAC(EVP_sha256(), NGX_HTTP_POW_HMAC_KEY, sizeof(NGX_HTTP_POW_HMAC_KEY),
|
||||
(u_char *)challenge, sizeof(*challenge) - EVP_MAX_MD_SIZE,
|
||||
challenge->digest, NULL);
|
||||
|
||||
return challenge;
|
||||
}
|
||||
|
||||
static ngx_str_t
|
||||
*ngx_http_pow_challenge_marshal(ngx_http_pow_challenge_t *challenge,
|
||||
ngx_pool_t *pool)
|
||||
{
|
||||
ngx_str_t challenge_string;
|
||||
ngx_str_t *marshalled;
|
||||
size_t encoded_length;
|
||||
u_char *buf;
|
||||
|
||||
challenge_string.data = (u_char *)challenge;
|
||||
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));
|
||||
|
||||
marshalled->len = encoded_length;
|
||||
marshalled->data = buf;
|
||||
|
||||
ngx_encode_base64(marshalled, &challenge_string);
|
||||
|
||||
return marshalled;
|
||||
}
|
||||
|
||||
static ngx_http_pow_challenge_t NGX_HTTP_POW_UNUSED
|
||||
*ngx_http_pow_challenge_unmarshal(ngx_str_t *marshalled, ngx_pool_t *pool)
|
||||
{
|
||||
ngx_http_pow_challenge_t *challenge;
|
||||
ngx_str_t *unmarshalled;
|
||||
|
||||
challenge = ngx_palloc(pool, sizeof(*challenge));
|
||||
unmarshalled = ngx_palloc(pool, sizeof(ngx_str_t));
|
||||
|
||||
unmarshalled->data = (u_char *)challenge;
|
||||
unmarshalled->len = sizeof(*challenge);
|
||||
|
||||
ngx_decode_base64(unmarshalled, marshalled);
|
||||
|
||||
return challenge;
|
||||
}
|
||||
|
||||
static void
|
||||
ngx_http_pow_require_pow(ngx_http_request_t *r)
|
||||
{
|
||||
ngx_http_pow_challenge_t *challenge;
|
||||
ngx_table_elt_t *header;
|
||||
ngx_str_t *marshalled;
|
||||
|
||||
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;
|
||||
|
||||
r->headers_out.status = NGX_HTTP_UNAUTHORIZED;
|
||||
r->headers_out.content_length = 0;
|
||||
|
||||
ngx_http_send_header(r);
|
||||
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_str_t pow_string;
|
||||
ngx_str_t *marshalled;
|
||||
size_t encoded_length;
|
||||
u_char *buf;
|
||||
|
||||
pow_string.data = (u_char *)pow;
|
||||
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));
|
||||
|
||||
marshalled->len = encoded_length;
|
||||
marshalled->data = buf;
|
||||
|
||||
ngx_encode_base64(marshalled, &pow_string);
|
||||
|
||||
return marshalled;
|
||||
}
|
||||
|
||||
static ngx_http_pow_t
|
||||
*ngx_http_pow_unmarshal(ngx_pool_t *pool, ngx_str_t *marshalled)
|
||||
{
|
||||
ngx_http_pow_t *pow;
|
||||
ngx_str_t *unmarshalled;
|
||||
|
||||
pow = ngx_palloc(pool, sizeof(*pow));
|
||||
unmarshalled = ngx_palloc(pool, sizeof(ngx_str_t));
|
||||
|
||||
unmarshalled->data = (u_char *)pow;
|
||||
unmarshalled->len = sizeof(*pow);
|
||||
|
||||
ngx_decode_base64(unmarshalled, marshalled);
|
||||
|
||||
return pow;
|
||||
}
|
||||
|
||||
static bool
|
||||
ngx_http_pow_check_difficulty(ngx_http_pow_t *pow, uint32_t difficulty)
|
||||
{
|
||||
size_t i;
|
||||
size_t zero_bytes = difficulty / 8;
|
||||
size_t remaining_bits = difficulty % 8;
|
||||
|
||||
static u_char lut[] = {
|
||||
0b11111111, 0b01111111, 0b00111111, 0b00011111,
|
||||
0b00001111, 0b00000111, 0b00000011, 0b00000001
|
||||
};
|
||||
|
||||
for (i = 0; i < zero_bytes; i++) {
|
||||
if (pow->hash[i] > 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (pow->hash[zero_bytes + 1] ^ lut[remaining_bits]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
ngx_http_pow_check_digest(void *data, size_t len, u_char *digest)
|
||||
{
|
||||
u_char d[EVP_MAX_MD_SIZE];
|
||||
|
||||
HMAC(EVP_sha256(), NGX_HTTP_POW_HMAC_KEY, sizeof(NGX_HTTP_POW_HMAC_KEY),
|
||||
data, len, d, NULL);
|
||||
|
||||
/* CRYPTO_memcmp returns 0 if compared values are equal, else != 0 */
|
||||
if (CRYPTO_memcmp(digest, d, EVP_MAX_MD_SIZE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
ngx_http_pow_check_pow(ngx_http_request_t *r)
|
||||
{
|
||||
ngx_http_pow_challenge_t *challenge;
|
||||
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);
|
||||
|
||||
/* No PoW cookie is present */
|
||||
if (!ngx_http_parse_multi_header_lines(r, r->headers_in.cookie,
|
||||
&pow_cookie_name, &pow_cookie_value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pow = ngx_http_pow_unmarshal(r->pool, &pow_cookie_value);
|
||||
challenge = &pow->challenge;
|
||||
|
||||
/* Check digest matches */
|
||||
if (!ngx_http_pow_check_digest(challenge,
|
||||
sizeof(*challenge) - sizeof(challenge->digest),
|
||||
challenge->digest)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check pow is still valid */
|
||||
if (challenge->created + challenge->ttl > ngx_time()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check pow difficulty */
|
||||
ngx_http_pow_check_difficulty(pow, challenge->difficulty);
|
||||
|
||||
/* Check pow is correct */
|
||||
if (!ngx_http_pow_check_digest(pow,
|
||||
sizeof(*pow) - sizeof(pow->hash),
|
||||
pow->hash)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static ngx_int_t
|
||||
ngx_http_pow_handler(ngx_http_request_t *r)
|
||||
{
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
|
||||
"ngx_http_pow_handler was invoked");
|
||||
/* PoW is either not present or presetn, but incorrect, require PoW */
|
||||
if (!ngx_http_pow_check_pow(r)) {
|
||||
ngx_http_pow_require_pow(r);
|
||||
return NGX_DONE;
|
||||
}
|
||||
|
||||
return NGX_DECLINED;
|
||||
/* PoW is present and correct, resume processing */
|
||||
return NGX_DECLINED;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,8 +297,5 @@ ngx_http_pow_init(ngx_conf_t *cf)
|
||||
|
||||
*h = ngx_http_pow_handler;
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0,
|
||||
"ngx_http_pow_init was invoked");
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user