blob: 6e6940eea9061e4491694eb3345ab663443a0e7c [file] [log] [blame]
/*
* key.c -- Common key handling code for swtpm and swtpm_cuse
*
* (c) Copyright IBM Corporation 2014, 2015.
*
* Author: Stefan Berger <stefanb@us.ibm.com>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 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.
*
* Neither the names of the IBM Corporation nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "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 COPYRIGHT
* HOLDER OR CONTRIBUTORS 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.
*/
#include "config.h"
#include <openssl/sha.h>
#include <openssl/evp.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include "key.h"
#include "logging.h"
#include "utils.h"
/*
* key_format_from_string:
* Convert the string into a key format identifier
* @format: either 'hex' or 'binary'
*
* Returns a key format identifier
*/
enum key_format
key_format_from_string(const char *format)
{
if (!strcmp(format, "hex")) {
return KEY_FORMAT_HEX;
} else if (!strcmp(format, "binary")) {
return KEY_FORMAT_BINARY;
}
logprintf(STDERR_FILENO, "Unknown key format '%s'.\n", format);
return KEY_FORMAT_UNKNOWN;
}
/*
* encryption_mode_from_string:
* Convert the string into a encryption mode identifier
* @mode: string describing encryption mode
* @keylen: the length of the key in bytes
*
* Returns an encryption mode identifier
*/
enum encryption_mode
encryption_mode_from_string(const char *mode, size_t *keylen)
{
if (!strcmp(mode, "aes-cbc") || !strcmp(mode, "aes-128-cbc")) {
*keylen = 128/8;
return ENCRYPTION_MODE_AES_CBC;
} else if (!strcmp(mode, "aes-256-cbc")) {
*keylen = 256/8;
return ENCRYPTION_MODE_AES_CBC;
}
return ENCRYPTION_MODE_UNKNOWN;
}
/*
* kdf_identifier_from_string:
* Convert the string in a kdf identifier
* @mode: string describing the kdf
*
* Return a kdf identifier
*/
enum kdf_identifier
kdf_identifier_from_string(const char *kdf)
{
if (!strcmp(kdf, "sha512")) {
return KDF_IDENTIFIER_SHA512;
} else if (!strcmp(kdf, "pbkdf2")) {
return KDF_IDENTIFIER_PBKDF2;
}
return KDF_IDENTIFIER_UNKNOWN;
}
/*
* key_stream_to_bin
* Convert a stream of ASCII hex digits into a key; convert a maximum of
* bin_size bytes;
*
* @input: input data holding hex digits
* @bin: output field of bin_size
* @bin_size: max. number of bytes to convert
*
* Returns the number of digits that were converted.
*/
static ssize_t
key_stream_to_bin(const char *input, unsigned char *bin, size_t bin_size)
{
ssize_t digits = 0;
int n, num;
while (input[digits] &&
!isspace((int)input[digits]) &&
bin_size > (size_t)digits / 2) {
num = sscanf(&input[digits], "%2hhx%n", &bin[digits/2], &n);
if (num != 1 || n != 2)
return -1;
digits += 2;
}
if (input[digits] && !isspace((int)input[digits]))
return -1;
return (digits != 0) ? digits : -1;
}
/*
* key_parse_as_hexkey:
* Parse the raw key data as a key in ASCII hex format; they key may
* have a leading '0x'.
* @rawkey: ASCII data for a hex key with possible leading '0x'
* @key: buffer for key
* @keylen: actual key len returned by this function
* @maxkeylen: the max. size of the key; this is equivalent to the size of
* the key buffer
* Returns 0 on success, -1 on failure
*/
static int
key_parse_as_hexkey(const char *rawkey,
unsigned char *key, size_t *keylen, size_t maxkeylen)
{
ssize_t digits;
off_t offset = 0;
if (!strncmp(rawkey, "0x", 2))
offset = 2;
digits = key_stream_to_bin(&rawkey[offset], key, maxkeylen);
if (digits < 0) {
logprintf(STDERR_FILENO,
"Could not parse key hex string into %zu byte buffer.\n",
maxkeylen);
return -1;
} else if (digits == 128/4) {
*keylen = 128/8;
} else if (digits == 256/4) {
*keylen = 256/8;
} else {
logprintf(STDERR_FILENO,
"Unsupported key length with %zu digits.\n",
digits);
return -1;
}
if (*keylen < maxkeylen) {
logprintf(STDERR_FILENO,
"The provided key is too short. Got %zu bytes, need %zu.\n",
*keylen, maxkeylen);
return -1;
}
return 0;
}
/*
* key_load_key_fd:
* Load the raw key data from a file and convert it to a key.
* @fd: file descriptor to read raw key data from
* @keyformat: the format the raw key data are in; may either indicate
* binary data or hex string
* @key: the buffer for holding the converted key
* @keylen: the actual key len of the converted key returned by this
* function
* @maxkeylen: the max. size of the key; corresponds to the size of the
* key buffer
*/
int
key_load_key_fd(int fd, enum key_format keyformat,
unsigned char *key, size_t *keylen, size_t maxkeylen)
{
int ret = -1;
char filebuffer[2 + 256/4 + 1 + 1];
ssize_t len;
len = read_eintr(fd, filebuffer, sizeof(filebuffer) - 1);
if (len < 0) {
logprintf(STDERR_FILENO, "Unable to read key: %s\n",
strerror(errno));
return -1;
}
filebuffer[len] = 0;
switch (keyformat) {
case KEY_FORMAT_BINARY:
*keylen = len;
if (maxkeylen < (size_t)len) {
logprintf(STDERR_FILENO,
"Key is larger than buffer (%zu > %zu).\n",
len, maxkeylen);
return -1;
}
memcpy(key, filebuffer, len);
ret = 0;
break;
case KEY_FORMAT_HEX:
if (key_parse_as_hexkey(filebuffer, key, keylen, maxkeylen) < 0)
return -1;
ret = 0;
break;
case KEY_FORMAT_UNKNOWN:
break;
}
return ret;
}
/*
* key_load_key:
* Load the raw key data from a file and convert it to a key.
* @filename: file holding the raw key data
* @keyformat: the format the raw key data are in; may either indicate
* binary data or hex string
* @key: the buffer for holding the converted key
* @keylen: the actual key len of the converted key returned by this
* function
* @maxkeylen: the max. size of the key; corresponds to the size of the
* key buffer
*/
int
key_load_key(const char *filename, enum key_format keyformat,
unsigned char *key, size_t *keylen, size_t maxkeylen)
{
int ret;
int fd;
fd = open(filename, O_RDONLY);
if (fd < 0) {
logprintf(STDERR_FILENO, "Unable to open file %s: %s\n",
filename, strerror(errno));
return -1;
}
ret = key_load_key_fd(fd, keyformat, key, keylen, maxkeylen);
close(fd);
return ret;
}
/*
* key_from_pwdfile:
* Read the password from the given file descriptor, convert the password into
* a key by applying a KDF on the password and use the first bytes
* of the hash as the key.
* @fd: file descriptor to read password from
* @key: the buffer for holding the key
* @keylen: the actual key len of the converted key returned by this
* function
* @maxkeylen: the max. size of the key; corresponds to the size of the
* key buffer
* @kdfid: the kdf to invoke to create the key
*/
int
key_from_pwdfile_fd(int fd, unsigned char *key, size_t *keylen,
size_t maxkeylen, enum kdf_identifier kdfid)
{
unsigned char *filebuffer = NULL;
size_t filelen = 1024;
off_t offset = 0;
ssize_t len;
unsigned char hashbuf[SHA512_DIGEST_LENGTH];
int ret = -1;
const unsigned char salt[] = {'s','w','t','p','m'};
if (maxkeylen > sizeof(hashbuf)) {
logprintf(STDERR_FILENO,
"Request keylength is too big (%zu > %zu)\n",
maxkeylen, sizeof(hashbuf));
return -1;
}
while (true) {
unsigned char *tmp = filebuffer;
filebuffer = realloc(tmp, filelen);
if (!filebuffer) {
logprintf(STDERR_FILENO,
"Could not allocate %zu bytes for filebuffer\n",
filelen);
free(tmp);
goto exit;
}
len = read_eintr(fd, &filebuffer[offset], filelen - offset);
if (len < 0) {
logprintf(STDERR_FILENO,
"Unable to read passphrase: %s\n",
strerror(errno));
goto exit;
}
/* EOF ? */
if ((size_t)len < filelen - offset) {
len += offset;
break;
}
/* expecting more bytes */
offset += len;
filelen += 1024;
}
*keylen = maxkeylen;
switch (kdfid) {
case KDF_IDENTIFIER_SHA512:
if (sizeof(hashbuf) < *keylen) {
logprintf(STDERR_FILENO,
"Requested %zu bytes for key, only got %zu.\n",
*keylen, sizeof(hashbuf));
goto exit;
}
SHA512(filebuffer, len, hashbuf);
memcpy(key, hashbuf, *keylen);
break;
case KDF_IDENTIFIER_PBKDF2:
if (PKCS5_PBKDF2_HMAC((const char *)filebuffer, len,
salt, sizeof(salt), 1000,
EVP_sha512(), *keylen, key) != 1) {
logprintf(STDERR_FILENO,
"PKCS5_PBKDF2_HMAC with SHA512 failed\n");
goto exit;
}
break;
case KDF_IDENTIFIER_UNKNOWN:
logprintf(STDERR_FILENO,
"Unknown KDF\n");
goto exit;
}
ret = 0;
exit:
free(filebuffer);
return ret;
}
/*
* key_from_pwdfile:
* Read the password from the given file, convert the password into
* a key by applying a KDF on the password and use the first bytes
* of the hash as the key.
* @filename: name of the file holding the password
* @key: the buffer for holding the key
* @keylen: the actual key len of the converted key returned by this
* function
* @maxkeylen: the max. size of the key; corresponds to the size of the
* key buffer
* @kdfid: the kdf to invoke to create the key
*/
int
key_from_pwdfile(const char *filename, unsigned char *key, size_t *keylen,
size_t maxkeylen, enum kdf_identifier kdfid)
{
int ret;
int fd;
fd = open(filename, O_RDONLY);
if (fd < 0) {
logprintf(STDERR_FILENO,
"Unable to open file %s : %s\n",
filename, strerror(errno));
return -1;
}
ret = key_from_pwdfile_fd(fd, key, keylen, maxkeylen, kdfid);
close(fd);
return ret;
}