Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HMAC signing for http push #5

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
65 changes: 65 additions & 0 deletions common/hmac.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* nodewatcher-agent - remote monitoring daemon
*
* Copyright (C) 2017 Anej Placer <[email protected]>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
* for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <nodewatcher-agent/hmac.h>

void sha256(const BYTE *text, BYTE *buf)
{
SHA256_CTX ctx;
sha256_init(&ctx);
sha256_update(&ctx, text, strlen((char*) text));
sha256_final(&ctx, buf);
}

void hmac_sha256(BYTE *key, int key_len, const BYTE *data, int data_len, BYTE *hmac_out)
{
BYTE key_buf[SHA256_BLOCK_SIZE] = {0};

if (key_len > SHA256_BLOCK_SIZE) {
sha256(key, key_buf);
}
if (key_len < SHA256_BLOCK_SIZE) {
memcpy(key_buf, key, key_len);
}

for (int i = 0; i < SHA256_BLOCK_SIZE; i++) {
key_buf[i] ^= 0x36;
}

size_t buf_size = SHA256_BLOCK_SIZE + data_len;
BYTE *in_buf = (BYTE*) malloc(buf_size);
memset(in_buf, 0x00, buf_size);
memcpy(in_buf, key_buf, SHA256_BLOCK_SIZE);
memcpy(in_buf + SHA256_BLOCK_SIZE, data, data_len);
memset(hmac_out, 0x00, SHA256_HASH_SIZE);
sha256(in_buf, hmac_out);

for (int i = 0; i < SHA256_BLOCK_SIZE; i++) {
key_buf[i] ^= 0x36 ^ 0x5c;
}

buf_size = SHA256_BLOCK_SIZE + SHA256_HASH_SIZE + 1;
in_buf = (BYTE*) realloc(in_buf, buf_size);
memset(in_buf, 0x00, buf_size);
memcpy(in_buf, key_buf, SHA256_BLOCK_SIZE);
memcpy(in_buf + SHA256_BLOCK_SIZE, hmac_out, SHA256_HASH_SIZE);
memset(hmac_out, 0x00, SHA256_HASH_SIZE);
sha256(in_buf, hmac_out);

free(in_buf);
}
159 changes: 159 additions & 0 deletions common/sha256.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*********************************************************************
* Filename: sha256.c
* Author: Brad Conte (brad AT bradconte.com)
* Copyright:
* Disclaimer: This code is presented "as is" without any guarantees.
* Details: Implementation of the SHA-256 hashing algorithm.
SHA-256 is one of the three algorithms in the SHA2
specification. The others, SHA-384 and SHA-512, are not
offered in this implementation.
Algorithm specification can be found here:
* http://csrc.nist.gov/publications/fips/fips180-2/fips180-2withchangenotice.pdf
This implementation uses little endian byte order.
*********************************************************************/

/*************************** HEADER FILES ***************************/
#include <nodewatcher-agent/sha256.h>

#include <stdlib.h>
#include <memory.h>

/****************************** MACROS ******************************/
#define ROTLEFT(a,b) (((a) << (b)) | ((a) >> (32-(b))))
#define ROTRIGHT(a,b) (((a) >> (b)) | ((a) << (32-(b))))

#define CH(x,y,z) (((x) & (y)) ^ (~(x) & (z)))
#define MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
#define EP0(x) (ROTRIGHT(x,2) ^ ROTRIGHT(x,13) ^ ROTRIGHT(x,22))
#define EP1(x) (ROTRIGHT(x,6) ^ ROTRIGHT(x,11) ^ ROTRIGHT(x,25))
#define SIG0(x) (ROTRIGHT(x,7) ^ ROTRIGHT(x,18) ^ ((x) >> 3))
#define SIG1(x) (ROTRIGHT(x,17) ^ ROTRIGHT(x,19) ^ ((x) >> 10))

/**************************** VARIABLES *****************************/
static const WORD k[64] = {
0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
};

/*********************** FUNCTION DEFINITIONS ***********************/
void sha256_transform(SHA256_CTX *ctx, const BYTE data[])
{
WORD a, b, c, d, e, f, g, h, i, j, t1, t2, m[64];

for (i = 0, j = 0; i < 16; ++i, j += 4)
m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]);
for ( ; i < 64; ++i)
m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16];

a = ctx->state[0];
b = ctx->state[1];
c = ctx->state[2];
d = ctx->state[3];
e = ctx->state[4];
f = ctx->state[5];
g = ctx->state[6];
h = ctx->state[7];

for (i = 0; i < 64; ++i) {
t1 = h + EP1(e) + CH(e,f,g) + k[i] + m[i];
t2 = EP0(a) + MAJ(a,b,c);
h = g;
g = f;
f = e;
e = d + t1;
d = c;
c = b;
b = a;
a = t1 + t2;
}

ctx->state[0] += a;
ctx->state[1] += b;
ctx->state[2] += c;
ctx->state[3] += d;
ctx->state[4] += e;
ctx->state[5] += f;
ctx->state[6] += g;
ctx->state[7] += h;
}

void sha256_init(SHA256_CTX *ctx)
{
ctx->datalen = 0;
ctx->bitlen = 0;
ctx->state[0] = 0x6a09e667;
ctx->state[1] = 0xbb67ae85;
ctx->state[2] = 0x3c6ef372;
ctx->state[3] = 0xa54ff53a;
ctx->state[4] = 0x510e527f;
ctx->state[5] = 0x9b05688c;
ctx->state[6] = 0x1f83d9ab;
ctx->state[7] = 0x5be0cd19;
}

void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len)
{
WORD i;

for (i = 0; i < len; ++i) {
ctx->data[ctx->datalen] = data[i];
ctx->datalen++;
if (ctx->datalen == 64) {
sha256_transform(ctx, ctx->data);
ctx->bitlen += 512;
ctx->datalen = 0;
}
}
}

void sha256_final(SHA256_CTX *ctx, BYTE hash[])
{
WORD i;

i = ctx->datalen;

// Pad whatever data is left in the buffer.
if (ctx->datalen < 56) {
ctx->data[i++] = 0x80;
while (i < 56)
ctx->data[i++] = 0x00;
}
else {
ctx->data[i++] = 0x80;
while (i < 64)
ctx->data[i++] = 0x00;
sha256_transform(ctx, ctx->data);
memset(ctx->data, 0, 56);
}

// Append to the padding the total message's length in bits and transform.
ctx->bitlen += ctx->datalen * 8;
ctx->data[63] = ctx->bitlen;
ctx->data[62] = ctx->bitlen >> 8;
ctx->data[61] = ctx->bitlen >> 16;
ctx->data[60] = ctx->bitlen >> 24;
ctx->data[59] = ctx->bitlen >> 32;
ctx->data[58] = ctx->bitlen >> 40;
ctx->data[57] = ctx->bitlen >> 48;
ctx->data[56] = ctx->bitlen >> 56;
sha256_transform(ctx, ctx->data);

// Since this implementation uses little endian byte ordering and SHA uses big endian,
// reverse all the bytes when copying the final state to the output hash.
for (i = 0; i < 4; ++i) {
hash[i] = (ctx->state[0] >> (24 - i * 8)) & 0x000000ff;
hash[i + 4] = (ctx->state[1] >> (24 - i * 8)) & 0x000000ff;
hash[i + 8] = (ctx->state[2] >> (24 - i * 8)) & 0x000000ff;
hash[i + 12] = (ctx->state[3] >> (24 - i * 8)) & 0x000000ff;
hash[i + 16] = (ctx->state[4] >> (24 - i * 8)) & 0x000000ff;
hash[i + 20] = (ctx->state[5] >> (24 - i * 8)) & 0x000000ff;
hash[i + 24] = (ctx->state[6] >> (24 - i * 8)) & 0x000000ff;
hash[i + 28] = (ctx->state[7] >> (24 - i * 8)) & 0x000000ff;
}
}
30 changes: 30 additions & 0 deletions include/nodewatcher-agent/hmac.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* nodewatcher-agent - remote monitoring daemon
*
* Copyright (C) 2017 Anej Placer <[email protected]>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
* for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef NODEWATCHER_AGENT_HMAC_H
#define NODEWATCHER_AGENT_HMAC_H

#include <nodewatcher-agent/sha256.h>

#define SHA256_HASH_SIZE 32
#define SHA256_BLOCK_SIZE 64

void sha256(const BYTE *text, BYTE *buf);
void hmac_sha256(BYTE *key, int key_len, const BYTE *data, int data_len, BYTE *hmac_out);

#endif // NODEWATCHER_AGENT_HMAC_H
31 changes: 31 additions & 0 deletions include/nodewatcher-agent/sha256.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*********************************************************************
* Filename: sha256.h
* Author: Brad Conte (brad AT bradconte.com)
* Copyright:
* Disclaimer: This code is presented "as is" without any guarantees.
* Details: Defines the API for the corresponding SHA1 implementation.
*********************************************************************/

#ifndef SHA256_H
#define SHA256_H

/*************************** HEADER FILES ***************************/
#include <stddef.h>

/**************************** DATA TYPES ****************************/
typedef unsigned char BYTE; // 8-bit byte
typedef unsigned int WORD; // 32-bit word, change to "long" for 16-bit machines

typedef struct {
BYTE data[64];
WORD datalen;
unsigned long long bitlen;
WORD state[8];
} SHA256_CTX;

/*********************** FUNCTION DECLARATIONS **********************/
void sha256_init(SHA256_CTX *ctx);
void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len);
void sha256_final(SHA256_CTX *ctx, BYTE hash[]);

#endif // SHA256_H
71 changes: 55 additions & 16 deletions modules/http_push.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
#include <nodewatcher-agent/module.h>
#include <nodewatcher-agent/json.h>
#include <nodewatcher-agent/utils.h>
#include <nodewatcher-agent/hmac.h>

#include <syslog.h>
#include <curl.h>
#include <strings.h>

/* Timestamp when last successful push occurred. */
static time_t last_push_at = 0;
Expand Down Expand Up @@ -49,6 +51,7 @@ static int nw_http_push_start_acquire_data(struct nodewatcher_module *module,

/* Get the configured URL from UCI. */
char *url = nw_uci_get_string(uci, "nodewatcher.@agent[0].push_url");

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this empty line?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Readability habit I guess... You want me to stop doing that or just wanted an explanation? :)

if (url) {
/* Collect all the module data and perform the push. */
json_object *data = nw_module_get_output();
Expand All @@ -61,30 +64,66 @@ static int nw_http_push_start_acquire_data(struct nodewatcher_module *module,
/* Default. */
timeout = 5;
}

const char* data_string = json_object_to_json_string(data);

curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout);
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_POST, 1);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_object_to_json_string(data));
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data_string);

const char *auth_type = nw_uci_get_string(uci, "nodewatcher.@agent[0].push_authentication_method");

if (strcasecmp(auth_type, "hmac") == 0) {
const char *hmac_key = nw_uci_get_string(uci, "nodewatcher.@agent[0].hmac_key");

if (hmac_key) {
unsigned char hmac_out[SHA256_HASH_SIZE];
hmac_sha256((unsigned char*) hmac_key, strlen(hmac_key), (unsigned char*) data_string, strlen(data_string), hmac_out);

char signature[SHA256_HASH_SIZE + 1] = {0};

if (nw_base64_encode(hmac_out, SHA256_HASH_SIZE, signature, SHA256_HASH_SIZE) == 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

base64 inflates the output size, so this probably isn't correct.

char signature_header[26+sizeof(signature)] = {0};
strcat(strcpy(signature_header, "X-Nodewatcher-Signature: "), signature);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Odd at least. Can you use snprintf?
Or, how about just initializing to "X-Nodewatcher-Signature: " instead of {0}?


struct curl_slist *chunk = NULL;
chunk = curl_slist_append(chunk, "X-Nodewatcher-Signature-Algorithm: hmac-sha256");
chunk = curl_slist_append(chunk, signature_header);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
}
}

free((char*) hmac_key);

} else {

#if LIBCURL_VERSION_NUM >= 0x072700
/* Pin server-side public key when configured. */
char *server_pubkey = nw_uci_get_string(uci, "nodewatcher.@agent[0].push_server_pubkey");
if (server_pubkey) {
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_easy_setopt(curl, CURLOPT_PINNEDPUBLICKEY, server_pubkey);
free(server_pubkey);
}
/* Pin server-side public key when configured. */
char *server_pubkey = nw_uci_get_string(uci, "nodewatcher.@agent[0].push_server_pubkey");

if (server_pubkey) {
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_easy_setopt(curl, CURLOPT_PINNEDPUBLICKEY, server_pubkey);
free(server_pubkey);
}
#endif
/* Setup client authentication when configured. */
char *client_certificate = nw_uci_get_string(uci, "nodewatcher.@agent[0].push_client_certificate");
char *client_key = nw_uci_get_string(uci, "nodewatcher.@agent[0].push_client_key");
if (client_certificate && client_key) {
curl_easy_setopt(curl, CURLOPT_SSLCERT, client_certificate);
curl_easy_setopt(curl, CURLOPT_SSLKEY, client_key);
/* Setup client authentication when configured. */
char *client_certificate = nw_uci_get_string(uci, "nodewatcher.@agent[0].push_client_certificate");
char *client_key = nw_uci_get_string(uci, "nodewatcher.@agent[0].push_client_key");

if (client_certificate && client_key) {
curl_easy_setopt(curl, CURLOPT_SSLCERT, client_certificate);
curl_easy_setopt(curl, CURLOPT_SSLKEY, client_key);
}

free(client_certificate);
free(client_key);
}

free(client_certificate);
free(client_key);
free((char*) data_string);
free((char*) auth_type);

/* Provide a buffer to store errors in. */
char errbuf[CURL_ERROR_SIZE];
Expand Down