commit 65f1c2aaf99002d8062b4334de89bebe0e416fd4
parent 94c2b71f62b90efcfbff5df34dad39fda4b7cd0c
Author: Michael Savage <mikejsavage@gmail.com>
Date: Mon, 5 May 2014 14:11:32 +0100
Better random device and error handling
- add checks to ensure the random device is actually a random device
- add bcrypt.random to change the random device in use
- propagate error codes, and produce better error messages
Diffstat:
README.md | | | 23 | +++++++++++++++++++++++ |
src/main.c | | | 102 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------- |
tests.lua | | | 12 | +++++++++--- |
3 files changed, 121 insertions(+), 16 deletions(-)
diff --git a/README.md b/README.md
@@ -31,3 +31,26 @@ You can also explicitly pass a salt to `bcrypt.digest`:
local digest = bcrypt.digest( "password", salt )
assert( bcrypt.verify( "password", digest ) )
+
+If you want to use a different random device, you can use
+`bcrypt.random`:
+
+ local bcrypt = require( "bcrypt" )
+ bcrypt.random( "/dev/random" )
+ local digest = bcrypt.digest( "password", rounds )
+
+ -- bcrypt.random( "/dev/null" ) - fails
+
+
+Chroot
+------
+
+If you want to use `lua-bcrypt` from inside a chroot, `/dev/urandom`
+must still exist when `require( "bcrypt" )` is called, even if you are
+planning to use a different random device. The reasoning behind this is
+that chroots are a less common use case of this library, and we should
+fail as early as possible on errors in the common use case.
+
+Therefore, if you wish to use chroot, you should run:
+
+ mknod -m 644 /path/to/chroot/dev/urandom c 1 9
diff --git a/src/main.c b/src/main.c
@@ -1,7 +1,6 @@
-#include <stdlib.h>
#include <string.h>
-#include <fcntl.h>
-#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <errno.h>
#include <lua.h>
@@ -13,25 +12,87 @@
#define CRYPT_OUTPUT_SIZE ( 7 + 22 + 31 + 1 )
#define CRYPT_GENSALT_OUTPUT_SIZE ( 7 + 22 + 1 )
#define ENTROPY_SIZE 32
+#define DEFAULT_RANDOM "/dev/urandom"
#if LUA_VERSION_NUM < 502
#define luaL_newlib( L, l ) ( lua_newtable( L ), luaL_register( L, NULL, l ) )
#endif
-static int urandom;
+enum {
+ BC_ERR_API = 1,
+ BC_ERR_RNG,
+ BC_ERR_NORNG,
+};
+
+static FILE * random_file = NULL;
+
+static const char * my_strerror( const int err ) {
+ switch( err ) {
+ case BC_ERR_API:
+ return strerror( errno );
+
+ case BC_ERR_RNG:
+ return "Bad entropy source";
+
+ case BC_ERR_NORNG:
+ return "No entropy source";
+
+ default:
+ return ".";
+ }
+}
+
+// http://insanecoding.blogspot.ca/2014/05/a-good-idea-with-bad-usage-devurandom.html
+static int open_random( const char * const path ) {
+ FILE * f = fopen( path, "r" );
+
+ if( f == NULL ) {
+ return BC_ERR_API;
+ }
+
+ int fd = fileno( f );
+ struct stat stat_buf;
+
+ if( fd == -1 || fstat( fd, &stat_buf ) != 0 ) {
+ return BC_ERR_API;
+ }
+
+ // is this a random device?
+ if( S_ISCHR( stat_buf.st_mode ) != 0 && stat_buf.st_rdev != makedev( 1, 8 ) && stat_buf.st_rdev != makedev( 1, 9 ) ) {
+ return BC_ERR_RNG;
+ }
+
+ random_file = f;
+
+ return 0;
+}
+
+static int read_random( char * const buf, const size_t len ) {
+ if( random_file == NULL ) {
+ return BC_ERR_NORNG;
+ }
+
+ size_t bytes_read = fread( buf, 1, len, random_file );
+
+ if( bytes_read < len ) {
+ return BC_ERR_API;
+ }
+
+ return 0;
+}
static int makesalt( char * const salt, const size_t size, const int rounds ) {
char entropy[ ENTROPY_SIZE ];
- const ssize_t bytes = read( urandom, entropy, sizeof( entropy ) );
+ int rv_random = read_random( entropy, sizeof( entropy ) );
- if( bytes != sizeof( entropy ) ) {
- return 1;
+ if( rv_random != 0 ) {
+ return rv_random;
}
const char * const ok = crypt_gensalt_rn( "$2y$", rounds, entropy, sizeof( entropy ), salt, size );
if( ok == NULL ) {
- return 1;
+ return BC_ERR_API;
}
return 0;
@@ -52,7 +113,7 @@ static int luabcrypt_digest( lua_State * const L ) {
int rv_salt = makesalt( newsalt, sizeof( newsalt ), rounds );
if( rv_salt != 0 ) {
- lua_pushstring( L, strerror( errno ) );
+ lua_pushstring( L, my_strerror( rv_salt ) );
return lua_error( L );
}
@@ -84,7 +145,7 @@ static int luabcrypt_salt( lua_State * const L ) {
int rv = makesalt( salt, sizeof( salt ), rounds );
if( rv != 0 ) {
- lua_pushstring( L, strerror( errno ) );
+ lua_pushstring( L, my_strerror( rv ) );
return lua_error( L );
}
@@ -111,18 +172,33 @@ static int luabcrypt_verify( lua_State * const L ) {
return 1;
}
+static int luabcrypt_random( lua_State * const L ) {
+ const char * const path = luaL_checkstring( L, 1 );
+
+ int rv = open_random( path );
+
+ if( rv != 0 ) {
+ lua_pushstring( L, strerror( errno ) );
+
+ return lua_error( L );
+ }
+
+ return 0;
+}
+
static const struct luaL_Reg luabcrypt_lib[] = {
{ "digest", luabcrypt_digest },
{ "salt", luabcrypt_salt },
{ "verify", luabcrypt_verify },
+ { "random", luabcrypt_random },
{ NULL, NULL },
};
LUALIB_API int luaopen_bcrypt( lua_State * const L ) {
- urandom = open( "/dev/urandom", O_RDONLY );
+ int rv = open_random( DEFAULT_RANDOM );
- if( urandom == -1 ) {
- lua_pushstring( L, strerror( errno ) );
+ if( rv != 0 ) {
+ lua_pushstring( L, my_strerror( rv ) );
return lua_error( L );
}
diff --git a/tests.lua b/tests.lua
@@ -2,23 +2,29 @@
local bcrypt = require( "bcrypt" )
+-- test salt generation
local salt = bcrypt.salt( 5 )
assert( salt:len() == 29 )
+-- test verification
local digest = bcrypt.digest( salt, salt )
assert( bcrypt.verify( salt, digest ) )
+
+-- test verification failure
assert( not bcrypt.verify( salt:gsub( "(.)$", function( ch )
return string.char( ( ch:byte() + 1 ) % 255 )
end ), digest ) )
+-- test we can't use a bad entropy source
+local ok = pcall( bcrypt.random, "/dev/null" )
+assert( not ok )
+
+-- some test inputs, mostly taken from john the ripper
local tests = {
{
"$2y$04$TnjywYklQbbZjdjBgBoA4e9G7RJt9blgMgsCvUvus4Iv4TENB5nHy",
"test"
},
-
- -- rest are taken from John the Ripper
- -- http://cvsweb.openwall.com/cgi/cvsweb.cgi/Owl/packages/john/john/src/BF_fmt.c?rev=HEAD
{
"$2y$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW",
"U*U"