1 /*
   2  * This file and its contents are supplied under the terms of the
   3  * Common Development and Distribution License (), version 1.0.
   4  * You may only use this file in accordance with the terms of version
   5  * 1.0 of the CDDL.
   6  *
   7  * A full copy of the text of the CDDL should have accompanied this
   8  * source.  A copy of the CDDL is also available via the Internet at
   9  * http://www.illumos.org/license/CDDL.
  10  */
  11 
  12 /*
  13  * Copyright 2019 Joyent, Inc.
  14  * Copyright 2020 Oxide Computer Company
  15  */
  16 
  17 #include <stdio.h>
  18 #include <stdlib.h>
  19 #include <string.h>
  20 #include <strings.h>
  21 #include <errno.h>
  22 #include <err.h>
  23 
  24 #include <sys/debug.h>
  25 
  26 #include "cryptotest.h"
  27 #include "parser_runner.h"
  28 
  29 #define DATA_PATH       "/opt/crypto-tests/share"
  30 
  31 /*
  32  * Parse NIST test vector data into a format that is simple to run the digest
  33  * tests against.  The parsing logic is not meant to be especially robust given
  34  * that we control the data fed into it.
  35  */
  36 
  37 struct parser_ctx {
  38         FILE    *pc_file;
  39         size_t  pc_hash_sz;
  40         char    *pc_line_buf;
  41         size_t  pc_line_sz;
  42 };
  43 
  44 parser_ctx_t *
  45 parser_init(const char *path, size_t hash_len, int *errp)
  46 {
  47         FILE *fp;
  48         parser_ctx_t *ctx;
  49 
  50         /* sanity check for SHA1 -> SHA512 */
  51         ASSERT(hash_len >= 20 && hash_len <= 64);
  52 
  53         fp = fopen(path, "r");
  54         if (fp == NULL) {
  55                 *errp = errno;
  56                 return (NULL);
  57         }
  58         ctx = malloc(sizeof (*ctx));
  59         if (ctx == NULL) {
  60                 *errp = ENOMEM;
  61                 (void) fclose(fp);
  62                 return (NULL);
  63         }
  64         ctx->pc_file = fp;
  65         ctx->pc_hash_sz = hash_len;
  66         ctx->pc_line_buf = NULL;
  67         ctx->pc_line_sz = 0;
  68 
  69         return (ctx);
  70 }
  71 
  72 void
  73 parser_fini(parser_ctx_t *ctx)
  74 {
  75         free(ctx->pc_line_buf);
  76         (void) fclose(ctx->pc_file);
  77         free(ctx);
  78 }
  79 
  80 static size_t
  81 hex2bytes(const char *hexbuf, size_t hexlen, uchar_t *outbuf, size_t outlen)
  82 {
  83         size_t count = 0;
  84         /* naive and lazy conversion */
  85         errno = 0;
  86         while (hexlen > 1) {
  87                 long res;
  88                 char buf[3] = {hexbuf[0], hexbuf[1], '\0'};
  89 
  90                 res = strtol(buf, NULL, 16);
  91                 if (errno != 0) {
  92                         break;
  93                 }
  94                 *outbuf = res & 0xff;
  95 
  96                 hexbuf += 2;
  97                 hexlen -= 2;
  98                 outbuf += 1;
  99                 outlen += 1;
 100                 count++;
 101 
 102                 if (outbuf == 0) {
 103                         break;
 104                 }
 105         }
 106 
 107         return (count);
 108 }
 109 
 110 static int
 111 read_len(parser_ctx_t *ctx, size_t *lenp, size_t *szp)
 112 {
 113         ssize_t sz;
 114         long parsed;
 115         const char *search = "Len = ";
 116         const size_t search_len = strlen(search);
 117 
 118         errno = 0;
 119         sz = getline(&ctx->pc_line_buf, &ctx->pc_line_sz, ctx->pc_file);
 120         if (sz < 1) {
 121                 int err = errno;
 122 
 123                 if (err == 0 || err == ENOENT) {
 124                         /* EOF reached, bail */
 125                         return (-1);
 126                 } else {
 127                         return (err);
 128                 }
 129         }
 130         *szp = sz;
 131         if (strncmp(ctx->pc_line_buf, search, search_len) != 0) {
 132                 return (-1);
 133         }
 134 
 135         errno = 0;
 136         parsed = strtol(ctx->pc_line_buf + search_len, NULL, 10);
 137         if (parsed == 0 && errno != 0) {
 138                 return (errno);
 139         }
 140         if (parsed < 0) {
 141                 return (EINVAL);
 142         }
 143 
 144         /* length in file is in bits, while we want bytes */
 145         *lenp = (size_t)parsed / 8;
 146         return (0);
 147 }
 148 
 149 static int
 150 read_msg(parser_ctx_t *ctx, uchar_t *msgbuf, size_t msglen)
 151 {
 152         ssize_t sz;
 153         const char *search = "Msg = ";
 154         const size_t search_len = strlen(search);
 155 
 156         sz = getline(&ctx->pc_line_buf, &ctx->pc_line_sz, ctx->pc_file);
 157         if (sz < 0) {
 158                 return (errno);
 159         }
 160         if (strncmp(ctx->pc_line_buf, search, search_len) != 0) {
 161                 return (-1);
 162         }
 163 
 164         if (msgbuf == NULL) {
 165                 ASSERT(msglen == 0);
 166                 return (0);
 167         }
 168 
 169         size_t parsed;
 170         parsed = hex2bytes(ctx->pc_line_buf + search_len, sz - search_len,
 171             msgbuf, msglen);
 172         if (parsed != msglen) {
 173                 ASSERT3U(parsed, <, msglen);
 174                 return (-1);
 175         }
 176 
 177         return (0);
 178 }
 179 
 180 static int
 181 read_md(parser_ctx_t *ctx, uchar_t *mdbuf, size_t mdlen)
 182 {
 183         ssize_t sz;
 184         const char *search = "MD = ";
 185         const size_t search_len = strlen(search);
 186 
 187         sz = getline(&ctx->pc_line_buf, &ctx->pc_line_sz, ctx->pc_file);
 188         if (sz < 0) {
 189                 return (errno);
 190         }
 191         if (strncmp(ctx->pc_line_buf, search, search_len) != 0) {
 192                 return (-1);
 193         }
 194 
 195         size_t parsed;
 196         parsed = hex2bytes(ctx->pc_line_buf + search_len, sz - search_len,
 197             mdbuf, mdlen);
 198         if (parsed != mdlen) {
 199                 ASSERT3U(parsed, <, mdlen);
 200                 return (-1);
 201         }
 202 
 203         return (0);
 204 }
 205 
 206 parser_entry_t *
 207 parser_read(parser_ctx_t *ctx, int *errp)
 208 {
 209         int err = 0;
 210         parser_entry_t *res = NULL;
 211         uchar_t *msgbuf = NULL;
 212         uchar_t *mdbuf = NULL;
 213 
 214         while (feof(ctx->pc_file) == 0) {
 215                 int ret;
 216                 size_t msglen, sz;
 217 
 218                 ret = read_len(ctx, &msglen, &sz);
 219                 if (ret == -1) {
 220                         /*
 221                          * Did not find a properly formatted "Len = <num>", but
 222                          * no hard errors were incurred while looking for one,
 223                          * so continue searching.
 224                          */
 225                         continue;
 226                 } else if (ret != 0) {
 227                         err = ret;
 228                         break;
 229                 }
 230 
 231                 if (msglen != 0) {
 232                         msgbuf = calloc(msglen, 1);
 233                         if (msgbuf == NULL) {
 234                                 err = ENOMEM;
 235                                 break;
 236                         }
 237                 }
 238 
 239                 ret = read_msg(ctx, msgbuf, msglen);
 240                 if (ret == -1) {
 241                         /*
 242                          * Did not find properly formatted "Msg = <hex data>".
 243                          * Restart the search for a new record.
 244                          */
 245                         free(msgbuf);
 246                         msgbuf = NULL;
 247                         continue;
 248                 } else if (ret != 0) {
 249                         err = ret;
 250                         break;
 251                 }
 252 
 253                 mdbuf = calloc(1, ctx->pc_hash_sz);
 254                 if (mdbuf == NULL) {
 255                         err = ENOMEM;
 256                         break;
 257                 }
 258                 ret = read_md(ctx, mdbuf, ctx->pc_hash_sz);
 259                 if (ret == -1) {
 260                         /*
 261                          * Did not find properly formatted "MD = <hash>".
 262                          * Restart search for new record.
 263                          */
 264                         free(msgbuf);
 265                         free(mdbuf);
 266                         msgbuf = mdbuf = NULL;
 267                         continue;
 268                 } else if (ret != 0) {
 269                         err = ret;
 270                         break;
 271                 }
 272 
 273                 res = malloc(sizeof (*res));
 274                 if (res == NULL) {
 275                         err = ENOMEM;
 276                         break;
 277                 }
 278                 res->pe_msg = msgbuf;
 279                 res->pe_msglen = msglen;
 280                 res->pe_hash = mdbuf;
 281                 break;
 282         }
 283 
 284         if (err != 0) {
 285                 ASSERT(res == NULL);
 286                 free(msgbuf);
 287                 free(mdbuf);
 288         }
 289 
 290         /* EOF status indicated by err == 0 and res == NULL */
 291         *errp = err;
 292         return (res);
 293 }
 294 
 295 void
 296 parser_free(parser_entry_t *ent)
 297 {
 298         free(ent->pe_msg);
 299         free(ent->pe_hash);
 300         free(ent);
 301 }
 302 
 303 /*
 304  * With the above parser, run a the vectors through a given crypto test.
 305  */
 306 int
 307 digest_runner(char *mech_name, const char *input_file, size_t digest_len)
 308 {
 309         int fails = 0, error;
 310         uint8_t N[1024];
 311         size_t updatelens[] = {
 312                 1, 8, 33, 67, CTEST_UPDATELEN_WHOLE, CTEST_UPDATELEN_END
 313         };
 314         cryptotest_t args = {
 315                 .out = N,
 316                 .outlen = sizeof (N),
 317                 .mechname = mech_name,
 318                 .updatelens = updatelens
 319         };
 320         parser_ctx_t *ctx;
 321         parser_entry_t *ent;
 322 
 323         /*
 324          * XXX: This could be changed to generate a path relative to that of
 325          * the executable to find the data files
 326          */
 327         char *path = NULL;
 328         if (asprintf(&path, "%s/%s", DATA_PATH, input_file) < 0) {
 329                 err(EXIT_FAILURE, NULL);
 330         }
 331 
 332         ctx = parser_init(path, digest_len, &error);
 333         if (ctx == NULL) {
 334                 err(EXIT_FAILURE, "%s", path);
 335         }
 336         free(path);
 337 
 338         error = 0;
 339         while ((ent = parser_read(ctx, &error)) != NULL) {
 340                 args.in = ent->pe_msg;
 341                 args.inlen = ent->pe_msglen;
 342 
 343                 fails += run_test(&args, ent->pe_hash, digest_len, DIGEST_FG);
 344                 parser_free(ent);
 345         }
 346         if (error != 0) {
 347                 err(EXIT_FAILURE, NULL);
 348         }
 349         parser_fini(ctx);
 350 
 351         return (fails);
 352 }