1*4d92dbe9Sriastradh /* $NetBSD: crypt-argon2.c,v 1.22 2024/07/23 22:37:11 riastradh Exp $ */ 281085b6fSriastradh 32e485d4aSjhigh /* 42e485d4aSjhigh * Copyright (c) 2009 The NetBSD Foundation, Inc. 52e485d4aSjhigh * All rights reserved. 62e485d4aSjhigh * 72e485d4aSjhigh * Redistribution and use in source and binary forms, with or without 82e485d4aSjhigh * modification, are permitted provided that the following conditions 92e485d4aSjhigh * are met: 102e485d4aSjhigh * 1. Redistributions of source code must retain the above copyright 112e485d4aSjhigh * notice, this list of conditions and the following disclaimer. 122e485d4aSjhigh * 2. Redistributions in binary form must reproduce the above copyright 132e485d4aSjhigh * notice, this list of conditions and the following disclaimer in the 142e485d4aSjhigh * documentation and/or other materials provided with the distribution. 152e485d4aSjhigh * 162e485d4aSjhigh * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 172e485d4aSjhigh * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 182e485d4aSjhigh * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 192e485d4aSjhigh * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 202e485d4aSjhigh * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 212e485d4aSjhigh * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 222e485d4aSjhigh * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 232e485d4aSjhigh * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 242e485d4aSjhigh * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 252e485d4aSjhigh * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 262e485d4aSjhigh * POSSIBILITY OF SUCH DAMAGE. 272e485d4aSjhigh */ 282e485d4aSjhigh 2981085b6fSriastradh #include <sys/cdefs.h> 30*4d92dbe9Sriastradh __RCSID("$NetBSD: crypt-argon2.c,v 1.22 2024/07/23 22:37:11 riastradh Exp $"); 3181085b6fSriastradh 3208100292Snia #include <sys/resource.h> 3308100292Snia #include <sys/param.h> 3408100292Snia #include <sys/sysctl.h> 3508100292Snia #include <sys/syslimits.h> 3608100292Snia 37b302373fSjhigh #include <stdlib.h> 38b302373fSjhigh #include <stdio.h> 39b302373fSjhigh #include <unistd.h> 40b302373fSjhigh #include <stdio.h> 41b302373fSjhigh #include <string.h> 42b302373fSjhigh #include <time.h> 43b302373fSjhigh #include <pwd.h> 44b302373fSjhigh #include <errno.h> 45b302373fSjhigh #include <argon2.h> 46b302373fSjhigh 47b302373fSjhigh #include <err.h> 48b302373fSjhigh #include "crypt.h" 49b302373fSjhigh 5008100292Snia crypt_private int 5108100292Snia estimate_argon2_params(argon2_type, uint32_t *, 5208100292Snia uint32_t *, uint32_t *); 5308100292Snia 54b302373fSjhigh /* defaults pulled from run.c */ 55b302373fSjhigh #define HASHLEN 32 56b302373fSjhigh #define T_COST_DEF 3 57b302373fSjhigh #define LOG_M_COST_DEF 12 /* 2^12 = 4 MiB */ 58b302373fSjhigh #define LANES_DEF 1 59b302373fSjhigh #define THREADS_DEF 1 60b302373fSjhigh #define OUTLEN_DEF 32 61b302373fSjhigh #define MAX_PASS_LEN 128 62b302373fSjhigh 63b302373fSjhigh #define ARGON2_CONTEXT_INITIALIZER \ 64b302373fSjhigh {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, \ 65b302373fSjhigh T_COST_DEF, LOG_M_COST_DEF,\ 66b302373fSjhigh LANES_DEF, THREADS_DEF, \ 67b302373fSjhigh ARGON2_VERSION_NUMBER, 0, 0, ARGON2_DEFAULT_FLAGS} 68b302373fSjhigh 69b302373fSjhigh #define ARGON2_ARGON2_STR "argon2" 70b302373fSjhigh #define ARGON2_ARGON2I_STR "argon2i" 71b302373fSjhigh #define ARGON2_ARGON2D_STR "argon2d" 72b302373fSjhigh #define ARGON2_ARGON2ID_STR "argon2id" 73b302373fSjhigh 7408100292Snia /* 7508100292Snia * Unpadded Base64 calculations are taken from the Apache2/CC-0 7608100292Snia * licensed libargon2 for compatibility 7708100292Snia */ 78f7145efdSnia 79f7145efdSnia /* 80f7145efdSnia * Some macros for constant-time comparisons. These work over values in 81f7145efdSnia * the 0..255 range. Returned value is 0x00 on "false", 0xFF on "true". 82f7145efdSnia */ 83f7145efdSnia #define EQ(x, y) ((((0U - ((unsigned)(x) ^ (unsigned)(y))) >> 8) & 0xFF) ^ 0xFF) 84f7145efdSnia #define GT(x, y) ((((unsigned)(y) - (unsigned)(x)) >> 8) & 0xFF) 85f7145efdSnia #define GE(x, y) (GT(y, x) ^ 0xFF) 86f7145efdSnia #define LT(x, y) GT(y, x) 87f7145efdSnia #define LE(x, y) GE(y, x) 88f7145efdSnia 89f7145efdSnia static unsigned 90f7145efdSnia b64_char_to_byte(int c) 91f7145efdSnia { 92f7145efdSnia unsigned x; 93f7145efdSnia 94f7145efdSnia x = (GE(c, 'A') & LE(c, 'Z') & (c - 'A')) | 95f7145efdSnia (GE(c, 'a') & LE(c, 'z') & (c - ('a' - 26))) | 96f7145efdSnia (GE(c, '0') & LE(c, '9') & (c - ('0' - 52))) | (EQ(c, '+') & 62) | 97f7145efdSnia (EQ(c, '/') & 63); 98f7145efdSnia return x | (EQ(x, 0) & (EQ(c, 'A') ^ 0xFF)); 99f7145efdSnia } 100f7145efdSnia 101f7145efdSnia static const char * 102f7145efdSnia from_base64(void *dst, size_t *dst_len, const char *src) 103f7145efdSnia { 104f7145efdSnia size_t len; 105f7145efdSnia unsigned char *buf; 106f7145efdSnia unsigned acc, acc_len; 107f7145efdSnia 108f7145efdSnia buf = (unsigned char *)dst; 109f7145efdSnia len = 0; 110f7145efdSnia acc = 0; 111f7145efdSnia acc_len = 0; 112f7145efdSnia for (;;) { 113f7145efdSnia unsigned d; 114f7145efdSnia 115f7145efdSnia d = b64_char_to_byte(*src); 116f7145efdSnia if (d == 0xFF) { 117f7145efdSnia break; 118f7145efdSnia } 119f7145efdSnia src++; 120f7145efdSnia acc = (acc << 6) + d; 121f7145efdSnia acc_len += 6; 122f7145efdSnia if (acc_len >= 8) { 123f7145efdSnia acc_len -= 8; 124f7145efdSnia if ((len++) >= *dst_len) { 125f7145efdSnia return NULL; 126f7145efdSnia } 127f7145efdSnia *buf++ = (acc >> acc_len) & 0xFF; 128f7145efdSnia } 129f7145efdSnia } 130f7145efdSnia 131f7145efdSnia /* 132f7145efdSnia * If the input length is equal to 1 modulo 4 (which is 133f7145efdSnia * invalid), then there will remain 6 unprocessed bits; 134f7145efdSnia * otherwise, only 0, 2 or 4 bits are buffered. The buffered 135f7145efdSnia * bits must also all be zero. 136f7145efdSnia */ 137f7145efdSnia if (acc_len > 4 || (acc & (((unsigned)1 << acc_len) - 1)) != 0) { 138f7145efdSnia return NULL; 139f7145efdSnia } 140f7145efdSnia *dst_len = len; 141f7145efdSnia return src; 142f7145efdSnia } 143f7145efdSnia 14408100292Snia /* 14508100292Snia * Used to find default parameters that perform well on the host 14608100292Snia * machine. Inputs should dereference to either 0 (to estimate), 14708100292Snia * or desired value. 14808100292Snia */ 14908100292Snia crypt_private int 15008100292Snia estimate_argon2_params(argon2_type atype, uint32_t *etime, 15108100292Snia uint32_t *ememory, uint32_t *ethreads) 15208100292Snia { 15308100292Snia const int mib[] = { CTL_HW, HW_USERMEM64 }; 15408100292Snia struct timespec tp1, tp2, delta; 15508100292Snia char tmp_salt[16]; 15608100292Snia char tmp_pwd[16]; 15708100292Snia uint32_t tmp_hash[32]; 15808100292Snia char tmp_encoded[256]; 15908100292Snia struct rlimit rlim; 1605a3dc900Snia uint64_t max_mem; /* usermem64 returns bytes */ 16108100292Snia size_t max_mem_sz = sizeof(max_mem); 16208100292Snia /* low values from argon2 test suite... */ 1635a3dc900Snia uint32_t memory = 256; /* 256k; argon2 wants kilobytes */ 164ac442901Snia uint32_t time = 3; 16508100292Snia uint32_t threads = 1; 16608100292Snia 16708100292Snia if (*ememory < ARGON2_MIN_MEMORY) { 16808100292Snia /* 16908100292Snia * attempt to find a reasonble bound for memory use 17008100292Snia */ 17108100292Snia if (sysctl(mib, __arraycount(mib), 17208100292Snia &max_mem, &max_mem_sz, NULL, 0) < 0) { 17308100292Snia goto error; 17408100292Snia } 17508100292Snia if (getrlimit(RLIMIT_AS, &rlim) < 0) 17608100292Snia goto error; 17708100292Snia if (max_mem > rlim.rlim_cur && rlim.rlim_cur != RLIM_INFINITY) 17808100292Snia max_mem = rlim.rlim_cur; 17908100292Snia 18008100292Snia /* 18108100292Snia * Note that adding memory also greatly slows the algorithm. 18208100292Snia * Do we need to be concerned about memory usage during 18308100292Snia * concurrent connections? 18408100292Snia */ 1855a3dc900Snia max_mem /= 1000000; /* bytes down to mb */ 18608100292Snia if (max_mem > 30000) { 187cb1631f6Snia memory = 32768; 188cb1631f6Snia } else if (max_mem > 15000) { 189cb1631f6Snia memory = 16384; 19008100292Snia } else if (max_mem > 7000) { 191cb1631f6Snia memory = 8192; 192cb1631f6Snia } else if (max_mem > 3000) { 19308100292Snia memory = 4096; 194cb1631f6Snia } else if (max_mem > 900) { 195cb1631f6Snia memory = 1024; 19608100292Snia } else if (max_mem > 24) { 19708100292Snia memory = 256; 19808100292Snia } else { 19908100292Snia memory = ARGON2_MIN_MEMORY; 20008100292Snia } 20108100292Snia } else { 20208100292Snia memory = *ememory; 20308100292Snia } 20408100292Snia 20508100292Snia if (*etime < ARGON2_MIN_TIME) { 20608100292Snia /* 20708100292Snia * just fill these with random stuff since we'll immediately 20808100292Snia * discard them after calculating hashes for 1 second 20908100292Snia */ 21008100292Snia arc4random_buf(tmp_pwd, sizeof(tmp_pwd)); 21108100292Snia arc4random_buf(tmp_salt, sizeof(tmp_salt)); 21208100292Snia 21308100292Snia if (clock_gettime(CLOCK_MONOTONIC, &tp1) == -1) 21408100292Snia goto error; 2153fb27417Smlelstv for (; time < ARGON2_MAX_TIME; ++time) { 21608100292Snia if (argon2_hash(time, memory, threads, 21708100292Snia tmp_pwd, sizeof(tmp_pwd), 21808100292Snia tmp_salt, sizeof(tmp_salt), 21908100292Snia tmp_hash, sizeof(tmp_hash), 22008100292Snia tmp_encoded, sizeof(tmp_encoded), 22108100292Snia atype, ARGON2_VERSION_NUMBER) != ARGON2_OK) { 22208100292Snia goto reset; 22308100292Snia } 22408100292Snia if (clock_gettime(CLOCK_MONOTONIC, &tp2) == -1) 22508100292Snia break; 22608100292Snia if (timespeccmp(&tp1, &tp2, >)) 22708100292Snia break; /* broken system... */ 22808100292Snia timespecsub(&tp2, &tp1, &delta); 2293fb27417Smlelstv if (delta.tv_sec >= 1) 2303fb27417Smlelstv break; 23108100292Snia } 23208100292Snia } else { 23308100292Snia time = *etime; 23408100292Snia } 23508100292Snia 23608100292Snia error: 23708100292Snia *etime = time; 23808100292Snia *ememory = memory; 23908100292Snia *ethreads = threads; 24008100292Snia return 0; 24108100292Snia reset: 242ac442901Snia time = 3; 24308100292Snia memory = 256; 24408100292Snia threads = 1; 24508100292Snia goto error; 24608100292Snia } 24708100292Snia 24808100292Snia 249b302373fSjhigh /* process params to argon2 */ 250b302373fSjhigh /* we don't force param order as input, */ 251b302373fSjhigh /* but we do provide the expected order to argon2 api */ 252f7145efdSnia static int 253f7145efdSnia decode_option(argon2_context *ctx, argon2_type *atype, const char *option) 254b302373fSjhigh { 255b302373fSjhigh size_t tmp = 0; 2568012ca3fSmsaitoh char *in = 0, *inp; 257b302373fSjhigh char *a = 0; 258b302373fSjhigh char *p = 0; 259b302373fSjhigh size_t sl; 260b302373fSjhigh int error = 0; 261b302373fSjhigh 262b302373fSjhigh in = (char *)strdup(option); 263b302373fSjhigh inp = in; 264b302373fSjhigh 265b302373fSjhigh if (*inp == '$') inp++; 266b302373fSjhigh 267b302373fSjhigh a = strsep(&inp, "$"); 268b302373fSjhigh 269b302373fSjhigh sl = strlen(a); 270b302373fSjhigh 271b302373fSjhigh if (sl == strlen(ARGON2_ARGON2I_STR) && 272b302373fSjhigh !(strcmp(ARGON2_ARGON2I_STR, a))) { 273b302373fSjhigh *atype=Argon2_i; 274b302373fSjhigh } else if (sl == strlen(ARGON2_ARGON2D_STR) && 275b302373fSjhigh !(strcmp(ARGON2_ARGON2D_STR, a))) { 276b302373fSjhigh *atype=Argon2_d; 277b302373fSjhigh } 278b302373fSjhigh else if (sl == strlen(ARGON2_ARGON2ID_STR) && 279b302373fSjhigh !(strcmp(ARGON2_ARGON2ID_STR, a))) { 280b302373fSjhigh *atype=Argon2_id; 281b302373fSjhigh } else { /* default to id, we assume simple mistake */ 282b302373fSjhigh /* don't abandon yet */ 283b302373fSjhigh *atype=Argon2_id; 284b302373fSjhigh } 285b302373fSjhigh 286b302373fSjhigh a = strsep(&inp, "$"); 287b302373fSjhigh 288d595d5c3Snia /* parse the version number of the hash, if it's there */ 289d595d5c3Snia if (strncmp(a, "v=", 2) == 0) { 290d595d5c3Snia a += 2; 291b302373fSjhigh if ((getnum(a, &tmp))<0) { /* on error, default to current */ 292b302373fSjhigh /* should start thinking about aborting */ 29388cf5018Snia ctx->version = ARGON2_VERSION_10; 294b302373fSjhigh } else { 295b302373fSjhigh ctx->version = tmp; 296b302373fSjhigh } 297b302373fSjhigh a = strsep(&inp, "$"); 298d595d5c3Snia } else { 299d595d5c3Snia /* 300d595d5c3Snia * This is a parameter list, not a version number, use the 301d595d5c3Snia * default version. 302d595d5c3Snia */ 30388cf5018Snia ctx->version = ARGON2_VERSION_10; 304d595d5c3Snia } 305b302373fSjhigh 306b302373fSjhigh /* parse labelled argon2 params */ 307b302373fSjhigh /* m_cost (m) 308b302373fSjhigh * t_cost (t) 309b302373fSjhigh * threads (p) 310b302373fSjhigh */ 311b302373fSjhigh while ((p = strsep(&a, ","))) { 312b302373fSjhigh switch (*p) { 313b302373fSjhigh case 'm': 314b302373fSjhigh p += strlen("m="); 315b302373fSjhigh if ((getnum(p, &tmp)) < 0) { 316b302373fSjhigh --error; 317b302373fSjhigh } else { 318b302373fSjhigh ctx->m_cost = tmp; 319b302373fSjhigh } 320b302373fSjhigh break; 321b302373fSjhigh case 't': 322b302373fSjhigh p += strlen("t="); 323b302373fSjhigh if ((getnum(p, &tmp)) < 0) { 324b302373fSjhigh --error; 325b302373fSjhigh } else { 326b302373fSjhigh ctx->t_cost = tmp; 327b302373fSjhigh } 328b302373fSjhigh break; 329b302373fSjhigh case 'p': 330b302373fSjhigh p += strlen("p="); 331b302373fSjhigh if ((getnum(p, &tmp)) < 0) { 332b302373fSjhigh --error; 333b302373fSjhigh } else { 334b302373fSjhigh ctx->threads = tmp; 335b302373fSjhigh } 336b302373fSjhigh break; 337b302373fSjhigh default: 338e7c71814Sabs free(in); 339b302373fSjhigh return -1; 340b302373fSjhigh 341b302373fSjhigh } 342b302373fSjhigh } 343b302373fSjhigh 344b302373fSjhigh a = strsep(&inp, "$"); 345c44afc56Smartin if (a == NULL) { 34604799ba5Sabs free(in); 34704799ba5Sabs return -1; 34804799ba5Sabs } 349b302373fSjhigh 350f7145efdSnia sl = ctx->saltlen; 351f7145efdSnia 352e7c71814Sabs if (from_base64(ctx->salt, &sl, a) == NULL) { 353e7c71814Sabs free(in); 354f7145efdSnia return -1; 355e7c71814Sabs } 356f7145efdSnia 357f7145efdSnia ctx->saltlen = sl; 358b302373fSjhigh 359b302373fSjhigh a = strsep(&inp, "$"); 360b302373fSjhigh 361d595d5c3Snia if (a) { 362b302373fSjhigh snprintf((char *)ctx->pwd, ctx->pwdlen, "%s", a); 363b302373fSjhigh } else { 364b302373fSjhigh /* don't care if passwd hash is missing */ 365b302373fSjhigh /* if missing, most likely coming from */ 366b302373fSjhigh /* pwhash or similar */ 367b302373fSjhigh } 368b302373fSjhigh 369b302373fSjhigh /* free our token buffer */ 370b302373fSjhigh free(in); 371b302373fSjhigh 372b302373fSjhigh /* 0 on success, <0 otherwise */ 373b302373fSjhigh return error; 374b302373fSjhigh } 375b302373fSjhigh 376f9151ba9Snia crypt_private char * 377b302373fSjhigh __crypt_argon2(const char *pw, const char * salt) 378b302373fSjhigh { 379b302373fSjhigh /* we use the libargon2 api to generate */ 380b302373fSjhigh /* return code */ 381b302373fSjhigh int rc = 0; 382b302373fSjhigh /* output buffer */ 383b302373fSjhigh char ebuf[32]; 384b302373fSjhigh /* argon2 variable, default to id */ 385b302373fSjhigh argon2_type atype = Argon2_id; 386b302373fSjhigh /* default to current argon2 version */ 387b302373fSjhigh /* argon2 context to collect params */ 388b302373fSjhigh argon2_context ctx = ARGON2_CONTEXT_INITIALIZER; 389b302373fSjhigh /* argon2 encoded buffer */ 390b302373fSjhigh char encodebuf[256]; 391b302373fSjhigh /* argon2 salt buffer */ 392b302373fSjhigh char saltbuf[128]; 393b302373fSjhigh /* argon2 pwd buffer */ 394b302373fSjhigh char pwdbuf[128]; 395b302373fSjhigh /* returned static buffer */ 396b302373fSjhigh static char rbuf[512]; 397b302373fSjhigh 398b302373fSjhigh /* clear buffers */ 39980833458Snia explicit_memset(rbuf, 0, sizeof(rbuf)); 400b302373fSjhigh 401b302373fSjhigh /* we use static buffers to avoid allocation */ 402b302373fSjhigh /* and easier cleanup */ 403b302373fSjhigh ctx.out = (uint8_t *)encodebuf; 404b302373fSjhigh ctx.outlen = sizeof(encodebuf); 405b302373fSjhigh 406b302373fSjhigh ctx.salt = (uint8_t *)saltbuf; 407b302373fSjhigh ctx.saltlen = sizeof(saltbuf); 408b302373fSjhigh 409b302373fSjhigh ctx.pwd = (uint8_t *)pwdbuf; 410b302373fSjhigh ctx.pwdlen = sizeof(pwdbuf); 411b302373fSjhigh 412b302373fSjhigh /* decode salt string to argon2 params */ 413b302373fSjhigh /* argon2 context for param collection */ 414b302373fSjhigh rc = decode_option(&ctx, &atype, salt); 415b302373fSjhigh 416b302373fSjhigh if (rc < 0) { 417b302373fSjhigh /* unable to parse input params */ 418364b5e26Snia return NULL; 419b302373fSjhigh } 420b302373fSjhigh 421b302373fSjhigh rc = argon2_hash(ctx.t_cost, ctx.m_cost, 422f7145efdSnia ctx.threads, pw, strlen(pw), ctx.salt, ctx.saltlen, 423f7145efdSnia ebuf, sizeof(ebuf), encodebuf, sizeof(encodebuf), 424f7145efdSnia atype, ctx.version); 425b302373fSjhigh 426b302373fSjhigh if (rc != ARGON2_OK) { 427d595d5c3Snia fprintf(stderr, "argon2: failed: %s\n", 428d595d5c3Snia argon2_error_message(rc)); 429364b5e26Snia return NULL; 430b302373fSjhigh } 431b302373fSjhigh 43280833458Snia memcpy(rbuf, encodebuf, sizeof(encodebuf)); 433b302373fSjhigh 434b302373fSjhigh /* clear buffers */ 43580833458Snia explicit_memset(ebuf, 0, sizeof(ebuf)); 436b0735155Snia explicit_memset(encodebuf, 0, sizeof(encodebuf)); 437b0735155Snia explicit_memset(saltbuf, 0, sizeof(saltbuf)); 438b0735155Snia explicit_memset(pwdbuf, 0, sizeof(pwdbuf)); 439b302373fSjhigh 440b302373fSjhigh /* return encoded str */ 441b302373fSjhigh return rbuf; 442b302373fSjhigh } 443