1 /* Wrapper for hmac openssl implementation.
2 *
3 * Copyright (c) 2021 Red Hat, Inc.
4 * Written by Iker Pedrosa <ipedrosa@redhat.com>
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, and the entire permission notice in its entirety,
11 * including the disclaimer of warranties.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. The name of the author may not be used to endorse or promote
16 * products derived from this software without specific prior
17 * written permission.
18 *
19 * ALTERNATIVELY, this product may be distributed under the terms of
20 * the GNU Public License, in which case the provisions of the GPL are
21 * required INSTEAD OF the above restrictions. (This clause is
22 * necessary due to a potential bad interaction between the GPL and
23 * the restrictions contained in a BSD-style copyright.)
24 *
25 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
26 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
27 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
28 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
29 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
30 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
31 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
33 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
35 * OF THE POSSIBILITY OF SUCH DAMAGE.
36 *
37 */
38
39 #include "config.h"
40
41 #ifdef WITH_OPENSSL
42
43 #include <sys/stat.h>
44 #include <fcntl.h>
45 #include <syslog.h>
46 #include <unistd.h>
47 #include <string.h>
48 #include <errno.h>
49 #include <openssl/evp.h>
50 #include <openssl/params.h>
51 #include <openssl/core_names.h>
52
53 #include <security/pam_ext.h>
54 #include <security/pam_modutil.h>
55
56 #include "hmac_openssl_wrapper.h"
57 #include "pam_inline.h"
58
59 #define LOGIN_DEFS "/etc/login.defs"
60 #define CRYPTO_KEY "HMAC_CRYPTO_ALGO"
61 #define DEFAULT_ALGORITHM "SHA512"
62 #define MAX_HMAC_LENGTH 512
63 #define MAX_KEY_LENGTH EVP_MAX_KEY_LENGTH
64
65 static char *
66 get_crypto_algorithm(pam_handle_t *pamh, int debug){
67 char *config_value = NULL;
68
69 config_value = pam_modutil_search_key(pamh, LOGIN_DEFS, CRYPTO_KEY);
70
71 if (config_value == NULL) {
72 config_value = strdup(DEFAULT_ALGORITHM);
73 if (debug) {
74 pam_syslog(pamh, LOG_DEBUG,
75 "Key [%s] not found, falling back to default algorithm [%s]\n",
76 CRYPTO_KEY, DEFAULT_ALGORITHM);
77 }
78 }
79
80 return config_value;
81 }
82
83 static int
84 generate_key(pam_handle_t *pamh, char **key, size_t key_size)
85 {
86 int fd = 0;
87 size_t bytes_read = 0;
88 char * tmp = NULL;
89
90 fd = open("/dev/urandom", O_RDONLY);
91 if (fd == -1) {
92 pam_syslog(pamh, LOG_ERR, "Cannot open /dev/urandom: %m");
93 return PAM_AUTH_ERR;
94 }
95
96 tmp = malloc(key_size);
97 if (!tmp) {
98 pam_syslog(pamh, LOG_CRIT, "Not enough memory");
99 close(fd);
100 return PAM_AUTH_ERR;
101 }
102
103 bytes_read = pam_modutil_read(fd, tmp, key_size);
104 close(fd);
105
106 if (bytes_read < key_size) {
107 pam_syslog(pamh, LOG_ERR, "Short read on random device");
108 free(tmp);
109 return PAM_AUTH_ERR;
110 }
111
112 *key = tmp;
113
114 return PAM_SUCCESS;
115 }
116
117 static int
118 read_file(pam_handle_t *pamh, int fd, char **text, size_t *text_length)
119 {
120 struct stat st;
121 size_t bytes_read = 0;
122 char *tmp = NULL;
123
124 if (fstat(fd, &st) == -1) {
125 pam_syslog(pamh, LOG_ERR, "Unable to stat file: %m");
126 close(fd);
127 return PAM_AUTH_ERR;
128 }
129
130 if (st.st_size == 0) {
131 pam_syslog(pamh, LOG_ERR, "Key file size cannot be 0");
132 close(fd);
133 return PAM_AUTH_ERR;
134 }
135
136 tmp = malloc(st.st_size);
137 if (!tmp) {
138 pam_syslog(pamh, LOG_CRIT, "Not enough memory");
139 close(fd);
140 return PAM_AUTH_ERR;
141 }
142
143 bytes_read = pam_modutil_read(fd, tmp, st.st_size);
144 close(fd);
145
146 if (bytes_read < (size_t)st.st_size) {
147 pam_syslog(pamh, LOG_ERR, "Short read on key file");
148 pam_overwrite_n(tmp, st.st_size);
149 free(tmp);
150 return PAM_AUTH_ERR;
151 }
152
153 *text = tmp;
154 *text_length = st.st_size;
155
156 return PAM_SUCCESS;
157 }
158
159 static int
160 write_file(pam_handle_t *pamh, const char *file_name, char *text,
161 size_t text_length, uid_t owner, gid_t group)
162 {
163 int fd = 0;
164 size_t bytes_written = 0;
165
166 fd = open(file_name,
167 O_WRONLY | O_CREAT | O_TRUNC,
168 S_IRUSR | S_IWUSR);
169 if (fd == -1) {
170 pam_syslog(pamh, LOG_ERR, "Unable to open [%s]: %m", file_name);
171 pam_overwrite_n(text, text_length);
172 free(text);
173 return PAM_AUTH_ERR;
174 }
175
176 if (fchown(fd, owner, group) == -1) {
177 pam_syslog(pamh, LOG_ERR, "Unable to change ownership [%s]: %m", file_name);
178 pam_overwrite_n(text, text_length);
179 free(text);
180 close(fd);
181 return PAM_AUTH_ERR;
182 }
183
184 bytes_written = pam_modutil_write(fd, text, text_length);
185 close(fd);
186
187 if (bytes_written < text_length) {
188 pam_syslog(pamh, LOG_ERR, "Short write on %s", file_name);
189 free(text);
190 return PAM_AUTH_ERR;
191 }
192
193 return PAM_SUCCESS;
194 }
195
196 static int
197 key_management(pam_handle_t *pamh, const char *file_name, char **text,
198 size_t text_length, uid_t owner, gid_t group)
199 {
200 int fd = 0;
201
202 fd = open(file_name, O_RDONLY | O_NOFOLLOW);
203 if (fd == -1) {
204 if (errno == ENOENT) {
205 if (generate_key(pamh, text, text_length)) {
206 pam_syslog(pamh, LOG_ERR, "Unable to generate key");
207 return PAM_AUTH_ERR;
208 }
209
210 if (write_file(pamh, file_name, *text, text_length, owner, group)) {
211 pam_syslog(pamh, LOG_ERR, "Unable to write key");
212 return PAM_AUTH_ERR;
213 }
214 } else {
215 pam_syslog(pamh, LOG_ERR, "Unable to open %s: %m", file_name);
216 return PAM_AUTH_ERR;
217 }
218 } else {
219 if (read_file(pamh, fd, text, &text_length)) {
220 pam_syslog(pamh, LOG_ERR, "Error reading key file %s\n", file_name);
221 return PAM_AUTH_ERR;
222 }
223 }
224
225 return PAM_SUCCESS;
226 }
227
228 static int
229 hmac_management(pam_handle_t *pamh, int debug, void **out, size_t *out_length,
230 char *key, size_t key_length,
231 const void *text, size_t text_length)
232 {
233 int ret = PAM_AUTH_ERR;
234 EVP_MAC *evp_mac = NULL;
235 EVP_MAC_CTX *ctx = NULL;
236 unsigned char *hmac_message = NULL;
237 size_t hmac_length;
238 char *algo = NULL;
239 OSSL_PARAM subalg_param[] = { OSSL_PARAM_END, OSSL_PARAM_END };
240
241 algo = get_crypto_algorithm(pamh, debug);
242
243 subalg_param[0] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST,
244 algo,
245 0);
246
247 evp_mac = EVP_MAC_fetch(NULL, "HMAC", NULL);
248 if (evp_mac == NULL) {
249 pam_syslog(pamh, LOG_ERR, "Unable to create hmac implementation");
250 goto done;
251 }
252
253 ctx = EVP_MAC_CTX_new(evp_mac);
254 if (ctx == NULL) {
255 pam_syslog(pamh, LOG_ERR, "Unable to create hmac context");
256 goto done;
257 }
258
259 ret = EVP_MAC_init(ctx, (const unsigned char *)key, key_length, subalg_param);
260 if (ret == 0) {
261 pam_syslog(pamh, LOG_ERR, "Unable to initialize hmac context");
262 goto done;
263 }
264
265 ret = EVP_MAC_update(ctx, (const unsigned char *)text, text_length);
266 if (ret == 0) {
267 pam_syslog(pamh, LOG_ERR, "Unable to update hmac context");
268 goto done;
269 }
270
271 hmac_message = (unsigned char*)malloc(sizeof(unsigned char) * MAX_HMAC_LENGTH);
272 if (!hmac_message) {
273 pam_syslog(pamh, LOG_CRIT, "Not enough memory");
274 goto done;
275 }
276
277 ret = EVP_MAC_final(ctx, hmac_message, &hmac_length, MAX_HMAC_LENGTH);
278 if (ret == 0) {
279 pam_syslog(pamh, LOG_ERR, "Unable to calculate hmac message");
280 goto done;
281 }
282
283 *out_length = hmac_length;
284 *out = malloc(*out_length);
285 if (*out == NULL) {
286 pam_syslog(pamh, LOG_CRIT, "Not enough memory");
287 goto done;
288 }
289
290 memcpy(*out, hmac_message, *out_length);
291 ret = PAM_SUCCESS;
292
293 done:
294 if (hmac_message != NULL) {
295 free(hmac_message);
296 }
297 if (key != NULL) {
298 pam_overwrite_n(key, key_length);
299 free(key);
300 }
301 if (ctx != NULL) {
302 EVP_MAC_CTX_free(ctx);
303 }
304 if (evp_mac != NULL) {
305 EVP_MAC_free(evp_mac);
306 }
307 free(algo);
308
309 return ret;
310 }
311
312 int
313 hmac_size(pam_handle_t *pamh, int debug, size_t *hmac_length)
314 {
315 int ret = PAM_AUTH_ERR;
316 EVP_MAC *evp_mac = NULL;
317 EVP_MAC_CTX *ctx = NULL;
318 const unsigned char key[] = "ThisIsJustAKey";
319 size_t key_length = MAX_KEY_LENGTH;
320 char *algo = NULL;
321 OSSL_PARAM subalg_param[] = { OSSL_PARAM_END, OSSL_PARAM_END };
322
323 algo = get_crypto_algorithm(pamh, debug);
324
325 subalg_param[0] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST,
326 algo,
327 0);
328
329 evp_mac = EVP_MAC_fetch(NULL, "HMAC", NULL);
330 if (evp_mac == NULL) {
331 pam_syslog(pamh, LOG_ERR, "Unable to create hmac implementation");
332 goto done;
333 }
334
335 ctx = EVP_MAC_CTX_new(evp_mac);
336 if (ctx == NULL) {
337 pam_syslog(pamh, LOG_ERR, "Unable to create hmac context");
338 goto done;
339 }
340
341 ret = EVP_MAC_init(ctx, key, key_length, subalg_param);
342 if (ret == 0) {
343 pam_syslog(pamh, LOG_ERR, "Unable to initialize hmac context");
344 goto done;
345 }
346
347 *hmac_length = EVP_MAC_CTX_get_mac_size(ctx);
348 ret = PAM_SUCCESS;
349
350 done:
351 if (ctx != NULL) {
352 EVP_MAC_CTX_free(ctx);
353 }
354 if (evp_mac != NULL) {
355 EVP_MAC_free(evp_mac);
356 }
357 free(algo);
358
359 return ret;
360 }
361
362 int
363 hmac_generate(pam_handle_t *pamh, int debug, void **mac, size_t *mac_length,
364 const char *key_file, uid_t owner, gid_t group,
365 const void *text, size_t text_length)
366 {
367 char *key = NULL;
368 size_t key_length = MAX_KEY_LENGTH;
369
370 if (key_management(pamh, key_file, &key, key_length, owner, group)) {
371 return PAM_AUTH_ERR;
372 }
373
374 if (hmac_management(pamh, debug, mac, mac_length, key, key_length,
375 text, text_length)) {
376 return PAM_AUTH_ERR;
377 }
378
379 return PAM_SUCCESS;
380 }
381
382 #endif /* WITH_OPENSSL */