/* -*- 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> |
#ifdef HAVE_NETTLE_SHA3_H |
#include <nettle/sha3.h> |
#endif |
#ifdef HAVE_NETTLE_RIPEMD160_H |
#include <nettle/ripemd160.h> |
#endif |
#ifdef HAVE_NETTLE_GOSTHASH94_H |
#include <nettle/gosthash94.h> |
#endif |
#include <nettle/nettle-meta.h> |
|
#include <stdio.h> |
#include <stdarg.h> |
#include "fdlib.h" |
|
/*! @module Nettle */ |
|
/*! @class Hash |
*! |
*! Represents information about a hash algorithm, such as |
*! name, digest size, and internal block size. |
*/ |
PIKECLASS Hash |
{ |
/*! @decl inherit __builtin.Nettle.Hash |
*/ |
INHERIT "__builtin.Nettle.Hash"; |
|
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("Hash not properly initialized.\n"); |
|
push_text(THIS->meta->name); |
} |
|
/*! @decl int(0..) digest_size(void) |
*! |
*! Returns the size of a hash digest. |
*/ |
PIKEFUN int(0..) digest_size() |
optflags OPT_TRY_OPTIMIZE; |
{ |
if (!THIS->meta) |
Pike_error("Hash 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("Hash not properly initialized.\n"); |
|
push_int(THIS->meta->block_size); |
} |
|
|
/*! @decl string(0..255) hash(string(0..255) data) |
*! |
*! Works as a (faster) shortcut for |
*! @expr{State()->update(data)->digest()@}, where State is |
*! the hash state class corresponding to this Hash. |
*! |
*! @seealso |
*! @[State()->update()] and @[State()->digest()]. |
*/ |
PIKEFUN string(0..255) hash(string(0..255) 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("Hash not properly initialized.\n"); |
NO_WIDE_STRING(in); |
|
ctx = 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 > HASH_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{State()->update(Stdio.read_file(file))->digest()@}, |
*! where State is the hash state class corresponding to this |
*! Hash. |
*! |
*! @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], @[State()->update()] and |
*! @[State()->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 = alloca(meta->context_size); |
if (!ctx) |
SIMPLE_OUT_OF_MEMORY_ERROR("hash", meta->context_size); |
|
read_buffer=xalloc(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(0..255) password, @ |
*! string(0..255) salt, int 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(0..255) password, |
string(0..255) 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 = alloca(meta->context_size); |
if (!ctx) |
SIMPLE_OUT_OF_MEMORY_ERROR("crypt_hash", meta->context_size); |
|
abcbuf = 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("Hash->INIT\n"); |
THIS->meta = NULL; |
} |
|
/*! @class State |
*! |
*! Base class for hashing contexts. |
*/ |
PIKECLASS State |
program_flags PROGRAM_USES_PARENT|PROGRAM_NEEDS_PARENT|PROGRAM_CLEAR_STORAGE; |
{ |
DOCSTART() @decl inherit Hash::State |
DOCEND() |
|
EXTRA |
{ |
/* Perform an inherit of the State class (if any) that our parent |
* may contain via its inherit of __builtin.Nettle.Hash. |
*/ |
struct program *parent_prog = Pike_compiler->previous->new_program; |
struct object *parent_obj = Pike_compiler->previous->fake_object; |
int parent_State_fun_num = |
really_low_find_shared_string_identifier(MK_STRING("State"), |
parent_prog, |
SEE_PROTECTED|SEE_PRIVATE); |
if (parent_State_fun_num >= 0) { |
struct program *parent_State_prog = |
low_program_from_function(parent_obj, parent_State_fun_num); |
if (parent_State_prog) { |
low_inherit(parent_State_prog, 0, |
parent_State_fun_num + |
parent_prog->inherits[1].identifier_level, |
1 + 42, 0, NULL); |
} |
} |
} |
|
#define GET_META(o) \ |
( ((struct Nettle_Hash_struct *)parent_storage(1, Nettle_Hash_program))->meta ) |
|
CVAR void *ctx; |
|
/* FIXME: Create should copy state from the other object, if |
* provided. */ |
|
/*! @decl State update(string(0..255) data) |
*! |
*! Hashes more data. |
*! |
*! @returns |
*! Returns @expr{this@} in order to simplify chaining |
*! of function calls. |
*/ |
PIKEFUN object update(string(0..255) data) |
optflags OPT_SIDE_EFFECT; |
rawtype tFunc(tStr8, tObjImpl_NETTLE_HASH_STATE); |
{ |
void *ctx = THIS->ctx; |
const struct nettle_hash *meta = |
GET_META(Pike_fp->current_object); |
|
if (!ctx || !meta) |
Pike_error("State not properly initialized.\n"); |
|
NO_WIDE_STRING(data); |
|
/* Only thread this block for significant data size */ |
if (data->len > HASH_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 digest, 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; |
|
meta = GET_META(Pike_fp->current_object); |
|
if (!THIS->ctx || !meta) |
Pike_error("State not properly initialized.\n"); |
|
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("State->INIT\n"); |
THIS->ctx = NULL; |
} |
|
EXIT |
{ |
werror("State->EXIT\n"); |
if (THIS->ctx) |
{ |
const struct nettle_hash *meta = |
GET_META(Pike_fp->current_object); |
if (meta) { |
memset(THIS->ctx, 0, meta->context_size); |
} |
} |
} |
} |
|
/*! @endclass State */ |
|
} |
|
/*! @endclass Hash */ |
|
/* The algorithm objects can be overloaded in pike. */ |
|
#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 |
|
#ifdef HAVE_NETTLE_SHA224_INIT |
|
#cmod_define PIKE_NAME SHA224 |
#cmod_define NETTLE_NAME sha224 |
#cmod_include "hash.H" |
#cmod_undef PIKE_NAME |
#cmod_undef NETTLE_NAME |
|
#endif |
|
#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 */ |
|
#ifdef HAVE_NETTLE_SHA3_H |
|
#cmod_define PIKE_NAME SHA3_224 |
#cmod_define NETTLE_NAME sha3_224 |
#cmod_include "hash.H" |
#cmod_undef PIKE_NAME |
#cmod_undef NETTLE_NAME |
|
#cmod_define PIKE_NAME SHA3_256 |
#cmod_define NETTLE_NAME sha3_256 |
#cmod_include "hash.H" |
#cmod_undef PIKE_NAME |
#cmod_undef NETTLE_NAME |
|
#cmod_define PIKE_NAME SHA3_384 |
#cmod_define NETTLE_NAME sha3_384 |
#cmod_include "hash.H" |
#cmod_undef PIKE_NAME |
#cmod_undef NETTLE_NAME |
|
#cmod_define PIKE_NAME SHA3_512 |
#cmod_define NETTLE_NAME sha3_512 |
#cmod_include "hash.H" |
#cmod_undef PIKE_NAME |
#cmod_undef NETTLE_NAME |
|
#endif /* HAVE_NETTLE_SHA3_H */ |
|
#ifdef HAVE_NETTLE_RIPEMD160_H |
|
#cmod_define PIKE_NAME RIPEMD160 |
#cmod_define NETTLE_NAME ripemd160 |
#cmod_include "hash.H" |
#cmod_undef PIKE_NAME |
#cmod_undef NETTLE_NAME |
|
#endif |
|
#ifdef HAVE_NETTLE_GOSTHASH94_H |
|
#cmod_define PIKE_NAME GOST94 |
#cmod_define NETTLE_NAME gosthash94 |
#cmod_include "hash.H" |
#cmod_undef PIKE_NAME |
#cmod_undef NETTLE_NAME |
|
#endif |
|
/*! @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 */ |
|