/*
SSSD
Encryption/Decryption primitives
Authors:
Simo Sorce <simo@redhat.com>
Copyright (C) Simo Sorce 2016
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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <talloc.h>
#include <errno.h>
#include "util/util.h"
#include "util/crypto/sss_crypto.h"
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/crypto.h>
#include "sss_openssl.h"
struct cipher_mech {
const EVP_CIPHER * (*cipher)(void);
const EVP_MD * (*digest)(void);
} mechs[] = {
{ EVP_aes_256_cbc, EVP_sha256 }
};
int sss_encrypt(TALLOC_CTX *mem_ctx, enum encmethod enctype,
uint8_t *key, size_t keylen,
const uint8_t *plaintext, size_t plainlen,
uint8_t **ciphertext, size_t *cipherlen)
{
const EVP_CIPHER *cipher;
const EVP_MD *digest;
EVP_PKEY *hmackey = NULL;
EVP_CIPHER_CTX *ctx;
EVP_MD_CTX *mdctx = NULL;
uint8_t *out = NULL;
int evpkeylen;
int evpivlen;
int hmaclen;
int outlen, tmplen;
size_t slen;
int ret;
if (!plaintext || !plainlen) return EINVAL;
if (enctype != AES256CBC_HMAC_SHA256) return EINVAL;
cipher = mechs[AES256CBC_HMAC_SHA256].cipher();
digest = mechs[AES256CBC_HMAC_SHA256].digest();
evpkeylen = EVP_CIPHER_key_length(cipher);
if (!key || keylen != evpkeylen) return EINVAL;
hmackey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, key, keylen);
if (!hmackey) return ENOMEM;
/* We have no function to return the size of the output for arbitray HMAC
* algorithms so we just truncate to the key size should the hmac be bigger
* (or pad with zeros should the HMAC be smaller) */
hmaclen = keylen;
evpivlen = EVP_CIPHER_iv_length(cipher);
outlen = plainlen + (2 * EVP_CIPHER_block_size(cipher))
+ evpivlen + hmaclen;
out = talloc_zero_size(mem_ctx, outlen);
/* First Encrypt */
if (evpivlen != 0) {
RAND_bytes(out, evpivlen);
}
ctx = EVP_CIPHER_CTX_new();
if (ctx == NULL) {
ret = ENOMEM;
goto done;
}
ret = EVP_EncryptInit_ex(ctx, cipher, 0, key, evpivlen ? out : NULL);
if (ret != 1) {
ret = EFAULT;
goto done;
}
outlen = evpivlen;
tmplen = 0;
ret = EVP_EncryptUpdate(ctx, out + outlen, &tmplen, plaintext, plainlen);
if (ret != 1) {
ret = EFAULT;
goto done;
}
outlen += tmplen;
ret = EVP_EncryptFinal_ex(ctx, out + outlen, &tmplen);
if (ret != 1) {
ret = EFAULT;
goto done;
}
outlen += tmplen;
/* Then HMAC */
mdctx = EVP_MD_CTX_new();
if (mdctx == NULL) {
ret = ENOMEM;
goto done;
}
ret = EVP_DigestInit_ex(mdctx, digest, NULL);
if (ret != 1) {
ret = EFAULT;
goto done;
}
ret = EVP_DigestSignInit(mdctx, NULL, digest, NULL, hmackey);
if (ret != 1) {
ret = EFAULT;
goto done;
}
ret = EVP_DigestSignUpdate(mdctx, out, outlen);
if (ret != 1) {
ret = EFAULT;
goto done;
}
slen = hmaclen;
ret = EVP_DigestSignFinal(mdctx, &out[outlen], &slen);
if (ret != 1) {
ret = EFAULT;
goto done;
}
outlen += hmaclen;
*ciphertext = out;
*cipherlen = outlen;
ret = EOK;
done:
EVP_MD_CTX_free(mdctx);
EVP_CIPHER_CTX_free(ctx);
EVP_PKEY_free(hmackey);
return ret;
}
int sss_decrypt(TALLOC_CTX *mem_ctx, enum encmethod enctype,
uint8_t *key, size_t keylen,
const uint8_t *ciphertext, size_t cipherlen,
uint8_t **plaintext, size_t *plainlen)
{
const EVP_CIPHER *cipher;
const EVP_MD *digest;
EVP_PKEY *hmackey = NULL;
EVP_CIPHER_CTX *ctx = NULL;
EVP_MD_CTX *mdctx;
const uint8_t *iv = NULL;
uint8_t *out;
int evpkeylen;
int evpivlen;
int hmaclen;
int outlen, tmplen;
size_t slen;
int ret;
if (!ciphertext || !cipherlen) return EINVAL;
if (enctype != AES256CBC_HMAC_SHA256) return EINVAL;
cipher = mechs[AES256CBC_HMAC_SHA256].cipher();
digest = mechs[AES256CBC_HMAC_SHA256].digest();
evpkeylen = EVP_CIPHER_key_length(cipher);
if (!key || keylen != evpkeylen) return EINVAL;
hmackey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, key, keylen);
if (!hmackey) return ENOMEM;
/* We have no function to return the size of the output for arbitray HMAC
* algorithms so we just assume it was truncated to the key size should
* the hmac be bigger (or pad with zeros should the HMAC be smaller) */
hmaclen = keylen;
evpivlen = EVP_CIPHER_iv_length(cipher);
out = talloc_zero_size(mem_ctx, cipherlen);
/* First check HMAC */
mdctx = EVP_MD_CTX_new();
if (mdctx == NULL) {
ret = ENOMEM;
goto done;
}
ret = EVP_DigestInit_ex(mdctx, digest, NULL);
if (ret != 1) {
ret = EFAULT;
goto done;
}
ret = EVP_DigestSignInit(mdctx, NULL, digest, NULL, hmackey);
if (ret != 1) {
ret = EFAULT;
goto done;
}
ret = EVP_DigestSignUpdate(mdctx, ciphertext, cipherlen - hmaclen);
if (ret != 1) {
ret = EFAULT;
goto done;
}
slen = hmaclen;
ret = EVP_DigestSignFinal(mdctx, out, &slen);
if (ret != 1) {
ret = EFAULT;
goto done;
}
ret = CRYPTO_memcmp(&ciphertext[cipherlen - hmaclen], out, hmaclen);
if (ret != 0) {
ret = EFAULT;
goto done;
}
/* Then Decrypt */
if (evpivlen != 0) {
iv = ciphertext;
}
ctx = EVP_CIPHER_CTX_new();
if (ctx == NULL) {
ret = ENOMEM;
goto done;
}
ret = EVP_DecryptInit_ex(ctx, cipher, 0, key, iv);
if (ret != 1) {
ret = EFAULT;
goto done;
}
ret = EVP_DecryptUpdate(ctx, out, &outlen,
ciphertext + evpivlen,
cipherlen - evpivlen - hmaclen);
if (ret != 1) {
ret = EFAULT;
goto done;
}
ret = EVP_DecryptFinal_ex(ctx, out + outlen, &tmplen);
if (ret != 1) {
ret = EFAULT;
goto done;
}
outlen += tmplen;
*plaintext = out;
*plainlen = outlen;
ret = EOK;
done:
EVP_MD_CTX_free(mdctx);
EVP_CIPHER_CTX_free(ctx);
EVP_PKEY_free(hmackey);
return ret;
}