From 5556facb9415eece13647a8f00b7ef96c894c9b1 Mon Sep 17 00:00:00 2001 From: tobil4sk Date: Tue, 16 Aug 2022 16:16:59 +0100 Subject: [PATCH 01/20] Add argon2 binding for neko --- lib/argon2/.gitignore | 6 +++ lib/argon2/CMakeLists.txt | 46 +++++++++++++++++++++ lib/argon2/README.md | 12 ++++++ lib/argon2/haxelib.json | 7 ++++ lib/argon2/native/argon2.c | 69 +++++++++++++++++++++++++++++++ lib/argon2/src/argon2/Argon2id.hx | 21 ++++++++++ lib/argon2/test.hxml | 4 ++ lib/argon2/test/Test.hx | 13 ++++++ libs.hxml | 1 + 9 files changed, 179 insertions(+) create mode 100644 lib/argon2/.gitignore create mode 100644 lib/argon2/CMakeLists.txt create mode 100644 lib/argon2/README.md create mode 100644 lib/argon2/haxelib.json create mode 100644 lib/argon2/native/argon2.c create mode 100644 lib/argon2/src/argon2/Argon2id.hx create mode 100644 lib/argon2/test.hxml create mode 100644 lib/argon2/test/Test.hx diff --git a/lib/argon2/.gitignore b/lib/argon2/.gitignore new file mode 100644 index 000000000..cd2d3d017 --- /dev/null +++ b/lib/argon2/.gitignore @@ -0,0 +1,6 @@ +*.n +*.ndll + +obj/ +bin/ +build/ diff --git a/lib/argon2/CMakeLists.txt b/lib/argon2/CMakeLists.txt new file mode 100644 index 000000000..c3155e3ef --- /dev/null +++ b/lib/argon2/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required(VERSION 3.23) + +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 +) diff --git a/lib/argon2/README.md b/lib/argon2/README.md new file mode 100644 index 000000000..c9786d027 --- /dev/null +++ b/lib/argon2/README.md @@ -0,0 +1,12 @@ +# Argon2 Neko bindings + +## Building + +To build the ndll file, run the following commands: + +```sh +mkdir build +cd build +cmake .. +make +``` diff --git a/lib/argon2/haxelib.json b/lib/argon2/haxelib.json new file mode 100644 index 000000000..c55dff798 --- /dev/null +++ b/lib/argon2/haxelib.json @@ -0,0 +1,7 @@ +{ + "name": "argon2", + "description": "Neko wrapper for C argon2 implementation", + "version": "0.0.0", + "classPath": "src", + "license": "MIT" +} diff --git a/lib/argon2/native/argon2.c b/lib/argon2/native/argon2.c new file mode 100644 index 000000000..33cc9cb92 --- /dev/null +++ b/lib/argon2/native/argon2.c @@ -0,0 +1,69 @@ +#include +#include + +#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); + + value hash_string = alloc_empty_string(encoded_len); + + 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); diff --git a/lib/argon2/src/argon2/Argon2id.hx b/lib/argon2/src/argon2/Argon2id.hx new file mode 100644 index 000000000..285d1322f --- /dev/null +++ b/lib/argon2/src/argon2/Argon2id.hx @@ -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); +} diff --git a/lib/argon2/test.hxml b/lib/argon2/test.hxml new file mode 100644 index 000000000..92800d361 --- /dev/null +++ b/lib/argon2/test.hxml @@ -0,0 +1,4 @@ +--main Test +-p test +-lib argon2 +--neko bin/test.n diff --git a/lib/argon2/test/Test.hx b/lib/argon2/test/Test.hx new file mode 100644 index 000000000..385bae7c0 --- /dev/null +++ b/lib/argon2/test/Test.hx @@ -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")); +} diff --git a/libs.hxml b/libs.hxml index ef9ca5b34..d93b20062 100644 --- a/libs.hxml +++ b/libs.hxml @@ -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 From a0b6a1871c588ac5f0b23d4ae7e3d7b65f6608af Mon Sep 17 00:00:00 2001 From: tobil4sk Date: Mon, 22 Aug 2022 19:26:04 +0100 Subject: [PATCH 02/20] [argon2] Account for null bytes in neko strings --- lib/argon2/native/argon2.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/argon2/native/argon2.c b/lib/argon2/native/argon2.c index 33cc9cb92..d3861fbc0 100644 --- a/lib/argon2/native/argon2.c +++ b/lib/argon2/native/argon2.c @@ -41,7 +41,8 @@ value generate_argon2id_hash(value time_cost, value memory_cost, value paralleli 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); - value hash_string = alloc_empty_string(encoded_len); + // 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) { From 5b26d1d776cff17370d65e834bf0cd2e16c4568e Mon Sep 17 00:00:00 2001 From: tobil4sk Date: Mon, 22 Aug 2022 23:13:06 +0100 Subject: [PATCH 03/20] Implement argon2id hashing --- server_api.hxml | 15 +++++++------ server_each.hxml | 3 ++- skeema/User.sql | 2 ++ src/haxelib/server/Hashing.hx | 41 +++++++++++++++++++++++++++++++++++ src/haxelib/server/SiteDb.hx | 3 +++ 5 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 src/haxelib/server/Hashing.hx diff --git a/server_api.hxml b/server_api.hxml index bcaedbe8c..2459209e7 100644 --- a/server_api.hxml +++ b/server_api.hxml @@ -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 diff --git a/server_each.hxml b/server_each.hxml index 04cdc26e2..01a2f0c02 100644 --- a/server_each.hxml +++ b/server_each.hxml @@ -9,7 +9,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') \ No newline at end of file +--macro keep('StringBuf') diff --git a/skeema/User.sql b/skeema/User.sql index 3a90d1b93..78d043b03 100644 --- a/skeema/User.sql +++ b/skeema/User.sql @@ -4,5 +4,7 @@ CREATE TABLE `User` ( `fullname` mediumtext NOT NULL, `email` mediumtext NOT NULL, `pass` mediumtext NOT NULL, + `salt` binary(32) NOT NULL, + `hashmethod` mediumtext NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/src/haxelib/server/Hashing.hx b/src/haxelib/server/Hashing.hx new file mode 100644 index 000000000..154bec66d --- /dev/null +++ b/src/haxelib/server/Hashing.hx @@ -0,0 +1,41 @@ +package haxelib.server; + +@:enum abstract HashMethod(String) { + /** Represents argon2id hashing. **/ + var Argon2id = "argon2id"; + /** Represents rehashing an md5 hash with argon2id. **/ + var Md5 = "md5"; +} + +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/random"); + return randomFile.read(32); + } + + /** + 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 put through the + hashing method specified by `method`. + **/ + public static inline function verify(hash:String, password:String, method:HashMethod):Bool { + // work out md5 hash regardless, to prevent time based attacks + var md5 = haxe.crypto.Md5.encode(password); + return switch method { + case Md5: + argon2.Argon2id.verify(hash, md5); + case Argon2id: + argon2.Argon2id.verify(hash, password); + } + } +} diff --git a/src/haxelib/server/SiteDb.hx b/src/haxelib/server/SiteDb.hx index 482a0e71a..9196f6e4d 100644 --- a/src/haxelib/server/SiteDb.hx +++ b/src/haxelib/server/SiteDb.hx @@ -24,6 +24,7 @@ package haxelib.server; import sys.db.*; import sys.db.Types; import haxelib.server.Paths.*; +import haxelib.server.Hashing.HashMethod; class User extends Object { @@ -32,6 +33,8 @@ class User extends Object { public var fullname : String; public var email : String; public var pass : String; + public var salt : haxe.io.Bytes; + public var hashmethod : HashMethod; } From a1be3ba5e419f7767a24481845c66e0bf53816ae Mon Sep 17 00:00:00 2001 From: tobil4sk Date: Mon, 22 Aug 2022 23:52:20 +0100 Subject: [PATCH 04/20] [earthly] Update to build argon2.ndll --- .earthlyignore | 4 ++++ Earthfile | 40 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 .earthlyignore diff --git a/.earthlyignore b/.earthlyignore new file mode 100644 index 000000000..6c844307c --- /dev/null +++ b/.earthlyignore @@ -0,0 +1,4 @@ +lib/argon2/build +lib/argon2/bin +lib/argon2/ndll +lib/argon2/obj diff --git a/Earthfile b/Earthfile index 1469e68d8..f123016e2 100644 --- a/Earthfile +++ b/Earthfile @@ -32,6 +32,18 @@ neko-latest: RUN tar --strip-components=1 -xf "$FILENAME" -C neko SAVE ARTIFACT neko/* +INSTALL_NEKO: + COMMAND + ARG NEKOPATH=/neko + COPY +neko-latest/* "$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-latest: ARG FILENAME=haxe_2022-08-09_development_779b005.tar.gz RUN haxeArch=$(case "$TARGETARCH" in \ @@ -106,10 +118,7 @@ devcontainer-base: && rm -rf /var/lib/apt/lists/* # install neko nightly - COPY +neko-latest/neko /usr/bin/neko - COPY +neko-latest/libneko.so* /usr/lib/ - RUN mkdir -p /usr/lib/neko/ - COPY +neko-latest/*.ndll /usr/lib/neko/ + DO +INSTALL_NEKO RUN neko -version # install haxe nightly @@ -234,6 +243,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 @@ -346,11 +356,32 @@ 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 \ + && rm -r /var/lib/apt/lists/* + + RUN curl -fsSL "https://github.com/Kitware/CMake/releases/download/v3.24.0/cmake-3.24.0-linux-x86_64.sh" -o cmake-install.sh \ + && sh cmake-install.sh --skip-license --prefix /usr/local \ + && rm cmake-install.sh + + 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 + 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 @@ -454,6 +485,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 WORKDIR /src From acda9d33c29870cd7b0c051f641303fbf8cc8bb0 Mon Sep 17 00:00:00 2001 From: tobil4sk Date: Wed, 21 Sep 2022 17:59:58 +0100 Subject: [PATCH 05/20] Implement database updating system This allows password to be automatically rehashed if the database is old --- skeema/Meta.sql | 5 ++++ src/haxelib/server/SiteDb.hx | 8 +++++- src/haxelib/server/Update.hx | 48 ++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 skeema/Meta.sql create mode 100644 src/haxelib/server/Update.hx diff --git a/skeema/Meta.sql b/skeema/Meta.sql new file mode 100644 index 000000000..1c5a1a5c5 --- /dev/null +++ b/skeema/Meta.sql @@ -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; diff --git a/src/haxelib/server/SiteDb.hx b/src/haxelib/server/SiteDb.hx index 9196f6e4d..91f7b7d88 100644 --- a/src/haxelib/server/SiteDb.hx +++ b/src/haxelib/server/SiteDb.hx @@ -120,6 +120,11 @@ class Developer extends Object { } +class Meta extends Object { + var id:SString<1>; + public var dbVersion:SUInt; +} + class SiteDb { static var db : Connection; //TODO: this whole configuration business is rather messy to say the least @@ -148,7 +153,8 @@ class SiteDb { Project.manager, Tag.manager, Version.manager, - Developer.manager + Developer.manager, + Meta.manager ]; for (m in managers) if (!TableCreate.exists(m)) diff --git a/src/haxelib/server/Update.hx b/src/haxelib/server/Update.hx new file mode 100644 index 000000000..3130f5c9c --- /dev/null +++ b/src/haxelib/server/Update.hx @@ -0,0 +1,48 @@ +package haxelib.server; + +import sys.db.TableCreate; +import haxelib.server.SiteDb; + +/** + Handles server database updates from old versions of the database. +**/ +class Update { + /** The current version of the database. **/ + static inline var CURRENT_VERSION = 1; + + /** + Checks which updates are needed and if there are any needed, runs them. + **/ + public static function runNeededUpdates() { + var meta = Meta.manager.all().first(); + + if (meta == null) { + // no meta data stored yet, so create it + meta = new Meta(); + meta.dbVersion = 0; + meta.insert(); + } + + if (meta.dbVersion == 0) { + rehashPasswords(); + } + + meta.dbVersion = CURRENT_VERSION; + meta.update(); + } + + static function rehashPasswords() { + // script used to update password hashes from md5 to md5 rehashed with argon2id + var users = User.manager.all(); + + for (user in users) { + var md5Hash = user.pass; + + user.salt = Hashing.generateSalt(); + user.pass = Hashing.hash(md5Hash, user.salt); + user.hashmethod = Md5; + user.update(); + } + } + +} From 1498a92f722ca860827a959046cef01517afaa33 Mon Sep 17 00:00:00 2001 From: tobil4sk Date: Wed, 21 Sep 2022 18:00:23 +0100 Subject: [PATCH 06/20] [test] Add tests for database updating system --- Earthfile | 4 +- integration_tests.hxml | 4 +- test/IntegrationTests.hx | 1 + .../integration/TestServerDatabaseUpdate.hx | 112 ++++++++++++++++++ 4 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 test/tests/integration/TestServerDatabaseUpdate.hx diff --git a/Earthfile b/Earthfile index f123016e2..4af21fa70 100644 --- a/Earthfile +++ b/Earthfile @@ -550,13 +550,15 @@ 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 COPY *.hxml . # for package.hxml - COPY haxelib.json README.md . + COPY haxelib.json README.md . COPY +run.n/run.n . COPY +ci-runner/ci.n bin/ci.n diff --git a/integration_tests.hxml b/integration_tests.hxml index 514501311..279889317 100644 --- a/integration_tests.hxml +++ b/integration_tests.hxml @@ -1,5 +1,7 @@ -cp src -cp test -lib hx3compat +-lib record-macros +-lib argon2 -main IntegrationTests --neko bin/integration_tests.n \ No newline at end of file +-neko bin/integration_tests.n diff --git a/test/IntegrationTests.hx b/test/IntegrationTests.hx index 532c1466e..69987a21c 100644 --- a/test/IntegrationTests.hx +++ b/test/IntegrationTests.hx @@ -250,6 +250,7 @@ class IntegrationTests extends TestBase { runner.add(new tests.integration.TestHg()); runner.add(new tests.integration.TestMisc()); runner.add(new tests.integration.TestFixRepo()); + runner.add(new tests.integration.TestServerDatabaseUpdate()); final success = runner.run(); diff --git a/test/tests/integration/TestServerDatabaseUpdate.hx b/test/tests/integration/TestServerDatabaseUpdate.hx new file mode 100644 index 000000000..5706a9e23 --- /dev/null +++ b/test/tests/integration/TestServerDatabaseUpdate.hx @@ -0,0 +1,112 @@ +package tests.integration; + +import haxe.io.Bytes; +import haxelib.server.Update; +import haxelib.server.Hashing; +import haxelib.server.SiteDb; + +class TestServerDatabaseUpdate extends IntegrationTests { + + override function setup() { + super.setup(); + SiteDb.init(); + } + + override function tearDown() { + SiteDb.cleanup(); + super.tearDown(); + } + + /** + Simulates an old database still containing md5 hashes + **/ + function simulateOldDatabase(users:Array<{ + user:String, + email:String, + fullname:String, + pw:String + }>) { + final meta = Meta.manager.all().first(); + meta.dbVersion = 0; + meta.update(); + + for (data in users) { + final user = new User(); + user.name = data.user; + user.fullname = data.fullname; + user.email = data.email; + user.pass = haxe.crypto.Md5.encode(data.pw); + // ignore salt and hashmethod + user.insert(); + } + } + + function testUpdate() { + simulateOldDatabase([foo, bar]); + + Update.runNeededUpdates(); + + final fooAccount = User.manager.search($name == foo.user).first(); + + assertEquals(fooAccount.pass, Hashing.hash(haxe.crypto.Md5.encode(foo.pw), fooAccount.salt)); + assertEquals(fooAccount.salt.length, 32); + assertEquals(fooAccount.hashmethod, cast Md5); + + final barAccount = User.manager.search($name == bar.user).first(); + + assertEquals(barAccount.pass, Hashing.hash(haxe.crypto.Md5.encode(bar.pw), barAccount.salt)); + assertEquals(barAccount.salt.length, 32); + assertEquals(barAccount.hashmethod, cast Md5); + } + + function createNewUserAccount(data:{ + user:String, + email:String, + fullname:String, + pw:String + }) { + final user = new User(); + user.name = data.user; + user.fullname = data.fullname; + user.email = data.email; + final salt = Hashing.generateSalt(); + user.pass = Hashing.hash(data.pw, salt); + user.salt = salt; + user.hashmethod = Argon2id; + user.insert(); + } + + function testReUpdate() { + simulateOldDatabase([foo]); + + // should fix foo account + Update.runNeededUpdates(); + + createNewUserAccount(bar); + createNewUserAccount(deepAuthor); + + // re-update should not change anything + Update.runNeededUpdates(); + + final fooAccount = User.manager.search($name == foo.user).first(); + + assertEquals(fooAccount.pass, Hashing.hash(haxe.crypto.Md5.encode(foo.pw), fooAccount.salt)); + assertEquals(fooAccount.salt.length, 32); + assertEquals(fooAccount.hashmethod, cast Md5); + + // accounts added after first update + + final barAccount = User.manager.search($name == bar.user).first(); + + assertEquals(barAccount.pass, Hashing.hash(bar.pw, barAccount.salt)); + assertEquals(barAccount.salt.length, 32); + assertEquals(barAccount.hashmethod, cast Argon2id); + + final deepAccount = User.manager.search($name == deepAuthor.user).first(); + + assertEquals(deepAccount.pass, Hashing.hash(deepAuthor.pw, deepAccount.salt)); + assertEquals(deepAccount.salt.length, 32); + assertEquals(deepAccount.hashmethod, cast Argon2id); + } + +} From 892b9a9f6312300b427b87a237a5d36b88f4d009 Mon Sep 17 00:00:00 2001 From: tobil4sk Date: Tue, 4 Oct 2022 17:55:13 +0100 Subject: [PATCH 07/20] Run database update script in SiteDb init for now --- src/haxelib/server/SiteDb.hx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/haxelib/server/SiteDb.hx b/src/haxelib/server/SiteDb.hx index 91f7b7d88..5e1e07101 100644 --- a/src/haxelib/server/SiteDb.hx +++ b/src/haxelib/server/SiteDb.hx @@ -159,6 +159,8 @@ class SiteDb { for (m in managers) if (!TableCreate.exists(m)) TableCreate.create(m); + + Update.runNeededUpdates(); } static public function cleanup() { From 6fe590cd7fecdf241d1e77de8086284a21b89b92 Mon Sep 17 00:00:00 2001 From: tobil4sk Date: Mon, 22 Aug 2022 22:40:51 +0100 Subject: [PATCH 08/20] Bump server api version to 4.0 This is so that we can drop support for submit and register for old clients, which use 3.0 --- Earthfile | 13 +++++++++++-- server_api.hxml | 3 ++- server_api_3.0.hxml | 9 +++++++++ src/haxelib/Data.hx | 9 +++++++++ src/haxelib/api/Connection.hx | 4 ++-- 5 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 server_api_3.0.hxml diff --git a/Earthfile b/Earthfile index 4af21fa70..05131f52e 100644 --- a/Earthfile +++ b/Earthfile @@ -422,13 +422,21 @@ haxelib-server-tasks: RUN haxe server_tasks.hxml SAVE ARTIFACT www/tasks.n +haxelib-server-api-3.0: + FROM +haxelib-server-builder + COPY server_api_3.0.hxml server_each.hxml . + COPY src src + COPY hx3compat hx3compat + RUN haxe server_api_3.0.hxml + SAVE ARTIFACT www/api/3.0/index.n + haxelib-server-api: FROM +haxelib-server-builder COPY server_api.hxml server_each.hxml . COPY src src COPY hx3compat hx3compat RUN haxe server_api.hxml - SAVE ARTIFACT www/api/3.0/index.n + SAVE ARTIFACT www/api/4.0/index.n haxelib-server-www-js: FROM +devcontainer-base @@ -509,7 +517,8 @@ haxelib-server: COPY +haxelib-server-website-highlighter/highlighter.js www/js/highlighter.js COPY +haxelib-server-website/index.n www/index.n COPY +haxelib-server-tasks/tasks.n www/tasks.n - COPY +haxelib-server-api/index.n www/api/3.0/index.n + COPY +haxelib-server-api-3.0/index.n www/api/3.0/index.n + COPY +haxelib-server-api/index.n www/api/4.0/index.n EXPOSE 80 diff --git a/server_api.hxml b/server_api.hxml index 2459209e7..9a63efa0a 100644 --- a/server_api.hxml +++ b/server_api.hxml @@ -1,8 +1,9 @@ -cp src --neko www/api/3.0/index.n +-neko www/api/4.0/index.n -main haxelib.server.Repo -lib aws-sdk-neko -lib record-macros -lib argon2 -dce no -D haxelib-api +-D haxelib-api-version=4.0 diff --git a/server_api_3.0.hxml b/server_api_3.0.hxml new file mode 100644 index 000000000..67f872d07 --- /dev/null +++ b/server_api_3.0.hxml @@ -0,0 +1,9 @@ +-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 +-D haxelib-api-version=3.0 diff --git a/src/haxelib/Data.hx b/src/haxelib/Data.hx index e76670b22..5b0b2a13e 100644 --- a/src/haxelib/Data.hx +++ b/src/haxelib/Data.hx @@ -174,6 +174,15 @@ class Data { public static var JSON(default, null) = "haxelib.json"; /** The name of the file containing project documentation. **/ public static var DOCXML(default, null) = "haxedoc.xml"; + /** The current haxelib server api version number. **/ + public static var API_VERSION(default, null) = + #if (!haxelib_api_version || haxelib_api_version == "4.0") + "4.0"; + #elseif (haxelib_api_version == "3.0") + "3.0"; + #elseif haxelib_api_version + #error "`-D haxelib-api-version` has been set to an invalid value" + #end /** The location of the repository in the haxelib server. **/ public static var REPOSITORY(default, null) = "files/3.0"; /** Regex matching alphanumeric strings, which can also include periods, dashes, or underscores. **/ diff --git a/src/haxelib/api/Connection.hx b/src/haxelib/api/Connection.hx index f3c930b7d..245458d45 100644 --- a/src/haxelib/api/Connection.hx +++ b/src/haxelib/api/Connection.hx @@ -69,7 +69,7 @@ private class ConnectionData { port: useSsl ? 443 : 80, dir: "", url: "index.n", - apiVersion: "3.0", + apiVersion: Data.API_VERSION, useSsl: useSsl }; } @@ -95,7 +95,7 @@ private class ConnectionData { port: port, dir: haxe.io.Path.addTrailingSlash(r.matched(4)), url: "index.n", - apiVersion: "3.0", + apiVersion: Data.API_VERSION, useSsl: useSsl }; } From 11befe7a9ab91dd40f3c35dfcf75e9b45babfd89 Mon Sep 17 00:00:00 2001 From: tobil4sk Date: Mon, 22 Aug 2022 23:21:56 +0100 Subject: [PATCH 09/20] Reject `submit` and `register` from old clients This is required to properly migrate away from md5, as old clients performed hashing on client side. --- src/haxelib/server/Repo.hx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/haxelib/server/Repo.hx b/src/haxelib/server/Repo.hx index cdbd144f2..765f8ff92 100644 --- a/src/haxelib/server/Repo.hx +++ b/src/haxelib/server/Repo.hx @@ -111,6 +111,12 @@ class Repo implements SiteApi { } public function register( name : String, pass : String, mail : String, fullname : String ) : Void { + #if (haxelib_api_version == "3.0") + throw "Outdated client\n\n" + + "Due to security improvements to Haxelib, account registrations from old haxelib clients\n" + + "have been disabled. To register an account, please first update your client, using:\n\n" + + "\thaxelib install haxelib\n"; + #end if( name.length < 3 ) throw "User name must be at least 3 characters"; if( !Data.alphanum.match(name) ) @@ -145,6 +151,13 @@ class Repo implements SiteApi { } public function checkPassword( user : String, pass : String ) : Bool { + #if (haxelib_api_version == "3.0") + // this is used only when submitting + throw "Outdated client\n\n" + + "Due to security improvements to Haxelib's api, library submissions from old Haxelib clients\n" + + "have been disabled. To submit a library, please first update your Haxelib client, using:\n\n" + + "\thaxelib install haxelib\n"; + #end var u = User.manager.search({ name : user }).first(); return u != null && u.pass == pass; } @@ -154,6 +167,12 @@ class Repo implements SiteApi { } public function processSubmit( id : String, user : String, pass : String ) : String { + #if (haxelib_api_version == "3.0") + throw "Outdated client\n\n" + + "Due to security improvements to Haxelib's api, library submissions from old Haxelib clients\n" + + "have been disabled. To submit a library, please first update your Haxelib client, using:\n\n" + + "\thaxelib install haxelib\n"; + #end var tmpFile = Path.join([TMP_DIR_NAME, Std.parseInt(id)+".tmp"]); return FileStorage.instance.readFile( tmpFile, From 2e91d62d2b477fb42794a52cf49386b641f3c03c Mon Sep 17 00:00:00 2001 From: tobil4sk Date: Mon, 22 Aug 2022 23:13:06 +0100 Subject: [PATCH 10/20] Update server code to use argon2id - Passwords are now hashed on the server, with a salt. - After the database update has run, for old accounts we are left with their old md5 hash rehashed using argon2id, so to verify their password, we hash first using md5 and then rehash with argon2id. --- src/haxelib/api/Connection.hx | 6 +++--- src/haxelib/client/Main.hx | 10 ++++------ src/haxelib/server/Repo.hx | 16 +++++++++++++--- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/haxelib/api/Connection.hx b/src/haxelib/api/Connection.hx index 245458d45..2b95b6fc5 100644 --- a/src/haxelib/api/Connection.hx +++ b/src/haxelib/api/Connection.hx @@ -523,9 +523,9 @@ class Connection { public static function getUserData(userName:String) return retry(data.site.user.bind(userName)); - /** Registers user with `name`, `encodedPassword`, `email`, and `fullname`. **/ - public static function register(name:String, encodedPassword:String, email:String, fullname:String) - return retry(data.site.register.bind(name, encodedPassword, email, fullname)); + /** Registers user with `name`, `password`, `email`, and `fullname`. **/ + public static function register(name:String, password:String, email:String, fullname:String) + return retry(data.site.register.bind(name, password, email, fullname)); /** Returns `true` if no user with `userName` exists yet. **/ public static function isNewUser(userName:String) diff --git a/src/haxelib/client/Main.hx b/src/haxelib/client/Main.hx index d138d58f1..d663e5baf 100644 --- a/src/haxelib/client/Main.hx +++ b/src/haxelib/client/Main.hx @@ -21,7 +21,6 @@ */ package haxelib.client; -import haxe.crypto.Md5; import haxe.iterators.ArrayIterator; import sys.FileSystem; @@ -358,9 +357,8 @@ class Main { final pass2 = getSecretArgument("Confirm"); if( pass != pass2 ) throw "Password does not match"; - final encodedPassword = Md5.encode(pass); - Connection.register(name, encodedPassword, email, fullname); - return encodedPassword; + Connection.register(name, pass, email, fullname); + return pass; } #if neko @@ -396,13 +394,13 @@ class Main { #end function readPassword(user:String, prompt = "Password"):String { - var password = Md5.encode(getSecretArgument(prompt)); + var password = getSecretArgument(prompt); var attempts = 5; while (!Connection.checkPassword(user, password)) { Cli.print('Invalid password for $user'); if (--attempts == 0) throw 'Failed to input correct password'; - password = Md5.encode(getSecretArgument('$prompt ($attempts more attempt${attempts == 1 ? "" : "s"})')); + password = getSecretArgument('$prompt ($attempts more attempt${attempts == 1 ? "" : "s"})'); } return password; } diff --git a/src/haxelib/server/Repo.hx b/src/haxelib/server/Repo.hx index 765f8ff92..30bd03ef1 100644 --- a/src/haxelib/server/Repo.hx +++ b/src/haxelib/server/Repo.hx @@ -110,6 +110,12 @@ class Repo implements SiteApi { }; } + static inline function setPassword(user:User, password:String) { + user.salt = Hashing.generateSalt(); + user.pass = Hashing.hash(password, user.salt); + user.hashmethod = Argon2id; + } + public function register( name : String, pass : String, mail : String, fullname : String ) : Void { #if (haxelib_api_version == "3.0") throw "Outdated client\n\n" @@ -130,9 +136,9 @@ class Repo implements SiteApi { var u = new User(); u.name = name; - u.pass = pass; u.email = mail; u.fullname = fullname; + setPassword(u,pass); u.insert(); } @@ -150,6 +156,10 @@ class Repo implements SiteApi { throw "User '"+user+"' is not a developer of project '"+prj+"'"; } + static inline function verifyPassword(user:User, password:String):Bool { + return Hashing.verify(user.pass, password, user.hashmethod); + } + public function checkPassword( user : String, pass : String ) : Bool { #if (haxelib_api_version == "3.0") // this is used only when submitting @@ -159,7 +169,7 @@ class Repo implements SiteApi { + "\thaxelib install haxelib\n"; #end var u = User.manager.search({ name : user }).first(); - return u != null && u.pass == pass; + return u != null && verifyPassword(u,pass); } public function getSubmitId() : String { @@ -183,7 +193,7 @@ class Repo implements SiteApi { var infos = Data.readDataFromZip(zip,CheckData); var u = User.manager.search({ name : user }).first(); - if( u == null || u.pass != pass ) + if( u == null || !verifyPassword(u,pass) ) throw "Invalid username or password"; // spam filter From b76cc59d08d39d95dca6adbeac6d7563934c222d Mon Sep 17 00:00:00 2001 From: tobil4sk Date: Mon, 22 Aug 2022 23:17:07 +0100 Subject: [PATCH 11/20] Migrate hash to argon2id when a user logs in This way we can gradually remove the rehashed md5 hashes --- src/haxelib/server/Repo.hx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/haxelib/server/Repo.hx b/src/haxelib/server/Repo.hx index 30bd03ef1..e6d03182f 100644 --- a/src/haxelib/server/Repo.hx +++ b/src/haxelib/server/Repo.hx @@ -157,7 +157,14 @@ class Repo implements SiteApi { } static inline function verifyPassword(user:User, password:String):Bool { - return Hashing.verify(user.pass, password, user.hashmethod); + var matched = Hashing.verify(user.pass, password, user.hashmethod); + if (matched && user.hashmethod == Md5) { + // update to argon2id properly + user.pass = Hashing.hash(password, user.salt); + user.hashmethod = Argon2id; + user.update(); + } + return matched; } public function checkPassword( user : String, pass : String ) : Bool { From d19e38f84e5e04df2d185e5a243f0e946985576c Mon Sep 17 00:00:00 2001 From: tobil4sk Date: Tue, 23 Aug 2022 00:08:11 +0100 Subject: [PATCH 12/20] [test] Add tests for hash method update on login --- test/IntegrationTests.hx | 1 + test/tests/integration/TestPasswords.hx | 51 +++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 test/tests/integration/TestPasswords.hx diff --git a/test/IntegrationTests.hx b/test/IntegrationTests.hx index 69987a21c..01d1f3b66 100644 --- a/test/IntegrationTests.hx +++ b/test/IntegrationTests.hx @@ -251,6 +251,7 @@ class IntegrationTests extends TestBase { runner.add(new tests.integration.TestMisc()); runner.add(new tests.integration.TestFixRepo()); runner.add(new tests.integration.TestServerDatabaseUpdate()); + runner.add(new tests.integration.TestPasswords()); final success = runner.run(); diff --git a/test/tests/integration/TestPasswords.hx b/test/tests/integration/TestPasswords.hx new file mode 100644 index 000000000..e0261ec48 --- /dev/null +++ b/test/tests/integration/TestPasswords.hx @@ -0,0 +1,51 @@ +package tests.integration; + +import haxelib.server.Hashing; +import haxelib.server.SiteDb; + +class TestPasswords extends IntegrationTests { + + /** + Simulates an old user account whose md5 hash was rehashed with argon2id. + **/ + function createOldUserAccount(data:{user:String, email:String, fullname:String, pw:String}) { + SiteDb.init(); + final user = new User(); + user.name = data.user; + user.fullname = data.fullname; + user.email = data.email; + final salt = Hashing.generateSalt(); + user.pass = Hashing.hash(haxe.crypto.Md5.encode(data.pw), salt); + user.salt = salt; + user.hashmethod = Md5; + user.insert(); + SiteDb.cleanup(); + } + + function getUser(name:String) { + SiteDb.init(); + final user = User.manager.search($name == name).first(); + SiteDb.cleanup(); + return user; + } + + public function testHashUpdate() { + createOldUserAccount(bar); + + // submitting should work with the password + final r = haxelib([ + "submit", + Path.join([IntegrationTests.projectRoot, "test/libraries/libBar.zip"]), + bar.pw + ]).result(); + assertSuccess(r); + + // after submission, should have updated to new hash properly + final user = getUser(bar.user); + + // hash method should be updated, as well as the hash itself + assertEquals(Argon2id, user.hashmethod); + assertEquals(Hashing.hash(bar.pw, user.salt), user.pass); + } + +} From 921476ec5ab3d2486fd5fde4c2bdb2fd7dfcafed Mon Sep 17 00:00:00 2001 From: tobil4sk Date: Wed, 24 Aug 2022 00:53:55 +0100 Subject: [PATCH 13/20] [test] Ensure failed login doesn't update hash --- test/tests/integration/TestPasswords.hx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/tests/integration/TestPasswords.hx b/test/tests/integration/TestPasswords.hx index e0261ec48..3cda2b5a7 100644 --- a/test/tests/integration/TestPasswords.hx +++ b/test/tests/integration/TestPasswords.hx @@ -48,4 +48,23 @@ class TestPasswords extends IntegrationTests { assertEquals(Hashing.hash(bar.pw, user.salt), user.pass); } + public function testFailedSubmit() { + createOldUserAccount(bar); + + // attempting to submit with incorrect password should make no difference + final r = haxelib([ + "submit", + Path.join([IntegrationTests.projectRoot, "test/libraries/libBar.zip"]), + ],"incorrect password\nincorrect password\nincorrect password\nincorrect password\nincorrect password\n").result(); + assertFail(r); + assertEquals("Error: Failed to input correct password", r.err.trim()); + + // after failed submission, the account should not have changed + final user = getUser(bar.user); + + // hash method and hash should remain the same + assertEquals(Md5, user.hashmethod); + assertEquals(Hashing.hash(haxe.crypto.Md5.encode(bar.pw), user.salt), user.pass); + } + } From aee9a634f9c364dc83706a83dc272ad920ae3a6a Mon Sep 17 00:00:00 2001 From: tobil4sk Date: Tue, 4 Oct 2022 23:55:58 +0100 Subject: [PATCH 14/20] [test] Ensure that password works after hash update --- test/tests/integration/TestPasswords.hx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/tests/integration/TestPasswords.hx b/test/tests/integration/TestPasswords.hx index 3cda2b5a7..5758cf231 100644 --- a/test/tests/integration/TestPasswords.hx +++ b/test/tests/integration/TestPasswords.hx @@ -46,6 +46,14 @@ class TestPasswords extends IntegrationTests { // hash method should be updated, as well as the hash itself assertEquals(Argon2id, user.hashmethod); assertEquals(Hashing.hash(bar.pw, user.salt), user.pass); + + // submitting should continue to work with the same password + final r = haxelib([ + "submit", + Path.join([IntegrationTests.projectRoot, "test/libraries/libBar2.zip"]), + bar.pw + ]).result(); + assertSuccess(r); } public function testFailedSubmit() { From 0ffa12dab95b5d0e80dc520316aa5bc2951ec189 Mon Sep 17 00:00:00 2001 From: tobil4sk Date: Wed, 5 Oct 2022 22:11:18 +0100 Subject: [PATCH 15/20] Add test environment for skeema --- skeema/.skeema | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/skeema/.skeema b/skeema/.skeema index 079e97052..2a30bb2f4 100644 --- a/skeema/.skeema +++ b/skeema/.skeema @@ -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 From a3460af6f3a874349b7f3925cf25ef0020246bfa Mon Sep 17 00:00:00 2001 From: tobil4sk Date: Fri, 7 Oct 2022 18:49:41 +0100 Subject: [PATCH 16/20] Temporarily disable failing test This test results in a request to lib.haxe.org, which does not provide api version 4.0 yet --- test/tests/TestInstaller.hx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/tests/TestInstaller.hx b/test/tests/TestInstaller.hx index 804255249..fb9eace21 100644 --- a/test/tests/TestInstaller.hx +++ b/test/tests/TestInstaller.hx @@ -76,11 +76,12 @@ class TestInstaller extends TestBase { } public function testInstallHxmlWithBackend() { + // TODO: Re-enable after #564 is merged into master // inferred from -cpp/--cpp flags - installer.installFromHxml("cpp.hxml", (libs) -> { - assertEquals(1, Lambda.count(libs, (lib) -> lib.name == "hxcpp")); - return false; - }); + // installer.installFromHxml("cpp.hxml", (libs) -> { + // assertEquals(1, Lambda.count(libs, (lib) -> lib.name == "hxcpp")); + // return false; + // }); // specified explicitly // test for issue #511 From 2e96da8d89596f8fac4bc40499d3f23483b137b8 Mon Sep 17 00:00:00 2001 From: tobil4sk Date: Sat, 26 Nov 2022 19:24:53 +0000 Subject: [PATCH 17/20] Automatically add new table columns This will be done via sql statements for now, because I'm not sure how to run `skeema push` automatically. --- src/haxelib/server/Update.hx | 8 +++++++- test/tests/integration/TestServerDatabaseUpdate.hx | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/haxelib/server/Update.hx b/src/haxelib/server/Update.hx index 3130f5c9c..86650eb82 100644 --- a/src/haxelib/server/Update.hx +++ b/src/haxelib/server/Update.hx @@ -1,6 +1,5 @@ package haxelib.server; -import sys.db.TableCreate; import haxelib.server.SiteDb; /** @@ -32,6 +31,13 @@ class Update { } static function rehashPasswords() { + // add missing columns first + sys.db.Manager.cnx.request(" + ALTER TABLE User + ADD COLUMN salt binary(32) NOT NULL, + ADD COLUMN hashmethod mediumtext NOT NULL; + "); + // script used to update password hashes from md5 to md5 rehashed with argon2id var users = User.manager.all(); diff --git a/test/tests/integration/TestServerDatabaseUpdate.hx b/test/tests/integration/TestServerDatabaseUpdate.hx index 5706a9e23..ca62e7bfb 100644 --- a/test/tests/integration/TestServerDatabaseUpdate.hx +++ b/test/tests/integration/TestServerDatabaseUpdate.hx @@ -1,6 +1,5 @@ package tests.integration; -import haxe.io.Bytes; import haxelib.server.Update; import haxelib.server.Hashing; import haxelib.server.SiteDb; @@ -39,6 +38,11 @@ class TestServerDatabaseUpdate extends IntegrationTests { // ignore salt and hashmethod user.insert(); } + sys.db.Manager.cnx.request(" + ALTER TABLE User + DROP COLUMN salt, + DROP COLUMN hashmethod; + "); } function testUpdate() { From 894950a5195409c179be91800ec03aafb89cd082 Mon Sep 17 00:00:00 2001 From: tobil4sk Date: Sat, 26 Nov 2022 19:26:23 +0000 Subject: [PATCH 18/20] Do not run updates on fresh databases --- src/haxelib/server/SiteDb.hx | 17 ++++++++++++++--- src/haxelib/server/Update.hx | 9 +++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/haxelib/server/SiteDb.hx b/src/haxelib/server/SiteDb.hx index 5e1e07101..d04779d8f 100644 --- a/src/haxelib/server/SiteDb.hx +++ b/src/haxelib/server/SiteDb.hx @@ -156,11 +156,22 @@ class SiteDb { Developer.manager, Meta.manager ]; - for (m in managers) - if (!TableCreate.exists(m)) + + var hasOldTables = false; + + for (m in managers) { + if (TableCreate.exists(m)) { + hasOldTables = true; + } else { TableCreate.create(m); + } + } - Update.runNeededUpdates(); + if (hasOldTables) { + Update.runNeededUpdates(); + } else { + Update.setupFresh(); + } } static public function cleanup() { diff --git a/src/haxelib/server/Update.hx b/src/haxelib/server/Update.hx index 86650eb82..7d8bf0e3b 100644 --- a/src/haxelib/server/Update.hx +++ b/src/haxelib/server/Update.hx @@ -30,6 +30,15 @@ class Update { meta.update(); } + /** + Sets up a fresh database + **/ + public static function setupFresh() { + var meta = new Meta(); + meta.dbVersion = CURRENT_VERSION; + meta.insert(); + } + static function rehashPasswords() { // add missing columns first sys.db.Manager.cnx.request(" From fd4990ef20c2956fa41b6ced6060b6a4984bba15 Mon Sep 17 00:00:00 2001 From: tobil4sk Date: Mon, 28 Nov 2022 19:49:47 +0000 Subject: [PATCH 19/20] Add missing file close --- src/haxelib/server/Hashing.hx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/haxelib/server/Hashing.hx b/src/haxelib/server/Hashing.hx index 154bec66d..cbbc91e1a 100644 --- a/src/haxelib/server/Hashing.hx +++ b/src/haxelib/server/Hashing.hx @@ -14,7 +14,9 @@ class Hashing { public static function generateSalt():haxe.io.Bytes { // currently only works on Linux var randomFile = sys.io.File.read("/dev/random"); - return randomFile.read(32); + var salt = randomFile.read(32); + randomFile.close(); + return salt; } /** @@ -36,6 +38,6 @@ class Hashing { argon2.Argon2id.verify(hash, md5); case Argon2id: argon2.Argon2id.verify(hash, password); - } + }; } } From e3b379cfe9be69f562862fb85739ebfea0e9c59b Mon Sep 17 00:00:00 2001 From: tobil4sk Date: Tue, 29 Nov 2022 14:54:10 +0000 Subject: [PATCH 20/20] Use urandom for salt generation --- src/haxelib/server/Hashing.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/haxelib/server/Hashing.hx b/src/haxelib/server/Hashing.hx index cbbc91e1a..af3babe7e 100644 --- a/src/haxelib/server/Hashing.hx +++ b/src/haxelib/server/Hashing.hx @@ -13,7 +13,7 @@ class Hashing { **/ public static function generateSalt():haxe.io.Bytes { // currently only works on Linux - var randomFile = sys.io.File.read("/dev/random"); + var randomFile = sys.io.File.read("/dev/urandom"); var salt = randomFile.read(32); randomFile.close(); return salt;