blob: c71205ad0809efa7ff10f61040e09d1af4f7de3c [file] [log] [blame]
/*
*
* Embedded Linux library
*
* Copyright (C) 2011-2014 Intel Corporation. All rights reserved.
*
* This library 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.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define _GNU_SOURCE
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include "useful.h"
#include "checksum.h"
#include "private.h"
#ifndef HAVE_LINUX_IF_ALG_H
#ifndef HAVE_LINUX_TYPES_H
typedef uint8_t __u8;
typedef uint16_t __u16;
typedef uint32_t __u32;
#else
#include <linux/types.h>
#endif
#ifndef AF_ALG
#define AF_ALG 38
#define PF_ALG AF_ALG
#endif
struct sockaddr_alg {
__u16 salg_family;
__u8 salg_type[14];
__u32 salg_feat;
__u32 salg_mask;
__u8 salg_name[64];
};
/* Socket options */
#define ALG_SET_KEY 1
#else
#include <linux/if_alg.h>
#endif
#ifndef SOL_ALG
#define SOL_ALG 279
#endif
struct checksum_info {
const char *name;
uint8_t digest_len;
bool supported;
};
static struct checksum_info checksum_algs[] = {
[L_CHECKSUM_MD4] = { .name = "md4", .digest_len = 16 },
[L_CHECKSUM_MD5] = { .name = "md5", .digest_len = 16 },
[L_CHECKSUM_SHA1] = { .name = "sha1", .digest_len = 20 },
[L_CHECKSUM_SHA256] = { .name = "sha256", .digest_len = 32 },
[L_CHECKSUM_SHA384] = { .name = "sha384", .digest_len = 48 },
[L_CHECKSUM_SHA512] = { .name = "sha512", .digest_len = 64 },
};
static struct checksum_info checksum_cmac_aes_alg =
{ .name = "cmac(aes)", .digest_len = 16 };
static struct checksum_info checksum_hmac_algs[] = {
[L_CHECKSUM_MD4] = { .name = "hmac(md4)", .digest_len = 16 },
[L_CHECKSUM_MD5] = { .name = "hmac(md5)", .digest_len = 16 },
[L_CHECKSUM_SHA1] = { .name = "hmac(sha1)", .digest_len = 20 },
[L_CHECKSUM_SHA256] = { .name = "hmac(sha256)", .digest_len = 32 },
[L_CHECKSUM_SHA384] = { .name = "hmac(sha384)", .digest_len = 48 },
[L_CHECKSUM_SHA512] = { .name = "hmac(sha512)", .digest_len = 64 },
};
static const struct {
struct checksum_info *list;
size_t n;
} checksum_info_table[] = {
{ checksum_algs, L_ARRAY_SIZE(checksum_algs) },
{ &checksum_cmac_aes_alg, 1 },
{ checksum_hmac_algs, L_ARRAY_SIZE(checksum_hmac_algs) },
{}
};
/**
* SECTION:checksum
* @short_description: Checksum handling
*
* Checksum handling
*/
#define is_valid_index(array, i) ((i) >= 0 && (i) < L_ARRAY_SIZE(array))
/**
* l_checksum:
*
* Opaque object representing the checksum.
*/
struct l_checksum {
int sk;
const struct checksum_info *alg_info;
};
static int create_alg(const char *alg)
{
struct sockaddr_alg salg;
int sk;
sk = socket(PF_ALG, SOCK_SEQPACKET | SOCK_CLOEXEC, 0);
if (sk < 0)
return -1;
memset(&salg, 0, sizeof(salg));
salg.salg_family = AF_ALG;
strcpy((char *) salg.salg_type, "hash");
strcpy((char *) salg.salg_name, alg);
if (bind(sk, (struct sockaddr *) &salg, sizeof(salg)) < 0) {
close(sk);
return -1;
}
return sk;
}
/**
* l_checksum_new:
* @type: checksum type
*
* Creates new #l_checksum, using the checksum algorithm @type.
*
* Returns: a newly allocated #l_checksum object.
**/
LIB_EXPORT struct l_checksum *l_checksum_new(enum l_checksum_type type)
{
struct l_checksum *checksum;
int fd;
if (!is_valid_index(checksum_algs, type) || !checksum_algs[type].name)
return NULL;
checksum = l_new(struct l_checksum, 1);
checksum->alg_info = &checksum_algs[type];
fd = create_alg(checksum->alg_info->name);
if (fd < 0)
goto error;
checksum->sk = accept4(fd, NULL, 0, SOCK_CLOEXEC);
close(fd);
if (checksum->sk < 0)
goto error;
return checksum;
error:
l_free(checksum);
return NULL;
}
LIB_EXPORT struct l_checksum *l_checksum_new_cmac_aes(const void *key,
size_t key_len)
{
struct l_checksum *checksum;
int fd;
fd = create_alg("cmac(aes)");
if (fd < 0)
return NULL;
if (setsockopt(fd, SOL_ALG, ALG_SET_KEY, key, key_len) < 0) {
close(fd);
return NULL;
}
checksum = l_new(struct l_checksum, 1);
checksum->sk = accept4(fd, NULL, 0, SOCK_CLOEXEC);
close(fd);
if (checksum->sk < 0) {
l_free(checksum);
return NULL;
}
checksum->alg_info = &checksum_cmac_aes_alg;
return checksum;
}
LIB_EXPORT struct l_checksum *l_checksum_new_hmac(enum l_checksum_type type,
const void *key, size_t key_len)
{
struct l_checksum *checksum;
int fd;
if (!is_valid_index(checksum_hmac_algs, type) ||
!checksum_hmac_algs[type].name)
return NULL;
fd = create_alg(checksum_hmac_algs[type].name);
if (fd < 0)
return NULL;
if (setsockopt(fd, SOL_ALG, ALG_SET_KEY, key, key_len) < 0) {
close(fd);
return NULL;
}
checksum = l_new(struct l_checksum, 1);
checksum->sk = accept4(fd, NULL, 0, SOCK_CLOEXEC);
close(fd);
if (checksum->sk < 0) {
l_free(checksum);
return NULL;
}
checksum->alg_info = &checksum_hmac_algs[type];
return checksum;
}
/**
* l_checksum_clone:
* @checksum: parent checksum object
*
* Creates a new checksum with an independent copy of parent @checksum's
* state. l_checksum_get_digest can then be called on the parent or the
* clone without affecting the state of the other object.
**/
LIB_EXPORT struct l_checksum *l_checksum_clone(struct l_checksum *checksum)
{
struct l_checksum *clone;
if (unlikely(!checksum))
return NULL;
clone = l_new(struct l_checksum, 1);
clone->sk = accept4(checksum->sk, NULL, 0, SOCK_CLOEXEC);
if (clone->sk < 0) {
l_free(clone);
return NULL;
}
clone->alg_info = checksum->alg_info;
return clone;
}
/**
* l_checksum_free:
* @checksum: checksum object
*
* Frees the memory allocated for @checksum.
**/
LIB_EXPORT void l_checksum_free(struct l_checksum *checksum)
{
if (unlikely(!checksum))
return;
close(checksum->sk);
l_free(checksum);
}
/**
* l_checksum_reset:
* @checksum: checksum object
*
* Resets the internal state of @checksum.
**/
LIB_EXPORT void l_checksum_reset(struct l_checksum *checksum)
{
if (unlikely(!checksum))
return;
send(checksum->sk, NULL, 0, 0);
}
/**
* l_checksum_update:
* @checksum: checksum object
* @data: data pointer
* @len: length of data
*
* Updates checksum from @data pointer with @len bytes.
*
* Returns: true if the operation succeeded, false otherwise.
**/
LIB_EXPORT bool l_checksum_update(struct l_checksum *checksum,
const void *data, size_t len)
{
ssize_t written;
if (unlikely(!checksum))
return false;
written = send(checksum->sk, data, len, MSG_MORE);
if (written < 0)
return false;
return true;
}
/**
* l_checksum_updatev:
* @checksum: checksum object
* @iov: iovec pointer
* @iov_len: Number of iovec entries
*
* This is a iovec based version of l_checksum_update; it updates the checksum
* based on contents of @iov and @iov_len.
*
* Returns: true if the operation succeeded, false otherwise.
**/
LIB_EXPORT bool l_checksum_updatev(struct l_checksum *checksum,
const struct iovec *iov, size_t iov_len)
{
struct msghdr msg;
ssize_t written;
if (unlikely(!checksum))
return false;
if (unlikely(!iov) || unlikely(!iov_len))
return false;
memset(&msg, 0, sizeof(msg));
msg.msg_iov = (struct iovec *) iov;
msg.msg_iovlen = iov_len;
written = sendmsg(checksum->sk, &msg, MSG_MORE);
if (written < 0)
return false;
return true;
}
/**
* l_checksum_get_digest:
* @checksum: checksum object
* @digest: output data buffer
* @len: length of output buffer
*
* Writes the digest from @checksum as raw binary data into the provided
* buffer or, if the buffer is shorter, the initial @len bytes of the digest
* data.
*
* Returns: Number of bytes written, or negative value if an error occurred.
**/
LIB_EXPORT ssize_t l_checksum_get_digest(struct l_checksum *checksum,
void *digest, size_t len)
{
ssize_t result;
if (unlikely(!checksum))
return -EINVAL;
if (unlikely(!digest))
return -EFAULT;
if (unlikely(!len))
return -EINVAL;
result = recv(checksum->sk, digest, len, 0);
if (result < 0)
return -errno;
if ((size_t) result < len && result < checksum->alg_info->digest_len)
return -EIO;
return result;
}
/**
* l_checksum_get_string:
* @checksum: checksum object
*
* Gets the digest from @checksum as hex encoded string.
*
* Returns: a newly allocated hex string
**/
LIB_EXPORT char *l_checksum_get_string(struct l_checksum *checksum)
{
unsigned char digest[64];
if (unlikely(!checksum))
return NULL;
l_checksum_get_digest(checksum, digest, sizeof(digest));
return l_util_hexstring(digest, checksum->alg_info->digest_len);
}
static void init_supported()
{
static bool initialized = false;
struct sockaddr_alg salg;
int sk;
unsigned int i, j;
if (likely(initialized))
return;
initialized = true;
sk = socket(PF_ALG, SOCK_SEQPACKET | SOCK_CLOEXEC, 0);
if (sk < 0)
return;
memset(&salg, 0, sizeof(salg));
salg.salg_family = AF_ALG;
strcpy((char *) salg.salg_type, "hash");
for (i = 0; checksum_info_table[i].list; i++)
for (j = 0; j < checksum_info_table[i].n; j++) {
struct checksum_info *info;
info = &checksum_info_table[i].list[j];
if (!info->name)
continue;
strcpy((char *) salg.salg_name, info->name);
if (bind(sk, (struct sockaddr *) &salg,
sizeof(salg)) < 0)
continue;
info->supported = true;
}
close(sk);
}
LIB_EXPORT bool l_checksum_is_supported(enum l_checksum_type type,
bool check_hmac)
{
const struct checksum_info *list;
init_supported();
if (!check_hmac) {
if (!is_valid_index(checksum_algs, type))
return false;
list = checksum_algs;
} else {
if (!is_valid_index(checksum_hmac_algs, type))
return false;
list = checksum_hmac_algs;
}
return list[type].supported;
}
LIB_EXPORT bool l_checksum_cmac_aes_supported()
{
init_supported();
return checksum_cmac_aes_alg.supported;
}
LIB_EXPORT ssize_t l_checksum_digest_length(enum l_checksum_type type)
{
return is_valid_index(checksum_algs, type) ?
checksum_algs[type].digest_len : 0;
}