From ad42d99e0b23f4e8960c882e356eb0add221e644 Mon Sep 17 00:00:00 2001 From: Lucas Gabriel Vuotto Date: Thu, 6 Jun 2024 12:41:44 +0000 Subject: [PATCH] auth: add HMAC implementation This allows for checking the SHA-512 implementation against Wycheproof via the HMAC tests. --- Makefile.inc | 2 +- README | 7 +- auth_hmac.c | 178 +++++++++++++++++++++++++++++++++ auth_hmac.h | 23 +++++ impl_hmac.h | 30 ++++++ lilcrypto.h | 2 + wycheproof/Makefile | 18 +++- wycheproof/mac.pl | 66 +++++++++++++ wycheproof_mac.c | 233 ++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 554 insertions(+), 5 deletions(-) create mode 100644 auth_hmac.c create mode 100644 auth_hmac.h create mode 100644 impl_hmac.h create mode 100644 wycheproof/mac.pl create mode 100644 wycheproof_mac.c diff --git a/Makefile.inc b/Makefile.inc index 2d7d972..295414f 100644 --- a/Makefile.inc +++ b/Makefile.inc @@ -1,5 +1,5 @@ LC_SRCS+= aead.c aead_chacha20_poly1305.c -LC_SRCS+= auth.c auth_poly1305.c +LC_SRCS+= auth.c auth_hmac.c auth_poly1305.c LC_SRCS+= cipher.c cipher_chacha20.c LC_SRCS+= hash.c hash_sha384_sha512.c LC_SRCS+= impl_chacha20.c impl_poly1305.c impl_sha512.c diff --git a/README b/README index 1b7e3dc..d92793e 100644 --- a/README +++ b/README @@ -28,6 +28,7 @@ Hash Authentication -------------- +- [x] HMAC - [x] Poly1305 Ciphers @@ -86,5 +87,7 @@ AEAD - [ ] Salsa20-Poly1305 (no Wycherproof test vector suite) - [ ] XSalsa20-Poly1305 (no Wycherproof test vector suite) -KDF? ----- +KDF +--- + +- [ ] HKDF diff --git a/auth_hmac.c b/auth_hmac.c new file mode 100644 index 0000000..deb61fb --- /dev/null +++ b/auth_hmac.c @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2024 Lucas Gabriel Vuotto + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include "lilcrypto.h" +#include "auth.h" +#include "hash.h" +#include "auth_hmac.h" +#include "impl_hmac.h" +#include "impl_sha512.h" + +#include "util.h" + + +#define HMAC_IPAD UINT8_C(0x36) +#define HMAC_OPAD UINT8_C(0x5c) + + +static int +hmac_common_init(void *arg, const uint8_t *key, size_t keylen) +{ + struct hmac_ctx *ctx = arg; + uint8_t ikeypad[HMAC_BLOCKSZ_MAX]; + size_t i, olen; + + if (keylen > ctx->blocksz) { + if (!lc_hash_init(ctx->hctx) || + !lc_hash_update(ctx->hctx, key, keylen) || + !lc_hash_final(ctx->hctx, ctx->key, &olen)) + return 0; + } else { + for (i = 0; i < keylen; i++) + ctx->key[i] = key[i]; + for (; i < ctx->blocksz; i++) + ctx->key[i] = 0; + } + + for (i = 0; i < ctx->blocksz; i++) + ikeypad[i] = ctx->key[i] ^ HMAC_IPAD; + + return lc_hash_init(ctx->hctx) && + lc_hash_update(ctx->hctx, ikeypad, ctx->blocksz); +} + +int +hmac_sha384_sha512_init(void *arg, const uint8_t *key, size_t keylen) +{ + struct hmac_ctx *ctx = arg; + + ctx->blocksz = SHA512_CHUNK; + + return hmac_common_init(ctx, key, keylen); +} + +int +hmac_update(void *arg, const uint8_t *in, size_t inlen) +{ + struct hmac_ctx *ctx = arg; + + return lc_hash_update(ctx->hctx, in, inlen); +} + +int +hmac_final(void *arg, uint8_t *out, size_t *outlen) +{ + struct hmac_ctx *ctx = arg; + struct lc_hash_ctx *hctx; + uint8_t m[HMAC_BLOCKSZ_MAX], okeypad[HMAC_BLOCKSZ_MAX]; + size_t i, olen; + int rc; + + if (out == NULL) { + (void)lc_hash_final(ctx->hctx, NULL, outlen); + return 1; + } + + hctx = ctx->hctx; + + *outlen = 0; + for (i = 0; i < ctx->blocksz; i++) + okeypad[i] = ctx->key[i] ^ HMAC_OPAD; + + rc = lc_hash_final(ctx->hctx, m, &olen) && + lc_hash_init(ctx->hctx) && + lc_hash_update(ctx->hctx, okeypad, ctx->blocksz) && + lc_hash_update(ctx->hctx, m, olen) && + lc_hash_final(ctx->hctx, out, outlen); + + lc_scrub(ctx, sizeof(*ctx)); + ctx->hctx = hctx; + + return rc; +} + +static void * +hmac_common_ctx_new(const struct lc_hash_impl *impl) +{ + struct hmac_ctx *ctx; + + ctx = malloc(sizeof(*ctx)); + if (ctx == NULL) + return NULL; + ctx->hctx = lc_hash_ctx_new(impl); + if (ctx->hctx == NULL) { + free(ctx); + return NULL; + } + + return ctx; +} + +static void * +hmac_sha384_ctx_new(void) +{ + return hmac_common_ctx_new(lc_hash_impl_sha384()); +} + +static void * +hmac_sha512_ctx_new(void) +{ + return hmac_common_ctx_new(lc_hash_impl_sha512()); +} + +static void +hmac_ctx_free(void *arg) +{ + struct hmac_ctx *ctx = arg; + + if (ctx != NULL) + lc_hash_ctx_free(ctx->hctx); +} + + +static struct lc_auth_impl hmac_sha384_impl = { + .init = &hmac_sha384_sha512_init, + .update = &hmac_update, + .final = &hmac_final, + .auth = NULL, + + .ctx_new = &hmac_sha384_ctx_new, + .ctx_free = &hmac_ctx_free, +}; + +static struct lc_auth_impl hmac_sha512_impl = { + .init = &hmac_sha384_sha512_init, + .update = &hmac_update, + .final = &hmac_final, + .auth = NULL, + + .ctx_new = &hmac_sha512_ctx_new, + .ctx_free = &hmac_ctx_free, +}; + +const struct lc_auth_impl * +lc_auth_impl_hmac_sha384(void) +{ + return &hmac_sha384_impl; +} + +const struct lc_auth_impl * +lc_auth_impl_hmac_sha512(void) +{ + return &hmac_sha512_impl; +} diff --git a/auth_hmac.h b/auth_hmac.h new file mode 100644 index 0000000..f5424cd --- /dev/null +++ b/auth_hmac.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2024 Lucas Gabriel Vuotto + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + + +int hmac_sha384_sha512_init(void *, const uint8_t *, size_t); +int hmac_update(void *, const uint8_t *, size_t); +int hmac_final(void *, uint8_t *, size_t *); diff --git a/impl_hmac.h b/impl_hmac.h new file mode 100644 index 0000000..ce96b24 --- /dev/null +++ b/impl_hmac.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024 Lucas Gabriel Vuotto + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include "lilcrypto.h" + + +#define HMAC_BLOCKSZ_MAX 128 + + +struct hmac_ctx { + struct lc_hash_ctx *hctx; + size_t blocksz; + uint8_t key[HMAC_BLOCKSZ_MAX]; +}; diff --git a/lilcrypto.h b/lilcrypto.h index e8d1f5b..70daec4 100644 --- a/lilcrypto.h +++ b/lilcrypto.h @@ -82,6 +82,8 @@ struct lc_auth_ctx *lc_auth_ctx_new(const struct lc_auth_impl *); void lc_auth_ctx_free(struct lc_auth_ctx *); const struct lc_auth_impl *lc_auth_impl_poly1305(void); +const struct lc_auth_impl *lc_auth_impl_hmac_sha384(void); +const struct lc_auth_impl *lc_auth_impl_hmac_sha512(void); /* diff --git a/wycheproof/Makefile b/wycheproof/Makefile index 56e9c3f..3f15ee7 100644 --- a/wycheproof/Makefile +++ b/wycheproof/Makefile @@ -1,17 +1,19 @@ .PATH: ${.CURDIR}/.. AEAD= wycheproof_aead +MAC= wycheproof_mac -PROGS= ${AEAD} +PROGS= ${AEAD} ${MAC} NOMAN= noman SRCS_wycheproof_aead= wycheproof_aead.c +SRCS_wycheproof_mac= wycheproof_mac.c DPADD+= ${.CURDIR}/../lib/obj/liblilcrypto.a LDADD+= ${.CURDIR}/../lib/obj/liblilcrypto.a -tests: all tests-aead +tests: all tests-aead tests-mac tests-aead: .ifndef WYCHEPROOF_DIR @@ -23,4 +25,16 @@ tests-aead: ${WYCHEPROOF_DIR}/testvectors_v1/chacha20_poly1305_test.json .endfor +tests-mac: +.ifndef WYCHEPROOF_DIR + @echo Undefined WYCHEPROOF_DIR; false +.endif +.for p in ${MAC} + perl ${.CURDIR}/mac.pl ${TESTOPTS} -x ./${p} \ + ${WYCHEPROOF_DIR}/testvectors/hmac_sha384_test.json \ + ${WYCHEPROOF_DIR}/testvectors_v1/hmac_sha384_test.json \ + ${WYCHEPROOF_DIR}/testvectors/hmac_sha512_test.json \ + ${WYCHEPROOF_DIR}/testvectors_v1/hmac_sha512_test.json +.endfor + .include diff --git a/wycheproof/mac.pl b/wycheproof/mac.pl new file mode 100644 index 0000000..d53b43e --- /dev/null +++ b/wycheproof/mac.pl @@ -0,0 +1,66 @@ +#!/usr/bin/env perl +use v5.38;; +use strict; +use warnings; + +use Getopt::Std; +use JSON::PP; + +my $progname = $0 =~ s@.*/@@r; + +sub slurp ($fh) { local $/; <$fh> } + +sub usage () +{ + say STDERR "Usage: $progname -x runner json_file [json_files ...]"; + exit 1; +} + +sub main () +{ + my %opts; + my $rc = 0; + + getopts("vx:", \%opts) && @ARGV > 0 or usage; + usage unless defined $opts{"x"}; + + for my $f (@ARGV) { + open(my $fh, "<", $f) or die "open failed: $!"; + + my $json = decode_json(slurp($fh)); + for my $testgroup ($json->{testGroups}->@*) { + for my $test ($testgroup->{tests}->@*) { + my @args; + + push(@args, $json->{algorithm}); + push(@args, "-K", $testgroup->{keySize}); + push(@args, "-k", $test->{key}); + push(@args, "-m", $test->{msg}); + push(@args, "-T", $testgroup->{tagSize}); + push(@args, "-t", $test->{tag}); + push(@args, "-v") if $opts{"v"}; + + open(my $th, "-|", $opts{"x"}, @args) or die; + my $result = slurp($th); + close($th); + + chomp($result); + if ($result ne $test->{result}) { + $rc = 1; + say STDERR "case $test->{tcId}: ", + "expected $test->{result}: ", + "$test->{comment} [", + join(",", $test->{flags}->@*), + "]"; + } + } + } + + close($fh); + } + + say "ALL TESTS PASSED!" if $rc == 0; + return $rc; +} + +exit main; diff --git a/wycheproof_mac.c b/wycheproof_mac.c new file mode 100644 index 0000000..7caab43 --- /dev/null +++ b/wycheproof_mac.c @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2024 Lucas Gabriel Vuotto + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "lilcrypto.h" + + +#define nelems(_a) (sizeof((_a)) / sizeof((_a)[0])) + + +static inline uint8_t +hex2num(char s) +{ + return s >= 'A' ? 10 + (s >= 'a' ? s - 'a' : s - 'A') : s - '0'; +} + +static int +hexparse(const char *s, uint8_t *out, size_t *outlen) +{ + size_t l; + + l = strlen(s); + if (l % 2 != 0) + return 0; + + if (out == NULL) { + *outlen = l / 2; + return 1; + } + + *outlen = 0; + while (*s != '\0') { + if (!isxdigit(s[0]) || !isxdigit(s[1])) + return 0; + *out++ = (hex2num(s[0]) << 4) | hex2num(s[1]); + (*outlen)++; + s += 2; + } + + return 1; +} + +struct kwimpl { + const char *kw; + const struct lc_auth_impl *(*impl)(void); +}; + +static int +kwimpl_cmp(const void *k0, const void *h0) +{ + const struct kwimpl *h = h0; + const char *k = k0; + + return strcmp(k, h->kw); +} + +static const struct lc_auth_impl * +kw2impl(const char *s) +{ + /* Needs to be sorted. */ + static const struct kwimpl tbl[] = { + { "HMACSHA384", &lc_auth_impl_hmac_sha384 }, + { "HMACSHA512", &lc_auth_impl_hmac_sha512 }, + }; + struct kwimpl *match; + + match = bsearch(s, tbl, nelems(tbl), sizeof(struct kwimpl), + &kwimpl_cmp); + + return match != NULL ? match->impl() : NULL; +} + +static void +usage(void) +{ + fprintf(stderr, "Usage: %s [options]\n", getprogname()); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + const struct lc_auth_impl *impl; + struct lc_auth_ctx *ctx; + uint8_t *key, *msg, *tag, *buf; + const char *errstr; + size_t keylen, msglen, taglen; + size_t keylenarg, taglenarg; + size_t l, olen; + int Kflag, kflag, mflag, Tflag, tflag; + int ch, verbose; + + if (argc < 2) + usage(); + + impl = kw2impl(argv[1]); + if (impl == NULL) + errx(1, "unsupported algorithm: %s", argv[1]); + + optind = 2; + Kflag = kflag = mflag = Tflag = tflag = 0; + verbose = 0; + while ((ch = getopt(argc, argv, "K:k:m:T:t:v")) != -1) { + switch (ch) { + case 'K': + Kflag = 1; + keylenarg = strtonum(optarg, 0, LLONG_MAX, &errstr); + if (errstr != NULL) + errx(1, "keylen is %s: %s", errstr, optarg); + if (keylenarg % 8 != 0) + errx(1, "unsupport K value: %zu", keylenarg); + keylenarg /= 8; + break; + case 'k': + kflag = 1; + (void)hexparse(optarg, NULL, &keylen); + if (keylen != 0) { + key = malloc(keylen); + if (key == NULL) + err(1, "out of memory"); + } else + key = NULL; + if (!hexparse(optarg, key, &l) || l != keylen) + errx(1, "invalid hex string: %s", optarg); + break; + case 'm': + mflag = 1; + (void)hexparse(optarg, NULL, &msglen); + if (msglen != 0) { + msg = malloc(msglen); + if (msg == NULL) + err(1, "out of memory"); + } else + msg = NULL; + if (!hexparse(optarg, msg, &l) || l != msglen) + errx(1, "invalid hex string: %s", optarg); + break; + case 'T': + Tflag = 1; + taglenarg = strtonum(optarg, 0, LLONG_MAX, &errstr); + if (errstr != NULL) + errx(1, "taglen is %s: %s", errstr, optarg); + taglenarg /= 8; + break; + case 't': + tflag = 1; + (void)hexparse(optarg, NULL, &taglen); + if (taglen != 0) { + tag = malloc(taglen); + if (tag == NULL) + err(1, "out of memory"); + } else + tag = NULL; + if (!hexparse(optarg, tag, &l) || l != taglen) + errx(1, "invalid hex string: %s", optarg); + break; + case 'v': + verbose = 1; + break; + default: + usage(); + break; + } + } + argc -= optind; + argv += optind; + + if (!(Kflag && kflag && mflag && Tflag && tflag)) + errx(1, "missing required arguments"); + + ctx = lc_auth_ctx_new(impl); + if (ctx == NULL) + errx(1, "can't allocate ctx"); + if (!lc_auth_init(ctx, key, keylenarg) || + !lc_auth_update(ctx, msg, msglen) || + !lc_auth_final(ctx, NULL, &olen)) { + puts("invalid"); + return 1; + } + + buf = malloc(olen); + if (buf == NULL) + err(1, "out of memory"); + + if (!lc_auth_final(ctx, buf, &olen)) { + puts("invalid"); + return 1; + } + + /* + * Tests include truncated output. Skip checking olen as it'll always + * be the full-length hash. + */ + if (taglen != taglenarg || + lc_ct_cmp(buf, tag, taglen) != 0) { + if (verbose) { + fprintf(stderr, "tag (%zu, %zu, %zu)\n", taglen, + taglenarg, olen); + lc_hexdump_fp(stderr, tag, taglen); + fprintf(stderr, "\n"); + lc_hexdump_fp(stderr, buf, olen); + fprintf(stderr, "\n"); + } + puts("invalid"); + return 1; + } + + free(buf); + lc_auth_ctx_free(ctx); + + puts("valid"); + return 0; +}