/* An implementation of HMAC using SHA-1. * * Copyright (c) 2003 Red Hat, Inc. * Written by Nalin Dahyabhai * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, and the entire permission notice in its entirety, * including the disclaimer of warranties. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote * products derived from this software without specific prior * written permission. * * ALTERNATIVELY, this product may be distributed under the terms of * the GNU Public License, in which case the provisions of the GPL are * required INSTEAD OF the above restrictions. (This clause is * necessary due to a potential bad interaction between the GPL and * the restrictions contained in a BSD-style copyright.) * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * */ /* See RFC 2104 for descriptions. */ #include #include #include #include #include #include #include #include #include #include #include #include "hmacsha1.h" #include "sha1.h" #define MINIMUM_KEY_SIZE SHA1_OUTPUT_SIZE #define MAXIMUM_KEY_SIZE SHA1_BLOCK_SIZE static void hmac_key_create(pam_handle_t *pamh, const char *filename, size_t key_size, uid_t owner, gid_t group) { int randfd, keyfd, i; size_t count; char *key; /* Open the destination file. */ keyfd = open(filename, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, S_IRUSR | S_IWUSR); if (keyfd == -1) { pam_syslog(pamh, LOG_ERR, "Cannot create %s: %m", filename); return; } if (fchown(keyfd, owner, group) == -1) { pam_syslog(pamh, LOG_ERR, "Cannot chown %s: %m", filename); return; } /* Open the random device to get key data. */ randfd = open("/dev/urandom", O_RDONLY); if (randfd == -1) { pam_syslog(pamh, LOG_ERR, "Cannot open /dev/urandom: %m"); close(keyfd); return; } /* Read random data for use as the key. */ key = malloc(key_size); count = 0; if (!key) { close(keyfd); close(randfd); return; } while (count < key_size) { i = read(randfd, key + count, key_size - count); if ((i == 0) || (i == -1)) { break; } count += i; } close(randfd); /* If we didn't get enough, stop here. */ if (count < key_size) { pam_syslog(pamh, LOG_ERR, "Short read on random device"); memset(key, 0, key_size); free(key); close(keyfd); return; } /* Now write the key. */ count = 0; while (count < key_size) { i = write(keyfd, key + count, key_size - count); if ((i == 0) || (i == -1)) { break; } count += i; } memset(key, 0, key_size); free(key); close(keyfd); } static void hmac_key_read(pam_handle_t *pamh, const char *filename, size_t default_key_size, uid_t owner, gid_t group, void **key, size_t *key_size) { char *tmp; int keyfd, i, count; struct stat st; tmp = NULL; *key = NULL; *key_size = 0; /* Try to open the key file. */ keyfd = open(filename, O_RDONLY); if (keyfd == -1) { /* No such thing? Create it. */ if (errno == ENOENT) { hmac_key_create(pamh, filename, default_key_size, owner, group); keyfd = open(filename, O_RDONLY); } else { pam_syslog(pamh, LOG_ERR, "Cannot open %s: %m", filename); } if (keyfd == -1) return; } /* If we failed to open the file, we're done. */ if (fstat(keyfd, &st) == -1) { close(keyfd); return; } /* Read the contents of the file. */ tmp = malloc(st.st_size); if (!tmp) { close(keyfd); return; } count = 0; while (count < st.st_size) { i = read(keyfd, tmp + count, st.st_size - count); if ((i == 0) || (i == -1)) { break; } count += i; } close(keyfd); /* Require that we got the expected amount of data. */ if (count < st.st_size) { memset(tmp, 0, st.st_size); free(tmp); return; } /* Pass the key back. */ *key = tmp; *key_size = st.st_size; } static void xor_block(unsigned char *p, unsigned char byte, size_t length) { size_t i; for (i = 0; i < length; i++) { p[i] = p[i] ^ byte; } } void hmac_sha1_generate(void **mac, size_t *mac_length, const void *raw_key, size_t raw_key_size, const void *text, size_t text_length) { unsigned char key[MAXIMUM_KEY_SIZE], tmp_key[MAXIMUM_KEY_SIZE]; size_t maximum_key_size = SHA1_BLOCK_SIZE, minimum_key_size = SHA1_OUTPUT_SIZE; const unsigned char ipad = 0x36, opad = 0x5c; struct sha1_context sha1; unsigned char inner[SHA1_OUTPUT_SIZE], outer[SHA1_OUTPUT_SIZE]; *mac = NULL; *mac_length = 0; #ifndef HMAC_ALLOW_SHORT_KEYS /* If the key is too short, don't bother. */ if (raw_key_size < minimum_key_size) { return; } #endif /* If the key is too long, "compress" it, else copy it and pad it * out with zero bytes. */ memset(key, 0, sizeof(key)); if (raw_key_size > maximum_key_size) { sha1_init(&sha1); sha1_update(&sha1, raw_key, raw_key_size); sha1_output(&sha1, key); } else { memmove(key, raw_key, raw_key_size); } /* Generate the inner sum. */ memcpy(tmp_key, key, sizeof(tmp_key)); xor_block(tmp_key, ipad, sizeof(tmp_key)); sha1_init(&sha1); sha1_update(&sha1, tmp_key, sizeof(tmp_key)); sha1_update(&sha1, text, text_length); sha1_output(&sha1, inner); /* Generate the outer sum. */ memcpy(tmp_key, key, sizeof(tmp_key)); xor_block(tmp_key, opad, sizeof(tmp_key)); sha1_init(&sha1); sha1_update(&sha1, tmp_key, sizeof(tmp_key)); sha1_update(&sha1, inner, sizeof(inner)); sha1_output(&sha1, outer); /* We don't need any of the keys any more. */ memset(key, 0, sizeof(key)); memset(tmp_key, 0, sizeof(tmp_key)); /* Allocate space to store the output. */ *mac_length = sizeof(outer); *mac = malloc(*mac_length); if (*mac == NULL) { *mac_length = 0; return; } memcpy(*mac, outer, *mac_length); } void hmac_sha1_generate_file(pam_handle_t *pamh, void **mac, size_t *mac_length, const char *keyfile, uid_t owner, gid_t group, const void *text, size_t text_length) { void *key; size_t key_length; hmac_key_read(pamh, keyfile, MAXIMUM_KEY_SIZE, owner, group, &key, &key_length); if (key == NULL) { *mac = NULL; *mac_length = 0; return; } hmac_sha1_generate(mac, mac_length, key, key_length, text, text_length); memset(key, 0, key_length); free(key); } size_t hmac_sha1_size(void) { return SHA1_OUTPUT_SIZE; }