pike.git / src / post_modules / Nettle / hash.cmod

version» Context lines:

pike.git/src/post_modules/Nettle/hash.cmod:1: + /* -*- c -*- + || This file is part of Pike. For copyright information see COPYRIGHT. + || Pike is distributed under GPL, LGPL and MPL. See the file COPYING + || for more information. + */    -  + #include "global.h" + #include "interpret.h" + #include "svalue.h" + #include "threads.h" +  + /* For this_object() */ + #include "object.h" + #include "module_support.h" + #include "pike_memory.h" +  + #include "nettle_config.h" +  + #ifdef HAVE_LIBNETTLE +  + DECLARATIONS +  + #include "nettle.h" +  + #include <nettle/md5.h> + #ifdef HAVE_NETTLE_MD4_INIT + #include <nettle/md4.h> + #include <nettle/md2.h> + #endif + #include <nettle/sha.h> + #include <nettle/nettle-meta.h> +  + #include <stdio.h> + #include <stdarg.h> + #include "fdlib.h" +  + #if 0 + static void + werror(const char *format, ...) + { +  va_list args; +  +  va_start(args, format); +  vfprintf(stderr, format, args); +  va_end(args); + } + #else + #define werror(x) + #endif +  + /*! @module Nettle */ +  + /*! @class HashInfo +  *! +  *! Represents information about a hash algorithm, such as +  *! name, digest size, and internal block size. +  */ + PIKECLASS HashInfo + { +  CVAR const struct nettle_hash *meta; +  +  /*! @decl string(0..255) name(void) +  *! +  *! Returns a human readable name for the algorithm. +  */ +  PIKEFUN string(0..255) name() +  optflags OPT_TRY_OPTIMIZE; +  { +  if (!THIS->meta) +  Pike_error("HashInfo not properly initialized.\n"); +  +  push_text(THIS->meta->name); +  } +  +  /*! @decl int(0..) digest_size(void) +  *! +  *! Returns the size of a hash digests. +  */ +  PIKEFUN int(0..) digest_size() +  optflags OPT_TRY_OPTIMIZE; +  { +  if (!THIS->meta) +  Pike_error("HashInfo not properly initialized.\n"); +  +  push_int(THIS->meta->digest_size); +  } +  +  /*! @decl int(0..) block_size(void) +  *! +  *! Returns the internal block size of the hash algorithm. +  */ +  PIKEFUN int(0..) block_size() +  optflags OPT_TRY_OPTIMIZE; +  { +  if (!THIS->meta) +  Pike_error("HashInfo not properly initialized.\n"); +  +  push_int(THIS->meta->block_size); +  } +  +  +  /*! @decl string(0..255) hash(string data) +  *! +  *! Works as a (faster) shortcut for +  *! @expr{HashState()->update(data)->digest()@}, where HashState is +  *! the hash state class corresponding to this HashInfo. +  *! +  *! @seealso +  *! @[HashState()->update()] and @[HashState()->digest()]. +  */ +  PIKEFUN string(0..255) hash(string in) +  optflags OPT_TRY_OPTIMIZE; +  { +  void *ctx; +  struct pike_string *out; +  unsigned digest_length; +  const struct nettle_hash *meta = THIS->meta; +  +  if (!meta) +  Pike_error("HashInfo not properly initialized.\n"); +  NO_WIDE_STRING(in); +  +  ctx = (void *)alloca(meta->context_size); +  if(!ctx) +  SIMPLE_OUT_OF_MEMORY_ERROR("hash", meta->context_size); +  +  /* Only thread this block for significant data size */ +  if (in->len > THREADS_ALLOW_THRESHOLD) { +  THREADS_ALLOW(); +  meta->init(ctx); +  meta->update(ctx, in->len, (const uint8_t *)in->str); +  THREADS_DISALLOW(); +  } else { +  meta->init(ctx); +  meta->update(ctx, in->len, (const uint8_t *)in->str); +  } +  +  digest_length = meta->digest_size; +  out = begin_shared_string(digest_length); +  meta->digest(ctx, digest_length, (uint8_t *)out->str); +  +  pop_n_elems(args); +  push_string(end_shared_string(out)); +  } +  +  int is_stdio_file(struct object *o) +  { +  struct program *p = o->prog; +  INT32 i = p->num_inherits; +  while( i-- ) +  { +  if( p->inherits[i].prog->id == PROG_STDIO_FD_ID || +  p->inherits[i].prog->id == PROG_STDIO_FD_REF_ID ) +  return 1; +  } +  return 0; +  } +  +  /*! @decl string(0..255) hash(Stdio.File file, void|int bytes) +  *! +  *! Works as a (faster) shortcut for +  *! @expr{HashState()->update(Stdio.read_file(file))->digest()@}, +  *! where HashState is the hash state class corresponding to this +  *! HashInfo. +  *! +  *! @param bytes +  *! The number of bytes of the file object @[file] that should be +  *! hashed. Negative numbers are ignored and the whole file is +  *! hashed. +  *! +  *! @seealso +  *! @[Stdio.File], @[HashState()->update()] and +  *! @[HashState()->digest()]. +  */ +  PIKEFUN string(0..255) hash(object in, void|int bytes) +  optflags OPT_EXTERNAL_DEPEND; +  { +  void *ctx; +  int len, fd; +  char *read_buffer; +  PIKE_STAT_T st; +  struct pike_string *out; +  const struct nettle_hash *meta = THIS->meta; +  +  if (!meta) +  Pike_error("HashInfo not properly initialized.\n"); +  +  if (!is_stdio_file(in)) +  Pike_error("Object not Fd or Fd_ref, or subclass.\n"); +  +  apply(in, "query_fd", 0); +  fd = Pike_sp[-1].u.integer; +  pop_stack(); +  +  if (fd_fstat(fd, &st)<0) +  Pike_error("File not found!\n"); +  +  if (!S_ISREG(st.st_mode)) +  Pike_error("Non-regular file.\n"); +  +  ctx = (void *)alloca(meta->context_size); +  if (!ctx) +  SIMPLE_OUT_OF_MEMORY_ERROR("hash", meta->context_size); +  +  read_buffer=(char *)malloc(8192); +  if (!read_buffer) +  SIMPLE_OUT_OF_MEMORY_ERROR("hash", 8192); +  +  THREADS_ALLOW(); +  meta->init(ctx); +  if(args==2 && bytes->u.integer>-1) { +  int bytes_left = bytes->u.integer; +  int read_bytes = MINIMUM(8192, bytes_left); +  while(read_bytes>0 && (len=fd_read(fd, read_buffer, read_bytes))>0) { +  meta->update(ctx, len, (const uint8_t *)read_buffer); +  bytes_left -= read_bytes; +  read_bytes = MINIMUM(8192, bytes_left); +  } +  } +  else +  while((len=fd_read(fd, read_buffer, 8192))>0) +  meta->update(ctx, len, (const uint8_t *)read_buffer); +  +  free(read_buffer); +  +  THREADS_DISALLOW(); +  out = begin_shared_string(meta->digest_size); +  meta->digest(ctx, meta->digest_size, (uint8_t *)out->str); +  +  pop_n_elems(args); +  push_string(end_shared_string(out)); +  } +  +  /* NOTE: This is NOT the MIME base64 table! */ +  static const char b64tab[64] = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; +  +  static inline void b64enc(char *dest, int a, int b, int c, int sz) +  { +  unsigned int bitbuf = a | (b << 8) | (c << 16); +  while (sz--) { +  *(dest++) = b64tab[bitbuf & 63]; +  bitbuf >>= 6; +  } +  } +  +  /*! @decl string(0..127) crypt_hash(string password, string salt, rounds) +  *! +  *! Password hashing function in @[crypt_md5()]-style. +  *! +  *! Implements the algorithm described in +  *! @url{http://www.akkadia.org/drepper/SHA-crypt.txt@}. +  *! +  *! This is the algorithm used by @tt{crypt(2)@} in +  *! methods @tt{$5$@} (SHA256) and @tt{$6$@} (SHA512). +  *! +  *! The @[password] memory will be cleared before released. +  *! +  *! @seealso +  *! @[crypt_md5()] +  */ +  PIKEFUN string(0..127) crypt_hash(string password, string salt, int rounds) +  { +  struct pike_string *res; +  const struct nettle_hash *meta = THIS->meta; +  void *ctx; +  uint8_t *abcbuf; +  uint8_t *dpbuf; +  uint8_t *dsbuf; +  +  unsigned char *p; +  unsigned char *s; +  int plen; +  int slen; +  int dsz = meta->digest_size; +  +  int i; +  int r; +  +  int a, b, c; +  +  if (!rounds) rounds = 5000; +  if (rounds < 1000) rounds = 1000; +  if (rounds > 999999999) rounds = 999999999; +  +  NO_WIDE_STRING(password); +  NO_WIDE_STRING(salt); +  +  password->flags |= STRING_CLEAR_ON_EXIT; +  +  ctx = (void *)alloca(meta->context_size); +  if (!ctx) +  SIMPLE_OUT_OF_MEMORY_ERROR("crypt_hash", meta->context_size); +  +  abcbuf = (uint8_t *)alloca(meta->digest_size * 3); +  if (!abcbuf) +  SIMPLE_OUT_OF_MEMORY_ERROR("crypt_hash", meta->digest_size * 3); +  +  dpbuf = abcbuf + meta->digest_size; +  dsbuf = dpbuf + meta->digest_size; +  +  /* NB: We use these to allow the compiler to +  * avoid dereferencing at every step. +  */ +  p = (unsigned char*)password->str; +  plen = password->len; +  s = (unsigned char*)salt->str; +  slen = salt->len; +  if (slen > 16) slen = 16; +  dsz = meta->digest_size; +  +  /* NB: We allocate the result here to avoid throwing away all the work +  * on out of memory at the end. +  */ +  if (dsz == 32) { +  /* 4 * (30/3) + 3 */ +  res = begin_shared_string(43); +  } else if (dsz == 64) { +  /* 4 * (63/3) + 2 */ +  res = begin_shared_string(86); +  } else { +  Pike_error("crypt_hash() not supported for this digest size yet (%d).\n", +  dsz); +  } +  +  THREADS_ALLOW(); +  +  /* NB: Comments refer to http://www.akkadia.org/drepper/SHA-crypt.txt */ +  meta->init(ctx); /* 4 */ +  meta->update(ctx, plen, p); /* 5 */ +  meta->update(ctx, slen, s); /* 6 */ +  meta->update(ctx, plen, p); /* 7 */ +  meta->digest(ctx, dsz, abcbuf); /* 8 */ +  +  meta->init(ctx); /* 1 */ +  meta->update(ctx, plen, p); /* 2 */ +  meta->update(ctx, slen, s); /* 3 */ +  +  for (i = 0; i + dsz < plen; i += dsz) { /* 9 */ +  meta->update(ctx, dsz, abcbuf); +  } +  +  meta->update(ctx, plen - i, abcbuf); /* 10 */ +  +  for (i = 1; i < plen; i <<= 1) { /* 11 */ +  if (plen & i) { +  meta->update(ctx, dsz, abcbuf); +  } else { +  meta->update(ctx, plen, p); +  } +  } +  +  meta->digest(ctx, dsz, abcbuf); /* 12 */ +  +  meta->init(ctx); /* 13 */ +  for (i = 0; i < plen; i++) { /* 14 */ +  meta->update(ctx, plen, p); +  } +  meta->digest(ctx, dsz, dpbuf); /* 15 */ +  +  /* Sequence P is implicit. */ /* 16 */ +  +  meta->init(ctx); /* 17 */ +  for(i = 0; i < 16 + abcbuf[0]; i++) { /* 18 */ +  meta->update(ctx, slen, s); +  } +  meta->digest(ctx, dsz, dsbuf); /* 19 */ +  +  /* Sequence S is implicit. */ /* 20 */ +  +  for (r = 0; r < rounds; r++) { /* 21 */ +  meta->init(ctx); /* a */ +  if (r & 1) { /* b */ +  for (i = 0; i + dsz < plen; i += dsz) { +  meta->update(ctx, dsz, dpbuf); +  } +  meta->update(ctx, plen - i, dpbuf); +  } else { +  meta->update(ctx, dsz, abcbuf); /* c */ +  } +  if (r % 3) { /* d */ +  for (i = 0; i + dsz < slen; i += dsz) { +  meta->update(ctx, dsz, dsbuf); +  } +  meta->update(ctx, slen - i, dsbuf); +  } +  if (r % 7) { /* e */ +  for (i = 0; i + dsz < plen; i += dsz) { +  meta->update(ctx, dsz, dpbuf); +  } +  meta->update(ctx, plen - i, dpbuf); +  } +  if (r & 1) { /* f */ +  meta->update(ctx, dsz, abcbuf); +  } else { /* g */ +  for (i = 0; i + dsz < plen; i += dsz) { +  meta->update(ctx, dsz, dpbuf); +  } +  meta->update(ctx, plen - i, dpbuf); +  } +  meta->digest(ctx, dsz, abcbuf); /* h */ +  } +  THREADS_DISALLOW(); +  +  /* And now time for some pointless shuffling of the result. +  * Note that the shuffling is slightly different between +  * the two cases. +  * +  * This is followed by a custom base64-style encoding. +  */ +  c = 0; +  b = dsz/3; +  a = 2*b; +  if (dsz == 32) { +  for (i = 0, r = 0; i + 3 < dsz; i+=3, r+=4) { +  int t; +  b64enc(res->str + r, abcbuf[a], abcbuf[b], abcbuf[c], 4); +  +  t = a+1; +  a = b+1; +  b = c+1; +  c = t; +  } +  b64enc(res->str + r, abcbuf[30], abcbuf[31], 0, 3); +  } else { +  for (i = 0, r = 0; i + 3 < dsz; i+=3, r+=4) { +  int t; +  b64enc(res->str + r, abcbuf[a], abcbuf[b], abcbuf[c], 4); +  +  t = a+1; +  a = c+1; +  c = b+1; +  b = t; +  } +  b64enc(res->str + r, abcbuf[63], 0, 0, 2); +  } +  +  push_string(end_shared_string(res)); /* 22e */ +  +  /* Clean intermediate values. */ +  MEMSET(ctx, 0, meta->context_size); +  MEMSET(abcbuf, 0, 3*dsz); +  } +  +  INIT +  { +  werror("HashInfo->INIT\n"); +  THIS->meta = NULL; +  } + } +  + /*! @endclass HashInfo */ +  + #define GET_META(o) \ + ( ((struct HashInfo_struct *) get_storage((o), HashInfo_program)) \ +  ->meta) +  + /* The algorithm objects have to be implemented in pike. */ +  + /*! @class HashState +  *! +  *! Base class for hashing contexts. +  */ + PIKECLASS HashState + { +  INHERIT HashInfo; +  CVAR void *ctx; +  +  /* FIXME: Create should copy state from the other object, if +  * provided. */ +  +  /*! @decl HashState update(string data) +  *! +  *! Hashes more data. +  */ +  PIKEFUN object update(string data) +  optflags OPT_SIDE_EFFECT; +  { +  void *ctx = THIS->ctx; +  const struct nettle_hash *meta = +  GET_META(Pike_fp->current_object); +  +  if (!ctx || !meta) +  Pike_error("HashState not properly initialized.\n"); +  +  NO_WIDE_STRING(data); +  +  /* Only thread this block for significant data size */ +  if (data->len > THREADS_ALLOW_THRESHOLD) { +  THREADS_ALLOW(); +  meta->update(ctx, data->len, (const uint8_t *)data->str); +  THREADS_DISALLOW(); +  } else { +  meta->update(ctx, data->len, (const uint8_t *)data->str); +  } +  +  push_object(this_object()); +  } +  +  /*! @decl string(0..255) digest(int|void length) +  *! +  *! Generates a digests, and resets the hashing contents. +  *! +  *! @param length +  *! If the length argument is provided, the digest is truncated +  *! to the given length. +  *! +  *! @returns +  *! The digest. +  */ +  PIKEFUN string(0..255) digest(int|void arg) +  { +  const struct nettle_hash *meta; +  struct pike_string *digest; +  unsigned length; +  +  if (! THIS->ctx) +  Pike_error("HashState not properly initialized.\n"); +  +  meta = GET_META(Pike_fp->current_object); +  assert(meta); +  +  if (!arg) +  length = meta->digest_size; +  else +  { +  if (TYPEOF(*arg) != PIKE_T_INT) +  Pike_error("Bad argument type.\n"); +  if (arg->u.integer < 0) +  Pike_error("Invalid length, must be positive.\n"); +  if ((unsigned)arg->u.integer > meta->digest_size) +  Pike_error("Unsupported digest length.\n"); +  +  length = arg->u.integer; +  } +  +  digest = begin_shared_string(length); +  meta->digest(THIS->ctx, length, (uint8_t *)digest->str); +  push_string(end_shared_string(digest)); +  } +  +  INIT +  { +  werror("HashState->INIT\n"); +  THIS->ctx = NULL; +  } +  +  EXIT +  gc_trivial; +  { +  werror("HashState->EXIT\n"); +  if (THIS->ctx && Pike_fp->current_object->prog) +  { +  const struct nettle_hash *meta = +  GET_META(Pike_fp->current_object); +  assert(meta); +  memset(THIS->ctx, 0, meta->context_size); +  } +  } + } +  + /*! @endclass HashState */ +  + #cmod_define TOSTR(DEF) #DEF +  + #cmod_define PIKE_NAME MD5 + #cmod_define NETTLE_NAME md5 + #cmod_include "hash.H" + #cmod_undef PIKE_NAME + #cmod_undef NETTLE_NAME +  + #ifdef HAVE_NETTLE_MD4_INIT +  + #cmod_define PIKE_NAME MD4 + #cmod_define NETTLE_NAME md4 + #cmod_include "hash.H" + #cmod_undef PIKE_NAME + #cmod_undef NETTLE_NAME +  + #cmod_define PIKE_NAME MD2 + #cmod_define NETTLE_NAME md2 + #cmod_include "hash.H" + #cmod_undef PIKE_NAME + #cmod_undef NETTLE_NAME +  + #endif /* HAVE_NETTLE_MD4_INIT */ +  + #cmod_define PIKE_NAME SHA1 + #cmod_define NETTLE_NAME sha1 + #cmod_include "hash.H" + #cmod_undef PIKE_NAME + #cmod_undef NETTLE_NAME +  + #cmod_define PIKE_NAME SHA256 + #cmod_define NETTLE_NAME sha256 + #cmod_include "hash.H" + #cmod_undef PIKE_NAME + #cmod_undef NETTLE_NAME +  + #ifdef SHA384_DIGEST_SIZE +  + #cmod_define PIKE_NAME SHA384 + #cmod_define NETTLE_NAME sha384 + #cmod_include "hash.H" + #cmod_undef PIKE_NAME + #cmod_undef NETTLE_NAME +  + #endif /* SHA384_DIGEST_SIZE */ +  + #ifdef SHA512_DIGEST_SIZE +  + #cmod_define PIKE_NAME SHA512 + #cmod_define NETTLE_NAME sha512 + #cmod_include "hash.H" + #cmod_undef PIKE_NAME + #cmod_undef NETTLE_NAME +  + #endif /* SHA512_DIGEST_SIZE */ +  + /*! @endmodule Nettle */ +  + void + hash_init(void) + { +  werror("Nettle, hash init\n"); +  INIT; + } +  + void + hash_exit(void) + { +  werror("Nettle, hash exit\n"); +  EXIT; + } +  + #endif /* HAVE_LIBNETTLE */   Newline at end of file added.