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