diff --git a/ci/install-deps.sh b/ci/install-deps.sh index 75d918b..9a7569a 100755 --- a/ci/install-deps.sh +++ b/ci/install-deps.sh @@ -12,7 +12,7 @@ sudo systemctl start mysql.service sudo apt-get update sudo apt-get install -y intltool libarchive-dev libcurl4-openssl-dev libevent-dev \ libfuse-dev libglib2.0-dev libjansson-dev libmysqlclient-dev libonig-dev \ -sqlite3 libsqlite3-dev libtool net-tools uuid-dev valac +sqlite3 libsqlite3-dev libtool net-tools uuid-dev valac libargon2-dev sudo systemctl start mysql.service pip install -r requirements.txt diff --git a/common/Makefile.am b/common/Makefile.am index 3d2b631..d70be78 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -7,6 +7,7 @@ proc_headers = \ noinst_HEADERS = \ diff-simple.h \ seafile-crypt.h \ + password-hash.h \ common.h \ branch-mgr.h \ fs-mgr.h \ diff --git a/common/commit-mgr.c b/common/commit-mgr.c index 4ba3e4f..6901e6e 100644 --- a/common/commit-mgr.c +++ b/common/commit-mgr.c @@ -170,6 +170,9 @@ seaf_commit_free (SeafCommit *commit) g_free (commit->client_version); g_free (commit->magic); g_free (commit->random_key); + g_free (commit->pwd_hash); + g_free (commit->pwd_hash_algo); + g_free (commit->pwd_hash_params); g_free (commit); } @@ -633,12 +636,18 @@ commit_to_json_object (SeafCommit *commit) if (commit->encrypted) { json_object_set_int_member (object, "enc_version", commit->enc_version); - if (commit->enc_version >= 1) + // If pwd_hash is set, the magic field is no longer included in the commit of the newly created repo. + if (commit->enc_version >= 1 && !commit->pwd_hash) json_object_set_string_member (object, "magic", commit->magic); if (commit->enc_version >= 2) json_object_set_string_member (object, "key", commit->random_key); if (commit->enc_version >= 3) json_object_set_string_member (object, "salt", commit->salt); + if (commit->pwd_hash) { + json_object_set_string_member (object, "pwd_hash", commit->pwd_hash); + json_object_set_string_member (object, "pwd_hash_algo", commit->pwd_hash_algo); + json_object_set_string_member (object, "pwd_hash_params", commit->pwd_hash_params); + } } if (commit->no_local_history) json_object_set_int_member (object, "no_local_history", 1); @@ -675,6 +684,9 @@ commit_from_json_object (const char *commit_id, json_t *object) const char *magic = NULL; const char *random_key = NULL; const char *salt = NULL; + const char *pwd_hash = NULL; + const char *pwd_hash_algo = NULL; + const char *pwd_hash_params = NULL; int no_local_history = 0; int version = 0; int conflict = 0, new_merge = 0; @@ -709,6 +721,9 @@ commit_from_json_object (const char *commit_id, json_t *object) && json_object_has_member (object, "enc_version")) { enc_version = json_object_get_int_member (object, "enc_version"); magic = json_object_get_string_member (object, "magic"); + pwd_hash = json_object_get_string_member (object, "pwd_hash"); + pwd_hash_algo = json_object_get_string_member (object, "pwd_hash_algo"); + pwd_hash_params = json_object_get_string_member (object, "pwd_hash_params"); } if (enc_version >= 2) @@ -739,6 +754,10 @@ commit_from_json_object (const char *commit_id, json_t *object) (second_parent_id && !is_object_id_valid(second_parent_id))) return commit; + // If pwd_hash is set, the magic field is no longer included in the commit of the newly created repo. + if (!magic) + magic = pwd_hash; + switch (enc_version) { case 0: break; @@ -794,12 +813,17 @@ commit_from_json_object (const char *commit_id, json_t *object) if (commit->encrypted) { commit->enc_version = enc_version; - if (enc_version >= 1) + if (enc_version >= 1 && !pwd_hash) commit->magic = g_strdup(magic); if (enc_version >= 2) commit->random_key = g_strdup (random_key); if (enc_version >= 3) commit->salt = g_strdup(salt); + if (pwd_hash) { + commit->pwd_hash = g_strdup (pwd_hash); + commit->pwd_hash_algo = g_strdup (pwd_hash_algo); + commit->pwd_hash_params = g_strdup (pwd_hash_params); + } } if (no_local_history) commit->no_local_history = TRUE; diff --git a/common/commit-mgr.h b/common/commit-mgr.h index 2784230..69de19f 100644 --- a/common/commit-mgr.h +++ b/common/commit-mgr.h @@ -36,6 +36,9 @@ struct _SeafCommit { char *magic; char *random_key; char *salt; + char *pwd_hash; + char *pwd_hash_algo; + char *pwd_hash_params; gboolean no_local_history; int version; diff --git a/common/password-hash.c b/common/password-hash.c new file mode 100644 index 0000000..b0cd13e --- /dev/null +++ b/common/password-hash.c @@ -0,0 +1,167 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#include +#include +#include +#include "password-hash.h" +#include "seafile-crypt.h" +#include + +#include "utils.h" +#include "log.h" + +// pbkdf2 +typedef struct Pbkdf2Params { + int iteration; +} Pbkdf2Params; + +static Pbkdf2Params * +parse_pbkdf2_sha256_params (const char *params_str) +{ + Pbkdf2Params *params = NULL; + if (!params_str) { + params = g_new0 (Pbkdf2Params, 1); + params->iteration = 1000; + return params; + } + int iteration; + iteration = atoi (params_str); + if (iteration <= 0) { + iteration = 1000; + } + + params = g_new0 (Pbkdf2Params, 1); + params->iteration = iteration; + return params; +} + +static int +pbkdf2_sha256_derive_key (const char *data_in, int in_len, + const char *salt, + Pbkdf2Params *params, + unsigned char *key) +{ + int iteration = params->iteration; + + unsigned char salt_bin[32]; + hex_to_rawdata (salt, salt_bin, 32); + + PKCS5_PBKDF2_HMAC (data_in, in_len, + salt_bin, sizeof(salt_bin), + iteration, + EVP_sha256(), + 32, key); + return 0; +} + +// argon2id +typedef struct Argon2idParams{ + gint64 time_cost; + gint64 memory_cost; + gint64 parallelism; +} Argon2idParams; + +// The arguments to argon2 are separated by commas. +// Example arguments format: +// 2,102400,8 +// The parameters are time_cost, memory_cost, parallelism from left to right. +static Argon2idParams * +parse_argon2id_params (const char *params_str) +{ + char **params; + Argon2idParams *argon2_params = g_new0 (Argon2idParams, 1); + if (params_str) + params = g_strsplit (params_str, ",", 3); + if (!params_str || g_strv_length(params) != 3) { + if (params_str) + g_strfreev (params); + argon2_params->time_cost = 2; // 2-pass computation + argon2_params->memory_cost = 102400; // 100 mebibytes memory usage + argon2_params->parallelism = 8; // number of threads and lanes + return argon2_params; + } + + char *p = NULL; + p = g_strstrip (params[0]); + argon2_params->time_cost = atoll (p); + if (argon2_params->time_cost <= 0) { + argon2_params->time_cost = 2; + } + + p = g_strstrip (params[1]); + argon2_params->memory_cost = atoll (p); + if (argon2_params->memory_cost <= 0) { + argon2_params->memory_cost = 102400; + } + + p = g_strstrip (params[2]); + argon2_params->parallelism = atoll (p); + if (argon2_params->parallelism <= 0) { + argon2_params->parallelism = 8; + } + + g_strfreev (params); + return argon2_params; +} + +static int +argon2id_derive_key (const char *data_in, int in_len, + const char *salt, + Argon2idParams *params, + unsigned char *key) +{ + unsigned char salt_bin[32]; + hex_to_rawdata (salt, salt_bin, 32); + + argon2id_hash_raw(params->time_cost, params->memory_cost, params->parallelism, + data_in, in_len, + salt_bin, sizeof(salt_bin), + key, 32); + + return 0; +} + +// parse_pwd_hash_params is used to parse default pwd hash algorithms. +void +parse_pwd_hash_params (const char *algo, const char *params_str, PwdHashParams *params) +{ + if (g_strcmp0 (algo, PWD_HASH_PDKDF2) == 0) { + params->algo = g_strdup (PWD_HASH_PDKDF2); + if (params_str) + params->params_str = g_strdup (params_str); + else + params->params_str = g_strdup ("1000"); + } else if (g_strcmp0 (algo, PWD_HASH_ARGON2ID) == 0) { + params->algo = g_strdup (PWD_HASH_ARGON2ID); + if (params_str) + params->params_str = g_strdup (params_str); + else + params->params_str = g_strdup ("2,102400,8"); + } else { + params->algo = NULL; + } + + seaf_message ("password hash algorithms: %s, params: %s\n ", params->algo, params->params_str); +} + +int +pwd_hash_derive_key (const char *data_in, int in_len, + const char *salt, + const char *algo, const char *params_str, + unsigned char *key) +{ + int ret = 0; + if (g_strcmp0 (algo, PWD_HASH_ARGON2ID) == 0) { + Argon2idParams *algo_params = parse_argon2id_params (params_str); + ret = argon2id_derive_key (data_in, in_len, + salt, algo_params, key); + g_free (algo_params); + return ret; + } else { + Pbkdf2Params *algo_params = parse_pbkdf2_sha256_params (params_str); + ret = pbkdf2_sha256_derive_key (data_in, in_len, + salt, algo_params, key); + g_free (algo_params); + return ret; + } +} diff --git a/common/password-hash.h b/common/password-hash.h new file mode 100644 index 0000000..8fc5d81 --- /dev/null +++ b/common/password-hash.h @@ -0,0 +1,23 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +#ifndef _PASSWORD_HASH_H +#define _PASSWORD_HASH_H + +#define PWD_HASH_PDKDF2 "pbkdf2_sha256" +#define PWD_HASH_ARGON2ID "argon2id" + +typedef struct _PwdHashParams { + char *algo; + char *params_str; +} PwdHashParams; + +void + parse_pwd_hash_params (const char *algo, const char *params_str, PwdHashParams *params); + +int +pwd_hash_derive_key (const char *data_in, int in_len, + const char *repo_salt, + const char *algo, const char *params_str, + unsigned char *key); + +#endif diff --git a/common/rpc-service.c b/common/rpc-service.c index 239f2be..cdcc77e 100644 --- a/common/rpc-service.c +++ b/common/rpc-service.c @@ -55,6 +55,8 @@ convert_repo (SeafRepo *r) g_object_set (repo, "id", r->id, "name", r->name, "desc", r->desc, "encrypted", r->encrypted, "magic", r->magic, "enc_version", r->enc_version, + "pwd_hash", r->pwd_hash, + "pwd_hash_algo", r->pwd_hash_algo, "pwd_hash_params", r->pwd_hash_params, "head_cmmt_id", r->head ? r->head->commit_id : NULL, "root", r->root_id, "version", r->version, "last_modify", r->last_modify, @@ -705,25 +707,47 @@ seafile_generate_magic_and_random_key(int enc_version, gchar salt[65] = {0}; gchar magic[65] = {0}; + gchar pwd_hash[65] = {0}; gchar random_key[97] = {0}; if (enc_version >= 3 && seafile_generate_repo_salt (salt) < 0) { return NULL; } - seafile_generate_magic (enc_version, repo_id, passwd, salt, magic); + const char *algo = NULL; + const char *params = NULL; + algo = seafile_crypt_get_default_pwd_hash_algo (); + params = seafile_crypt_get_default_pwd_hash_params (); + + if (algo != NULL) { + seafile_generate_pwd_hash (repo_id, passwd, salt, algo, params, pwd_hash); + } else { + seafile_generate_magic (enc_version, repo_id, passwd, salt, magic); + } if (seafile_generate_random_key (passwd, enc_version, salt, random_key) < 0) { return NULL; } SeafileEncryptionInfo *sinfo; - sinfo = g_object_new (SEAFILE_TYPE_ENCRYPTION_INFO, - "repo_id", repo_id, - "passwd", passwd, - "enc_version", enc_version, - "magic", magic, - "random_key", random_key, - NULL); + if (algo != NULL) { + sinfo = g_object_new (SEAFILE_TYPE_ENCRYPTION_INFO, + "repo_id", repo_id, + "passwd", passwd, + "enc_version", enc_version, + "pwd_hash", pwd_hash, + "pwd_hash_algo", algo, + "pwd_hash_params", params, + "random_key", random_key, + NULL); + } else { + sinfo = g_object_new (SEAFILE_TYPE_ENCRYPTION_INFO, + "repo_id", repo_id, + "passwd", passwd, + "enc_version", enc_version, + "magic", magic, + "random_key", random_key, + NULL); + } if (enc_version >= 3) g_object_set (sinfo, "salt", salt, NULL); @@ -1040,10 +1064,18 @@ retry: return -1; } - if (seafile_verify_repo_passwd (repo_id, old_passwd, repo->magic, - repo->enc_version, repo->salt) < 0) { - g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Incorrect password"); - return -1; + if (repo->pwd_hash_algo) { + if (seafile_pwd_hash_verify_repo_passwd (repo_id, old_passwd, repo->salt, + repo->pwd_hash, repo->pwd_hash_algo, repo->pwd_hash_params) < 0) { + g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Incorrect password"); + return -1; + } + } else { + if (seafile_verify_repo_passwd (repo_id, old_passwd, repo->magic, + repo->enc_version, repo->salt) < 0) { + g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Incorrect password"); + return -1; + } } parent = seaf_commit_manager_get_commit (seaf->commit_mgr, @@ -1056,9 +1088,15 @@ retry: goto out; } - char new_magic[65], new_random_key[97]; + char new_magic[65], new_pwd_hash[65], new_random_key[97]; - seafile_generate_magic (repo->enc_version, repo_id, new_passwd, repo->salt, new_magic); + if (repo->pwd_hash_algo) { + seafile_generate_pwd_hash (repo_id, new_passwd, repo->salt, + repo->pwd_hash_algo, repo->pwd_hash_params, new_pwd_hash); + } else { + seafile_generate_magic (repo->enc_version, repo_id, new_passwd, repo->salt, + new_magic); + } if (seafile_update_random_key (old_passwd, repo->random_key, new_passwd, new_random_key, repo->enc_version, repo->salt) < 0) { @@ -1066,7 +1104,11 @@ retry: goto out; } - memcpy (repo->magic, new_magic, 64); + if (repo->pwd_hash_algo) { + memcpy (repo->pwd_hash, new_pwd_hash, 64); + } else { + memcpy (repo->magic, new_magic, 64); + } memcpy (repo->random_key, new_random_key, 96); commit = seaf_commit_new (NULL, @@ -3064,6 +3106,9 @@ seafile_create_enc_repo (const char *repo_id, const char *random_key, const char *salt, int enc_version, + const char *pwd_hash, + const char *pwd_hash_algo, + const char *pwd_hash_params, GError **error) { if (!repo_id || !repo_name || !repo_desc || !owner_email) { @@ -3076,7 +3121,9 @@ seafile_create_enc_repo (const char *repo_id, ret = seaf_repo_manager_create_enc_repo (seaf->repo_mgr, repo_id, repo_name, repo_desc, owner_email, - magic, random_key, salt, enc_version, + magic, random_key, salt, + enc_version, + pwd_hash, pwd_hash_algo, pwd_hash_params, error); return ret; } diff --git a/common/seafile-crypt.c b/common/seafile-crypt.c index 4dfef7c..a6a4091 100644 --- a/common/seafile-crypt.c +++ b/common/seafile-crypt.c @@ -3,6 +3,7 @@ #include #include #include "seafile-crypt.h" +#include "password-hash.h" #include #include "utils.h" @@ -22,6 +23,14 @@ /* Should generate random salt for each repo. */ static unsigned char salt[8] = { 0xda, 0x90, 0x45, 0xc3, 0x06, 0xc7, 0xcc, 0x26 }; +static PwdHashParams default_params; + +void +seafile_crypt_init (const char *algo, const char *params) +{ + parse_pwd_hash_params (algo, params, &default_params); +} + SeafileCrypt * seafile_crypt_new (int version, unsigned char *key, unsigned char *iv) { @@ -35,6 +44,18 @@ seafile_crypt_new (int version, unsigned char *key, unsigned char *iv) return crypt; } +const char * +seafile_crypt_get_default_pwd_hash_algo () +{ + return default_params.algo; +} + +const char * +seafile_crypt_get_default_pwd_hash_params () +{ + return default_params.params_str; +} + int seafile_derive_key (const char *data_in, int in_len, int version, const char *repo_salt, @@ -156,6 +177,29 @@ seafile_generate_magic (int version, const char *repo_id, rawdata_to_hex (key, magic, 32); } +void +seafile_generate_pwd_hash (const char *repo_id, + const char *passwd, + const char *repo_salt, + const char *algo, + const char *params_str, + char *pwd_hash) +{ + GString *buf = g_string_new (NULL); + unsigned char key[32]; + + /* Compute a "pwd_hash" string from repo_id and passwd. + * This is used to verify the password given by user before decrypting + * data. + */ + g_string_append_printf (buf, "%s%s", repo_id, passwd); + + pwd_hash_derive_key (buf->str, buf->len, repo_salt, algo, params_str, key); + + g_string_free (buf, TRUE); + rawdata_to_hex (key, pwd_hash, 32); +} + int seafile_verify_repo_passwd (const char *repo_id, const char *passwd, @@ -189,6 +233,31 @@ seafile_verify_repo_passwd (const char *repo_id, return -1; } +int +seafile_pwd_hash_verify_repo_passwd (const char *repo_id, + const char *passwd, + const char *repo_salt, + const char *pwd_hash, + const char *algo, + const char *params_str) +{ + GString *buf = g_string_new (NULL); + unsigned char key[32]; + char hex[65]; + + g_string_append_printf (buf, "%s%s", repo_id, passwd); + + pwd_hash_derive_key (buf->str, buf->len, repo_salt, algo, params_str, key); + + g_string_free (buf, TRUE); + rawdata_to_hex (key, hex, 32); + + if (g_strcmp0 (hex, pwd_hash) == 0) + return 0; + else + return -1; +} + int seafile_decrypt_repo_enc_key (int enc_version, const char *passwd, const char *random_key, diff --git a/common/seafile-crypt.h b/common/seafile-crypt.h index 1d2ce4a..252bd96 100644 --- a/common/seafile-crypt.h +++ b/common/seafile-crypt.h @@ -27,9 +27,18 @@ struct SeafileCrypt { typedef struct SeafileCrypt SeafileCrypt; +void +seafile_crypt_init (const char *algo, const char *params); + SeafileCrypt * seafile_crypt_new (int version, unsigned char *key, unsigned char *iv); +const char * +seafile_crypt_get_default_pwd_hash_algo (); + +const char * +seafile_crypt_get_default_pwd_hash_params (); + /* Derive key and iv used by AES encryption from @data_in. key and iv is 16 bytes for version 1, and 32 bytes for version 2. @@ -77,6 +86,14 @@ seafile_generate_magic (int version, const char *repo_id, const char *repo_salt, char *magic); +void +seafile_generate_pwd_hash (const char *repo_id, + const char *passwd, + const char *repo_salt, + const char *algo, + const char *params_str, + char *pwd_hash); + int seafile_verify_repo_passwd (const char *repo_id, const char *passwd, @@ -84,6 +101,14 @@ seafile_verify_repo_passwd (const char *repo_id, int version, const char *repo_salt); +int +seafile_pwd_hash_verify_repo_passwd (const char *repo_id, + const char *passwd, + const char *repo_salt, + const char *pwd_hash, + const char *algo, + const char *params_str); + int seafile_decrypt_repo_enc_key (int enc_version, const char *passwd, const char *random_key, diff --git a/configure.ac b/configure.ac index d7acd85..2f30b83 100644 --- a/configure.ac +++ b/configure.ac @@ -274,6 +274,10 @@ PKG_CHECK_MODULES(JWT, [libjwt]) AC_SUBST(JWT_CFLAGS) AC_SUBST(JWT_LIBS) +PKG_CHECK_MODULES(ARGON2, [libargon2]) +AC_SUBST(ARGON2_CFLAGS) +AC_SUBST(ARGON2_LIBS) + if test x${compile_python} = xyes; then AM_PATH_PYTHON([2.6]) if test "$bwin32" = true; then diff --git a/fileserver/commitmgr/commitmgr.go b/fileserver/commitmgr/commitmgr.go index ed8c03b..34966d2 100644 --- a/fileserver/commitmgr/commitmgr.go +++ b/fileserver/commitmgr/commitmgr.go @@ -36,6 +36,9 @@ type Commit struct { Magic string `json:"magic,omitempty"` RandomKey string `json:"key,omitempty"` Salt string `json:"salt,omitempty"` + PwdHash string `json:"pwd_hash,omitempty"` + PwdHashAlgo string `json:"pwd_hash_algo,omitempty"` + PwdHashParams string `json:"pwd_hash_params,omitempty"` Version int `json:"version,omitempty"` Conflict int `json:"conflict,omitempty"` NewMerge int `json:"new_merge,omitempty"` diff --git a/fileserver/repomgr/repomgr.go b/fileserver/repomgr/repomgr.go index a617197..6bbdf83 100644 --- a/fileserver/repomgr/repomgr.go +++ b/fileserver/repomgr/repomgr.go @@ -37,12 +37,15 @@ type Repo struct { StoreID string // Encrypted repo info - IsEncrypted bool - EncVersion int - Magic string - RandomKey string - Salt string - Version int + IsEncrypted bool + EncVersion int + Magic string + RandomKey string + Salt string + PwdHash string + PwdHashAlgo string + PwdHashParams string + Version int } // VRepoInfo contains virtual repo information. @@ -132,20 +135,25 @@ func Get(id string) *Repo { if commit.Encrypted == "true" { repo.IsEncrypted = true repo.EncVersion = commit.EncVersion - if repo.EncVersion == 1 { + if repo.EncVersion == 1 && commit.PwdHash == "" { repo.Magic = commit.Magic } else if repo.EncVersion == 2 { - repo.Magic = commit.Magic repo.RandomKey = commit.RandomKey } else if repo.EncVersion == 3 { - repo.Magic = commit.Magic repo.RandomKey = commit.RandomKey repo.Salt = commit.Salt } else if repo.EncVersion == 4 { - repo.Magic = commit.Magic repo.RandomKey = commit.RandomKey repo.Salt = commit.Salt } + if repo.EncVersion >= 2 && commit.PwdHash == "" { + repo.Magic = commit.Magic + } + if commit.PwdHash != "" { + repo.PwdHash = commit.PwdHash + repo.PwdHashAlgo = commit.PwdHashAlgo + repo.PwdHashParams = commit.PwdHashParams + } } return repo @@ -158,20 +166,25 @@ func RepoToCommit(repo *Repo, commit *commitmgr.Commit) { if repo.IsEncrypted { commit.Encrypted = "true" commit.EncVersion = repo.EncVersion - if repo.EncVersion == 1 { + if repo.EncVersion == 1 && repo.PwdHash == "" { commit.Magic = repo.Magic } else if repo.EncVersion == 2 { - commit.Magic = repo.Magic commit.RandomKey = repo.RandomKey } else if repo.EncVersion == 3 { - commit.Magic = repo.Magic commit.RandomKey = repo.RandomKey commit.Salt = repo.Salt } else if repo.EncVersion == 4 { - commit.Magic = repo.Magic commit.RandomKey = repo.RandomKey commit.Salt = repo.Salt } + if repo.EncVersion >= 2 && repo.PwdHash == "" { + commit.Magic = repo.Magic + } + if repo.PwdHash != "" { + commit.PwdHash = repo.PwdHash + commit.PwdHashAlgo = repo.PwdHashAlgo + commit.PwdHashParams = repo.PwdHashParams + } } else { commit.Encrypted = "false" } @@ -260,6 +273,15 @@ func GetEx(id string) *Repo { repo.Magic = commit.Magic repo.RandomKey = commit.RandomKey repo.Salt = commit.Salt + } else if repo.EncVersion == 4 { + repo.Magic = commit.Magic + repo.RandomKey = commit.RandomKey + repo.Salt = commit.Salt + } + if commit.PwdHash != "" { + repo.PwdHash = commit.PwdHash + repo.PwdHashAlgo = commit.PwdHashAlgo + repo.PwdHashParams = commit.PwdHashParams } } diff --git a/fuse/Makefile.am b/fuse/Makefile.am index 3f3f176..d68df5c 100644 --- a/fuse/Makefile.am +++ b/fuse/Makefile.am @@ -36,11 +36,12 @@ seaf_fuse_SOURCES = seaf-fuse.c \ ../common/obj-store.c \ ../common/obj-backend-fs.c \ ../common/obj-backend-riak.c \ - ../common/seafile-crypt.c + ../common/seafile-crypt.c \ + ../common/password-hash.c seaf_fuse_LDADD = @GLIB2_LIBS@ @GOBJECT_LIBS@ @SSL_LIBS@ @LIB_RT@ @LIB_UUID@ \ -lsqlite3 @LIBEVENT_LIBS@ \ $(top_builddir)/common/cdc/libcdc.la \ @SEARPC_LIBS@ @JANSSON_LIBS@ @FUSE_LIBS@ @ZLIB_LIBS@ \ - @LDAP_LIBS@ @MYSQL_LIBS@ -lsqlite3 + @LDAP_LIBS@ @MYSQL_LIBS@ -lsqlite3 @ARGON2_LIBS@ diff --git a/include/seafile-rpc.h b/include/seafile-rpc.h index c9717b6..81d2f81 100644 --- a/include/seafile-rpc.h +++ b/include/seafile-rpc.h @@ -923,6 +923,9 @@ seafile_create_enc_repo (const char *repo_id, const char *random_key, const char *salt, int enc_version, + const char *pwd_hash, + const char *pwd_hash_algo, + const char *pwd_hash_params, GError **error); char * diff --git a/lib/repo.vala b/lib/repo.vala index 1b329c0..616da33 100644 --- a/lib/repo.vala +++ b/lib/repo.vala @@ -56,6 +56,9 @@ public class Repo : Object { public int enc_version { get; set; } public string random_key { get; set; } public string salt { get; set; } + public string pwd_hash { get; set; } + public string pwd_hash_algo { get; set; } + public string pwd_hash_params { get; set; } // Section 3: Client only information // Should be set for all client repo objects @@ -202,6 +205,9 @@ public class EncryptionInfo: Object { public string magic { get; set; } public string random_key { get; set; } public string salt { get; set; } + public string pwd_hash { get; set; } + public string pwd_hash_algo { get; set; } + public string pwd_hash_params { get; set; } } public class UserQuotaUsage: Object { diff --git a/lib/rpc_table.py b/lib/rpc_table.py index e10bb2f..3ee6347 100644 --- a/lib/rpc_table.py +++ b/lib/rpc_table.py @@ -68,6 +68,7 @@ func_table = [ [ "string", ["string", "string", "string", "string", "string", "string", "string", "int"] ], [ "string", ["string", "string", "string", "string", "string", "string", "string", "int64"] ], [ "string", ["string", "string", "string", "string", "string", "string", "string", "string", "string"] ], + [ "string", ["string", "string", "string", "string", "string", "string", "string", "int", "string", "string", "string"] ], [ "string", ["string", "int", "string", "string", "string", "string", "string", "string", "string", "string", "string", "string", "int", "string"] ], [ "string", ["string", "int", "string", "int", "int"] ], [ "string", ["string", "int", "string", "string", "string"] ], @@ -103,6 +104,7 @@ func_table = [ [ "object", ["string", "string", "string"] ], [ "object", ["string", "int", "string"] ], [ "object", ["int", "string", "string"] ], + [ "object", ["int", "string", "string", "string", "string"] ], [ "object", ["string", "string", "int", "int"] ], [ "object", ["string", "string", "string", "int"] ], [ "object", ["string", "string", "string", "string", "string", "string", "string", "int", "int"] ], diff --git a/python/seafile/rpcclient.py b/python/seafile/rpcclient.py index 6559c2d..54a0ad1 100644 --- a/python/seafile/rpcclient.py +++ b/python/seafile/rpcclient.py @@ -11,8 +11,8 @@ class SeafServerThreadedRpcClient(NamedPipeClient): pass create_repo = seafile_create_repo - @searpc_func("string", ["string", "string", "string", "string", "string", "string", "string", "int"]) - def seafile_create_enc_repo(repo_id, name, desc, owner_email, magic, random_key, salt, enc_version): + @searpc_func("string", ["string", "string", "string", "string", "string", "string", "string", "int", "string", "string", "string"]) + def seafile_create_enc_repo(repo_id, name, desc, owner_email, magic, random_key, salt, enc_version, pwd_hash, pwd_hash_algo, pwd_hash_params): pass create_enc_repo = seafile_create_enc_repo diff --git a/python/seaserv/api.py b/python/seaserv/api.py index ece5270..b4ba80b 100644 --- a/python/seaserv/api.py +++ b/python/seaserv/api.py @@ -89,8 +89,8 @@ class SeafileAPI(object): def create_repo(self, name, desc, username, passwd=None, enc_version=2, storage_id=None): return seafserv_threaded_rpc.create_repo(name, desc, username, passwd, enc_version) - def create_enc_repo(self, repo_id, name, desc, username, magic, random_key, salt, enc_version): - return seafserv_threaded_rpc.create_enc_repo(repo_id, name, desc, username, magic, random_key, salt, enc_version) + def create_enc_repo(self, repo_id, name, desc, username, magic, random_key, salt, enc_version, pwd_hash=None, pwd_hash_algo=None, pwd_hash_params=None): + return seafserv_threaded_rpc.create_enc_repo(repo_id, name, desc, username, magic, random_key, salt, enc_version, pwd_hash, pwd_hash_algo, pwd_hash_params) def get_repos_by_id_prefix(self, id_prefix, start=-1, limit=-1): """ diff --git a/server/Makefile.am b/server/Makefile.am index 88783c2..de1f15f 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -69,6 +69,7 @@ seaf_server_SOURCES = \ ../common/obj-store.c \ ../common/obj-backend-fs.c \ ../common/seafile-crypt.c \ + ../common/password-hash.c \ ../common/diff-simple.c \ ../common/mq-mgr.c \ ../common/user-mgr.c \ @@ -86,4 +87,4 @@ seaf_server_LDADD = $(top_builddir)/lib/libseafile_common.la \ @SEARPC_LIBS@ @JANSSON_LIBS@ ${LIB_WS32} @ZLIB_LIBS@ \ @LIBARCHIVE_LIBS@ @LIB_ICONV@ \ @LDAP_LIBS@ @MYSQL_LIBS@ -lsqlite3 \ - @CURL_LIBS@ @JWT_LIBS@ + @CURL_LIBS@ @JWT_LIBS@ @ARGON2_LIBS@ diff --git a/server/gc/Makefile.am b/server/gc/Makefile.am index e45a4cb..4ea9174 100644 --- a/server/gc/Makefile.am +++ b/server/gc/Makefile.am @@ -36,6 +36,7 @@ common_sources = \ ../../common/obj-store.c \ ../../common/obj-backend-fs.c \ ../../common/seafile-crypt.c \ + ../../common/password-hash.c \ ../../common/config-mgr.c seafserv_gc_SOURCES = \ @@ -48,7 +49,7 @@ seafserv_gc_LDADD = $(top_builddir)/common/cdc/libcdc.la \ $(top_builddir)/lib/libseafile_common.la \ @GLIB2_LIBS@ @GOBJECT_LIBS@ @SSL_LIBS@ @LIB_RT@ @LIB_UUID@ -lsqlite3 @LIBEVENT_LIBS@ \ @SEARPC_LIBS@ @JANSSON_LIBS@ ${LIB_WS32} @ZLIB_LIBS@ \ - @MYSQL_LIBS@ -lsqlite3 + @MYSQL_LIBS@ -lsqlite3 @ARGON2_LIBS@ seaf_fsck_SOURCES = \ seaf-fsck.c \ @@ -59,4 +60,4 @@ seaf_fsck_LDADD = $(top_builddir)/common/cdc/libcdc.la \ $(top_builddir)/lib/libseafile_common.la \ @GLIB2_LIBS@ @GOBJECT_LIBS@ @SSL_LIBS@ @LIB_RT@ @LIB_UUID@ -lsqlite3 @LIBEVENT_LIBS@ \ @SEARPC_LIBS@ @JANSSON_LIBS@ ${LIB_WS32} @ZLIB_LIBS@ \ - @MYSQL_LIBS@ -lsqlite3 + @MYSQL_LIBS@ -lsqlite3 @ARGON2_LIBS@ diff --git a/server/passwd-mgr.c b/server/passwd-mgr.c index d2c1709..36a68af 100644 --- a/server/passwd-mgr.c +++ b/server/passwd-mgr.c @@ -125,12 +125,23 @@ seaf_passwd_manager_set_passwd (SeafPasswdManager *mgr, return -1; } - if (seafile_verify_repo_passwd (repo->id, passwd, - repo->magic, repo->enc_version, repo->salt) < 0) { - seaf_repo_unref (repo); - g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, - "Incorrect password"); - return -1; + if (repo->pwd_hash_algo) { + if (seafile_pwd_hash_verify_repo_passwd (repo->id, passwd, repo->salt, + repo->pwd_hash, repo->pwd_hash_algo, repo->pwd_hash_params) < 0) { + seaf_repo_unref (repo); + g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, + "Incorrect password"); + return -1; + } + } else { + if (seafile_verify_repo_passwd (repo->id, passwd, + repo->magic, + repo->enc_version, repo->salt) < 0) { + seaf_repo_unref (repo); + g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, + "Incorrect password"); + return -1; + } } crypt_key = g_new0 (DecryptKey, 1); diff --git a/server/repo-mgr.c b/server/repo-mgr.c index 03acd42..08aa7b3 100644 --- a/server/repo-mgr.c +++ b/server/repo-mgr.c @@ -18,6 +18,7 @@ #include "fs-mgr.h" #include "seafile-error.h" #include "seafile-crypt.h" +#include "password-hash.h" #include "seaf-db.h" #include "seaf-utils.h" @@ -89,6 +90,8 @@ seaf_repo_free (SeafRepo *repo) if (repo->virtual_info) seaf_virtual_repo_info_free (repo->virtual_info); g_free (repo->last_modifier); + g_free (repo->pwd_hash_algo); + g_free (repo->pwd_hash_params); g_free (repo); } @@ -137,20 +140,25 @@ seaf_repo_from_commit (SeafRepo *repo, SeafCommit *commit) memcpy (repo->root_id, commit->root_id, 40); if (repo->encrypted) { repo->enc_version = commit->enc_version; - if (repo->enc_version == 1) + if (repo->enc_version == 1 && !commit->pwd_hash_algo) memcpy (repo->magic, commit->magic, 32); else if (repo->enc_version == 2) { - memcpy (repo->magic, commit->magic, 64); memcpy (repo->random_key, commit->random_key, 96); } else if (repo->enc_version == 3) { - memcpy (repo->magic, commit->magic, 64); memcpy (repo->random_key, commit->random_key, 96); memcpy (repo->salt, commit->salt, 64); } else if (repo->enc_version == 4) { - memcpy (repo->magic, commit->magic, 64); memcpy (repo->random_key, commit->random_key, 96); memcpy (repo->salt, commit->salt, 64); } + if (repo->enc_version >= 2 && !commit->pwd_hash_algo) { + memcpy (repo->magic, commit->magic, 64); + } + if (commit->pwd_hash_algo) { + memcpy (repo->pwd_hash, commit->pwd_hash, 64); + repo->pwd_hash_algo = g_strdup (commit->pwd_hash_algo); + repo->pwd_hash_params = g_strdup (commit->pwd_hash_params); + } } repo->no_local_history = commit->no_local_history; repo->version = commit->version; @@ -166,20 +174,25 @@ seaf_repo_to_commit (SeafRepo *repo, SeafCommit *commit) commit->repaired = repo->repaired; if (commit->encrypted) { commit->enc_version = repo->enc_version; - if (commit->enc_version == 1) + if (commit->enc_version == 1 && !repo->pwd_hash_algo) commit->magic = g_strdup (repo->magic); else if (commit->enc_version == 2) { - commit->magic = g_strdup (repo->magic); commit->random_key = g_strdup (repo->random_key); } else if (commit->enc_version == 3) { - commit->magic = g_strdup (repo->magic); commit->random_key = g_strdup (repo->random_key); commit->salt = g_strdup (repo->salt); } else if (commit->enc_version == 4) { - commit->magic = g_strdup (repo->magic); commit->random_key = g_strdup (repo->random_key); commit->salt = g_strdup (repo->salt); } + if (commit->enc_version >= 2 && !repo->pwd_hash_algo) { + commit->magic = g_strdup (repo->magic); + } + if (repo->pwd_hash_algo) { + commit->pwd_hash = g_strdup (repo->pwd_hash); + commit->pwd_hash_algo = g_strdup (repo->pwd_hash_algo); + commit->pwd_hash_params = g_strdup (repo->pwd_hash_params); + } } commit->no_local_history = repo->no_local_history; commit->version = repo->version; @@ -3728,16 +3741,37 @@ seaf_repo_manager_is_valid_filename (SeafRepoManager *mgr, return 1; } +typedef struct _RepoCryptCompat { + const char *magic; + const char *pwd_hash; + const char *pwd_hash_algo; + const char *pwd_hash_params; +} RepoCryptInfo; + +static +RepoCryptInfo * +repo_crypt_info_new (const char *magic, const char *pwd_hash, + const char *algo, const char *params) +{ + RepoCryptInfo *crypt_info = g_new0 (RepoCryptInfo, 1); + crypt_info->magic = magic; + crypt_info->pwd_hash = pwd_hash; + crypt_info->pwd_hash_algo = algo; + crypt_info->pwd_hash_params = params; + + return crypt_info; +} + static int create_repo_common (SeafRepoManager *mgr, const char *repo_id, const char *repo_name, const char *repo_desc, const char *user, - const char *magic, const char *random_key, const char *salt, int enc_version, + RepoCryptInfo *crypt_info, GError **error) { SeafRepo *repo = NULL; @@ -3751,9 +3785,27 @@ create_repo_common (SeafRepoManager *mgr, "Unsupported encryption version"); return -1; } + + if (crypt_info && crypt_info->pwd_hash_algo) { + if (g_strcmp0 (crypt_info->pwd_hash_algo, PWD_HASH_PDKDF2) != 0 && + g_strcmp0 (crypt_info->pwd_hash_algo, PWD_HASH_ARGON2ID) !=0) + { + seaf_warning ("Unsupported enc algothrims %s.\n", crypt_info->pwd_hash_algo); + g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, + "Unsupported encryption algothrims"); + return -1; + } + + if (!crypt_info->pwd_hash || strlen(crypt_info->pwd_hash) != 64) { + seaf_warning ("Bad pwd_hash.\n"); + g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, + "Bad pwd_hash"); + return -1; + } + } if (enc_version >= 2) { - if (!magic || strlen(magic) != 64) { + if (!crypt_info->pwd_hash_algo && (!crypt_info->magic || strlen(crypt_info->magic) != 64)) { seaf_warning ("Bad magic.\n"); g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_BAD_ARGS, "Bad magic"); @@ -3778,15 +3830,24 @@ create_repo_common (SeafRepoManager *mgr, repo = seaf_repo_new (repo_id, repo_name, repo_desc); repo->no_local_history = TRUE; + if (enc_version >= 2) { repo->encrypted = TRUE; repo->enc_version = enc_version; - memcpy (repo->magic, magic, 64); + if (!crypt_info->pwd_hash_algo) + memcpy (repo->magic, crypt_info->magic, 64); memcpy (repo->random_key, random_key, 96); } if (enc_version >= 3) memcpy (repo->salt, salt, 64); + if (crypt_info && crypt_info->pwd_hash_algo) { + // set pwd_hash fields here. + memcpy (repo->pwd_hash, crypt_info->pwd_hash, 64); + repo->pwd_hash_algo = g_strdup (crypt_info->pwd_hash_algo); + repo->pwd_hash_params = g_strdup (crypt_info->pwd_hash_params); + } + repo->version = CURRENT_REPO_VERSION; memcpy (repo->store_id, repo_id, 36); @@ -3851,7 +3912,9 @@ seaf_repo_manager_create_new_repo (SeafRepoManager *mgr, GError **error) { char *repo_id = NULL; - char salt[65], magic[65], random_key[97]; + char salt[65], magic[65], pwd_hash[65], random_key[97]; + const char *algo = seafile_crypt_get_default_pwd_hash_algo (); + const char *params = seafile_crypt_get_default_pwd_hash_params (); repo_id = gen_uuid (); @@ -3859,19 +3922,26 @@ seaf_repo_manager_create_new_repo (SeafRepoManager *mgr, if (seafile_generate_repo_salt (salt) < 0) { goto bad; } - seafile_generate_magic (enc_version, repo_id, passwd, salt, magic); + if (algo != NULL) { + seafile_generate_pwd_hash (repo_id, passwd, salt, algo, params, pwd_hash); + } else { + seafile_generate_magic (enc_version, repo_id, passwd, salt, magic); + } if (seafile_generate_random_key (passwd, enc_version, salt, random_key) < 0) { goto bad; } } int rc; - if (passwd) + if (passwd) { + RepoCryptInfo *crypt_info = repo_crypt_info_new (magic, pwd_hash, algo, params); rc = create_repo_common (mgr, repo_id, repo_name, repo_desc, owner_email, - magic, random_key, salt, enc_version, error); + random_key, salt, enc_version, crypt_info, error); + g_free (crypt_info); + } else rc = create_repo_common (mgr, repo_id, repo_name, repo_desc, owner_email, - NULL, NULL, NULL, -1, error); + NULL, NULL, -1, NULL, error); if (rc < 0) goto bad; @@ -3900,6 +3970,9 @@ seaf_repo_manager_create_enc_repo (SeafRepoManager *mgr, const char *random_key, const char *salt, int enc_version, + const char *pwd_hash, + const char *pwd_hash_algo, + const char *pwd_hash_params, GError **error) { if (!repo_id || !is_uuid_valid (repo_id)) { @@ -3916,9 +3989,13 @@ seaf_repo_manager_create_enc_repo (SeafRepoManager *mgr, return NULL; } + RepoCryptInfo *crypt_info = repo_crypt_info_new (magic, pwd_hash, pwd_hash_algo, pwd_hash_params); if (create_repo_common (mgr, repo_id, repo_name, repo_desc, owner_email, - magic, random_key, salt, enc_version, error) < 0) + random_key, salt, enc_version, crypt_info, error) < 0) { + g_free (crypt_info); return NULL; + } + g_free (crypt_info); if (seaf_repo_manager_set_repo_owner (mgr, repo_id, owner_email) < 0) { seaf_warning ("Failed to set repo owner.\n"); diff --git a/server/repo-mgr.h b/server/repo-mgr.h index 0be20fa..ec0a236 100644 --- a/server/repo-mgr.h +++ b/server/repo-mgr.h @@ -33,6 +33,9 @@ struct _SeafRepo { gboolean encrypted; int enc_version; gchar magic[65]; /* hash(repo_id + passwd), key stretched. */ + gchar pwd_hash[65]; /* hash(repo_id + passwd), key stretched. */ + gchar *pwd_hash_algo; + gchar *pwd_hash_params; gchar random_key[97]; gchar salt[65]; gboolean no_local_history; @@ -513,6 +516,9 @@ seaf_repo_manager_create_enc_repo (SeafRepoManager *mgr, const char *random_key, const char *salt, int enc_version, + const char *pwd_hash, + const char *pwd_hash_algo, + const char *pwd_hash_params, GError **error); /* Give a repo and a path in this repo, returns a list of commits, where every diff --git a/server/seaf-server.c b/server/seaf-server.c index 46c0b31..4df76c8 100644 --- a/server/seaf-server.c +++ b/server/seaf-server.c @@ -227,7 +227,7 @@ static void start_rpc_service (const char *seafile_dir, searpc_server_register_function ("seafserv-threaded-rpcserver", seafile_create_enc_repo, "seafile_create_enc_repo", - searpc_signature_string__string_string_string_string_string_string_string_int()); + searpc_signature_string__string_string_string_string_string_string_string_int_string_string_string()); searpc_server_register_function ("seafserv-threaded-rpcserver", seafile_get_commit, diff --git a/server/seafile-session.c b/server/seafile-session.c index ee8c075..f58e4f4 100644 --- a/server/seafile-session.c +++ b/server/seafile-session.c @@ -122,6 +122,8 @@ seafile_session_new(const char *central_config_dir, char *notif_server = NULL; int notif_port = 8083; char *private_key = NULL; + char *pwd_hash_algo = NULL; + char *pwd_hash_params = NULL; abs_ccnet_dir = ccnet_expand_path (ccnet_dir); abs_seafile_dir = ccnet_expand_path (seafile_dir); @@ -208,6 +210,17 @@ seafile_session_new(const char *central_config_dir, session->private_key = private_key; } + pwd_hash_algo = g_key_file_get_string (config, + "password_hash", "pwd_hash_algo", + NULL); + + pwd_hash_params = g_key_file_get_string (config, + "password_hash", "pwd_hash_params", + NULL); + seafile_crypt_init (pwd_hash_algo, pwd_hash_params); + g_free (pwd_hash_algo); + g_free (pwd_hash_params); + if (load_database_config (session) < 0) { seaf_warning ("Failed to load database config.\n"); goto onerror; @@ -309,6 +322,8 @@ onerror: free (abs_seafile_dir); free (abs_ccnet_dir); g_free (tmp_file_dir); + g_free (pwd_hash_algo); + g_free (pwd_hash_params); g_free (session); return NULL; } diff --git a/server/virtual-repo.c b/server/virtual-repo.c index a77ced0..95ea311 100644 --- a/server/virtual-repo.c +++ b/server/virtual-repo.c @@ -85,7 +85,17 @@ do_create_virtual_repo (SeafRepoManager *mgr, repo->enc_version = origin_repo->enc_version; if (repo->enc_version >= 3) memcpy (repo->salt, origin_repo->salt, 64); - seafile_generate_magic (repo->enc_version, repo_id, passwd, repo->salt, repo->magic); + if (origin_repo->pwd_hash_algo) + repo->pwd_hash_algo = g_strdup (origin_repo->pwd_hash_algo); + if (origin_repo->pwd_hash_params) + repo->pwd_hash_params = g_strdup (origin_repo->pwd_hash_params); + if (repo->pwd_hash_algo) { + seafile_generate_pwd_hash (repo_id, passwd, repo->salt, + repo->pwd_hash_algo, repo->pwd_hash_params, repo->pwd_hash); + memcpy (repo->magic, repo->pwd_hash, 32); + } else + seafile_generate_magic (repo->enc_version, repo_id, passwd, repo->salt, + repo->magic); if (repo->enc_version >= 2) memcpy (repo->random_key, origin_repo->random_key, 96); } @@ -220,15 +230,29 @@ create_virtual_repo_common (SeafRepoManager *mgr, return NULL; } - if (seafile_verify_repo_passwd (origin_repo_id, - passwd, - origin_repo->magic, - origin_repo->enc_version, - origin_repo->salt) < 0) { - g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, - "Incorrect password"); - seaf_repo_unref (origin_repo); - return NULL; + if (origin_repo->pwd_hash_algo) { + if (seafile_pwd_hash_verify_repo_passwd (origin_repo_id, + passwd, + origin_repo->salt, + origin_repo->pwd_hash, + origin_repo->pwd_hash_algo, + origin_repo->pwd_hash_params) < 0) { + g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, + "Incorrect password"); + seaf_repo_unref (origin_repo); + return NULL; + } + } else { + if (seafile_verify_repo_passwd (origin_repo_id, + passwd, + origin_repo->magic, + origin_repo->enc_version, + origin_repo->salt) < 0) { + g_set_error (error, SEAFILE_DOMAIN, SEAF_ERR_GENERAL, + "Incorrect password"); + seaf_repo_unref (origin_repo); + return NULL; + } } }