Check checksum of downloaded file if checksum is available

Metalink file contains several hash types of checksums, such as
md5, sha-1, sha-256, etc. To deal with these checksums, I created
abstraction layer based on lib/curl_md5.h and
lib/md5.c. Basically, they are almost the same but I changed the
code so that it is not hash type dependent. Currently,
GNUTLS(nettle or gcrypt) and OpenSSL functions are supported.

Checksum checking is done by reopening download file.  If there
is an I/O error, the current implementation just prints error
message and does not try next resource.

In this patch, the supported hash types are: md5, sha-1 and sha-256.
This commit is contained in:
Tatsuhiro Tsujikawa 2012-05-22 01:40:11 +09:00 committed by Daniel Stenberg
parent c3ef63f167
commit 9f7f7925da
3 changed files with 438 additions and 0 deletions

View File

@ -21,6 +21,16 @@
***************************************************************************/
#include "tool_setup.h"
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <sys/stat.h>
#ifdef HAVE_FCNTL_H
# include <fcntl.h>
#endif
#include "rawstr.h"
#include "tool_metalink.h"
@ -41,6 +51,228 @@
return PARAM_NO_MEM; \
} WHILE_FALSE
#ifdef USE_GNUTLS_NETTLE
#include <nettle/md5.h>
#include <nettle/sha.h>
typedef struct md5_ctx MD5_CTX;
static void MD5_Init(MD5_CTX * ctx)
{
md5_init(ctx);
}
static void MD5_Update(MD5_CTX * ctx,
const unsigned char * input,
unsigned int inputLen)
{
md5_update(ctx, inputLen, input);
}
static void MD5_Final(unsigned char digest[16], MD5_CTX * ctx)
{
md5_digest(ctx, 16, digest);
}
typedef struct sha1_ctx SHA_CTX;
static void SHA1_Init(SHA_CTX *ctx)
{
sha1_init(ctx);
}
static void SHA1_Update(SHA_CTX *ctx,
const unsigned char * input,
unsigned int inputLen)
{
sha1_update(ctx, inputLen, input);
}
static void SHA1_Final(unsigned char digest[20], SHA_CTX * ctx)
{
sha1_digest(ctx, 20, digest);
}
typedef struct sha256_ctx SHA256_CTX;
static void SHA256_Init(SHA256_CTX *ctx)
{
sha256_init(ctx);
}
static void SHA256_Update(SHA256_CTX *ctx,
const unsigned char * input,
unsigned int inputLen)
{
sha256_update(ctx, inputLen, input);
}
static void SHA256_Final(unsigned char digest[32], SHA256_CTX * ctx)
{
sha256_digest(ctx, 32, digest);
}
#else
#ifdef USE_GNUTLS
#include <gcrypt.h>
typedef gcry_md_hd_t MD5_CTX;
static void MD5_Init(MD5_CTX * ctx)
{
gcry_md_open(ctx, GCRY_MD_MD5, 0);
}
static void MD5_Update(MD5_CTX * ctx,
const unsigned char * input,
unsigned int inputLen)
{
gcry_md_write(*ctx, input, inputLen);
}
static void MD5_Final(unsigned char digest[16], MD5_CTX * ctx)
{
memcpy(digest, gcry_md_read(*ctx, 0), 16);
gcry_md_close(*ctx);
}
typedef gcry_md_hd_t SHA_CTX;
static void SHA1_Init(SHA_CTX * ctx)
{
gcry_md_open(ctx, GCRY_MD_SHA1, 0);
}
static void SHA1_Update(SHA_CTX * ctx,
const unsigned char * input,
unsigned int inputLen)
{
gcry_md_write(*ctx, input, inputLen);
}
static void SHA1_Final(unsigned char digest[20], SHA_CTX * ctx)
{
memcpy(digest, gcry_md_read(*ctx, 0), 20);
gcry_md_close(*ctx);
}
typedef gcry_md_hd_t SHA256_CTX;
static void SHA256_Init(SHA256_CTX * ctx)
{
gcry_md_open(ctx, GCRY_MD_SHA256, 0);
}
static void SHA256_Update(SHA256_CTX * ctx,
const unsigned char * input,
unsigned int inputLen)
{
gcry_md_write(*ctx, input, inputLen);
}
static void SHA256_Final(unsigned char digest[32], SHA256_CTX * ctx)
{
memcpy(digest, gcry_md_read(*ctx, 0), 32);
gcry_md_close(*ctx);
}
#else
#ifdef USE_SSLEAY
# ifdef USE_OPENSSL
# include <openssl/md5.h>
# include <openssl/sha.h>
# else
/* TODO What to do if USE_OPENSSL is undefined? */
# endif
#else /* USE_SSLEAY */
/* TODO hash functions for other libraries here */
#endif /* USE_SSLEAY */
#endif /* USE_GNUTLS */
#endif /* USE_GNUTLS_NETTLE */
const digest_params MD5_DIGEST_PARAMS[] = {
{
(Curl_digest_init_func) MD5_Init,
(Curl_digest_update_func) MD5_Update,
(Curl_digest_final_func) MD5_Final,
sizeof(MD5_CTX),
16
}
};
const digest_params SHA1_DIGEST_PARAMS[] = {
{
(Curl_digest_init_func) SHA1_Init,
(Curl_digest_update_func) SHA1_Update,
(Curl_digest_final_func) SHA1_Final,
sizeof(SHA_CTX),
20
}
};
const digest_params SHA256_DIGEST_PARAMS[] = {
{
(Curl_digest_init_func) SHA256_Init,
(Curl_digest_update_func) SHA256_Update,
(Curl_digest_final_func) SHA256_Final,
sizeof(SHA256_CTX),
32
}
};
digest_context *Curl_digest_init(const digest_params *dparams)
{
digest_context *ctxt;
/* Create digest context */
ctxt = malloc(sizeof *ctxt);
if(!ctxt)
return ctxt;
ctxt->digest_hashctx = malloc(dparams->digest_ctxtsize);
if(!ctxt->digest_hashctx) {
free(ctxt);
return NULL;
}
ctxt->digest_hash = dparams;
dparams->digest_init(ctxt->digest_hashctx);
return ctxt;
}
int Curl_digest_update(digest_context *context,
const unsigned char *data,
unsigned int len)
{
(*context->digest_hash->digest_update)(context->digest_hashctx, data, len);
return 0;
}
int Curl_digest_final(digest_context *context, unsigned char *result)
{
(*context->digest_hash->digest_final)(result, context->digest_hashctx);
free(context->digest_hashctx);
free(context);
return 0;
}
struct metalinkfile *new_metalinkfile(metalink_file_t *metalinkfile) {
struct metalinkfile *f;
f = (struct metalinkfile*)malloc(sizeof(struct metalinkfile));
@ -174,3 +406,143 @@ int check_metalink_content_type(const char *content_type)
{
return check_content_type(content_type, "application/metalink+xml");
}
static const metalink_digest_def SHA256_DIGEST_DEF[] = {
{"sha-256", SHA256_DIGEST_PARAMS}
};
static const metalink_digest_def SHA1_DIGEST_DEF[] = {
{"sha-1", SHA1_DIGEST_PARAMS}
};
static const metalink_digest_def MD5_DIGEST_DEF[] = {
{"md5", MD5_DIGEST_PARAMS}
};
/*
* The alias of supported hash functions in the order by preference
* (basically stronger hash comes first). We included "sha-256" and
* "sha256". The former is the name defined in the IANA registry named
* "Hash Function Textual Names". The latter is widely (and
* historically) used in Metalink version 3.
*/
static const metalink_digest_alias digest_aliases[] = {
{"sha-256", SHA256_DIGEST_DEF},
{"sha256", SHA256_DIGEST_DEF},
{"sha-1", SHA1_DIGEST_DEF},
{"sha1", SHA1_DIGEST_DEF},
{"md5", MD5_DIGEST_DEF},
{NULL, NULL}
};
static unsigned char hex_to_uint(const char *s)
{
int v[2];
int i;
for(i = 0; i < 2; ++i) {
v[i] = Curl_raw_toupper(s[i]);
if('0' <= v[i] && v[i] <= '9') {
v[i] -= '0';
}
else if('A' <= v[i] && v[i] <= 'Z') {
v[i] -= 'A'-10;
}
}
return (unsigned char)((v[0] << 4) | v[1]);
}
/*
* Check checksum of file denoted by filename. The expected hash value
* is given in hex_hash which is hex-encoded string.
*
* This function returns 1 if it succeeds or one of the following
* integers:
*
* 0:
* Checksum didn't match.
* -1:
* Could not open file; or could not read data from file.
*/
static int check_hash(const char *filename,
const metalink_digest_def *digest_def,
const char *hex_hash, FILE *error)
{
unsigned char *result;
digest_context *dctx;
int check_ok;
int fd;
size_t i;
fprintf(error, "Checking %s checksum of file %s\n", digest_def->hash_name,
filename);
fd = open(filename, O_RDONLY);
if(fd == -1) {
fprintf(error, "Could not open file %s: %s\n", filename, strerror(errno));
return -1;
}
dctx = Curl_digest_init(digest_def->dparams);
result = malloc(digest_def->dparams->digest_resultlen);
while(1) {
unsigned char buf[4096];
ssize_t len = read(fd, buf, sizeof(buf));
if(len == 0) {
break;
}
else if(len == -1) {
fprintf(error, "Could not read file %s: %s\n", filename,
strerror(errno));
Curl_digest_final(dctx, result);
close(fd);
return -1;
}
Curl_digest_update(dctx, buf, (unsigned int)len);
}
Curl_digest_final(dctx, result);
check_ok = 1;
for(i = 0; i < digest_def->dparams->digest_resultlen; ++i) {
if(hex_to_uint(&hex_hash[i*2]) != result[i]) {
check_ok = 0;
break;
}
}
free(result);
close(fd);
return check_ok;
}
int metalink_check_hash(struct Configurable *config,
struct metalinkfile *mlfile,
const char *filename)
{
metalink_checksum_t **checksum;
const metalink_digest_alias *digest_alias;
int rv;
if(!mlfile->file->checksums) {
return -2;
}
for(digest_alias = digest_aliases; digest_alias->alias_name;
++digest_alias) {
for(checksum = mlfile->file->checksums; *checksum; ++checksum) {
if(Curl_raw_equal(digest_alias->alias_name, (*checksum)->type) &&
strlen((*checksum)->hash) ==
digest_alias->digest_def->dparams->digest_resultlen*2) {
break;
}
}
if(*checksum) {
break;
}
}
if(!digest_alias->alias_name) {
fprintf(config->errors, "No supported checksum in Metalink file\n");
return -2;
}
rv = check_hash(filename, digest_alias->digest_def,
(*checksum)->hash, config->errors);
if(rv == 1) {
fprintf(config->errors, "Checksum matched\n");
}
else if(rv == 0) {
fprintf(config->errors, "Checksum didn't match\n");
}
return rv;
}

View File

@ -56,4 +56,61 @@ int parse_metalink(struct Configurable *config, const char *infile);
*/
int check_metalink_content_type(const char *content_type);
typedef void (* Curl_digest_init_func)(void *context);
typedef void (* Curl_digest_update_func)(void *context,
const unsigned char *data,
unsigned int len);
typedef void (* Curl_digest_final_func)(unsigned char *result, void *context);
typedef struct {
Curl_digest_init_func digest_init; /* Initialize context procedure */
Curl_digest_update_func digest_update; /* Update context with data */
Curl_digest_final_func digest_final; /* Get final result procedure */
unsigned int digest_ctxtsize; /* Context structure size */
unsigned int digest_resultlen; /* Result length (bytes) */
} digest_params;
typedef struct {
const digest_params *digest_hash; /* Hash function definition */
void *digest_hashctx; /* Hash function context */
} digest_context;
extern const digest_params MD5_DIGEST_PARAMS[1];
extern const digest_params SHA1_DIGEST_PARAMS[1];
extern const digest_params SHA256_DIGEST_PARAMS[1];
digest_context * Curl_digest_init(const digest_params *dparams);
int Curl_digest_update(digest_context *context,
const unsigned char *data,
unsigned int len);
int Curl_digest_final(digest_context *context, unsigned char *result);
typedef struct {
const char *hash_name;
const digest_params *dparams;
} metalink_digest_def;
typedef struct {
const char *alias_name;
const metalink_digest_def *digest_def;
} metalink_digest_alias;
/*
* Check checksum of file denoted by filename.
*
* This function returns 1 if the checksum matches or one of the
* following integers:
*
* 0:
* Checksum didn't match.
* -1:
* Could not open file; or could not read data from file.
* -2:
* No checksum in Metalink supported; or Metalink does not contain
* checksum.
*/
int metalink_check_hash(struct Configurable *config,
struct metalinkfile *mlfile,
const char *filename);
#endif /* HEADER_CURL_TOOL_METALINK_H */

View File

@ -1594,6 +1594,12 @@ int operate(struct Configurable *config, int argc, argv_item_t argv[])
fprintf(config->errors, "Could not parse Metalink file.\n");
}
}
else if(metalink && res == CURLE_OK && !metalink_next_res) {
int rv = metalink_check_hash(config, mlfile, outs.filename);
if(rv == 0) {
metalink_next_res = 1;
}
}
#endif /* HAVE_LIBMETALINK */
/* No more business with this output struct */
@ -1619,6 +1625,9 @@ int operate(struct Configurable *config, int argc, argv_item_t argv[])
break;
}
if(!metalink_next_res || *(++mlres) == NULL)
/* TODO If metalink_next_res is 1 and mlres is NULL,
* set res to error code
*/
break;
}
else