/* * * 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 #endif #define _GNU_SOURCE #include #include #include #include #include #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 #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 #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; }