2
0
mirror of https://github.com/xcat2/confluent.git synced 2025-08-23 19:50:23 +00:00

Implement hmac of apikey

For routed deployment, we have to preshare some information.

Additionally, the API arm mechanism gets too open ended.

Add support for using a shared secret over another
channel to do HMAC of a key to authenticate peer,
which has an alternate api arming mechanism
that is hardened.
This commit is contained in:
Jarrod Johnson
2022-03-08 14:46:00 -05:00
parent e67bab4f12
commit 0abe978bd9
5 changed files with 425 additions and 25 deletions

View File

@@ -1,4 +1,6 @@
/* Copyright 2019 Lenovo */
/* Copyright 2019-2021 Lenovo */
#include "sha-256.h"
#include <arpa/inet.h>
#include <crypt.h>
#include <net/if.h>
@@ -52,6 +54,10 @@ int main(int argc, char* argv[]) {
struct addrinfo *curr;
struct sockaddr_in net4bind;
struct sockaddr_in6 net6bind;
FILE *hmackeyfile;
uint8_t hmackey[64];
uint8_t hmac[32];
int hmackeysize = 0;
unsigned char buffer[MAXPACKET];
memset(&hints, 0, sizeof(struct addrinfo));
memset(&net4bind, 0, sizeof(struct sockaddr_in));
@@ -77,6 +83,12 @@ int main(int argc, char* argv[]) {
fprintf(stderr, "Missing node name and manager\n");
exit(1);
}
if (argc == 4) {
hmackeyfile = fopen(argv[3], "r");
hmackeysize = fread(hmackey, 1, 64, hmackeyfile);
fclose(hmackeyfile);
hmac_sha256(hmac, cryptedpass, strlen(cryptedpass), hmackey, hmackeysize);
}
sock = getaddrinfo(argv[2], "13001", &hints, &addrs);
if (sock != 0) {
fprintf(stderr, "Error trying to resolve %s\n", argv[2]);
@@ -118,7 +130,11 @@ int main(int argc, char* argv[]) {
}
slen = strlen(argv[1]) & 0xff;
dprintf(sock, "\x01%c%s", slen, argv[1]);
ret = write(sock, "\x00\x00", 2);
if (hmackeysize) {
ret = write(sock, "\x06\x20", 2);
ret = write(sock, hmac, 32);
} else
ret = write(sock, "\x00\x00", 2);
memset(buffer, 0, MAXPACKET);
ret = read(sock, buffer, 2);
while (buffer[0] != 255) {

View File

@@ -0,0 +1,256 @@
#include "sha-256.h"
#include <stdlib.h>
#define TOTAL_LEN_LEN 8
void hmac_sha256(uint8_t* hmac, char* msg, int msglen, char* key, int keylen) {
uint8_t *scratch;
uint8_t keyprime[SIZE_OF_SHA_256_CHUNK];
uint8_t keymod[SIZE_OF_SHA_256_CHUNK];
int padneeded;
if (keylen > SIZE_OF_SHA_256_CHUNK) {
calc_sha_256(keyprime, key, keylen);
keylen = SIZE_OF_SHA_256_HASH;
} else {
memcpy(keyprime, key, keylen);
}
padneeded = SIZE_OF_SHA_256_CHUNK - keylen;
if (padneeded) {
memset(keyprime + keylen, 0, padneeded);
}
for (padneeded=0; padneeded < SIZE_OF_SHA_256_CHUNK; padneeded++) {
keymod[padneeded] = keyprime[padneeded] ^ 0x36;
}
scratch = malloc(SIZE_OF_SHA_256_CHUNK + msglen);
memcpy(scratch, keymod, SIZE_OF_SHA_256_CHUNK);
memcpy(scratch + SIZE_OF_SHA_256_CHUNK, msg, msglen);
calc_sha_256(hmac, scratch, SIZE_OF_SHA_256_CHUNK + msglen);
for (padneeded=0; padneeded < SIZE_OF_SHA_256_CHUNK; padneeded++) {
keymod[padneeded] = keyprime[padneeded] ^ 0x5c;
}
free(scratch);
scratch = malloc(SIZE_OF_SHA_256_CHUNK + SIZE_OF_SHA_256_HASH);
memcpy(scratch, keymod, SIZE_OF_SHA_256_CHUNK);
memcpy(scratch + SIZE_OF_SHA_256_CHUNK, hmac, SIZE_OF_SHA_256_HASH);
calc_sha_256(hmac, scratch, SIZE_OF_SHA_256_CHUNK + SIZE_OF_SHA_256_HASH);
free(scratch);
}
/*
* Comments from pseudo-code at https://en.wikipedia.org/wiki/SHA-2 are reproduced here.
* When useful for clarification, portions of the pseudo-code are reproduced here too.
*/
/*
* @brief Rotate a 32-bit value by a number of bits to the right.
* @param value The value to be rotated.
* @param count The number of bits to rotate by.
* @return The rotated value.
*/
static inline uint32_t right_rot(uint32_t value, unsigned int count)
{
/*
* Defined behaviour in standard C for all count where 0 < count < 32, which is what we need here.
*/
return value >> count | value << (32 - count);
}
/*
* @brief Update a hash value under calculation with a new chunk of data.
* @param h Pointer to the first hash item, of a total of eight.
* @param p Pointer to the chunk data, which has a standard length.
*
* @note This is the SHA-256 work horse.
*/
static inline void consume_chunk(uint32_t *h, const uint8_t *p)
{
unsigned i, j;
uint32_t ah[8];
/* Initialize working variables to current hash value: */
for (i = 0; i < 8; i++)
ah[i] = h[i];
/*
* The w-array is really w[64], but since we only need 16 of them at a time, we save stack by
* calculating 16 at a time.
*
* This optimization was not there initially and the rest of the comments about w[64] are kept in their
* initial state.
*/
/*
* create a 64-entry message schedule array w[0..63] of 32-bit words (The initial values in w[0..63]
* don't matter, so many implementations zero them here) copy chunk into first 16 words w[0..15] of the
* message schedule array
*/
uint32_t w[16];
/* Compression function main loop: */
for (i = 0; i < 4; i++) {
for (j = 0; j < 16; j++) {
if (i == 0) {
w[j] =
(uint32_t)p[0] << 24 | (uint32_t)p[1] << 16 | (uint32_t)p[2] << 8 | (uint32_t)p[3];
p += 4;
} else {
/* Extend the first 16 words into the remaining 48 words w[16..63] of the
* message schedule array: */
const uint32_t s0 = right_rot(w[(j + 1) & 0xf], 7) ^ right_rot(w[(j + 1) & 0xf], 18) ^
(w[(j + 1) & 0xf] >> 3);
const uint32_t s1 = right_rot(w[(j + 14) & 0xf], 17) ^
right_rot(w[(j + 14) & 0xf], 19) ^ (w[(j + 14) & 0xf] >> 10);
w[j] = w[j] + s0 + w[(j + 9) & 0xf] + s1;
}
const uint32_t s1 = right_rot(ah[4], 6) ^ right_rot(ah[4], 11) ^ right_rot(ah[4], 25);
const uint32_t ch = (ah[4] & ah[5]) ^ (~ah[4] & ah[6]);
/*
* Initialize array of round constants:
* (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311):
*/
static const uint32_t k[] = {
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};
const uint32_t temp1 = ah[7] + s1 + ch + k[i << 4 | j] + w[j];
const uint32_t s0 = right_rot(ah[0], 2) ^ right_rot(ah[0], 13) ^ right_rot(ah[0], 22);
const uint32_t maj = (ah[0] & ah[1]) ^ (ah[0] & ah[2]) ^ (ah[1] & ah[2]);
const uint32_t temp2 = s0 + maj;
ah[7] = ah[6];
ah[6] = ah[5];
ah[5] = ah[4];
ah[4] = ah[3] + temp1;
ah[3] = ah[2];
ah[2] = ah[1];
ah[1] = ah[0];
ah[0] = temp1 + temp2;
}
}
/* Add the compressed chunk to the current hash value: */
for (i = 0; i < 8; i++)
h[i] += ah[i];
}
/*
* Public functions. See header file for documentation.
*/
void sha_256_init(struct Sha_256 *sha_256, uint8_t hash[SIZE_OF_SHA_256_HASH])
{
sha_256->hash = hash;
sha_256->chunk_pos = sha_256->chunk;
sha_256->space_left = SIZE_OF_SHA_256_CHUNK;
sha_256->total_len = 0;
/*
* Initialize hash values (first 32 bits of the fractional parts of the square roots of the first 8 primes
* 2..19):
*/
sha_256->h[0] = 0x6a09e667;
sha_256->h[1] = 0xbb67ae85;
sha_256->h[2] = 0x3c6ef372;
sha_256->h[3] = 0xa54ff53a;
sha_256->h[4] = 0x510e527f;
sha_256->h[5] = 0x9b05688c;
sha_256->h[6] = 0x1f83d9ab;
sha_256->h[7] = 0x5be0cd19;
}
void sha_256_write(struct Sha_256 *sha_256, const void *data, size_t len)
{
sha_256->total_len += len;
const uint8_t *p = data;
while (len > 0) {
/*
* If the input chunks have sizes that are multiples of the calculation chunk size, no copies are
* necessary. We operate directly on the input data instead.
*/
if (sha_256->space_left == SIZE_OF_SHA_256_CHUNK && len >= SIZE_OF_SHA_256_CHUNK) {
consume_chunk(sha_256->h, p);
len -= SIZE_OF_SHA_256_CHUNK;
p += SIZE_OF_SHA_256_CHUNK;
continue;
}
/* General case, no particular optimization. */
const size_t consumed_len = len < sha_256->space_left ? len : sha_256->space_left;
memcpy(sha_256->chunk_pos, p, consumed_len);
sha_256->space_left -= consumed_len;
len -= consumed_len;
p += consumed_len;
if (sha_256->space_left == 0) {
consume_chunk(sha_256->h, sha_256->chunk);
sha_256->chunk_pos = sha_256->chunk;
sha_256->space_left = SIZE_OF_SHA_256_CHUNK;
} else {
sha_256->chunk_pos += consumed_len;
}
}
}
uint8_t *sha_256_close(struct Sha_256 *sha_256)
{
uint8_t *pos = sha_256->chunk_pos;
size_t space_left = sha_256->space_left;
uint32_t *const h = sha_256->h;
/*
* The current chunk cannot be full. Otherwise, it would already have be consumed. I.e. there is space left for
* at least one byte. The next step in the calculation is to add a single one-bit to the data.
*/
*pos++ = 0x80;
--space_left;
/*
* Now, the last step is to add the total data length at the end of the last chunk, and zero padding before
* that. But we do not necessarily have enough space left. If not, we pad the current chunk with zeroes, and add
* an extra chunk at the end.
*/
if (space_left < TOTAL_LEN_LEN) {
memset(pos, 0x00, space_left);
consume_chunk(h, sha_256->chunk);
pos = sha_256->chunk;
space_left = SIZE_OF_SHA_256_CHUNK;
}
const size_t left = space_left - TOTAL_LEN_LEN;
memset(pos, 0x00, left);
pos += left;
size_t len = sha_256->total_len;
pos[7] = (uint8_t)(len << 3);
len >>= 5;
int i;
for (i = 6; i >= 0; --i) {
pos[i] = (uint8_t)len;
len >>= 8;
}
consume_chunk(h, sha_256->chunk);
/* Produce the final hash value (big-endian): */
int j;
uint8_t *const hash = sha_256->hash;
for (i = 0, j = 0; i < 8; i++) {
hash[j++] = (uint8_t)(h[i] >> 24);
hash[j++] = (uint8_t)(h[i] >> 16);
hash[j++] = (uint8_t)(h[i] >> 8);
hash[j++] = (uint8_t)h[i];
}
return sha_256->hash;
}
void calc_sha_256(uint8_t hash[SIZE_OF_SHA_256_HASH], const void *input, size_t len)
{
struct Sha_256 sha_256;
sha_256_init(&sha_256, hash);
sha_256_write(&sha_256, input, len);
(void)sha_256_close(&sha_256);
}

View File

@@ -0,0 +1,104 @@
#ifndef SHA_256_H
#define SHA_256_H
#include <stdint.h>
#include <string.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
* @brief Size of the SHA-256 sum. This times eight is 256 bits.
*/
#define SIZE_OF_SHA_256_HASH 32
/*
* @brief Size of the chunks used for the calculations.
*
* @note This should mostly be ignored by the user, although when using the streaming API, it has an impact for
* performance. Add chunks whose size is a multiple of this, and you will avoid a lot of superfluous copying in RAM!
*/
#define SIZE_OF_SHA_256_CHUNK 64
/*
* @brief The opaque SHA-256 type, that should be instantiated when using the streaming API.
*
* @note Although the details are exposed here, in order to make instantiation easy, you should refrain from directly
* accessing the fields, as they may change in the future.
*/
struct Sha_256 {
uint8_t *hash;
uint8_t chunk[SIZE_OF_SHA_256_CHUNK];
uint8_t *chunk_pos;
size_t space_left;
size_t total_len;
uint32_t h[8];
};
/*
* @brief The simple SHA-256 calculation function.
* @param hash Hash array, where the result is delivered.
* @param input Pointer to the data the hash shall be calculated on.
* @param len Length of the input data, in byte.
*
* @note If all of the data you are calculating the hash value on is available in a contiguous buffer in memory, this is
* the function you should use.
*
* @note If either of the passed pointers is NULL, the results are unpredictable.
*/
void calc_sha_256(uint8_t hash[SIZE_OF_SHA_256_HASH], const void *input, size_t len);
/*
* @brief Initialize a SHA-256 streaming calculation.
* @param sha_256 A pointer to a SHA-256 structure.
* @param hash Hash array, where the result will be delivered.
*
* @note If all of the data you are calculating the hash value on is not available in a contiguous buffer in memory, this is
* where you should start. Instantiate a SHA-256 structure, for instance by simply declaring it locally, make your hash
* buffer available, and invoke this function. Once a SHA-256 hash has been calculated (see further below) a SHA-256
* structure can be initialized again for the next calculation.
*
* @note If either of the passed pointers is NULL, the results are unpredictable.
*/
void hmac_sha256(uint8_t* hmac, char* msg, int msglen, char* key, int keylen);
void sha_256_init(struct Sha_256 *sha_256, uint8_t hash[SIZE_OF_SHA_256_HASH]);
/*
* @brief Stream more input data for an on-going SHA-256 calculation.
* @param sha_256 A pointer to a previously initialized SHA-256 structure.
* @param data Pointer to the data to be added to the calculation.
* @param len Length of the data to add, in byte.
*
* @note This function may be invoked an arbitrary number of times between initialization and closing, but the maximum
* data length is limited by the SHA-256 algorithm: the total number of bits (i.e. the total number of bytes times
* eight) must be representable by a 64-bit unsigned integer. While that is not a practical limitation, the results are
* unpredictable if that limit is exceeded.
*
* @note This function may be invoked on empty data (zero length), although that obviously will not add any data.
*
* @note If either of the passed pointers is NULL, the results are unpredictable.
*/
void sha_256_write(struct Sha_256 *sha_256, const void *data, size_t len);
/*
* @brief Conclude a SHA-256 streaming calculation, making the hash value available.
* @param sha_256 A pointer to a previously initialized SHA-256 structure.
* @return Pointer to the hash array, where the result is delivered.
*
* @note After this function has been invoked, the result is available in the hash buffer that initially was provided. A
* pointer to the hash value is returned for convenience, but you should feel free to ignore it: it is simply a pointer
* to the first byte of your initially provided hash array.
*
* @note If the passed pointer is NULL, the results are unpredictable.
*
* @note Invoking this function for a calculation with no data (the writing function has never been invoked, or it only
* has been invoked with empty data) is legal. It will calculate the SHA-256 value of the empty string.
*/
uint8_t *sha_256_close(struct Sha_256 *sha_256);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -549,6 +549,9 @@ node = {
# 'secret.snmppassword': {
# 'description': 'The password to use for SNMPv3 access to this node',
# },
'secret.selfapiarmtoken': {
'description': 'A one-time use shared secret to authenticate a node api token',
},
'secret.snmpcommunity': {
'description': ('SNMPv1 community string, it is highly recommended to'
'step up to SNMPv3'),

View File

@@ -21,6 +21,8 @@ import datetime
import eventlet
import eventlet.green.socket as socket
import eventlet.greenpool
import hashlib
import hmac
import os
import struct
@@ -31,6 +33,7 @@ import struct
# 3, len, token - echo reply
# 4, len, crypted - crypted apikey
# 5, 0, accept key
# 6, len, hmac - hmac of crypted key using shared secret for long-haul support
# 128, len, len, key - sealed key
class CredServer(object):
@@ -39,9 +42,9 @@ class CredServer(object):
def handle_client(self, client, peer):
try:
if not netutil.address_is_local(peer[0]):
client.close()
return
apiarmed = None
hmackey = None
hmacval = None
client.send(b'\xc2\xd1-\xa8\x80\xd8j\xba')
tlv = bytearray(client.recv(2))
if tlv[0] != 1:
@@ -49,28 +52,39 @@ class CredServer(object):
return
nodename = util.stringify(client.recv(tlv[1]))
tlv = bytearray(client.recv(2)) # should always be null
apimats = self.cfm.get_node_attributes(nodename,
['deployment.apiarmed', 'deployment.sealedapikey'])
apiarmed = apimats.get(nodename, {}).get('deployment.apiarmed', {}).get(
'value', None)
if not apiarmed:
if apimats.get(nodename, {}).get(
'deployment.sealedapikey', {}).get('value', None):
sealed = apimats[nodename]['deployment.sealedapikey'][
'value']
if not isinstance(sealed, bytes):
sealed = sealed.encode('utf8')
reply = b'\x80' + struct.pack('>H', len(sealed) + 1) + sealed + b'\x00'
client.send(reply)
client.close()
return
if apiarmed not in ('once', 'continuous'):
now = datetime.datetime.utcnow()
expiry = datetime.datetime.strptime(apiarmed, "%Y-%m-%dT%H:%M:%SZ")
if now > expiry:
self.cfm.set_node_attributes({nodename: {'deployment.apiarmed': ''}})
onlylocal = True
if tlv[0] == 6:
hmacval = client.recv(tlv[1])
hmackey = self.cfm.get_node_attributes(nodename, ['secret.selfapiarmtoken'], decrypt=True)
hmackey = hmackey.get(nodename, {}).get('secret.selfapiarmtoken', {}).get('value', None)
elif tlv[1]:
client.recv(tlv[1])
if not hmackey:
if not netutil.address_is_local(peer[0]):
client.close()
return
apimats = self.cfm.get_node_attributes(nodename,
['deployment.apiarmed', 'deployment.sealedapikey'])
apiarmed = apimats.get(nodename, {}).get('deployment.apiarmed', {}).get(
'value', None)
if not apiarmed:
if apimats.get(nodename, {}).get(
'deployment.sealedapikey', {}).get('value', None):
sealed = apimats[nodename]['deployment.sealedapikey'][
'value']
if not isinstance(sealed, bytes):
sealed = sealed.encode('utf8')
reply = b'\x80' + struct.pack('>H', len(sealed) + 1) + sealed + b'\x00'
client.send(reply)
client.close()
return
if apiarmed not in ('once', 'continuous'):
now = datetime.datetime.utcnow()
expiry = datetime.datetime.strptime(apiarmed, "%Y-%m-%dT%H:%M:%SZ")
if now > expiry:
self.cfm.set_node_attributes({nodename: {'deployment.apiarmed': ''}})
client.close()
return
client.send(b'\x02\x20')
rttoken = os.urandom(32)
client.send(rttoken)
@@ -88,7 +102,14 @@ class CredServer(object):
client.close()
return
echotoken = util.stringify(client.recv(tlv[1]))
if hmackey:
etok = echotoken.encode('utf8')
if hmacval != hmac.new(hmackey, etok, hashlib.sha256).digest():
client.close()
return
cfgupdate = {nodename: {'crypted.selfapikey': {'hashvalue': echotoken}, 'deployment.sealedapikey': '', 'deployment.apiarmed': ''}}
if hmackey:
self.cfm.clear_node_attributes([nodename], ['secret.selfapiarmtoken'])
if apiarmed == 'continuous':
del cfgupdate[nodename]['deployment.apiarmed']
self.cfm.set_node_attributes(cfgupdate)