X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=lib%2Futils_crypt.c;h=a5d9904d2bb2418e9c1a3997a4c3da26e9d0e8d7;hb=5dc654433c3816fd726f29c73a0fcdc0484148b8;hp=6ad4196e299ef6f4017c47088202b6f478f46ce8;hpb=a70b27762d9f17bf569d181c6bd8d6299df26807;p=platform%2Fupstream%2Fcryptsetup.git diff --git a/lib/utils_crypt.c b/lib/utils_crypt.c index 6ad4196..a5d9904 100644 --- a/lib/utils_crypt.c +++ b/lib/utils_crypt.c @@ -1,8 +1,30 @@ +/* + * utils_crypt - cipher utilities for cryptsetup + * + * Copyright (C) 2004-2007, Clemens Fruhwirth + * Copyright (C) 2009-2012, 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 + * version 2 as published by the Free Software Foundation. + * + * 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 #include #include #include #include +#include +#include #include #include #include @@ -13,22 +35,44 @@ #include "nls.h" #include "utils_crypt.h" +#define log_dbg(x) crypt_log(NULL, CRYPT_LOG_DEBUG, x) +#define log_err(cd, x) crypt_log(cd, CRYPT_LOG_ERROR, x) + struct safe_allocation { size_t size; char data[0]; }; -int crypt_parse_name_and_mode(const char *s, char *cipher, char *cipher_mode) +int crypt_parse_name_and_mode(const char *s, char *cipher, int *key_nums, + char *cipher_mode) { if (sscanf(s, "%" MAX_CIPHER_LEN_STR "[^-]-%" MAX_CIPHER_LEN_STR "s", cipher, cipher_mode) == 2) { if (!strcmp(cipher_mode, "plain")) strncpy(cipher_mode, "cbc-plain", 10); + if (key_nums) { + char *tmp = strchr(cipher, ':'); + *key_nums = tmp ? atoi(++tmp) : 1; + if (!*key_nums) + return -EINVAL; + } + + return 0; + } + + /* Short version for "empty" cipher */ + if (!strcmp(s, "null")) { + strncpy(cipher, "cipher_null", MAX_CIPHER_LEN); + strncpy(cipher_mode, "ecb", 9); + if (key_nums) + *key_nums = 0; return 0; } if (sscanf(s, "%" MAX_CIPHER_LEN_STR "[^-]", cipher) == 1) { strncpy(cipher_mode, "cbc-plain", 10); + if (key_nums) + *key_nums = 1; return 0; } @@ -48,7 +92,9 @@ void *crypt_safe_alloc(size_t size) return NULL; alloc->size = size; + memset(&alloc->data, 0, size); + /* coverity[leaked_storage] */ return &alloc->data; } @@ -59,7 +105,8 @@ void crypt_safe_free(void *data) if (!data) return; - alloc = data - offsetof(struct safe_allocation, data); + alloc = (struct safe_allocation *) + ((char *)data - offsetof(struct safe_allocation, data)); memset(data, 0, alloc->size); @@ -69,14 +116,15 @@ void crypt_safe_free(void *data) void *crypt_safe_realloc(void *data, size_t size) { + struct safe_allocation *alloc; void *new_data; new_data = crypt_safe_alloc(size); if (new_data && data) { - struct safe_allocation *alloc; - alloc = data - offsetof(struct safe_allocation, data); + alloc = (struct safe_allocation *) + ((char *)data - offsetof(struct safe_allocation, data)); if (size > alloc->size) size = alloc->size; @@ -143,7 +191,7 @@ static int interactive_pass(const char *prompt, char *pass, size_t maxlen, memcpy(&tmp, &orig, sizeof(tmp)); tmp.c_lflag &= ~ECHO; - if (write(outfd, prompt, strlen(prompt)) < 0) + if (prompt && write(outfd, prompt, strlen(prompt)) < 0) goto out_err; tcsetattr(infd, TCSAFLUSH, &tmp); @@ -161,135 +209,328 @@ out_err: return failed; } +static int crypt_get_key_tty(const char *prompt, + char **key, size_t *key_size, + int timeout, int verify, + struct crypt_device *cd) +{ + int key_size_max = DEFAULT_PASSPHRASE_SIZE_MAX; + int r = -EINVAL; + char *pass = NULL, *pass_verify = NULL; + + log_dbg("Interactive passphrase entry requested."); + + pass = crypt_safe_alloc(key_size_max + 1); + if (!pass) { + log_err(cd, _("Out of memory while reading passphrase.\n")); + return -ENOMEM; + } + + if (interactive_pass(prompt, pass, key_size_max, timeout)) { + log_err(cd, _("Error reading passphrase from terminal.\n")); + goto out_err; + } + pass[key_size_max] = '\0'; + + if (verify) { + pass_verify = crypt_safe_alloc(key_size_max); + if (!pass_verify) { + log_err(cd, _("Out of memory while reading passphrase.\n")); + r = -ENOMEM; + goto out_err; + } + + if (interactive_pass(_("Verify passphrase: "), + pass_verify, key_size_max, timeout)) { + log_err(cd, _("Error reading passphrase from terminal.\n")); + goto out_err; + } + + if (strncmp(pass, pass_verify, key_size_max)) { + log_err(cd, _("Passphrases do not match.\n")); + r = -EPERM; + goto out_err; + } + } + + *key = pass; + *key_size = strlen(pass); + r = 0; +out_err: + crypt_safe_free(pass_verify); + if (r) + crypt_safe_free(pass); + return r; +} + /* - * Password reading behaviour matrix of get_key - * FIXME: rewrite this from scratch. - * p v n h - * -----------------+---+---+---+--- - * interactive | Y | Y | Y | Inf - * from fd | N | N | Y | Inf - * from binary file | N | N | N | Inf or options->key_size - * - * Legend: p..prompt, v..can verify, n..newline-stop, h..read horizon - * - * Note: --key-file=- is interpreted as a read from a binary file (stdin) + * A simple call to lseek(3) might not be possible for some inputs (e.g. + * reading from a pipe), so this function instead reads of up to BUFSIZ bytes + * at a time until the specified number of bytes. It returns -1 on read error + * or when it reaches EOF before the requested number of bytes have been + * discarded. */ +static int keyfile_seek(int fd, size_t bytes) +{ + char tmp[BUFSIZ]; + size_t next_read; + ssize_t bytes_r; + off_t r; + + r = lseek(fd, bytes, SEEK_CUR); + if (r > 0) + return 0; + if (r < 0 && errno != ESPIPE) + return -1; + + while (bytes > 0) { + /* figure out how much to read */ + next_read = bytes > sizeof(tmp) ? sizeof(tmp) : bytes; + + bytes_r = read(fd, tmp, next_read); + if (bytes_r < 0) { + if (errno == EINTR) + continue; + + /* read error */ + return -1; + } + + if (bytes_r == 0) + /* EOF */ + break; + + bytes -= bytes_r; + } -int crypt_get_key(char *prompt, char **key, unsigned int *passLen, int key_size, + return bytes == 0 ? 0 : -1; +} + +/* + * Note: --key-file=- is interpreted as a read from a binary file (stdin) + * key_size_max == 0 means detect maximum according to input type (tty/file) + * timeout and verify options only applies to tty input + */ +int crypt_get_key(const char *prompt, + char **key, size_t *key_size, + size_t keyfile_offset, size_t keyfile_size_max, const char *key_file, int timeout, int verify, struct crypt_device *cd) { - int fd = -1; + int fd, regular_file, read_stdin, char_read, unlimited_read = 0; + int r = -EINVAL; char *pass = NULL; - int read_horizon; - int regular_file = 0; - int read_stdin; - int r; + size_t buflen, i, file_read_size; struct stat st; + *key = NULL; + *key_size = 0; + /* Passphrase read from stdin? */ read_stdin = (!key_file || !strcmp(key_file, "-")) ? 1 : 0; - /* read_horizon applies only for real keyfile, not stdin or terminal */ - read_horizon = (key_file && !read_stdin) ? key_size : 0 /* until EOF */; + if (read_stdin && isatty(STDIN_FILENO)) { + if (keyfile_offset) { + log_err(cd, _("Cannot use offset with terminal input.\n")); + return -EINVAL; + } + return crypt_get_key_tty(prompt, key, key_size, timeout, verify, cd); + } + + if (read_stdin) + log_dbg("STDIN descriptor passphrase entry requested."); + else + log_dbg("File descriptor passphrase entry requested."); + + /* If not requsted otherwise, we limit input to prevent memory exhaustion */ + if (keyfile_size_max == 0) { + keyfile_size_max = DEFAULT_KEYFILE_SIZE_MAXKB * 1024; + unlimited_read = 1; + } - /* Setup file descriptior */ fd = read_stdin ? STDIN_FILENO : open(key_file, O_RDONLY); if (fd < 0) { - crypt_log(cd, CRYPT_LOG_ERROR, - _("Failed to open key file.\n")); - goto out_err; + log_err(cd, _("Failed to open key file.\n")); + return -EINVAL; } - /* Interactive case */ - if(isatty(fd)) { - int i; - - pass = crypt_safe_alloc(MAX_TTY_PASSWORD_LEN); - if (!pass || interactive_pass(prompt, pass, MAX_TTY_PASSWORD_LEN, timeout)) { - crypt_log(cd, CRYPT_LOG_ERROR, - _("Error reading passphrase from terminal.\n")); + /* use 4k for buffer (page divisor but avoid huge pages) */ + buflen = 4096 - sizeof(struct safe_allocation); + regular_file = 0; + if(!read_stdin) { + if(stat(key_file, &st) < 0) { + log_err(cd, _("Failed to stat key file.\n")); goto out_err; } - if (verify) { - char pass_verify[MAX_TTY_PASSWORD_LEN]; - i = interactive_pass(_("Verify passphrase: "), pass_verify, sizeof(pass_verify), timeout); - if (i || strcmp(pass, pass_verify) != 0) { - crypt_log(cd, CRYPT_LOG_ERROR, - _("Passphrases do not match.\n")); - goto out_err; - } - memset(pass_verify, 0, sizeof(pass_verify)); - } - *passLen = strlen(pass); - *key = pass; - } else { - /* - * This is either a fd-input or a file, in neither case we can verify the input, - * however we don't stop on new lines if it's a binary file. - */ - int buflen, i; - - /* The following for control loop does an exhausting - * read on the key material file, if requested with - * key_size == 0, as it's done by LUKS. However, we - * should warn the user, if it's a non-regular file, - * such as /dev/random, because in this case, the loop - * will read forever. - */ - if(!read_stdin && read_horizon == 0) { - if(stat(key_file, &st) < 0) { - crypt_log(cd, CRYPT_LOG_ERROR, - _("Failed to stat key file.\n")); + if(S_ISREG(st.st_mode)) { + regular_file = 1; + file_read_size = (size_t)st.st_size; + + if (keyfile_offset > file_read_size) { + log_err(cd, _("Cannot seek to requested keyfile offset.\n")); goto out_err; } - if(!S_ISREG(st.st_mode)) - crypt_log(cd, CRYPT_LOG_NORMAL, - _("Warning: exhausting read requested, but key file" - " is not a regular file, function might never return.\n")); - else - regular_file = 1; + file_read_size -= keyfile_offset; + + /* known keyfile size, alloc it in one step */ + if (file_read_size >= keyfile_size_max) + buflen = keyfile_size_max; + else if (file_read_size) + buflen = file_read_size; } - buflen = 0; - for(i = 0; read_horizon == 0 || i < read_horizon; i++) { - if(i >= buflen - 1) { - buflen += 128; - pass = crypt_safe_realloc(pass, buflen); - if (!pass) { - crypt_log(cd, CRYPT_LOG_ERROR, - _("Out of memory while reading passphrase.\n")); - goto out_err; - } - } + } + + pass = crypt_safe_alloc(buflen); + if (!pass) { + log_err(cd, _("Out of memory while reading passphrase.\n")); + goto out_err; + } + + /* Discard keyfile_offset bytes on input */ + if (keyfile_offset && keyfile_seek(fd, keyfile_offset) < 0) { + log_err(cd, _("Cannot seek to requested keyfile offset.\n")); + goto out_err; + } - r = read(fd, pass + i, 1); - if (r < 0) { - crypt_log(cd, CRYPT_LOG_ERROR, - _("Error reading passphrase.\n")); + for(i = 0; i < keyfile_size_max; i++) { + if(i == buflen) { + buflen += 4096; + pass = crypt_safe_realloc(pass, buflen); + if (!pass) { + log_err(cd, _("Out of memory while reading passphrase.\n")); + r = -ENOMEM; goto out_err; } - - /* Stop on newline only if not requested read from keyfile */ - if(r == 0 || (!key_file && pass[i] == '\n')) - break; } - /* Fail if piped input dies reading nothing */ - if(!i && !regular_file) + + char_read = read(fd, &pass[i], 1); + if (char_read < 0) { + log_err(cd, _("Error reading passphrase.\n")); goto out_err; - pass[i] = 0; - *key = pass; - *passLen = i; + } + + /* Stop on newline only if not requested read from keyfile */ + if(char_read == 0 || (!key_file && pass[i] == '\n')) + break; + } + + /* Fail if piped input dies reading nothing */ + if(!i && !regular_file) { + log_dbg("Nothing read on input."); + r = -EPIPE; + goto out_err; + } + + /* Fail if we exceeded internal default (no specified size) */ + if (unlimited_read && i == keyfile_size_max) { + log_err(cd, _("Maximum keyfile size exceeded.\n")); + goto out_err; } - if(fd != STDIN_FILENO) - close(fd); - return 0; + if (!unlimited_read && i != keyfile_size_max) { + log_err(cd, _("Cannot read requested amount of data.\n")); + goto out_err; + } + + *key = pass; + *key_size = i; + r = 0; out_err: - if(fd >= 0 && fd != STDIN_FILENO) + if(fd != STDIN_FILENO) close(fd); - if(pass) + + if (r) crypt_safe_free(pass); - *key = NULL; - *passLen = 0; - return -EINVAL; + return r; +} + +ssize_t crypt_hex_to_bytes(const char *hex, char **result, int safe_alloc) +{ + char buf[3] = "xx\0", *endp, *bytes; + size_t i, len; + + len = strlen(hex); + if (len % 2) + return -EINVAL; + len /= 2; + + bytes = safe_alloc ? crypt_safe_alloc(len) : malloc(len); + if (!bytes) + return -ENOMEM; + + for (i = 0; i < len; i++) { + memcpy(buf, &hex[i * 2], 2); + bytes[i] = strtoul(buf, &endp, 16); + if (endp != &buf[2]) { + safe_alloc ? crypt_safe_free(bytes) : free(bytes); + return -EINVAL; + } + } + *result = bytes; + return i; +} + +/* + * Device size string parsing, suffixes: + * s|S - 512 bytes sectors + * k |K |m |M |g |G |t |T - 1024 base + * kiB|KiB|miB|MiB|giB|GiB|tiB|TiB - 1024 base + * kb |KB |mM |MB |gB |GB |tB |TB - 1000 base + */ +int crypt_string_to_size(struct crypt_device *cd, const char *s, uint64_t *size) +{ + char *endp = NULL; + size_t len; + uint64_t mult_base, mult, tmp; + + *size = strtoull(s, &endp, 10); + if (!isdigit(s[0]) || + (errno == ERANGE && *size == ULLONG_MAX) || + (errno != 0 && *size == 0)) + return -EINVAL; + + if (!endp || !*endp) + return 0; + + len = strlen(endp); + /* Allow "B" and "iB" suffixes */ + if (len > 3 || + (len == 3 && (endp[1] != 'i' || endp[2] != 'B')) || + (len == 2 && endp[1] != 'B')) + return -EINVAL; + + if (len == 1 || len == 3) + mult_base = 1024; + else + mult_base = 1000; + + mult = 1; + switch (endp[0]) { + case 's': + case 'S': mult = 512; + break; + case 't': + case 'T': mult *= mult_base; + /* Fall through */ + case 'g': + case 'G': mult *= mult_base; + /* Fall through */ + case 'm': + case 'M': mult *= mult_base; + /* Fall through */ + case 'k': + case 'K': mult *= mult_base; + break; + default: + return -EINVAL; + } + + tmp = *size * mult; + if ((tmp / *size) != mult) { + log_dbg("Device size overflow."); + return -EINVAL; + } + + *size = tmp; + return 0; }