1/* $NetBSD: argon2_utils.c,v 1.1 2021/11/22 14:34:35 nia Exp $ */ 2/*- 3 * Copyright (c) 2021 The NetBSD Foundation, Inc. 4 * All rights reserved. 5 * 6 * This code is derived from software contributed to The NetBSD Foundation 7 * by Nia Alarie. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 19 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 20 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 21 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 22 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 * POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31#include <sys/resource.h> 32#include <sys/sysctl.h> 33#include <argon2.h> 34#include <stdio.h> 35#include <stdlib.h> 36#include <time.h> 37#include <util.h> 38#include <err.h> 39 40#include "argon2_utils.h" 41 42#ifndef lint 43__RCSID("$NetBSD: argon2_utils.c,v 1.1 2021/11/22 14:34:35 nia Exp $"); 44#endif 45 46static size_t 47get_cpucount(void) 48{ 49 const int mib[] = { CTL_HW, HW_NCPUONLINE }; 50 int ncpuonline = 1; 51 size_t ncpuonline_len = sizeof(ncpuonline); 52 53 if (sysctl(mib, __arraycount(mib), 54 &ncpuonline, &ncpuonline_len, NULL, 0) == -1) { 55 return 1; 56 } 57 return ncpuonline; 58} 59 60static uint64_t 61get_usermem(void) 62{ 63 const int mib[] = { CTL_HW, HW_USERMEM64 }; 64 uint64_t usermem64 = 0; 65 size_t usermem64_len = sizeof(usermem64); 66 struct rlimit rlim; 67 68 if (sysctl(mib, __arraycount(mib), 69 &usermem64, &usermem64_len, NULL, 0) == -1) { 70 return 0; 71 } 72 73 if (getrlimit(RLIMIT_AS, &rlim) == -1) 74 return usermem64; 75 if (usermem64 > rlim.rlim_cur && rlim.rlim_cur != RLIM_INFINITY) 76 usermem64 = rlim.rlim_cur; 77 return usermem64; /* bytes */ 78} 79 80void 81argon2id_calibrate(size_t keylen, size_t saltlen, 82 size_t *iterations, size_t *memory, size_t *parallelism) 83{ 84 size_t mem = ARGON2_MIN_MEMORY; /* kilobytes */ 85 size_t time; 86 const size_t ncpus = get_cpucount(); 87 const uint64_t usermem = get_usermem(); /* bytes */ 88 struct timespec tp1, tp2; 89 struct timespec delta; 90 unsigned int limit = 0; 91 uint8_t *key = NULL, *salt = NULL; 92 uint8_t tmp_pwd[17]; /* just random data for testing */ 93 int ret = ARGON2_OK; 94 95 key = emalloc(keylen); 96 salt = emalloc(saltlen); 97 98 arc4random_buf(tmp_pwd, sizeof(tmp_pwd)); 99 arc4random_buf(salt, saltlen); 100 101 /* 1kb to argon2 per 100kb of user memory */ 102 mem = usermem / 100000; 103 104 /* 256k: reasonable lower bound from the argon2 test suite */ 105 if (mem < 256) 106 mem = 256; 107 108 fprintf(stderr, "calibrating argon2id parameters..."); 109 110 /* Decrease 'mem' if it slows down computation too much */ 111 112 do { 113 if (clock_gettime(CLOCK_MONOTONIC, &tp1) == -1) 114 goto error; 115 if ((ret = argon2_hash(ARGON2_MIN_TIME, mem, ncpus, 116 tmp_pwd, sizeof(tmp_pwd), 117 salt, saltlen, 118 key, keylen, 119 NULL, 0, 120 Argon2_id, ARGON2_VERSION_NUMBER)) != ARGON2_OK) { 121 goto error_argon2; 122 } 123 fprintf(stderr, "."); 124 if (clock_gettime(CLOCK_MONOTONIC, &tp2) == -1) 125 goto error; 126 if (timespeccmp(&tp1, &tp2, >)) 127 goto error_clock; 128 timespecsub(&tp2, &tp1, &delta); 129 if (delta.tv_sec >= 1) 130 mem /= 2; 131 if (mem < ARGON2_MIN_MEMORY) { 132 mem = ARGON2_MIN_MEMORY; 133 break; 134 } 135 } while (delta.tv_sec >= 1 && (limit++) < 3); 136 137 delta.tv_sec = 0; 138 delta.tv_nsec = 0; 139 140 /* Increase 'time' until we reach a second */ 141 142 for (time = ARGON2_MIN_TIME; delta.tv_sec < 1 && 143 time < ARGON2_MAX_TIME; time <<= 1) { 144 if (clock_gettime(CLOCK_MONOTONIC, &tp1) == -1) 145 goto error; 146 if ((ret = argon2_hash(time, mem, ncpus, 147 tmp_pwd, sizeof(tmp_pwd), 148 salt, saltlen, 149 key, keylen, 150 NULL, 0, 151 Argon2_id, ARGON2_VERSION_NUMBER)) != ARGON2_OK) { 152 goto error_argon2; 153 } 154 fprintf(stderr, "."); 155 if (clock_gettime(CLOCK_MONOTONIC, &tp2) == -1) 156 goto error; 157 if (timespeccmp(&tp1, &tp2, >)) 158 goto error_clock; 159 timespecsub(&tp2, &tp1, &delta); 160 } 161 162 if (time > ARGON2_MIN_TIME) 163 time >>= 1; 164 165 fprintf(stderr, " done\n"); 166 167 free(key); 168 free(salt); 169 *iterations = time; 170 *memory = mem; 171 *parallelism = ncpus; 172 return; 173 174error_argon2: 175 errx(EXIT_FAILURE, 176 " failed to calculate Argon2 hash, error code %d", ret); 177error_clock: 178 errx(EXIT_FAILURE, 179 " failed to calibrate hash parameters: broken monotonic clock?"); 180error: 181 err(EXIT_FAILURE, " failed to calibrate hash parameters"); 182} 183