diff options
author | Gordon Tetlow <gordon@FreeBSD.org> | 2023-11-03 17:37:48 +0000 |
---|---|---|
committer | Gordon Tetlow <gordon@FreeBSD.org> | 2023-11-03 17:37:48 +0000 |
commit | e4fe068d29c902225fa3733db09af214cbfb3c02 (patch) | |
tree | f8f9cbff2b52e2e05c2e8e64aea2fc3210583273 /contrib/pam_modules/pam_passwdqc/passwdqc_random.c | |
parent | 00d65bdc4b6c36b0692588d71ca18ff080826a75 (diff) |
Vendor import of pam_passwdqc v2.0.3.vendor/pam_modules/passwdqc/v2.0.3vendor/pam_modules
Diffstat (limited to 'contrib/pam_modules/pam_passwdqc/passwdqc_random.c')
-rw-r--r-- | contrib/pam_modules/pam_passwdqc/passwdqc_random.c | 244 |
1 files changed, 193 insertions, 51 deletions
diff --git a/contrib/pam_modules/pam_passwdqc/passwdqc_random.c b/contrib/pam_modules/pam_passwdqc/passwdqc_random.c index 0d9a04bc1c20..5d7c575ae43f 100644 --- a/contrib/pam_modules/pam_passwdqc/passwdqc_random.c +++ b/contrib/pam_modules/pam_passwdqc/passwdqc_random.c @@ -1,30 +1,86 @@ /* - * Copyright (c) 2000-2002 by Solar Designer. See LICENSE. + * Copyright (c) 2000-2002,2005,2008,2010,2013,2016,2021 by Solar Designer + * See LICENSE */ -#include <stdio.h> -#include <string.h> +#ifdef _MSC_VER +#define _CRT_NONSTDC_NO_WARNINGS /* we use POSIX function names */ +#include <windows.h> +#include <wincrypt.h> +#else +#include <limits.h> #include <unistd.h> #include <errno.h> #include <fcntl.h> +#endif + +#include <string.h> #include "passwdqc.h" +#include "wordset_4k.h" + +/* + * We separate words in the generated "passphrases" with random special + * characters out of a set of 16 (so we encode 4 bits per separator + * character). To enable the use of our "passphrases" within FTP URLs + * (and similar), we pick characters that are defined by RFC 3986 as + * being safe within "userinfo" part of URLs without encoding and + * without having a special meaning. Out of those, we avoid characters + * that are visually ambiguous or difficult over the phone. This + * happens to leave us with exactly 8 symbols, and we add 8 digits that + * are not visually ambiguous. Unfortunately, the exclamation mark + * might be confused for the digit 1 (which we don't use), though. + */ +#define SEPARATORS "-_!$&*+=23456789" + +/* + * Number of bits encoded per separator character. + */ +#define SEPARATOR_BITS 4 + +/* + * Number of bits encoded per word. We use 4096 words, which gives 12 bits, + * and we toggle the case of the first character, which gives one bit more. + */ +#define WORD_BITS 13 + +/* + * Number of bits encoded per separator and word. + */ +#define SWORD_BITS \ + (SEPARATOR_BITS + WORD_BITS) -#define SEPARATORS "_,.;:-!&" +/* + * Maximum number of words to use. + */ +#define WORDS_MAX 8 -static int read_loop(int fd, char *buffer, int count) +/* + * Minimum and maximum number of bits to encode. With the settings above, + * these are 24 and 136, respectively. + */ +#define BITS_MIN \ + (2 * (WORD_BITS - 1)) +#define BITS_MAX \ + (WORDS_MAX * SWORD_BITS) + +#ifndef _MSC_VER +static ssize_t read_loop(int fd, void *buffer, size_t count) { - int offset, block; + ssize_t offset, block; offset = 0; - while (count > 0) { - block = read(fd, &buffer[offset], count); + while (count > 0 && count <= SSIZE_MAX) { + block = read(fd, (char *)buffer + offset, count); if (block < 0) { - if (errno == EINTR) continue; + if (errno == EINTR) + continue; return block; } - if (!block) return offset; + + if (!block) + return offset; offset += block; count -= block; @@ -32,61 +88,147 @@ static int read_loop(int fd, char *buffer, int count) return offset; } +#endif -char *_passwdqc_random(passwdqc_params_t *params) +char *passwdqc_random(const passwdqc_params_qc_t *params) { - static char output[0x100]; - int bits; - int use_separators, count, i; - unsigned int length; - char *start, *end; - int fd; - unsigned char bytes[2]; + int bits = params->random_bits; /* further code assumes signed type */ + if (bits < BITS_MIN || bits > BITS_MAX) + return NULL; + +/* + * Calculate the number of words to use. The first word is always present + * (hence the "1 +" and the "- WORD_BITS"). Each one of the following words, + * if any, is prefixed by a separator character, so we use SWORD_BITS when + * calculating how many additional words to use. We divide "bits - WORD_BITS" + * by SWORD_BITS with rounding up (hence the addition of "SWORD_BITS - 1"). + */ + int word_count = 1 + (bits + (SWORD_BITS - 1 - WORD_BITS)) / SWORD_BITS; + +/* + * Special case: would we still encode enough bits if we omit the final word, + * but keep the would-be-trailing separator? + */ + int trailing_separator = (SWORD_BITS * (word_count - 1) >= bits); + word_count -= trailing_separator; + +/* + * To determine whether we need to use different separator characters or maybe + * not, calculate the number of words we'd need to use if we don't use + * different separators. We calculate it by dividing "bits" by WORD_BITS with + * rounding up (hence the addition of "WORD_BITS - 1"). The resulting number + * is either the same as or greater than word_count. Use different separators + * only if their use, in the word_count calculation above, has helped reduce + * word_count. + */ + int use_separators = ((bits + (WORD_BITS - 1)) / WORD_BITS != word_count); + trailing_separator &= use_separators; + +/* + * Toggle case of the first character of each word only if we wouldn't achieve + * sufficient entropy otherwise. + */ + int toggle_case = (bits > + ((WORD_BITS - 1) * word_count) + + (use_separators ? (SEPARATOR_BITS * (word_count - !trailing_separator)) : 0)); - if (!(bits = params->random_bits)) + char output[0x100]; + +/* + * Calculate and check the maximum possible length of a "passphrase" we may + * generate for a given word_count. We add 1 to WORDSET_4K_LENGTH_MAX to + * account for separators (whether different or not). When there's no + * trailing separator, we subtract 1. The check against sizeof(output) uses + * ">=" to account for NUL termination. + */ + unsigned int max_length = word_count * (WORDSET_4K_LENGTH_MAX + 1) - !trailing_separator; + if (max_length >= sizeof(output) || (int)max_length > params->max) return NULL; - count = 1 + ((bits - 12) + 14) / 15; - use_separators = ((bits + 11) / 12 != count); + unsigned char rnd[WORDS_MAX * 3]; - length = count * 7 - 1; - if (length >= sizeof(output) || (int)length > params->max) +#ifdef _MSC_VER + HCRYPTPROV hprov; + if (!CryptAcquireContextA(&hprov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) + return NULL; + memset(rnd, 0, sizeof(rnd)); /* old Windows would use previous content as extra seed */ + if (!CryptGenRandom(hprov, sizeof(rnd), rnd)) { + CryptReleaseContext(hprov, 0); + return NULL; + } + CryptReleaseContext(hprov, 0); +#else + int fd; + if ((fd = open("/dev/urandom", O_RDONLY)) < 0) return NULL; + if (read_loop(fd, rnd, sizeof(rnd)) != sizeof(rnd)) { + close(fd); + return NULL; + } + close(fd); +#endif - if ((fd = open("/dev/urandom", O_RDONLY)) < 0) return NULL; + size_t length = 0; + const unsigned char *rndptr; - length = 0; - do { - if (read_loop(fd, bytes, sizeof(bytes)) != sizeof(bytes)) { - close(fd); - return NULL; + for (rndptr = rnd; rndptr <= rnd + sizeof(rnd) - 3; rndptr += 3) { +/* + * Append a word. + * + * Treating *rndptr as little-endian, we use bits 0 to 11 for the word index + * and bit 13 for toggling the case of the first character. Bits 12, 14, and + * 15 are left unused. Bits 16 to 23 are left for the separator. + */ + unsigned int i = (((unsigned int)rndptr[1] & 0x0f) << 8) | rndptr[0]; + const char *start = _passwdqc_wordset_4k[i]; + const char *end = memchr(start, '\0', WORDSET_4K_LENGTH_MAX); + if (!end) + end = start + WORDSET_4K_LENGTH_MAX; + size_t extra = end - start; +/* The ">=" leaves room for either one more separator or NUL */ + if (length + extra >= sizeof(output)) + break; + memcpy(&output[length], start, extra); + if (toggle_case) { +/* Toggle case if bit set (we assume ASCII) */ + output[length] ^= rndptr[1] & 0x20; + bits--; } + length += extra; + bits -= WORD_BITS - 1; - i = (((int)bytes[1] & 0x0f) << 8) | (int)bytes[0]; - start = _passwdqc_wordset_4k[i]; - end = memchr(start, '\0', 6); - if (!end) end = start + 6; - if (length + (end - start) >= sizeof(output) - 1) { - close(fd); - return NULL; - } - memcpy(&output[length], start, end - start); - length += end - start; - bits -= 12; + if (bits <= 0) + break; - if (use_separators && bits > 3) { - i = ((int)bytes[1] & 0x70) >> 4; +/* + * Append a separator character. We use bits 16 to 19. Bits 20 to 23 are left + * unused. + * + * Special case: we may happen to leave a trailing separator if it provides + * enough bits on its own. With WORD_BITS 13 and SEPARATOR_BITS 4, this + * happens e.g. for bits values from 31 to 34, 48 to 51, 65 to 68. + */ + if (use_separators) { + i = rndptr[2] & 0x0f; output[length++] = SEPARATORS[i]; - bits -= 3; - } else - if (bits > 0) - output[length++] = ' '; - } while (bits > 0); + bits -= SEPARATOR_BITS; + } else { + output[length++] = SEPARATORS[0]; + } - memset(bytes, 0, sizeof(bytes)); - output[length] = '\0'; + if (bits <= 0) + break; + } - close(fd); + char *retval = NULL; + + if (bits <= 0 && length < sizeof(output)) { + output[length] = '\0'; + retval = strdup(output); + } + + _passwdqc_memzero(rnd, sizeof(rnd)); + _passwdqc_memzero(output, length); - return output; + return retval; } |