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