Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add server-side hashing of passwords #619

Open
wants to merge 11 commits into
base: development
Choose a base branch
from
4 changes: 4 additions & 0 deletions .earthlyignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
lib/argon2/build
lib/argon2/bin
lib/argon2/ndll
lib/argon2/obj
40 changes: 36 additions & 4 deletions Earthfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ neko:
RUN tar --strip-components=1 -xf "$FILENAME" -C neko
SAVE ARTIFACT neko/*

INSTALL_NEKO:
COMMAND
ARG NEKOPATH=/neko
COPY +neko/* "$NEKOPATH/"
ARG PREFIX=/usr/local
RUN bash -c "ln -s \"$NEKOPATH\"/{neko,nekoc,nekoml,nekotools} \"$PREFIX/bin/\""
RUN bash -c "ln -s \"$NEKOPATH\"/libneko.* \"$PREFIX/lib/\""
RUN bash -c "ln -s \"$NEKOPATH\"/neko.h \"$PREFIX/include/\""
RUN mkdir -p "$PREFIX/lib/neko/"
RUN bash -c "ln -s \"$NEKOPATH\"/*.ndll \"$PREFIX/lib/neko/\""
RUN ldconfig

haxe:
ARG FILENAME=haxe.tar.gz
RUN curl -fsSL "https://github.com/HaxeFoundation/haxe/releases/download/4.3.4/haxe-4.3.4-linux64.tar.gz" -o "$FILENAME"
Expand Down Expand Up @@ -101,10 +113,7 @@ devcontainer-base:
&& rm -rf /var/lib/apt/lists/*

# install neko
COPY +neko/neko /usr/bin/neko
COPY +neko/libneko.so* /usr/lib/
RUN mkdir -p /usr/lib/neko/
COPY +neko/*.ndll /usr/lib/neko/
DO +INSTALL_NEKO
RUN neko -version

# install haxe
Expand Down Expand Up @@ -223,6 +232,7 @@ haxelib-deps:
USER $USERNAME
COPY --chown=$USER_UID:$USER_GID libs.hxml run.n .
COPY --chown=$USER_UID:$USER_GID lib/record-macros lib/record-macros
COPY --chown=$USER_UID:$USER_GID lib/argon2 lib/argon2
RUN mkdir -p haxelib_global
RUN neko run.n setup haxelib_global
RUN haxe libs.hxml && rm haxelib_global/*.zip
Expand Down Expand Up @@ -334,11 +344,30 @@ aws-ndll:
FROM +haxelib-deps
SAVE ARTIFACT /workspace/haxelib_global/aws-sdk-neko/*/ndll/Linux64/aws.ndll

argon2-ndll:
# install build-essential, cmake, and neko
RUN apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y \
build-essential \
cmake \
&& rm -r /var/lib/apt/lists/*

DO +INSTALL_NEKO
RUN neko -version

COPY lib/argon2 lib/argon2
RUN mkdir lib/argon2/build
RUN cmake -B lib/argon2/build -S lib/argon2
RUN make -C lib/argon2/build Argon2
RUN make -C lib/argon2/build
SAVE ARTIFACT lib/argon2/build/argon2.ndll

haxelib-server-builder:
FROM haxe:3.4

WORKDIR /workspace
COPY lib/record-macros lib/record-macros
COPY lib/argon2 lib/argon2
COPY --chown=$USER_UID:$USER_GID +node-modules-dev/node_modules node_modules
COPY --chown=$USER_UID:$USER_GID +dts2hx-externs/dts2hx-generated lib/dts2hx-generated
COPY --chown=$USER_UID:$USER_GID +haxelib-deps/haxelib_global haxelib_global
Expand Down Expand Up @@ -443,6 +472,7 @@ haxelib-server:
&& apachectl stop

COPY +aws-ndll/aws.ndll /usr/lib/x86_64-linux-gnu/neko/aws.ndll
COPY +argon2-ndll/argon2.ndll /usr/lib/x86_64-linux-gnu/neko/argon2.ndll

# Need rclone to do the upload to R2
COPY +rclone/rclone /usr/local/bin/
Expand Down Expand Up @@ -510,6 +540,8 @@ ci-tests:
COPY hx3compat hx3compat
COPY lib/node-sys-db lib/node-sys-db
COPY lib/record-macros lib/record-macros
COPY lib/argon2 lib/argon2
COPY +argon2-ndll/argon2.ndll argon2.ndll
COPY src src
COPY www www
COPY test test
Expand Down
6 changes: 4 additions & 2 deletions integration_tests.hxml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
-cp src
-cp test
-lib hx3compat
-cp hx4compat/std
-cp crypto/src
-lib hx3compat
-lib record-macros
-lib argon2
-main IntegrationTests
-neko bin/integration_tests.n
-neko bin/integration_tests.n
6 changes: 6 additions & 0 deletions lib/argon2/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*.n
*.ndll

obj/
bin/
build/
46 changes: 46 additions & 0 deletions lib/argon2/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
cmake_minimum_required(VERSION 3.16.3)

project(Argon2Ndll C)

include(ExternalProject)

set(ARGON2_LIB ${CMAKE_BINARY_DIR}/libs/src/Argon2/libargon2.a)
set(ARGON2_INCLUDE ${CMAKE_BINARY_DIR}/libs/src/Argon2/include)

ExternalProject_Add(Argon2
PREFIX ${CMAKE_BINARY_DIR}/libs
DOWNLOAD_DIR ${CMAKE_BINARY_DIR}/libs/download
URL https://github.com/P-H-C/phc-winner-argon2/archive/refs/tags/20190702.tar.gz
URL_HASH SHA256=daf972a89577f8772602bf2eb38b6a3dd3d922bf5724d45e7f9589b5e830442c
BUILD_COMMAND cd ${CMAKE_BINARY_DIR}/libs/src/Argon2 && CFLAGS=-fPIC make
CONFIGURE_COMMAND echo skip config
INSTALL_COMMAND echo skip install
BYPRODUCTS ${ARGON2_LIB}
)

add_custom_command(
OUTPUT ${ARGON2_INCLUDE}/argon2.h
DEPENDS Argon2
)

add_custom_command(
OUTPUT ${ARGON2_LIB}
DEPENDS Argon2
)

add_library(argon2.ndll MODULE native/argon2.c)

target_include_directories(argon2.ndll PRIVATE ${ARGON2_INCLUDE})
target_link_libraries(argon2.ndll libneko.so ${ARGON2_LIB})

set_target_properties(argon2.ndll
PROPERTIES
PREFIX ""
OUTPUT_NAME argon2
SUFFIX .ndll
)

install(
TARGETS argon2.ndll
DESTINATION ${CMAKE_SOURCE_DIR}/ndll/Linux64
)
12 changes: 12 additions & 0 deletions lib/argon2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Argon2 Neko bindings

## Building

To build the ndll file, run the following commands:

```sh
mkdir build
cd build
cmake ..
make
```
7 changes: 7 additions & 0 deletions lib/argon2/haxelib.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "argon2",
"description": "Neko wrapper for C argon2 implementation",
"version": "0.0.0",
"classPath": "src",
"license": "MIT"
}
70 changes: 70 additions & 0 deletions lib/argon2/native/argon2.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#include <neko.h>
#include <stdio.h>

#include "argon2.h"

#define HASHLEN 32

static void handle_error(int rc) {
buffer b = alloc_buffer("Argon2 Error: ");
buffer_append(b, argon2_error_message(rc));
buffer_append(b, "\n");
val_throw(buffer_to_string(b));
}

value generate_argon2id_raw_hash(value time_cost, value memory_cost, value parallelism, value password, value salt) {
printf("hello\n");
val_check(time_cost, int);
val_check(memory_cost, int);
val_check(parallelism, int);
val_check(password, string);
val_check(salt, string);

value hash = alloc_empty_string(HASHLEN);

int rc = argon2id_hash_raw(val_int(time_cost), val_int(memory_cost), val_int(parallelism), val_string(password), val_strlen(password), val_string(salt), val_strlen(salt), val_string(hash), HASHLEN);
if (rc != ARGON2_OK) {
handle_error(rc);
}

return hash;
}

value generate_argon2id_hash(value time_cost, value memory_cost, value parallelism, value password, value salt) {
val_check(time_cost, int);
val_check(memory_cost, int);
val_check(parallelism, int);
val_check(password, string);
val_check(salt, string);

size_t salt_len = val_strlen(salt);
size_t password_len = val_strlen(password);
size_t encoded_len = argon2_encodedlen(val_int(time_cost), val_int(memory_cost), val_int(parallelism), salt_len, HASHLEN, Argon2_id);

// encoded_len takes into account null terminator, however, alloc_empty_string adds an extra byte for that anyway
value hash_string = alloc_empty_string(encoded_len - 1);

int rc = argon2id_hash_encoded(val_int(time_cost), val_int(memory_cost), val_int(parallelism), val_string(password), password_len, val_string(salt), salt_len, HASHLEN, val_string(hash_string), encoded_len);
if (rc != ARGON2_OK) {
handle_error(rc);
}

return hash_string;
}

value verify_argon2id(value hash, value password) {
val_check(hash, string);
val_check(password, string);

int rc = argon2id_verify(val_string(hash), val_string(password), val_strlen(password));
if (rc == ARGON2_OK)
return val_true;
if (rc == ARGON2_VERIFY_MISMATCH)
return val_false;
handle_error(rc);
return val_false;
}

DEFINE_PRIM(generate_argon2id_raw_hash, 5);
DEFINE_PRIM(generate_argon2id_hash, 5);
DEFINE_PRIM(verify_argon2id, 2);
21 changes: 21 additions & 0 deletions lib/argon2/src/argon2/Argon2id.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package argon2;

import haxe.io.Bytes;

class Argon2id {
public static function generateHash(password:String, salt:Bytes, timeCost:Int, memoryCost:Int, parallelism:Int):String {
return new String(untyped generate_argon2id_hash(timeCost, memoryCost, parallelism, password.__s, salt.getData()));
}

public static function generateRawHash(password:String, salt:Bytes, timeCost:Int, memoryCost:Int, parallelism:Int) {
return new String(untyped generate_argon2id_raw_hash(timeCost, memoryCost, parallelism, password.__s, salt.getData()));
}

public static function verify(hash:String, password:String) {
return untyped verify_argon2id(hash.__s, password.__s);
}

static var generate_argon2id_hash = neko.Lib.load("argon2", "generate_argon2id_hash", 5);
static var generate_argon2id_raw_hash = neko.Lib.load("argon2", "generate_argon2id_raw_hash", 5);
static var verify_argon2id = neko.Lib.load("argon2", "verify_argon2id", 2);
}
4 changes: 4 additions & 0 deletions lib/argon2/test.hxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
--main Test
-p test
-lib argon2
--neko bin/test.n
13 changes: 13 additions & 0 deletions lib/argon2/test/Test.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import argon2.Argon2id;
import haxe.io.Bytes;

function main() {
final hash = Argon2id.generateRawHash("hello", Bytes.ofString("tesfdfdafdafsagfagahraegfaharegh"), 2, 1 << 16, 1);
trace(hash);

final hash = Argon2id.generateHash("hello", Bytes.ofString("tesfdfdafdafsagfagahraegfaharegh"), 2, 1 << 16, 1);
trace(hash);

trace(Argon2id.verify(hash, "hello"));
trace(Argon2id.verify(hash, "hi"));
}
2 changes: 1 addition & 1 deletion lib/record-macros
1 change: 1 addition & 0 deletions libs.hxml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@
-cmd curl -sSLk https://lib.haxe.org/files/3.0/utest-1,9,6.zip -o haxelib_global/utest.zip && neko run.n install --always --skip-dependencies haxelib_global/utest.zip
-cmd curl -sSLk https://lib.haxe.org/files/3.0/hxnodejs-12,1,0.zip -o haxelib_global/hxnodejs.zip && neko run.n install --always --skip-dependencies haxelib_global/hxnodejs.zip
-cmd neko run.n dev record-macros lib/record-macros
-cmd neko run.n dev argon2 lib/argon2
15 changes: 8 additions & 7 deletions server_api.hxml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
-cp src
-neko www/api/3.0/index.n
-main haxelib.server.Repo
-lib aws-sdk-neko
-lib record-macros
-dce no
-D haxelib_api
-cp src
-neko www/api/3.0/index.n
-main haxelib.server.Repo
-lib aws-sdk-neko
-lib record-macros
-lib argon2
-dce no
-D haxelib-api
3 changes: 2 additions & 1 deletion server_each.hxml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
-lib aws-sdk-neko
-lib record-macros
-lib html-haxe-code-highlighter:0.1.2
-lib argon2
-D server
-D getter_support
# https://github.com/HaxeFoundation/haxe/issues/4903
--macro keep('StringBuf')
--macro keep('StringBuf')
6 changes: 6 additions & 0 deletions skeema/.skeema
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@ flavor=mysql:5.7
host=haxe-org.ct0xwjh6v08k.eu-west-1.rds.amazonaws.com
port=3306
schema=haxelib

[test]
flavor=mysql:5.7
host=localhost
port=3306
schema=haxelib
5 changes: 5 additions & 0 deletions skeema/Meta.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CREATE TABLE `Meta` (
`id` enum('') NOT NULL DEFAULT '',
`dbVersion` int(10) unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
1 change: 1 addition & 0 deletions skeema/User.sql
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ CREATE TABLE `User` (
`fullname` mediumtext NOT NULL,
`email` mediumtext NOT NULL,
`pass` mediumtext NOT NULL,
`salt` binary(32) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
28 changes: 28 additions & 0 deletions src/haxelib/server/Hashing.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package haxelib.server;

class Hashing {
/**
Generates a cryptographically secure random salt.
**/
public static function generateSalt():haxe.io.Bytes {
// currently only works on Linux
var randomFile = sys.io.File.read("/dev/urandom");
var salt = randomFile.read(32);
randomFile.close();
return salt;
}

/**
Hashes `password` using `salt`
**/
public static inline function hash(password:String, salt:haxe.io.Bytes) {
return argon2.Argon2id.generateHash(password, salt, 2, 1 << 16, 1);
}

/**
Verifies whether `password` matches `hash` after being hashed.
**/
public static inline function verify(hash:String, password:String):Bool {
return argon2.Argon2id.verify(hash, password);
}
}
Loading
Loading