From 21dec54769b2d8d80e57765b345af5d092639376 Mon Sep 17 00:00:00 2001 From: Garrett D'Amore Date: Tue, 5 Dec 2017 14:22:45 -0800 Subject: Added SHA1 implementation and test suite. --- src/CMakeLists.txt | 1 + src/supplemental/sha1/CMakeLists.txt | 12 ++ src/supplemental/sha1/sha1.c | 247 +++++++++++++++++++++++++++++++++++ src/supplemental/sha1/sha1.h | 26 ++++ tests/CMakeLists.txt | 1 + tests/sha1.c | 63 +++++++++ 6 files changed, 350 insertions(+) create mode 100644 src/supplemental/sha1/CMakeLists.txt create mode 100644 src/supplemental/sha1/sha1.c create mode 100644 src/supplemental/sha1/sha1.h create mode 100644 tests/sha1.c diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2ab202d9..93fa390d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -121,6 +121,7 @@ endif() add_subdirectory(supplemental/base64) add_subdirectory(supplemental/mbedtls) +add_subdirectory(supplemental/sha1) add_subdirectory(protocol/bus0) add_subdirectory(protocol/pair0) diff --git a/src/supplemental/sha1/CMakeLists.txt b/src/supplemental/sha1/CMakeLists.txt new file mode 100644 index 00000000..8496777b --- /dev/null +++ b/src/supplemental/sha1/CMakeLists.txt @@ -0,0 +1,12 @@ +# +# Copyright 2017 Capitar IT Group BV +# Copyright 2017 Staysail Systems, Inc. +# +# This software is supplied under the terms of the MIT License, a +# copy of which should be located in the distribution where this +# file was obtained (LICENSE.txt). A copy of the license may also be +# found online at https://opensource.org/licenses/MIT. +# + +set(SHA1_SOURCES supplemental/sha1/sha1.c supplemental/sha1/sha1.h) +set(NNG_SOURCES ${NNG_SOURCES} ${SHA1_SOURCES} PARENT_SCOPE) diff --git a/src/supplemental/sha1/sha1.c b/src/supplemental/sha1/sha1.c new file mode 100644 index 00000000..39a98e07 --- /dev/null +++ b/src/supplemental/sha1/sha1.c @@ -0,0 +1,247 @@ +// +// Copyright 2017 Staysail Systems, Inc. +// Copyright 2017 Capitar IT Group BV +// +// This software is supplied under the terms of the MIT License, a +// copy of which should be located in the distribution where this +// file was obtained (LICENSE.txt). A copy of the license may also be +// found online at https://opensource.org/licenses/MIT. +// + +// This file represents a modification of Paul E. Jones' implementation. +// We have adapted this code for C99, removed the error checks on input size, +// and adjusted names to fit within NNG. We also updated the code to emit +// the digest as a byte array, following convention. The original code was +// distributed with the following notice: + +// Copyright (C) 1998, 2009 +// Paul E. Jones +// +// Freeware Public License (FPL) +// +// This software is licensed as "freeware." Permission to distribute +// this software in source and binary forms, including incorporation +// into other products, is hereby granted without a fee. THIS SOFTWARE +// IS PROVIDED 'AS IS' AND WITHOUT ANY EXPRESSED OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS FOR A PARTICULAR PURPOSE. THE AUTHOR SHALL NOT BE HELD +// LIABLE FOR ANY DAMAGES RESULTING FROM THE USE OF THIS SOFTWARE, EITHER +// DIRECTLY OR INDIRECTLY, INCLUDING, BUT NOT LIMITED TO, LOSS OF DATA +// OR DATA BEING RENDERED INACCURATE. + +// This file implements the Secure Hashing Standard, defined in FIPS PUB 180-1 +// and RFC 3174. This particular implementation has not undergone any NIST +// validation. Furthermore, SHA-1 has been found to be insufficiently strong +// against cryptanalysis, and it's use is specifically discouraged in new +// security-sensitive applications. Nonetheless, it is useful for non-secure +// applications such as basic message validation. In the websocket protocol +// (RFC 6455), SHA-1's use is limited to a non-security sensitive context. + +// This implementation assumes an 8-bit byte, and a C99 compilation +// environment including support for 64-bit integers. It does not +// detect overflows caused by issuing too large messages (2^56 bytes is the +// maximum message size) or caused by incorrect usage. The results in either +// of those circumstances are undefined. + +#include +#include + +#include "sha1.h" + +// Define the circular shift macro +#define nni_sha1_circular_shift(bits, word) \ + ((((word) << (bits)) & 0xFFFFFFFF) | ((word) >> (32 - (bits)))) + +static void nni_sha1_process(nni_sha1_ctx *); +static void nni_sha1_pad(nni_sha1_ctx *); + +// nni_sha1_init initializes the context to an initial value. +void +nni_sha1_init(nni_sha1_ctx *ctx) +{ + ctx->len = 0; + ctx->idx = 0; + + ctx->digest[0] = 0x67452301; + ctx->digest[1] = 0xEFCDAB89; + ctx->digest[2] = 0x98BADCFE; + ctx->digest[3] = 0x10325476; + ctx->digest[4] = 0xC3D2E1F0; +} + +// nni_sha1_final runs the final padding for the digest, and stores +// the resulting digest in the supplied output buffer. +void +nni_sha1_final(nni_sha1_ctx *ctx, uint8_t digest[20]) +{ + nni_sha1_pad(ctx); + for (int i = 0; i < 5; i++) { + digest[i * 4] = (ctx->digest[i] >> 24) & 0xff; + digest[i * 4 + 1] = (ctx->digest[i] >> 16) & 0xff; + digest[i * 4 + 2] = (ctx->digest[i] >> 8) & 0xff; + digest[i * 4 + 3] = (ctx->digest[i] >> 0) & 0xff; + } +} + +// nni_sha1 is a convenience that does the entire init, update, and final +// sequence in a single operation. +void +nni_sha1(const uint8_t *msg, size_t length, uint8_t digest[20]) +{ + nni_sha1_ctx ctx; + + nni_sha1_init(&ctx); + nni_sha1_update(&ctx, msg, length); + nni_sha1_final(&ctx, digest); +} + +// nni_sha1_update updates the SHA1 context, reading from the message supplied. +void +nni_sha1_update(nni_sha1_ctx *ctx, const uint8_t *msg, size_t length) +{ + if (!length) { + return; + } + + while (length--) { + // memcpy might be faster... + ctx->blk[ctx->idx++] = (*msg & 0xFF); + ctx->len += 8; + + if (ctx->idx == 64) { + // This will reset the index back to zero. + nni_sha1_process(ctx); + } + + msg++; + } +} + +// nni_sha1_process processes the next 512 bites of the message stored +// in the blk array. +void +nni_sha1_process(nni_sha1_ctx *ctx) +{ + const unsigned K[] = // Constants defined in SHA-1 + { 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6 }; + unsigned temp; // Temporary word value + unsigned W[80]; // Word sequence + unsigned A, B, C, D, E; // Word buffers + + // Initialize the first 16 words in the array W + for (int t = 0; t < 16; t++) { + W[t] = ((unsigned) ctx->blk[t * 4]) << 24; + W[t] |= ((unsigned) ctx->blk[t * 4 + 1]) << 16; + W[t] |= ((unsigned) ctx->blk[t * 4 + 2]) << 8; + W[t] |= ((unsigned) ctx->blk[t * 4 + 3]); + } + + for (int t = 16; t < 80; t++) { + W[t] = nni_sha1_circular_shift( + 1, W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16]); + } + + A = ctx->digest[0]; + B = ctx->digest[1]; + C = ctx->digest[2]; + D = ctx->digest[3]; + E = ctx->digest[4]; + + for (int t = 0; t < 20; t++) { + temp = nni_sha1_circular_shift(5, A) + ((B & C) | ((~B) & D)) + + E + W[t] + K[0]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = nni_sha1_circular_shift(30, B); + B = A; + A = temp; + } + + for (int t = 20; t < 40; t++) { + temp = nni_sha1_circular_shift(5, A) + (B ^ C ^ D) + E + W[t] + + K[1]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = nni_sha1_circular_shift(30, B); + B = A; + A = temp; + } + + for (int t = 40; t < 60; t++) { + temp = nni_sha1_circular_shift(5, A) + + ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = nni_sha1_circular_shift(30, B); + B = A; + A = temp; + } + + for (int t = 60; t < 80; t++) { + temp = nni_sha1_circular_shift(5, A) + (B ^ C ^ D) + E + W[t] + + K[3]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = nni_sha1_circular_shift(30, B); + B = A; + A = temp; + } + + ctx->digest[0] = (ctx->digest[0] + A) & 0xFFFFFFFF; + ctx->digest[1] = (ctx->digest[1] + B) & 0xFFFFFFFF; + ctx->digest[2] = (ctx->digest[2] + C) & 0xFFFFFFFF; + ctx->digest[3] = (ctx->digest[3] + D) & 0xFFFFFFFF; + ctx->digest[4] = (ctx->digest[4] + E) & 0xFFFFFFFF; + + ctx->idx = 0; +} + +// nni_sha1_pad pads the message, adding the length. This is done +// when finishing the message. +// +// According to the standard, the message must be padded to an even 512 bits. +// The first padding bit must be a '1'. The last 64 bits represent the length +// of the original message. All bits in between should be 0. This function +// will pad the message according to those rules by filling the blk array +// accordingly. It will also call nni_sha1_process() appropriately. When it +// returns, it can be assumed that the message digest has been computed. +void +nni_sha1_pad(nni_sha1_ctx *ctx) +{ + // Check to see if the current message block is too small to hold + // the initial padding bits and length. If so, we will pad the + // block, process it, and then continue padding into a second block. + if (ctx->idx > 55) { + ctx->blk[ctx->idx++] = 0x80; + while (ctx->idx < 64) { + ctx->blk[ctx->idx++] = 0; + } + + nni_sha1_process(ctx); + + while (ctx->idx < 56) { + ctx->blk[ctx->idx++] = 0; + } + } else { + ctx->blk[ctx->idx++] = 0x80; + while (ctx->idx < 56) { + ctx->blk[ctx->idx++] = 0; + } + } + + // Store the message length as the last 8 octets (big endian) + ctx->blk[56] = (ctx->len >> 56) & 0xff; + ctx->blk[57] = (ctx->len >> 48) & 0xff; + ctx->blk[58] = (ctx->len >> 40) & 0xff; + ctx->blk[59] = (ctx->len >> 32) & 0xff; + ctx->blk[60] = (ctx->len >> 24) & 0xff; + ctx->blk[61] = (ctx->len >> 16) & 0xff; + ctx->blk[62] = (ctx->len >> 8) & 0xff; + ctx->blk[63] = (ctx->len) & 0xff; + + nni_sha1_process(ctx); +} diff --git a/src/supplemental/sha1/sha1.h b/src/supplemental/sha1/sha1.h new file mode 100644 index 00000000..91554dd7 --- /dev/null +++ b/src/supplemental/sha1/sha1.h @@ -0,0 +1,26 @@ +// +// Copyright 2017 Staysail Systems, Inc. +// Copyright 2017 Capitar IT Group BV +// +// This software is supplied under the terms of the MIT License, a +// copy of which should be located in the distribution where this +// file was obtained (LICENSE.txt). A copy of the license may also be +// found online at https://opensource.org/licenses/MIT. +// + +#ifndef NNG_SUPPLEMENTAL_SHA1_SHA1_H +#define NNG_SUPPLEMENTAL_SHA1_SHA1_H + +typedef struct { + uint32_t digest[5]; // resulting digest + uint64_t len; // length in bits + uint8_t blk[64]; // message block + int idx; // index of next byte in block +} nni_sha1_ctx; + +extern void nni_sha1_init(nni_sha1_ctx *); +extern void nni_sha1_update(nni_sha1_ctx *, const uint8_t *, size_t); +extern void nni_sha1_final(nni_sha1_ctx *, uint8_t[20]); +extern void nn_sha1(const uint8_t *, size_t, uint8_t[20]); + +#endif // NNG_SUPPLEMENTAL_SHA1_SHA1_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index aa6905f2..77eb0bc5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -136,6 +136,7 @@ add_nng_test(pollfd 5) add_nng_test(pubsub 5) add_nng_test(reconnect 5) add_nng_test(resolv 10) +add_nng_test(sha1 5) add_nng_test(sock 5) add_nng_test(survey 5) add_nng_test(synch 5) diff --git a/tests/sha1.c b/tests/sha1.c new file mode 100644 index 00000000..f26a5b9c --- /dev/null +++ b/tests/sha1.c @@ -0,0 +1,63 @@ +// +// Copyright 2017 Staysail Systems, Inc. +// Copyright 2017 Capitar IT Group BV +// +// This software is supplied under the terms of the MIT License, a +// copy of which should be located in the distribution where this +// file was obtained (LICENSE.txt). A copy of the license may also be +// found online at https://opensource.org/licenses/MIT. +// + +#include +#include + +#include "convey.h" + +#include "nng.h" + +#include "supplemental/sha1/sha1.h" + +// The following test vectors are from RFC 3174. +#define TEST1 "abc" +#define TEST2a "abcdbcdecdefdefgefghfghighijhi" +#define TEST2b "jkijkljklmklmnlmnomnopnopq" +#define TEST2 TEST2a TEST2b +#define TEST3 "a" +#define TEST4a "01234567012345670123456701234567" +#define TEST4b "01234567012345670123456701234567" +/* an exact multiple of 512 bits */ +#define TEST4 TEST4a TEST4b + +char *testarray[4] = { TEST1, TEST2, TEST3, TEST4 }; +int repeatcount[4] = { 1, 1, 1000000, 10 }; +char *resultarray[4] = { + "A9 99 3E 36 47 06 81 6A BA 3E 25 71 78 50 C2 6C 9C D0 D8 9D", + "84 98 3E 44 1C 3B D2 6E BA AE 4A A1 F9 51 29 E5 E5 46 70 F1", + "34 AA 97 3C D4 C4 DA A4 F6 1E EB 2B DB AD 27 31 65 34 01 6F", + "DE A3 56 A2 CD DD 90 C7 A7 EC ED C5 EB B5 63 93 4F 46 04 52" +}; + +TestMain("SHA1 Verification", { + + Convey("SHA1 Works", { + for (int i = 0; i < 4; i++) { + nni_sha1_ctx ctx; + size_t slen = strlen(testarray[i]); + uint8_t digest[20]; + char strout[20 * 3 + 1]; + memset(digest, 0, sizeof(digest)); + nni_sha1_init(&ctx); + for (int j = 0; j < repeatcount[i]; j++) { + nni_sha1_update( + &ctx, (uint8_t *) testarray[i], slen); + } + nni_sha1_final(&ctx, digest); + for (int j = 0; j < 20; j++) { + snprintf( + strout + j * 3, 4, "%02X ", digest[j]); + } + strout[20 * 3 - 1] = '\0'; + So(strcmp(strout, resultarray[i]) == 0); + } + }); +}); -- cgit v1.2.3-70-g09d2