Skip to content
This repository has been archived by the owner. It is now read-only.
Permalink
Browse files
Add supervision tree on the way to a proper OTP application
  • Loading branch information
Hunter Morris committed Mar 31, 2011
1 parent d9d3d27 commit 14d7d981621ea8d849fdf541d246153da3a9fe12
Showing 12 changed files with 366 additions and 105 deletions.
@@ -2,7 +2,9 @@
ebin/*.beam
ebin/bcrypt.app
_build/
lib/bcrypt/bcrypt
priv/bcrypt
priv/bcrypt_nif.so
logs/*

## libtool
.deps
@@ -1,6 +1,6 @@
The Erlang code is subject to this license:

%% Copyright (c) 2008 Hunter Morris <huntermorris@gmail.com>
%% Copyright (c) 2011 Hunter Morris <hunter.morris@smarkets.com>

%% Permission to use, copy, modify, and distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
@@ -0,0 +1,221 @@
/*
* Copyright (c) 2011 Hunter Morris <hunter.morris@smarkets.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <erl_interface.h>
#include <ei.h>
#include <unistd.h>

#include "erl_blf.h"

#define dec_int16(s) ((((unsigned char*)(s))[0] << 8) | \
(((unsigned char*)(s))[1]))

#define enc_int16(i, s) {((unsigned char*)(s))[0] = ((i) >> 8) & 0xff; \
((unsigned char*)(s))[1] = (i) & 0xff;}

#define BUFSIZE (1 << 16)
#define CMD_SALT 0
#define CMD_HASHPW 1

typedef unsigned char byte;

char *bcrypt(const char *, const char *);
void encode_salt(char *, u_int8_t *, u_int16_t, u_int8_t);

/* These methods came from the Erlang port command tutorial:
* http://www.erlang.org/doc/tutorial/c_port.html#4.2
*/
static int
read_buf(int fd, byte *buf, int len)
{
int i, got = 0;
do {
if ((i = read(fd, buf+got, len-got)) <= 0) {
if (i == 0) return got;
if (errno != EINTR)
return got;
i = 0;
}
got += i;
} while (got < len);
return (len);
}

static int
read_cmd(byte *buf)
{
int len;
if (read_buf(0, buf, 2) != 2)
return 0;
len = dec_int16(buf);
if (read_buf(0, buf, len) != len)
return 0;
return 1;
}

static int
write_buf(int fd, byte *buf, int len)
{
int i, done = 0;
do {
if ((i = write(fd, buf+done, len-done)) < 0) {
if (errno != EINTR)
return (i);
i = 0;
}
done += i;
} while (done < len);
return (len);
}

static int
write_cmd(byte *buf, int len)
{
byte hd[2];
enc_int16(len, hd);
if (write_buf(1, hd, 2) != 2)
return 0;
if (write_buf(1, buf, len) != len)
return 0;
return 1;
}

static int
process_reply(ETERM *pid, int cmd, const char *res)
{
ETERM *result;
int len, retval;
byte *buf;
result = erl_format("{~i, ~w, ~s}", cmd, pid, res);
len = erl_term_len(result);
buf = erl_malloc(len);
erl_encode(result, buf);
retval = write_cmd(buf, len);
erl_free_term(result);
erl_free(buf);
return retval;
}

static int
process_encode_salt(ETERM *pid, ETERM *data)
{
int retval = 0;
ETERM *pattern, *cslt, *lr;
byte *csalt = NULL;
long log_rounds = -1;
int csaltlen = -1;
char ret[64];
pattern = erl_format("{Csalt, LogRounds}");
if (erl_match(pattern, data)) {
cslt = erl_var_content(pattern, "Csalt");
csaltlen = ERL_BIN_SIZE(cslt);
csalt = ERL_BIN_PTR(cslt);
lr = erl_var_content(pattern, "LogRounds");
log_rounds = ERL_INT_UVALUE(lr);
if (16 != csaltlen) {
retval = process_reply(pid, CMD_SALT, "Invalid salt length");
} else if (log_rounds < 4 || log_rounds > 31) {
retval = process_reply(pid, CMD_SALT, "Invalid number of rounds");
} else {
encode_salt(ret, (u_int8_t*)csalt, csaltlen, log_rounds);
retval = process_reply(pid, CMD_SALT, ret);
}
erl_free_term(cslt);
erl_free_term(lr);
};
erl_free_term(pattern);
return retval;
}

static int
process_hashpw(ETERM *pid, ETERM *data)
{
int retval = 0;
ETERM *pattern, *pwd, *slt;
char *password, *salt;
char *ret = NULL;
pattern = erl_format("{Pass, Salt}");
if (erl_match(pattern, data)) {
pwd = erl_var_content(pattern, "Pass");
password = erl_iolist_to_string(pwd);
slt = erl_var_content(pattern, "Salt");
salt = erl_iolist_to_string(slt);
if (NULL == (ret = bcrypt(password, salt)) ||
0 == strcmp(ret, ":")) {
retval = process_reply(pid, CMD_HASHPW, "Invalid salt");
} else {
retval = process_reply(pid, CMD_HASHPW, ret);
}
erl_free_term(pwd);
erl_free_term(slt);
erl_free(password);
erl_free(salt);
};
erl_free_term(pattern);
return retval;
}

static int
process_command(unsigned char *buf)
{
int retval = 0;
ETERM *pattern, *tuple, *cmd, *port, *data;
pattern = erl_format("{Cmd, Port, Data}");
tuple = erl_decode(buf);
if (erl_match(pattern, tuple)) {
cmd = erl_var_content(pattern, "Cmd");
port = erl_var_content(pattern, "Port");
data = erl_var_content(pattern, "Data");
switch (ERL_INT_VALUE(cmd)) {
case CMD_SALT:
retval = process_encode_salt(port, data);
break;
case CMD_HASHPW:
retval = process_hashpw(port, data);
break;
};
erl_free_term(cmd);
erl_free_term(port);
erl_free_term(data);
}
erl_free_term(pattern);
erl_free_term(tuple);
return retval;
}

static void
loop(void)
{
byte buf[BUFSIZE];
int retval = 0;
do {
if (read_cmd(buf) > 0)
retval = process_command(buf);
else
retval = 0;
} while (retval);
}

int
main(int argc, char *argv[])
{
erl_init(NULL, 0);
loop();
return 0;
}
BIN +19.9 KB (120%) rebar
Binary file not shown.
@@ -1,3 +1,8 @@
%% -*- mode: erlang;erlang-indent-level: 2;indent-tabs-mode: nil -*-
{so_name, "bcrypt_nif.so"}.
{so_specs,
[{"priv/bcrypt_nif.so",
["c_src/blowfish.c", "c_src/bcrypt.c", "c_src/bcrypt_nif.c"]},
{"priv/bcrypt",
["c_src/blowfish.c", "c_src/bcrypt.c", "c_src/bcrypt_port.c"]}]}.

{erl_opts, [debug_info]}.
@@ -1,12 +1,19 @@
%% -*- mode: erlang;erlang-indent-level: 2;indent-tabs-mode: nil -*-
{application, bcrypt,
[{description, "An Erlang wrapper (NIF or port program) for the OpenBSD password scheme, bcrypt."},
{vsn, "0.3.0"},
{registered, []},
{vsn, "0.4.0"},
{registered, [bcrypt_sup, bcrypt_nif_worker, bcrypt_port_sup, bcrypt_pool]},
{mod, {bcrypt_app, []}},
{applications, [kernel, stdlib, crypto]},
{env, [
% Default number of 'rounds', defining the hashing complexity
{default_log_rounds, 12}
{default_log_rounds, 12},

% Mechanism to use 'nif' or 'port'
{mechanism, nif},

% Size of port program pool
{pool_size, 4}
]}
]
}.
@@ -1,83 +1,29 @@
%% @author Hunter Morris <hunter.morris@smarkets.com>
%% @copyright 2011 Hunter Morris
%%
%% @doc Wrapper around the OpenBSD Blowfish password hashing algorithm, as
%% described in "A Future-Adaptable Password Scheme" by Niels Provos and
%% David Mazieres: http://www.openbsd.org/papers/bcrypt-paper.ps
%% @end
%%
%% Permission to use, copy, modify, and distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.

%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
%% Copyright (c) 2011 Hunter Morris
%% Distributed under the MIT license; see LICENSE for details.
-module(bcrypt).
-author('Hunter Morris <huntermorris@gmail.com>').
-author('Hunter Morris <hunter.morris@smarkets.com>').

%% API
-export([init/0]).
-export([gen_salt/0, gen_salt/1]).
-export([hash/2, hashpw/2]).

-define(DEFAULT_LOG_ROUNDS, 12).
-define(MAX_LOG_ROUNDS(L), L < 32).
-define(MIN_LOG_ROUNDS(L), L > 3).

-on_load(init/0).

%%--------------------------------------------------------------------
%% @doc Load the bcrypt NIFs
%% @spec init() -> ok
%% @end
%%--------------------------------------------------------------------
init() ->
Dir = case code:priv_dir(bcrypt) of
{error, bad_name} -> "../priv";
Priv -> Priv
end,
erlang:load_nif(filename:join(Dir, "bcrypt"), 0).
-export([start/0, stop/0]).
-export([mechanism/0]).
-export([gen_salt/0, gen_salt/1, hashpw/2]).

nif_stub_error(Line) -> erlang:nif_error({nif_not_loaded,module,?MODULE,line,Line}).
start() -> application:start(bcrypt).
stop() -> application:stop(bcrypt).

%%--------------------------------------------------------------------
%% @doc Generate a salt with the default number of rounds, 12.
%% @see gen_salt/1
%% @spec gen_salt() -> string()
%% @end
%%--------------------------------------------------------------------
gen_salt() ->
gen_salt(?DEFAULT_LOG_ROUNDS).
mechanism() ->
{ok, M} = application:get_env(bcrypt, mechanism),
M.

%%--------------------------------------------------------------------
%% @doc Generate a random text salt for use with hashpw/3. LogRounds
%% defines the complexity of the hashing, increasing the cost as
%% 2^log_rounds.
%% @spec gen_salt(integer()) -> string()
%% @end
%%--------------------------------------------------------------------
gen_salt(LogRounds) when is_integer(LogRounds),
?MAX_LOG_ROUNDS(LogRounds),
?MIN_LOG_ROUNDS(LogRounds) ->
R = crypto:rand_bytes(16),
encode_salt(R, LogRounds).
gen_salt() -> do_gen_salt(mechanism()).
gen_salt(Rounds) -> do_gen_salt(mechanism(), Rounds).
hashpw(Password, Salt) -> do_hashpw(mechanism(), Password, Salt).

encode_salt(_R, _LogRounds) ->
nif_stub_error(?LINE).
do_gen_salt(nif) -> bcrypt_nif_worker:gen_salt();
do_gen_salt(port) -> bcrypt_pool:gen_salt().

%%--------------------------------------------------------------------
%% @doc Hash the specified password and the salt using the OpenBSD
%% Blowfish password hashing algorithm. Returns the hashed password.
%% @spec hash(Password::binary(), Salt::binary()) -> string()
%% @end
%%--------------------------------------------------------------------
hash(Password, Salt) when is_binary(Password), is_binary(Salt) ->
hashpw(Password, Salt).
do_gen_salt(nif, Rounds) -> bcrypt_nif_worker:gen_salt(Rounds);
do_gen_salt(port, Rounds) -> bcrypt_pool:gen_salt(Rounds).

hashpw(_Password, _Salt) ->
nif_stub_error(?LINE).
do_hashpw(nif, Password, Salt) -> bcrypt_nif_worker:hashpw(Password, Salt);
do_hashpw(port, Password, Salt) -> bcrypt_pool:hashpw(Password, Salt).
@@ -0,0 +1,16 @@
%% Copyright (c) 2011 Hunter Morris
%% Distributed under the MIT license; see LICENSE for details.
-module(bcrypt_app).
-author('Hunter Morris <hunter.morris@smarkets.com>').

-behaviour(application).

-export([start/2, stop/1]).

start(normal, _Args) ->
case bcrypt_sup:start_link() of
{ok, Pid} -> {ok, Pid};
{error, _} = Error -> Error
end.

stop(_State) -> ok.

0 comments on commit 14d7d98

Please sign in to comment.