blob: a17040be7250ac8517ee9af3b62e9f46e4fe035b [file] [log] [blame] [edit]
/*
* Copyright (C) 2014-2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <assert.h>
#include <lk/compiler.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/rand.h>
#include "crypt.h"
/* Backwards compatability */
#if OPENSSL_VERSION_NUMBER < 0x10100000
static void EVP_CIPHER_CTX_reset(EVP_CIPHER_CTX* ctx) {
EVP_CIPHER_CTX_cleanup(ctx);
EVP_CIPHER_CTX_init(ctx);
}
static HMAC_CTX* HMAC_CTX_new(void) {
HMAC_CTX* ctx = malloc(sizeof(HMAC_CTX));
if (ctx) {
HMAC_CTX_init(ctx);
}
return ctx;
}
static void HMAC_CTX_reset(HMAC_CTX* ctx) {
HMAC_CTX_cleanup(ctx);
HMAC_CTX_init(ctx);
}
static void HMAC_CTX_free(HMAC_CTX* ctx) {
if (ctx) {
HMAC_CTX_cleanup(ctx);
free(ctx);
}
}
#endif
/*
* The OpenSSL 1.1.0 API requires we allocate these dynamically. Cache them
* globally to avoid alocator thrash and the potential for another dynamic
* failure.
*/
static EVP_CIPHER_CTX* cipher_ctx;
static HMAC_CTX* hmac_ctx;
void crypt_init(void) {
assert(!cipher_ctx);
assert(!hmac_ctx);
cipher_ctx = EVP_CIPHER_CTX_new();
assert(cipher_ctx);
hmac_ctx = HMAC_CTX_new();
assert(hmac_ctx);
}
void crypt_shutdown(void) {
EVP_CIPHER_CTX_free(cipher_ctx);
cipher_ctx = NULL;
HMAC_CTX_free(hmac_ctx);
hmac_ctx = NULL;
}
/**
* crypt - Helper function for encrypt and decrypt.
* @key: Key object.
* @data_in_out: Data to encrypt or decrypt.
* @data_size: Number of bytes in @data_in_out.
* @iv: Initialization vector to use for Cipher Block Chaining.
* @encrypt: %true to select encrypt, %false to select decrypt.
*
* Return: 0 on success, -1 if an error was detected.
*/
static int crypt(const struct key* key,
void* data_in_out,
size_t data_size,
const struct iv* iv,
bool encrypt) {
int evp_ret;
const EVP_CIPHER* cipher;
int out_data_size;
size_t key_len;
/*
* Make sure iv is large enough. Current implementation allows static
* check.
* TODO: Switch to runtime check for selcted cipher if EVP_MAX_IV_LENGTH
* changes to cover larger ivs used by other cipers.
*/
STATIC_ASSERT(sizeof(*iv) >= EVP_MAX_IV_LENGTH);
cipher = EVP_aes_128_ctr();
key_len = EVP_CIPHER_key_length(cipher);
if (key_len > sizeof(*key)) {
fprintf(stderr, "key too small for selected cipher, %zd < %zd\n",
sizeof(*key), key_len);
evp_ret = 0;
goto err;
}
assert(cipher_ctx);
EVP_CIPHER_CTX_reset(cipher_ctx);
evp_ret = EVP_CipherInit_ex(cipher_ctx, cipher, NULL, key->byte, iv->byte,
encrypt);
if (!evp_ret) {
fprintf(stderr, "EVP_CipherInit_ex failed\n");
goto err;
}
evp_ret = EVP_CIPHER_CTX_set_padding(cipher_ctx, 0);
if (!evp_ret) {
fprintf(stderr, "EVP_CIPHER_CTX_set_padding failed\n");
goto err;
}
evp_ret = EVP_CipherUpdate(cipher_ctx, data_in_out, &out_data_size,
data_in_out, data_size);
if (!evp_ret) {
fprintf(stderr, "EVP_CipherUpdate failed\n");
goto err;
}
if (out_data_size != (int)data_size) {
fprintf(stderr, "bad output data size %d != %zd\n", out_data_size,
data_size);
evp_ret = 0;
goto err;
}
evp_ret = EVP_CipherFinal_ex(cipher_ctx, NULL, &out_data_size);
if (!evp_ret) {
fprintf(stderr, "EVP_CipherFinal_ex failed\n");
goto err;
}
err:
return evp_ret ? 0 : -1;
}
/**
* str_hash - Cacluate a 64-bit hash for a string.
* @str: Key object.
*
* Return: Low 8 bytes of SHA1 hash as a little endian 64 bit value.
*/
uint64_t str_hash(const char* str) {
int evp_ret;
size_t len = strlen(str);
uint8_t md[EVP_MAX_MD_SIZE];
uint64_t ret;
evp_ret = EVP_Digest(str, len, md, NULL, EVP_sha1(), NULL);
if (!evp_ret) {
fprintf(stderr, "EVP_Digest failed\n");
assert(false);
return 0;
}
STATIC_ASSERT(sizeof(ret) <= sizeof(md));
memcpy(&ret, md, sizeof(ret));
return ret;
}
/**
* calculate_mac - Calulate keyed-hash message authentication code (HMAC SHA256)
* @key: Key object.
* @mac: Mac object to return calulated mac in.
* @data: Data to calculate mac for.
* @data_size: Number of bytes in @data.
*
* Return: 0 on success, -1 if an error was detected.
*/
int calculate_mac(const struct key* key,
struct mac* mac,
const void* data,
size_t data_size) {
int hmac_ret;
unsigned int md_len;
unsigned char mac_buf[EVP_MAX_MD_SIZE];
assert(hmac_ctx);
HMAC_CTX_reset(hmac_ctx);
hmac_ret = HMAC_Init_ex(hmac_ctx, key, sizeof(*key), EVP_sha256(), NULL);
if (!hmac_ret) {
fprintf(stderr, "HMAC_Init_ex failed\n");
goto err;
}
hmac_ret = HMAC_Update(hmac_ctx, data, data_size);
if (!hmac_ret) {
fprintf(stderr, "HMAC_Update failed\n");
goto err;
}
hmac_ret = HMAC_Final(hmac_ctx, mac_buf, &md_len);
if (!hmac_ret) {
fprintf(stderr, "HMAC_Final failed\n");
goto err;
}
if (md_len < sizeof(*mac)) {
fprintf(stderr, "bad md_len %d < %zd\n", md_len, sizeof(*mac));
hmac_ret = 0;
goto err;
}
memcpy(mac, mac_buf, sizeof(*mac));
err:
return hmac_ret ? 0 : -1;
}
/**
* generate_iv - Generate a random iv value.
* @iv_out: IV object.
*
* Return: 0 on success, -1 if an error was detected.
*/
int generate_iv(struct iv* iv_out) {
int rand_ret;
rand_ret = RAND_bytes(iv_out->byte, sizeof(iv_out->byte));
if (!rand_ret) {
fprintf(stderr, "RAND_bytes failed\n");
}
return rand_ret ? 0 : -1;
}
/**
* storage_encrypt - Encrypt data using AES-128-CTR.
* @key: Key object.
* @data_in_out: Data to encrypt.
* @data_size: Number of bytes in @data_in_out, but be a multiple of
* AES_BLOCK_SIZE.
* @iv_in: Initialization vector to use for Cipher Block Chaining.
*
* Return: 0 on success, -1 if an error was detected.
*/
int storage_encrypt(const struct key* key,
void* data_in_out,
size_t data_size,
const struct iv* iv_in) {
return crypt(key, data_in_out, data_size, iv_in, true);
}
/**
* storage_decrypt - Decrypt data using AES-128-CTR.
* @key: Key object.
* @data_in_out: Data to decrypt.
* @data_size: Number of bytes in @data_in_out, but be a multiple of
* AES_BLOCK_SIZE.
* @iv_in: Initialization vector to use for Cipher Block Chaining.
*
* Return: 0 on success, -1 if an error was detected.
*/
int storage_decrypt(const struct key* key,
void* data_in_out,
size_t data_size,
const struct iv* iv_in) {
return crypt(key, data_in_out, data_size, iv_in, false);
}