Files
swupd-client/src/hash.c
T
Patrick McCarty c42f8a3aa1 Initial commit
Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2016-02-24 09:34:13 -08:00

255 lines
5.9 KiB
C

/*
* Software Updater - client side
*
* Copyright © 2012-2016 Intel Corporation.
*
* 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, version 2 or later of the License.
*
* 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/>.
*
* Authors:
* Arjan van de Ven <arjan@linux.intel.com>
* Tim Pepper <timothy.c.pepper@linux.intel.com>
*
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <openssl/hmac.h>
#include "swupd.h"
#include "xattrs.h"
void hash_assign(char *src, char *dst)
{
memcpy(dst, src, SWUPD_HASH_LEN-1);
dst[SWUPD_HASH_LEN-1] = '\0';
}
bool hash_compare(char *hash1, char *hash2)
{
if (bcmp(hash1, hash2, SWUPD_HASH_LEN-1) == 0) {
return true;
} else {
return false;
}
}
bool hash_is_zeros(char *hash)
{
return hash_compare("0000000000000000000000000000000000000000000000000000000000000000", hash);
}
static void hash_set_zeros(char *hash)
{
hash_assign("0000000000000000000000000000000000000000000000000000000000000000", hash);
}
#if 0
static bool hash_is_ones(char *hash)
{
return hash_compare("1111111111111111111111111111111111111111111111111111111111111111", hash);
}
#endif
static void hash_set_ones(char *hash)
{
hash_assign("1111111111111111111111111111111111111111111111111111111111111111", hash);
}
static void hmac_sha256_for_data(char *hash,
const unsigned char *key, size_t key_len,
const unsigned char *data, size_t data_len)
{
unsigned char digest[EVP_MAX_MD_SIZE];
unsigned int digest_len = 0;
char *digest_str;
unsigned int i;
if (data == NULL) {
hash_set_zeros(hash);
return;
}
if (HMAC(EVP_sha256(), (const void *)key, key_len, data, data_len, digest, &digest_len) == NULL) {
hash_set_zeros(hash);
return;
}
digest_str = calloc((digest_len * 2) + 1, sizeof(char));
if (digest_str == NULL) {
abort();
}
for (i = 0; i < digest_len; i++) {
sprintf(&digest_str[i * 2], "%02x", (unsigned int)digest[i]);
}
hash_assign(digest_str, hash);
free(digest_str);
}
static void hmac_sha256_for_string(char *hash,
const unsigned char *key, size_t key_len,
const char *str)
{
if (str == NULL) {
hash_set_zeros(hash);
return;
}
hmac_sha256_for_data(hash, key, key_len, (const unsigned char *)str, strlen(str));
}
static void hmac_compute_key(const char *filename,
const struct update_stat *updt_stat,
char *key, size_t *key_len, bool use_xattrs)
{
char *xattrs_blob = (void *)0xdeadcafe;
size_t xattrs_blob_len = 0;
if (use_xattrs) {
xattrs_get_blob(filename, &xattrs_blob, &xattrs_blob_len);
}
hmac_sha256_for_data(key, (const unsigned char *)updt_stat,
sizeof(struct update_stat),
(const unsigned char *)xattrs_blob,
xattrs_blob_len);
if (hash_is_zeros(key)) {
*key_len = 0;
} else {
*key_len = SWUPD_HASH_LEN-1;
}
if (xattrs_blob_len != 0) {
free(xattrs_blob);
}
}
/* provide a wrapper for compute_hash() because we want a cheap-out option in
* case we are looking for missing files only:
* zeros hash: file missing
* ones hash: file present */
int compute_hash_lazy(struct file *file, char *filename)
{
struct stat sb;
if (lstat(filename, &sb) == 0) {
hash_set_ones(file->hash);
} else {
hash_set_zeros(file->hash);
}
return 0;
}
/* this function MUST be kept in sync with the server
* return is -1 if there was an error. If the file does not exist,
* a "0000000..." hash is returned as is our convention in the manifest
* for deleted files. Otherwise file->hash is set to a non-zero hash. */
/* TODO: how should we properly handle compute_hash() failures? */
int compute_hash(struct file *file, char *filename)
{
int ret;
char key[SWUPD_HASH_LEN];
size_t key_len;
unsigned char *blob;
FILE *fl;
if (file->is_deleted) {
hash_set_zeros(file->hash);
return 0;
}
hash_set_zeros(key);
if (file->is_link) {
char link[PATH_MAXLEN];
memset(link, 0, PATH_MAXLEN);
ret = readlink(filename, link, PATH_MAXLEN - 1);
if (ret >= 0) {
hmac_compute_key(filename, &file->stat, key, &key_len, file->use_xattrs);
hmac_sha256_for_string(file->hash,
(const unsigned char *)key,
key_len,
link);
return 0;
} else {
return -1;
}
}
if (file->is_dir) {
hmac_compute_key(filename, &file->stat, key, &key_len, file->use_xattrs);
hmac_sha256_for_string(file->hash,
(const unsigned char *)key,
key_len,
file->filename); //file->filename not filename
return 0;
}
/* if we get here, this is a regular file */
fl = fopen(filename, "r");
if (!fl) {
return -1;
}
blob = mmap(NULL, file->stat.st_size, PROT_READ, MAP_PRIVATE, fileno(fl), 0);
if (blob == MAP_FAILED && file->stat.st_size != 0) {
abort();
}
hmac_compute_key(filename, &file->stat, key, &key_len, file->use_xattrs);
hmac_sha256_for_data(file->hash,
(const unsigned char *)key,
key_len,
blob,
file->stat.st_size);
munmap(blob, file->stat.st_size);
fclose(fl);
return 0;
}
bool verify_file(struct file *file, char *filename)
{
struct file *local = calloc(1, sizeof(struct file));
if (local == NULL) {
abort();
}
local->filename = file->filename;
local->use_xattrs = true;
populate_file_struct(local, filename);
if (compute_hash(local, filename) != 0) {
free(local);
return false;
}
/* Check if manifest hash matches local file hash */
if (hash_compare(file->hash, local->hash)) {
free(local);
return true;
} else {
free(local);
return false;
}
}