Check checksum of downloaded file if checksum is available
[platform/upstream/curl.git] / src / tool_metalink.c
index d31502a..4fb0015 100644 (file)
  ***************************************************************************/
 #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"
     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;
+}