| /* |
| * cryptsetup kernel RNG access functions |
| * |
| * Copyright (C) 2010-2017, Red Hat, Inc. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version 2 |
| * of the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| */ |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <assert.h> |
| #include <sys/select.h> |
| |
| #include "libcryptsetup.h" |
| #include "internal.h" |
| |
| #ifndef O_CLOEXEC |
| #define O_CLOEXEC 0 |
| #endif |
| |
| static int random_initialised = 0; |
| |
| #define URANDOM_DEVICE "/dev/urandom" |
| static int urandom_fd = -1; |
| |
| #define RANDOM_DEVICE "/dev/random" |
| static int random_fd = -1; |
| |
| /* Read random chunk - gathered data usually appears with this granularity */ |
| #define RANDOM_DEVICE_CHUNK 8 |
| |
| /* Timeout to print warning if no random data (entropy) */ |
| #define RANDOM_DEVICE_TIMEOUT 5 |
| |
| /* URANDOM_DEVICE access */ |
| static int _get_urandom(struct crypt_device *ctx __attribute__((unused)), |
| char *buf, size_t len) |
| { |
| int r; |
| size_t old_len = len; |
| char *old_buf = buf; |
| |
| assert(urandom_fd != -1); |
| |
| while(len) { |
| r = read(urandom_fd, buf, len); |
| if (r == -1 && errno != EINTR) |
| return -EINVAL; |
| if (r > 0) { |
| len -= r; |
| buf += r; |
| } |
| } |
| |
| assert(len == 0); |
| assert((size_t)(buf - old_buf) == old_len); |
| |
| return 0; |
| } |
| |
| static void _get_random_progress(struct crypt_device *ctx, int warn, |
| size_t expected_len, size_t read_len) |
| { |
| if (warn) |
| log_std(ctx, |
| _("System is out of entropy while generating volume key.\n" |
| "Please move mouse or type some text in another window " |
| "to gather some random events.\n")); |
| |
| log_std(ctx, _("Generating key (%d%% done).\n"), |
| (int)((expected_len - read_len) * 100 / expected_len)); |
| } |
| |
| /* RANDOM_DEVICE access */ |
| static int _get_random(struct crypt_device *ctx, char *buf, size_t len) |
| { |
| int r, warn_once = 1; |
| size_t n, old_len = len; |
| char *old_buf = buf; |
| fd_set fds; |
| struct timeval tv; |
| |
| assert(random_fd != -1); |
| |
| while (len) { |
| FD_ZERO(&fds); |
| FD_SET(random_fd, &fds); |
| |
| tv.tv_sec = RANDOM_DEVICE_TIMEOUT; |
| tv.tv_usec = 0; |
| |
| r = select(random_fd + 1, &fds, NULL, NULL, &tv); |
| if(r == -1) |
| return -EINVAL; |
| |
| if(!r) { |
| _get_random_progress(ctx, warn_once, old_len, len); |
| warn_once = 0; |
| continue; |
| } |
| |
| do { |
| n = RANDOM_DEVICE_CHUNK; |
| if (len < RANDOM_DEVICE_CHUNK) |
| n = len; |
| |
| r = read(random_fd, buf, n); |
| |
| if (r == -1 && errno == EINTR) { |
| r = 0; |
| continue; |
| } |
| |
| /* bogus read? */ |
| if(r > (int)n) |
| return -EINVAL; |
| |
| /* random device is opened with O_NONBLOCK, EAGAIN is expected */ |
| if (r == -1 && (errno != EAGAIN && errno != EWOULDBLOCK)) |
| return -EINVAL; |
| |
| if (r > 0) { |
| len -= r; |
| buf += r; |
| } |
| } while (len && r > 0); |
| } |
| |
| assert(len == 0); |
| assert((size_t)(buf - old_buf) == old_len); |
| |
| if (!warn_once) |
| _get_random_progress(ctx, 0, old_len, len); |
| |
| return 0; |
| } |
| /* Initialisation of both RNG file descriptors is mandatory */ |
| int crypt_random_init(struct crypt_device *ctx) |
| { |
| if (random_initialised) |
| return 0; |
| |
| /* Used for CRYPT_RND_NORMAL */ |
| if(urandom_fd == -1) |
| urandom_fd = open(URANDOM_DEVICE, O_RDONLY | O_CLOEXEC); |
| if(urandom_fd == -1) |
| goto fail; |
| |
| /* Used for CRYPT_RND_KEY */ |
| if(random_fd == -1) |
| random_fd = open(RANDOM_DEVICE, O_RDONLY | O_NONBLOCK | O_CLOEXEC); |
| if(random_fd == -1) |
| goto fail; |
| |
| if (crypt_fips_mode()) |
| log_verbose(ctx, _("Running in FIPS mode.\n")); |
| |
| random_initialised = 1; |
| return 0; |
| fail: |
| crypt_random_exit(); |
| log_err(ctx, _("Fatal error during RNG initialisation.\n")); |
| return -ENOSYS; |
| } |
| |
| int crypt_random_get(struct crypt_device *ctx, char *buf, size_t len, int quality) |
| { |
| int status, rng_type; |
| |
| switch(quality) { |
| case CRYPT_RND_NORMAL: |
| status = _get_urandom(ctx, buf, len); |
| break; |
| case CRYPT_RND_SALT: |
| if (crypt_fips_mode()) |
| status = crypt_backend_rng(buf, len, quality, 1); |
| else |
| status = _get_urandom(ctx, buf, len); |
| break; |
| case CRYPT_RND_KEY: |
| if (crypt_fips_mode()) { |
| status = crypt_backend_rng(buf, len, quality, 1); |
| break; |
| } |
| rng_type = ctx ? crypt_get_rng_type(ctx) : |
| crypt_random_default_key_rng(); |
| switch (rng_type) { |
| case CRYPT_RNG_URANDOM: |
| status = _get_urandom(ctx, buf, len); |
| break; |
| case CRYPT_RNG_RANDOM: |
| status = _get_random(ctx, buf, len); |
| break; |
| default: |
| abort(); |
| } |
| break; |
| default: |
| log_err(ctx, _("Unknown RNG quality requested.\n")); |
| return -EINVAL; |
| } |
| |
| if (status) |
| log_err(ctx, _("Error %d reading from RNG: %s\n"), |
| errno, strerror(errno)); |
| |
| return status; |
| } |
| |
| void crypt_random_exit(void) |
| { |
| random_initialised = 0; |
| |
| if(random_fd != -1) { |
| (void)close(random_fd); |
| random_fd = -1; |
| } |
| |
| if(urandom_fd != -1) { |
| (void)close(urandom_fd); |
| urandom_fd = -1; |
| } |
| } |
| |
| int crypt_random_default_key_rng(void) |
| { |
| /* coverity[pointless_string_compare] */ |
| if (!strcmp(DEFAULT_RNG, RANDOM_DEVICE)) |
| return CRYPT_RNG_RANDOM; |
| |
| /* coverity[pointless_string_compare] */ |
| if (!strcmp(DEFAULT_RNG, URANDOM_DEVICE)) |
| return CRYPT_RNG_URANDOM; |
| |
| /* RNG misconfiguration is fatal */ |
| abort(); |
| } |