xref: /netbsd-src/sbin/cgdconfig/argon2_utils.c (revision 1569bcc0b3557876b2e672fed62df3c63e63e2b2)
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