/*
* aes_cbc.c
*
* MontaVista RMCP+ code for doing AES-CBC-128
*
* Author: MontaVista Software, Inc.
* Corey Minyard <minyard@mvista.com>
* source@mvista.com
*
* Copyright 2004 MontaVista Software Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
*
* THIS SOFTWARE IS PROVIDED ``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 AUTHOR 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <config.h>
#ifdef HAVE_OPENSSL
#include <errno.h>
#include <string.h>
#include <openssl/evp.h>
#include <OpenIPMI/ipmi_lan.h>
#include <OpenIPMI/internal/ipmi_malloc.h>
typedef struct aes_cbc_info_s
{
unsigned char k2[16];
} aes_cbc_info_t;
static int
aes_cbc_init(ipmi_con_t *ipmi, ipmi_rmcpp_auth_t *ainfo, void **conf_data)
{
aes_cbc_info_t *info;
unsigned int k2len;
info = ipmi_mem_alloc(sizeof(*info));
if (!info)
return ENOMEM;
if (ipmi_rmcpp_auth_get_k2_len(ainfo) < 16)
return EINVAL;
memcpy(info->k2, ipmi_rmcpp_auth_get_k2(ainfo, &k2len), 16);
*conf_data = info;
return 0;
}
static void
aes_cbc_free(ipmi_con_t *ipmi, void *conf_data)
{
aes_cbc_info_t *info = conf_data;
memset(info->k2, 0, 16);
ipmi_mem_free(info);
}
static int
aes_cbc_encrypt(ipmi_con_t *ipmi,
void *conf_data,
unsigned char **payload,
unsigned int *header_len,
unsigned int *payload_len,
unsigned int *max_payload_len)
{
aes_cbc_info_t *info = conf_data;
unsigned char *iv;
unsigned int l = *payload_len;
unsigned int i;
unsigned char *d;
EVP_CIPHER_CTX *ctx;
int rv;
int outlen;
int tmplen;
unsigned char *padpos;
unsigned char padval;
unsigned int padlen;
if (!info)
return EINVAL;
/* Check for init vector room. */
if (*header_len < 16)
return E2BIG;
/* Calculate the number of padding bytes -> e. Note that the pad
length byte is included, thus the +1. We then do the padding. */
padlen = 15 - (l % 16);
l += padlen + 1;
if (l > *max_payload_len)
return E2BIG;
/* We store the unencrypted data here, then crypt into the real
data. */
d = ipmi_mem_alloc(l);
if (!d)
return ENOMEM;
memcpy(d, *payload, *payload_len);
/* Now add the padding. */
padpos = d + *payload_len;
padval = 1;
for (i=0; i<padlen; i++, padpos++, padval++)
*padpos = padval;
*padpos = padlen;
/* Now create the initialization vector, including making room for it. */
iv = (*payload)-16;
rv = ipmi->os_hnd->get_random(ipmi->os_hnd, iv, 16);
if (rv) {
ipmi_mem_free(d);
return rv;
}
*header_len -= 16;
*max_payload_len += 16;
ctx = EVP_CIPHER_CTX_new();
if (!ctx) {
rv = ENOMEM;
goto out_cleanup;
}
/* Ok, we're set to do the crypt operation. */
EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, info->k2, iv);
EVP_CIPHER_CTX_set_padding(ctx, 0);
if (!EVP_EncryptUpdate(ctx, *payload, &outlen, d, l)) {
rv = ENOMEM; /* right? */
goto out_cleanup;
}
if (!EVP_EncryptFinal_ex(ctx, (*payload) + outlen, &tmplen)) {
rv = ENOMEM; /* right? */
goto out_cleanup;
}
outlen += tmplen;
/* Don't call EncryptFinal_ex, it adds 16 bytes of useless data.
We have already 16-byte aligned the data, no need for it. */
*payload = iv;
*payload_len = outlen + 16;
out_cleanup:
EVP_CIPHER_CTX_free(ctx);
ipmi_mem_free(d);
return rv;
}
static int
aes_cbc_decrypt(ipmi_con_t *ipmi,
void *conf_data,
unsigned char **payload,
unsigned int *payload_len)
{
aes_cbc_info_t *info = conf_data;
unsigned int l = *payload_len;
unsigned char *d;
unsigned char *p;
EVP_CIPHER_CTX *ctx;
int outlen;
int rv = 0;
unsigned char *pad;
int padlen;
if (!info)
return EINVAL;
if (l < 32)
/* Not possible with this algorithm. */
return EINVAL;
l -= 16;
/* We store the encrypted data here, then decrypt into the real
data. */
d = ipmi_mem_alloc(l);
if (!d)
return ENOMEM;
p = (*payload)+16;
memcpy(d, p, l);
/* Ok, we're set to do the decrypt operation. */
ctx = EVP_CIPHER_CTX_new();
if (!ctx) {
rv = ENOMEM;
goto out_cleanup;
}
EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, info->k2, *payload);
EVP_CIPHER_CTX_set_padding(ctx, 0);
if (!EVP_DecryptUpdate(ctx, p, &outlen, d, l)) {
rv = EINVAL;
goto out_cleanup;
}
if (outlen < 16) {
rv = EINVAL;
goto out_cleanup;
}
/* Now remove the padding */
pad = p + outlen - 1;
padlen = *pad;
if (padlen >= 16) {
rv = EINVAL;
goto out_cleanup;
}
outlen--;
pad--;
while (padlen) {
if (*pad != padlen) {
rv = EINVAL;
goto out_cleanup;
}
outlen--;
pad--;
padlen--;
}
*payload = p;
*payload_len = outlen;
out_cleanup:
EVP_CIPHER_CTX_free(ctx);
ipmi_mem_free(d);
return rv;
}
static ipmi_rmcpp_confidentiality_t aes_conf =
{
.conf_init = aes_cbc_init,
.conf_free = aes_cbc_free,
.conf_encrypt = aes_cbc_encrypt,
.conf_decrypt = aes_cbc_decrypt
};
#endif /* HAVE_OPENSSL */
int
i_ipmi_aes_cbc_init(void)
{
#ifdef HAVE_OPENSSL
int rv = 0;
rv = ipmi_rmcpp_register_confidentiality
(IPMI_LANP_CONFIDENTIALITY_ALGORITHM_AES_CBC_128, &aes_conf);
if (rv)
return rv;
#endif
return 0;
}
void
i_ipmi_aes_cbc_shutdown(void)
{
#ifdef HAVE_OPENSSL
ipmi_rmcpp_register_confidentiality
(IPMI_LANP_CONFIDENTIALITY_ALGORITHM_AES_CBC_128, NULL);
#endif
}