seafile/common/seafile-crypt.c
feiniks f5d8b38bd0
Support password hash (#2783)
* Support password hash

* Add rpc signature and verify pwd hash

---------

Co-authored-by: yangheran <heran.yang@seafile.com>
2024-10-16 20:11:40 +08:00

711 lines
20 KiB
C

/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
#include "common.h"
#include <string.h>
#include <glib.h>
#include "seafile-crypt.h"
#include "password-hash.h"
#ifdef USE_GPL_CRYPTO
#include <gnutls/gnutls.h>
#include <gnutls/crypto.h>
#include <nettle/pbkdf2.h>
#else
#include <openssl/aes.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#endif
#include "utils.h"
#include "log.h"
/*
The EVP_EncryptXXX and EVP_DecryptXXX series of functions have a
weird choice of returned value.
*/
#define ENC_SUCCESS 1
#define ENC_FAILURE 0
#define DEC_SUCCESS 1
#define DEC_FAILURE 0
#define KEYGEN_ITERATION 1 << 19
#define KEYGEN_ITERATION2 1000
/* Should generate random salt for each repo. */
static unsigned char salt[8] = { 0xda, 0x90, 0x45, 0xc3, 0x06, 0xc7, 0xcc, 0x26 };
SeafileCrypt *
seafile_crypt_new (int version, unsigned char *key, unsigned char *iv)
{
SeafileCrypt *crypt = g_new0 (SeafileCrypt, 1);
crypt->version = version;
if (version == 1)
memcpy (crypt->key, key, 16);
else
memcpy (crypt->key, key, 32);
memcpy (crypt->iv, iv, 16);
return crypt;
}
int
seafile_derive_key (const char *data_in, int in_len, int version,
const char *repo_salt,
unsigned char *key, unsigned char *iv)
{
#ifdef USE_GPL_CRYPTO
if (version != 2) {
seaf_warning ("Encrypted library version %d is not supported.\n", version);
return -1;
}
pbkdf2_hmac_sha256 (in_len, (const guchar *)data_in, KEYGEN_ITERATION2,
sizeof(salt), salt, 32, key);
pbkdf2_hmac_sha256 (32, (const guchar *)key, 10, sizeof(salt), salt, 16, iv);
return 0;
#else
if (version >= 3) {
unsigned char repo_salt_bin[32];
hex_to_rawdata (repo_salt, repo_salt_bin, 32);
PKCS5_PBKDF2_HMAC (data_in, in_len,
repo_salt_bin, sizeof(repo_salt_bin),
KEYGEN_ITERATION2,
EVP_sha256(),
32, key);
PKCS5_PBKDF2_HMAC ((char *)key, 32,
repo_salt_bin, sizeof(repo_salt_bin),
10,
EVP_sha256(),
16, iv);
return 0;
} else if (version == 2) {
PKCS5_PBKDF2_HMAC (data_in, in_len,
salt, sizeof(salt),
KEYGEN_ITERATION2,
EVP_sha256(),
32, key);
PKCS5_PBKDF2_HMAC ((char *)key, 32,
salt, sizeof(salt),
10,
EVP_sha256(),
16, iv);
return 0;
} else if (version == 1) {
EVP_BytesToKey (EVP_aes_128_cbc(), /* cipher mode */
EVP_sha1(), /* message digest */
salt, /* salt */
(unsigned char*)data_in,
in_len,
KEYGEN_ITERATION, /* iteration times */
key, /* the derived key */
iv); /* IV, initial vector */
return 0;
} else {
EVP_BytesToKey (EVP_aes_128_ecb(), /* cipher mode */
EVP_sha1(), /* message digest */
NULL, /* salt */
(unsigned char*)data_in,
in_len,
3, /* iteration times */
key, /* the derived key */
iv); /* IV, initial vector */
return 0;
}
#endif
}
int
seafile_generate_repo_salt (char *repo_salt)
{
unsigned char repo_salt_bin[32];
#ifdef USE_GPL_CRYPTO
int rc = gnutls_rnd (GNUTLS_RND_RANDOM, repo_salt_bin, sizeof(repo_salt_bin));
#else
int rc = RAND_bytes (repo_salt_bin, sizeof(repo_salt_bin));
#endif
if (rc != 1) {
seaf_warning ("Failed to generate salt for repo encryption.\n");
return -1;
}
rawdata_to_hex (repo_salt_bin, repo_salt, 32);
return 0;
}
int
seafile_generate_random_key (const char *passwd,
int version,
const char *repo_salt,
char *random_key)
{
SeafileCrypt *crypt;
unsigned char secret_key[32], *rand_key;
int outlen;
unsigned char key[32], iv[16];
#ifdef USE_GPL_CRYPTO
if (gnutls_rnd (GNUTLS_RND_RANDOM, secret_key, sizeof(secret_key)) < 0) {
seaf_warning ("Failed to generate secret key for repo encryption.\n");
return -1;
}
#else
if (RAND_bytes (secret_key, sizeof(secret_key)) != 1) {
seaf_warning ("Failed to generate secret key for repo encryption.\n");
return -1;
}
#endif
seafile_derive_key (passwd, strlen(passwd), version, repo_salt, key, iv);
crypt = seafile_crypt_new (version, key, iv);
seafile_encrypt ((char **)&rand_key, &outlen,
(char *)secret_key, sizeof(secret_key), crypt);
rawdata_to_hex (rand_key, random_key, 48);
g_free (crypt);
g_free (rand_key);
return 0;
}
void
seafile_generate_magic (int version, const char *repo_id,
const char *passwd,
const char *repo_salt,
char *magic)
{
GString *buf = g_string_new (NULL);
unsigned char key[32], iv[16];
/* Compute a "magic" 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);
seafile_derive_key (buf->str, buf->len, version, repo_salt, key, iv);
g_string_free (buf, TRUE);
rawdata_to_hex (key, magic, 32);
}
void
seafile_generate_pwd_hash (int version,
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);
if (version <= 2) {
// use fixed repo salt
char fixed_salt[64] = {0};
rawdata_to_hex(salt, fixed_salt, 8);
pwd_hash_derive_key (buf->str, buf->len, fixed_salt, algo, params_str, key);
} else {
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,
const char *magic,
int version,
const char *repo_salt)
{
GString *buf = g_string_new (NULL);
unsigned char key[32], iv[16];
char hex[65];
if (version != 1 && version != 2 && version != 3 && version != 4) {
seaf_warning ("Unsupported enc_version %d.\n", version);
return -1;
}
/* Recompute the magic and compare it with the one comes with the repo. */
g_string_append_printf (buf, "%s%s", repo_id, passwd);
seafile_derive_key (buf->str, buf->len, version, repo_salt, key, iv);
g_string_free (buf, TRUE);
if (version >= 2)
rawdata_to_hex (key, hex, 32);
else
rawdata_to_hex (key, hex, 16);
if (g_strcmp0 (hex, magic) == 0)
return 0;
else
return -1;
}
int
seafile_pwd_hash_verify_repo_passwd (int version,
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);
if (version <= 2) {
// use fixed repo salt
char fixed_salt[64] = {0};
rawdata_to_hex(salt, fixed_salt, 8);
pwd_hash_derive_key (buf->str, buf->len, fixed_salt, algo, params_str, key);
} else {
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,
const char *repo_salt,
unsigned char *key_out, unsigned char *iv_out)
{
unsigned char key[32], iv[16];
seafile_derive_key (passwd, strlen(passwd), enc_version, repo_salt, key, iv);
if (enc_version == 1) {
memcpy (key_out, key, 16);
memcpy (iv_out, iv, 16);
return 0;
} else if (enc_version >= 2) {
unsigned char enc_random_key[48], *dec_random_key;
int outlen;
SeafileCrypt *crypt;
if (random_key == NULL || random_key[0] == 0) {
seaf_warning ("Empty random key.\n");
return -1;
}
hex_to_rawdata (random_key, enc_random_key, 48);
crypt = seafile_crypt_new (enc_version, key, iv);
if (seafile_decrypt ((char **)&dec_random_key, &outlen,
(char *)enc_random_key, 48,
crypt) < 0) {
seaf_warning ("Failed to decrypt random key.\n");
g_free (crypt);
return -1;
}
g_free (crypt);
seafile_derive_key ((char *)dec_random_key, 32, enc_version,
repo_salt,
key, iv);
memcpy (key_out, key, 32);
memcpy (iv_out, iv, 16);
g_free (dec_random_key);
return 0;
}
return -1;
}
int
seafile_update_random_key (const char *old_passwd, const char *old_random_key,
const char *new_passwd, char *new_random_key,
int enc_version, const char *repo_salt)
{
unsigned char key[32], iv[16];
unsigned char random_key_raw[48], *secret_key, *new_random_key_raw;
int secret_key_len, random_key_len;
SeafileCrypt *crypt;
/* First, use old_passwd to decrypt secret key from old_random_key. */
seafile_derive_key (old_passwd, strlen(old_passwd), enc_version,
repo_salt, key, iv);
hex_to_rawdata (old_random_key, random_key_raw, 48);
crypt = seafile_crypt_new (enc_version, key, iv);
if (seafile_decrypt ((char **)&secret_key, &secret_key_len,
(char *)random_key_raw, 48,
crypt) < 0) {
seaf_warning ("Failed to decrypt random key.\n");
g_free (crypt);
return -1;
}
g_free (crypt);
/* Second, use new_passwd to encrypt secret key. */
seafile_derive_key (new_passwd, strlen(new_passwd), enc_version,
repo_salt, key, iv);
crypt = seafile_crypt_new (enc_version, key, iv);
seafile_encrypt ((char **)&new_random_key_raw, &random_key_len,
(char *)secret_key, secret_key_len, crypt);
rawdata_to_hex (new_random_key_raw, new_random_key, 48);
g_free (secret_key);
g_free (new_random_key_raw);
g_free (crypt);
return 0;
}
#ifdef USE_GPL_CRYPTO
int
seafile_encrypt (char **data_out,
int *out_len,
const char *data_in,
const int in_len,
SeafileCrypt *crypt)
{
char *buf = NULL, *enc_buf = NULL;
int buf_size, remain;
guint8 padding;
gnutls_cipher_hd_t handle;
gnutls_datum_t key, iv;
int rc, ret = 0;
buf_size = BLK_SIZE * ((in_len / BLK_SIZE) + 1);
remain = buf_size - in_len;
buf = g_new (char, buf_size);
memcpy (buf, data_in, in_len);
padding = (guint8)remain;
memset (buf + in_len, padding, remain);
key.data = crypt->key;
key.size = sizeof(crypt->key);
iv.data = crypt->iv;
iv.size = sizeof(crypt->iv);
rc = gnutls_cipher_init (&handle, GNUTLS_CIPHER_AES_256_CBC, &key, &iv);
if (rc < 0) {
seaf_warning ("Failed to init cipher: %s\n", gnutls_strerror(rc));
ret = -1;
goto out;
}
enc_buf = g_new (char, buf_size);
rc = gnutls_cipher_encrypt2 (handle, buf, buf_size, enc_buf, buf_size);
if (rc < 0) {
seaf_warning ("Failed to encrypt: %s\n", gnutls_strerror(rc));
ret = -1;
gnutls_cipher_deinit (handle);
goto out;
}
gnutls_cipher_deinit (handle);
out:
g_free (buf);
if (ret < 0) {
g_free (enc_buf);
*data_out = NULL;
*out_len = -1;
} else {
*data_out = enc_buf;
*out_len = buf_size;
}
return ret;
}
int
seafile_decrypt (char **data_out,
int *out_len,
const char *data_in,
const int in_len,
SeafileCrypt *crypt)
{
char *dec_buf = NULL;
gnutls_cipher_hd_t handle;
gnutls_datum_t key, iv;
int rc, ret = 0;
guint8 padding;
int remain;
if (in_len <= 0 || in_len % BLK_SIZE != 0) {
seaf_warning ("Invalid encrypted buffer size.\n");
return -1;
}
key.data = crypt->key;
key.size = sizeof(crypt->key);
iv.data = crypt->iv;
iv.size = sizeof(crypt->iv);
rc = gnutls_cipher_init (&handle, GNUTLS_CIPHER_AES_256_CBC, &key, &iv);
if (rc < 0) {
seaf_warning ("Failed to init cipher: %s\n", gnutls_strerror(rc));
ret = -1;
goto out;
}
dec_buf = g_new (char, in_len);
rc = gnutls_cipher_decrypt2 (handle, data_in, in_len, dec_buf, in_len);
if (rc < 0) {
seaf_warning ("Failed to decrypt data: %s\n", gnutls_strerror(rc));
ret = -1;
gnutls_cipher_deinit (handle);
goto out;
}
padding = dec_buf[in_len - 1];
remain = padding;
*out_len = (in_len - remain);
*data_out = dec_buf;
gnutls_cipher_deinit (handle);
out:
if (ret < 0) {
g_free (dec_buf);
*data_out = NULL;
*out_len = -1;
}
return ret;
}
#else
int
seafile_encrypt (char **data_out,
int *out_len,
const char *data_in,
const int in_len,
SeafileCrypt *crypt)
{
*data_out = NULL;
*out_len = -1;
/* check validation */
if ( data_in == NULL || in_len <= 0 || crypt == NULL) {
seaf_warning ("Invalid params.\n");
return -1;
}
EVP_CIPHER_CTX *ctx;
int ret;
int blks;
/* Prepare CTX for encryption. */
ctx = EVP_CIPHER_CTX_new ();
if (crypt->version == 1)
ret = EVP_EncryptInit_ex (ctx,
EVP_aes_128_cbc(), /* cipher mode */
NULL, /* engine, NULL for default */
crypt->key, /* derived key */
crypt->iv); /* initial vector */
else if (crypt->version == 3)
ret = EVP_EncryptInit_ex (ctx,
EVP_aes_128_ecb(), /* cipher mode */
NULL, /* engine, NULL for default */
crypt->key, /* derived key */
crypt->iv); /* initial vector */
else
ret = EVP_EncryptInit_ex (ctx,
EVP_aes_256_cbc(), /* cipher mode */
NULL, /* engine, NULL for default */
crypt->key, /* derived key */
crypt->iv); /* initial vector */
if (ret == ENC_FAILURE) {
EVP_CIPHER_CTX_free (ctx);
return -1;
}
/* Allocating output buffer. */
/*
For EVP symmetric encryption, padding is always used __even if__
data size is a multiple of block size, in which case the padding
length is the block size. so we have the following:
*/
blks = (in_len / BLK_SIZE) + 1;
*data_out = (char *)g_malloc (blks * BLK_SIZE);
if (*data_out == NULL) {
seaf_warning ("failed to allocate the output buffer.\n");
goto enc_error;
}
int update_len, final_len;
/* Do the encryption. */
ret = EVP_EncryptUpdate (ctx,
(unsigned char*)*data_out,
&update_len,
(unsigned char*)data_in,
in_len);
if (ret == ENC_FAILURE)
goto enc_error;
/* Finish the possible partial block. */
ret = EVP_EncryptFinal_ex (ctx,
(unsigned char*)*data_out + update_len,
&final_len);
*out_len = update_len + final_len;
/* out_len should be equal to the allocated buffer size. */
if (ret == ENC_FAILURE || *out_len != (blks * BLK_SIZE))
goto enc_error;
EVP_CIPHER_CTX_free (ctx);
return 0;
enc_error:
EVP_CIPHER_CTX_free (ctx);
*out_len = -1;
if (*data_out != NULL)
g_free (*data_out);
*data_out = NULL;
return -1;
}
int
seafile_decrypt (char **data_out,
int *out_len,
const char *data_in,
const int in_len,
SeafileCrypt *crypt)
{
*data_out = NULL;
*out_len = -1;
/* Check validation. Because padding is always used, in_len must
* be a multiple of BLK_SIZE */
if ( data_in == NULL || in_len <= 0 || in_len % BLK_SIZE != 0 ||
crypt == NULL) {
seaf_warning ("Invalid param(s).\n");
return -1;
}
EVP_CIPHER_CTX *ctx;
int ret;
/* Prepare CTX for decryption. */
ctx = EVP_CIPHER_CTX_new ();
if (crypt->version == 1)
ret = EVP_DecryptInit_ex (ctx,
EVP_aes_128_cbc(), /* cipher mode */
NULL, /* engine, NULL for default */
crypt->key, /* derived key */
crypt->iv); /* initial vector */
else if (crypt->version == 3)
ret = EVP_DecryptInit_ex (ctx,
EVP_aes_128_ecb(), /* cipher mode */
NULL, /* engine, NULL for default */
crypt->key, /* derived key */
crypt->iv); /* initial vector */
else
ret = EVP_DecryptInit_ex (ctx,
EVP_aes_256_cbc(), /* cipher mode */
NULL, /* engine, NULL for default */
crypt->key, /* derived key */
crypt->iv); /* initial vector */
if (ret == DEC_FAILURE) {
EVP_CIPHER_CTX_free (ctx);
return -1;
}
/* Allocating output buffer. */
*data_out = (char *)g_malloc (in_len);
if (*data_out == NULL) {
seaf_warning ("failed to allocate the output buffer.\n");
goto dec_error;
}
int update_len, final_len;
/* Do the decryption. */
ret = EVP_DecryptUpdate (ctx,
(unsigned char*)*data_out,
&update_len,
(unsigned char*)data_in,
in_len);
if (ret == DEC_FAILURE)
goto dec_error;
/* Finish the possible partial block. */
ret = EVP_DecryptFinal_ex (ctx,
(unsigned char*)*data_out + update_len,
&final_len);
*out_len = update_len + final_len;
/* out_len should be smaller than in_len. */
if (ret == DEC_FAILURE || *out_len > in_len)
goto dec_error;
EVP_CIPHER_CTX_free (ctx);
return 0;
dec_error:
EVP_CIPHER_CTX_free (ctx);
*out_len = -1;
if (*data_out != NULL)
g_free (*data_out);
*data_out = NULL;
return -1;
}
#endif /* USE_GPL_CRYPTO */