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
46 static size_t
get_cpucount(void)47 get_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
60 static uint64_t
get_usermem(void)61 get_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
80 void
argon2id_calibrate(size_t keylen,size_t saltlen,size_t * iterations,size_t * memory,size_t * parallelism)81 argon2id_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
174 error_argon2:
175 errx(EXIT_FAILURE,
176 " failed to calculate Argon2 hash, error code %d", ret);
177 error_clock:
178 errx(EXIT_FAILURE,
179 " failed to calibrate hash parameters: broken monotonic clock?");
180 error:
181 err(EXIT_FAILURE, " failed to calibrate hash parameters");
182 }
183