From c54c7e3541d548f0d1f206277e9cf04baff07051 Mon Sep 17 00:00:00 2001 From: Benjamin Barenblat Date: Fri, 28 Jun 2013 18:25:45 -0700 Subject: Initial commit --- src/interface/bcrypt.h | 28 ++++++++++ src/interface/bcrypt.ur | 127 ++++++++++++++++++++++++++++++++++++++++++++ src/interface/bcrypt.urs | 58 ++++++++++++++++++++ src/interface/bcryptFfi.urs | 33 ++++++++++++ src/interface/config.urp.in | 16 ++++++ src/interface/lib.urp | 27 ++++++++++ 6 files changed, 289 insertions(+) create mode 100644 src/interface/bcrypt.h create mode 100644 src/interface/bcrypt.ur create mode 100644 src/interface/bcrypt.urs create mode 100644 src/interface/bcryptFfi.urs create mode 100644 src/interface/config.urp.in create mode 100644 src/interface/lib.urp (limited to 'src/interface') diff --git a/src/interface/bcrypt.h b/src/interface/bcrypt.h new file mode 100644 index 0000000..5d16a1d --- /dev/null +++ b/src/interface/bcrypt.h @@ -0,0 +1,28 @@ +/* bcrypt.h -- low-level FFI to the bcrypt library + * Copyright (C) 2013 Benjamin Barenblat + * + * This library is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this library. If not, see . */ + +/* This file describes the interface to the bcrypt FFI in C. The file + * 'bcryptFfi.urs' describes the interface in Ur. As that file, in contrast to + * this one, is intended for human consumption, all interface description + * comments reside there. */ + +#include + +uw_Basis_string uw_BcryptFfi_randomSetting(uw_context); + +uw_Basis_string uw_BcryptFfi_crypt(uw_context, + uw_Basis_string setting, + uw_Basis_string password); diff --git a/src/interface/bcrypt.ur b/src/interface/bcrypt.ur new file mode 100644 index 0000000..fd48c12 --- /dev/null +++ b/src/interface/bcrypt.ur @@ -0,0 +1,127 @@ +(* bcrypt.ur -- high-level FFI to the bcrypt library +Copyright (C) 2013 Benjamin Barenblat + +This library is free software: you can redistribute it and/or modify it under +the terms of the GNU Affero General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) any +later version. + +This library is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU Affero General Public License for more +details. + +You should have received a copy of the GNU Affero General Public License along +with this library. If not, see . *) + + +(********************************** Utility **********************************) + +(* Value-level identity *) +fun id [t ::: Type] (x : t) : t = x + +(* Converts a boolean to an option. Useful for chaining a bunch of boolean +assertions into the option monad. *) +fun assert (cond : bool) : option unit = if cond then Some () else None + + +(*** String processing functions ***) + +(* Removes the first 'n' characters from 's' *) +fun drop (n : int) (s : string) : string = + String.substring s { Start = n, Len = String.length s - n } + +(* Extracts the last 'n' characters from 's' *) +fun takeLast (n : int) (s : string) : string = + String.substring s { Start = String.length s - n, Len = n } + +(* Removes the last 'n' characters from 's' *) +fun dropLast (n : int) (s : string) : string = + String.substring s { Start = 0, Len = String.length s - n } + +(* A predicate on characters matching [./A-Za-z0-9], the set of characters used +in bcrypt's weird base64 representation. *) +fun isValidHashCharacter (c : char) : bool = + Char.isAlnum c || c = #"." || c = #"/" + + +(******************************** The setting ********************************) + +structure Setting = struct + (* I use smart constructors (primarily 'read') to ensure that this is + always a valid bcrypt setting. *) + type t = string + + val eq_t = eq_string + + val show_t = mkShow id + + fun ofString (s : string) : option t = + (* Ah, what I'd give for a regular expression engine that can simply + recognize /\$2[axy]?\$[0-9]{2}\$[./A-Za-z0-9]{22}/! Alas, that is a + task for another adventurer. *) + assert (String.lengthGe s 28); + (* Rip off the $2 or $2a (or $2y or $2n) at the start. *) + assert (String.sub s 0 = #"$"); + assert (String.sub s 1 = #"2"); + withIdStripped <- + (let val classIdentifier = String.sub s 2 in + if classIdentifier = #"a" + || classIdentifier = #"y" + || classIdentifier = #"n" + then Some (drop 3 s) + else if classIdentifier = #"$" + then Some (drop 2 s) + else None + end); + (* Check for a valid number of rounds. *) + assert (String.sub withIdStripped 0 = #"$"); + assert (Char.isDigit (String.sub withIdStripped 1)); + assert (Char.isDigit (String.sub withIdStripped 2)); + assert (String.sub withIdStripped 3 = #"$"); + (* Check the salt's base64 representation. *) + let val salt = drop 4 withIdStripped in + assert (String.length salt = 22); + assert (String.all isValidHashCharacter salt); + return s + end + + fun ofStringError (s : string) : t = + Option.get (error {[s]} is an invalid bcrypt setting) + (ofString s) + + val read_t = mkRead ofStringError ofString + + val sql_t = sql_prim + + val random = BcryptFfi.randomSetting +end + + +(******************************* The algorithm *******************************) + +type hashedString = string + +val eq_hashedString = eq_string + +val show_hashedString = mkShow id + +fun setting (s : hashedString) = dropLast 31 s + +fun ofString (s : string) : option hashedString = + (* Once again, a regular expression library would be nice. In the + meantime, the last 31 characters must be the hash, and the others must be + the setting. *) + assert (String.lengthGe s 59); + assert (String.all isValidHashCharacter (takeLast 31 s)); + Monad.ignore (@read Setting.read_t (setting s)); + return s + +fun ofStringError (s : string) : hashedString = + Option.get (error {[s]} is an invalid bcrypt hash) (ofString s) + +val read_hashedString = mkRead ofStringError ofString + +val sql_hashedString = sql_prim + +val crypt setting password = BcryptFfi.crypt (show setting) password diff --git a/src/interface/bcrypt.urs b/src/interface/bcrypt.urs new file mode 100644 index 0000000..a474ef0 --- /dev/null +++ b/src/interface/bcrypt.urs @@ -0,0 +1,58 @@ +(* bcrypt.urs -- high-level FFI to the bcrypt library +Copyright (C) 2013 Benjamin Barenblat + +This library is free software: you can redistribute it and/or modify it under +the terms of the GNU Affero General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) any +later version. + +This library is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU Affero General Public License for more +details. + +You should have received a copy of the GNU Affero General Public License along +with this library. If not, see . *) + +(* In contrast to the 'BcryptFfi' module, 'Bcrypt' is a high-level, typesafe +interface to bcrypt. You should use 'Bcrypt' instead of 'BcryptFfi' whenever +possible; the former is implemented on top of the latter. + +The bcrypt algorithm takes as input a "setting" and a password and produces a +hash. The "setting" is a data structure describing the version of bcrypt in +use, a tunable parameter defining how slow you would like the hash to be, and +the salt for the hash. *) + + +(******************************** The setting ********************************) + +structure Setting : sig + type t + val eq_t : eq t + val show_t : show t + val read_t : read t + val sql_t : sql_injectable t + + (* Creates a setting with a pseudorandom salt and the default number of + rounds. The salt comes from /dev/urandom, which is not a cryptographically + secure source, but it should be good enough. *) + val random : transaction t + + (* TODO: Write a function to create a setting with pseudorandom salt and a + specified number of rounds. *) +end + + +(******************************* The algorithm *******************************) + +type hashedString +val eq_hashedString : eq hashedString +val show_hashedString : show hashedString +val read_hashedString : read hashedString +val sql_hashedString : sql_injectable hashedString + +(* Extracts the setting from a hashed password. *) +val setting : hashedString -> Setting.t + +(* Performs a bcrypt operation. *) +val crypt : Setting.t -> string -> hashedString diff --git a/src/interface/bcryptFfi.urs b/src/interface/bcryptFfi.urs new file mode 100644 index 0000000..a2ef47d --- /dev/null +++ b/src/interface/bcryptFfi.urs @@ -0,0 +1,33 @@ +(* bcryptFfi.urs -- low-level FFI to the bcrypt library +Copyright (C) 2013 Benjamin Barenblat + +This library is free software: you can redistribute it and/or modify it under +the terms of the GNU Affero General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) any +later version. + +This library is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU Affero General Public License for more +details. + +You should have received a copy of the GNU Affero General Public License along +with this library. If not, see . *) + +(* In contrast to the 'Bcrypt' module, 'BcryptFfi' is a low-level, non-typesafe +interface to bcrypt. It is memory-safe, though, and it's provided as an +"escape hatch" when 'Bcrypt's interface is inadequate. + +Unlike 'Bcrypt', which is implemented in Ur, 'BcryptFfi' is actually +implemented in C. This file describes the interface to the bcrypt FFI in Ur. +The file 'bcrypt.h' describes the interface in C. *) + +(* Creates a setting with a pseudorandom salt and the default number of rounds. +The salt comes from /dev/urandom, which is not a cryptographically secure +source, but it should be good enough. *) +val randomSetting : transaction string + +(* Runs crypt(3) with the bcrypt algorithm. *) +val crypt : string (* setting *) + -> string (* password *) + -> string diff --git a/src/interface/config.urp.in b/src/interface/config.urp.in new file mode 100644 index 0000000..8f47818 --- /dev/null +++ b/src/interface/config.urp.in @@ -0,0 +1,16 @@ +# config.urp -- compile-time constants for bcrypt library +# Copyright (C) 2013 Benjamin Barenblat +# +# This library is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this library. If not, see . +include @includedir@/urweb/bcrypt.h diff --git a/src/interface/lib.urp b/src/interface/lib.urp new file mode 100644 index 0000000..35bbb37 --- /dev/null +++ b/src/interface/lib.urp @@ -0,0 +1,27 @@ +# lib.urp -- bcrypt library +# Copyright (C) 2013 Benjamin Barenblat +# +# This library is free software: you can redistribute it and/or modify it under +# the terms of the GNU Affero General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) any +# later version. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more +# details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this library. If not, see . +# +# The config library has compile-time constants needed to build applications +# with urweb_bcrypt. +library config +ffi bcryptFfi +link -lurweb_bcrypt + +$/char +$/monad +$/option +$/string +bcrypt -- cgit v1.2.3