summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChristian Brabandt <cb@256bit.org>2023-04-23 17:50:22 +0100
committerBram Moolenaar <Bram@vim.org>2023-04-23 17:50:22 +0100
commitaae583441bcdbb0bfef3b8a1d193e04ae09ca95d (patch)
treee3cf7c27fc02e1232fe5ab8f8da32581e9782718 /src
parentdcd40cfca0fe0e65d405ce9711745a8ec846c30c (diff)
downloadvim-git-aae583441bcdbb0bfef3b8a1d193e04ae09ca95d.tar.gz
patch 9.0.1481: decrypting with libsodium may fail if the library changesv9.0.1481
Problem: Decrypting with libsodium may fail if the library changes. Solution: Add parameters used to the encrypted file header. (Christian Brabandt, closes #12279)
Diffstat (limited to 'src')
-rw-r--r--src/blowfish.c11
-rw-r--r--src/buffer.c4
-rw-r--r--src/crypt.c276
-rw-r--r--src/crypt_zip.c5
-rw-r--r--src/fileio.c35
-rw-r--r--src/memline.c29
-rw-r--r--src/option.c2
-rw-r--r--src/optionstr.c2
-rw-r--r--src/proto/blowfish.pro2
-rw-r--r--src/proto/crypt.pro3
-rw-r--r--src/proto/crypt_zip.pro2
-rw-r--r--src/structs.h15
-rw-r--r--src/testdir/test_crypt.vim132
-rw-r--r--src/version.c2
14 files changed, 404 insertions, 116 deletions
diff --git a/src/blowfish.c b/src/blowfish.c
index 88d4bce9e..6a339143c 100644
--- a/src/blowfish.c
+++ b/src/blowfish.c
@@ -641,11 +641,8 @@ crypt_blowfish_decode(
int
crypt_blowfish_init(
cryptstate_T *state,
- char_u* key,
- char_u* salt,
- int salt_len,
- char_u* seed,
- int seed_len)
+ char_u *key,
+ crypt_arg_T *arg)
{
bf_state_T *bfs = ALLOC_CLEAR_ONE(bf_state_T);
@@ -660,8 +657,8 @@ crypt_blowfish_init(
if (blowfish_self_test() == FAIL)
return FAIL;
- bf_key_init(bfs, key, salt, salt_len);
- bf_cfb_init(bfs, seed, seed_len);
+ bf_key_init(bfs, key, arg->cat_salt, arg->cat_salt_len);
+ bf_cfb_init(bfs, arg->cat_seed, arg->cat_seed_len);
return OK;
}
diff --git a/src/buffer.c b/src/buffer.c
index 6d4c4359f..98cca6ca7 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -2362,8 +2362,8 @@ free_buf_options(
#endif
#ifdef FEAT_CRYPT
# ifdef FEAT_SODIUM
- if ((buf->b_p_key != NULL) && (*buf->b_p_key != NUL) &&
- (crypt_get_method_nr(buf) == CRYPT_M_SOD))
+ if (buf->b_p_key != NULL && *buf->b_p_key != NUL
+ && crypt_method_is_sodium(crypt_get_method_nr(buf)))
crypt_sodium_munlock(buf->b_p_key, STRLEN(buf->b_p_key));
# endif
clear_string_option(&buf->b_p_key);
diff --git a/src/crypt.c b/src/crypt.c
index a66c0043a..ca7a3e7dc 100644
--- a/src/crypt.c
+++ b/src/crypt.c
@@ -34,6 +34,8 @@ typedef struct {
char *magic; // magic bytes stored in file header
int salt_len; // length of salt, or 0 when not using salt
int seed_len; // length of seed, or 0 when not using seed
+ int add_len; // additional length in the header needed for storing
+ // custom data
#ifdef CRYPT_NOT_INPLACE
int works_inplace; // encryption/decryption can be done in-place
#endif
@@ -44,7 +46,7 @@ typedef struct {
// Function pointer for initializing encryption/decryption.
int (* init_fn)(cryptstate_T *state, char_u *key,
- char_u *salt, int salt_len, char_u *seed, int seed_len);
+ crypt_arg_T *arg);
// Function pointers for encoding/decoding from one buffer into another.
// Optional, however, these or the _buffer ones should be configured.
@@ -73,9 +75,12 @@ typedef struct {
char_u *p2, int last);
} cryptmethod_T;
-static int crypt_sodium_init_(cryptstate_T *state, char_u *key, char_u *salt, int salt_len, char_u *seed, int seed_len);
+static int crypt_sodium_init_(cryptstate_T *state, char_u *key, crypt_arg_T *arg);
static long crypt_sodium_buffer_decode(cryptstate_T *state, char_u *from, size_t len, char_u **buf_out, int last);
static long crypt_sodium_buffer_encode(cryptstate_T *state, char_u *from, size_t len, char_u **buf_out, int last);
+#if defined(FEAT_EVAL) && defined(FEAT_SODIUM)
+static void crypt_sodium_report_hash_params( unsigned long long opslimit, unsigned long long ops_def, size_t memlimit, size_t mem_def, int alg, int alg_def);
+#endif
// index is method_nr of cryptstate_T, CRYPT_M_*
static cryptmethod_T cryptmethods[CRYPT_M_COUNT] = {
@@ -85,6 +90,7 @@ static cryptmethod_T cryptmethods[CRYPT_M_COUNT] = {
"VimCrypt~01!",
0,
0,
+ 0,
#ifdef CRYPT_NOT_INPLACE
TRUE,
#endif
@@ -102,6 +108,7 @@ static cryptmethod_T cryptmethods[CRYPT_M_COUNT] = {
"VimCrypt~02!",
8,
8,
+ 0,
#ifdef CRYPT_NOT_INPLACE
TRUE,
#endif
@@ -119,6 +126,7 @@ static cryptmethod_T cryptmethods[CRYPT_M_COUNT] = {
"VimCrypt~03!",
8,
8,
+ 0,
#ifdef CRYPT_NOT_INPLACE
TRUE,
#endif
@@ -130,7 +138,7 @@ static cryptmethod_T cryptmethods[CRYPT_M_COUNT] = {
crypt_blowfish_encode, crypt_blowfish_decode,
},
- // XChaCha20 using libsodium
+ // XChaCha20 using libsodium; implementation issues
{
"xchacha20",
"VimCrypt~04!",
@@ -140,6 +148,29 @@ static cryptmethod_T cryptmethods[CRYPT_M_COUNT] = {
16,
#endif
8,
+ 0,
+#ifdef CRYPT_NOT_INPLACE
+ FALSE,
+#endif
+ FALSE,
+ NULL,
+ crypt_sodium_init_,
+ NULL, NULL,
+ crypt_sodium_buffer_encode, crypt_sodium_buffer_decode,
+ NULL, NULL,
+ },
+ // XChaCha20 using libsodium; stores parameters in header
+ {
+ "xchacha20v2",
+ "VimCrypt~05!",
+#ifdef FEAT_SODIUM
+ crypto_pwhash_argon2id_SALTBYTES, // 16
+#else
+ 16,
+#endif
+ 8,
+ // sizeof(crypto_pwhash_OPSLIMIT_INTERACTIVE + crypto_pwhash_MEMLIMIT_INTERACTIVE + crypto_pwhash_ALG_DEFAULT)
+ 20,
#ifdef CRYPT_NOT_INPLACE
FALSE,
#endif
@@ -370,6 +401,15 @@ crypt_get_method_nr(buf_T *buf)
}
/*
+ * Returns True for Sodium Encryption.
+ */
+ int
+crypt_method_is_sodium(int method)
+{
+ return method == CRYPT_M_SOD || method == CRYPT_M_SOD2;
+}
+
+/*
* Return TRUE when the buffer uses an encryption method that encrypts the
* whole undo file, not only the text.
*/
@@ -387,7 +427,8 @@ crypt_get_header_len(int method_nr)
{
return CRYPT_MAGIC_LEN
+ cryptmethods[method_nr].salt_len
- + cryptmethods[method_nr].seed_len;
+ + cryptmethods[method_nr].seed_len
+ + cryptmethods[method_nr].add_len;
}
@@ -445,10 +486,7 @@ crypt_self_test(void)
crypt_create(
int method_nr,
char_u *key,
- char_u *salt,
- int salt_len,
- char_u *seed,
- int seed_len)
+ crypt_arg_T *crypt_arg)
{
cryptstate_T *state = ALLOC_ONE(cryptstate_T);
@@ -456,8 +494,7 @@ crypt_create(
return state;
state->method_nr = method_nr;
- if (cryptmethods[method_nr].init_fn(
- state, key, salt, salt_len, seed, seed_len) == FAIL)
+ if (cryptmethods[method_nr].init_fn(state, key, crypt_arg) == FAIL)
{
vim_free(state);
return NULL;
@@ -476,17 +513,22 @@ crypt_create_from_header(
char_u *key,
char_u *header)
{
- char_u *salt = NULL;
- char_u *seed = NULL;
- int salt_len = cryptmethods[method_nr].salt_len;
- int seed_len = cryptmethods[method_nr].seed_len;
-
- if (salt_len > 0)
- salt = header + CRYPT_MAGIC_LEN;
- if (seed_len > 0)
- seed = header + CRYPT_MAGIC_LEN + salt_len;
-
- return crypt_create(method_nr, key, salt, salt_len, seed, seed_len);
+ crypt_arg_T arg;
+
+ CLEAR_FIELD(arg);
+ arg.cat_init_from_file = TRUE;
+
+ arg.cat_salt_len = cryptmethods[method_nr].salt_len;
+ arg.cat_seed_len = cryptmethods[method_nr].seed_len;
+ arg.cat_add_len = cryptmethods[method_nr].add_len;
+ if (arg.cat_salt_len > 0)
+ arg.cat_salt = header + CRYPT_MAGIC_LEN;
+ if (arg.cat_seed_len > 0)
+ arg.cat_seed = header + CRYPT_MAGIC_LEN + arg.cat_salt_len;
+ if (arg.cat_add_len > 0)
+ arg.cat_add = header + CRYPT_MAGIC_LEN + arg.cat_salt_len + arg.cat_seed_len;
+
+ return crypt_create(method_nr, key, &arg);
}
/*
@@ -540,24 +582,29 @@ crypt_create_for_writing(
int *header_len)
{
int len = crypt_get_header_len(method_nr);
- char_u *salt = NULL;
- char_u *seed = NULL;
- int salt_len = cryptmethods[method_nr].salt_len;
- int seed_len = cryptmethods[method_nr].seed_len;
+ crypt_arg_T arg;
cryptstate_T *state;
+ CLEAR_FIELD(arg);
+ arg.cat_salt_len = cryptmethods[method_nr].salt_len;
+ arg.cat_seed_len = cryptmethods[method_nr].seed_len;
+ arg.cat_add_len = cryptmethods[method_nr].add_len;
+ arg.cat_init_from_file = FALSE;
+
*header_len = len;
*header = alloc(len);
if (*header == NULL)
return NULL;
mch_memmove(*header, cryptmethods[method_nr].magic, CRYPT_MAGIC_LEN);
- if (salt_len > 0 || seed_len > 0)
+ if (arg.cat_salt_len > 0 || arg.cat_seed_len > 0 || arg.cat_add_len > 0)
{
- if (salt_len > 0)
- salt = *header + CRYPT_MAGIC_LEN;
- if (seed_len > 0)
- seed = *header + CRYPT_MAGIC_LEN + salt_len;
+ if (arg.cat_salt_len > 0)
+ arg.cat_salt = *header + CRYPT_MAGIC_LEN;
+ if (arg.cat_seed_len > 0)
+ arg.cat_seed = *header + CRYPT_MAGIC_LEN + arg.cat_salt_len;
+ if (arg.cat_add_len > 0)
+ arg.cat_add = *header + CRYPT_MAGIC_LEN + arg.cat_salt_len + arg.cat_seed_len;
// TODO: Should this be crypt method specific? (Probably not worth
// it). sha2_seed is pretty bad for large amounts of entropy, so make
@@ -565,16 +612,16 @@ crypt_create_for_writing(
#ifdef FEAT_SODIUM
if (sodium_init() >= 0)
{
- if (salt_len > 0)
- randombytes_buf(salt, salt_len);
- if (seed_len > 0)
- randombytes_buf(seed, seed_len);
+ if (arg.cat_salt_len > 0)
+ randombytes_buf(arg.cat_salt, arg.cat_salt_len);
+ if (arg.cat_seed_len > 0)
+ randombytes_buf(arg.cat_seed, arg.cat_seed_len);
}
else
#endif
- sha2_seed(salt, salt_len, seed, seed_len);
+ sha2_seed(arg.cat_salt, arg.cat_salt_len, arg.cat_seed, arg.cat_seed_len);
}
- state = crypt_create(method_nr, key, salt, salt_len, seed, seed_len);
+ state = crypt_create(method_nr, key, &arg);
if (state == NULL)
VIM_CLEAR(*header);
return state;
@@ -587,7 +634,7 @@ crypt_create_for_writing(
crypt_free_state(cryptstate_T *state)
{
#ifdef FEAT_SODIUM
- if (state->method_nr == CRYPT_M_SOD)
+ if (crypt_method_is_sodium(state->method_nr))
{
sodium_munlock(((sodium_state_T *)state->method_state)->key,
crypto_box_SEEDBYTES);
@@ -742,7 +789,7 @@ crypt_free_key(char_u *key)
void
crypt_check_method(int method)
{
- if (method < CRYPT_M_BF2)
+ if (method < CRYPT_M_BF2 || method == CRYPT_M_SOD)
{
msg_scroll = TRUE;
msg(_("Warning: Using a weak encryption method; see :help 'cm'"));
@@ -754,7 +801,7 @@ crypt_check_method(int method)
crypt_check_swapfile_curbuf(void)
{
int method = crypt_get_method_nr(curbuf);
- if (method == CRYPT_M_SOD)
+ if (crypt_method_is_sodium(method))
{
// encryption uses padding and MAC, that does not work very well with
// swap and undo files, so disable them
@@ -827,7 +874,7 @@ crypt_get_key(
}
// since the user typed this, no need to wait for return
- if (crypt_get_method_nr(curbuf) != CRYPT_M_SOD)
+ if (!crypt_method_is_sodium(crypt_get_method_nr(curbuf)))
{
if (msg_didout)
msg_putchar('\n');
@@ -861,16 +908,16 @@ crypt_append_msg(
crypt_sodium_init_(
cryptstate_T *state UNUSED,
char_u *key UNUSED,
- char_u *salt UNUSED,
- int salt_len UNUSED,
- char_u *seed UNUSED,
- int seed_len UNUSED)
+ crypt_arg_T *arg UNUSED)
{
# ifdef FEAT_SODIUM
// crypto_box_SEEDBYTES == crypto_secretstream_xchacha20poly1305_KEYBYTES
unsigned char dkey[crypto_box_SEEDBYTES]; // 32
sodium_state_T *sd_state;
int retval = 0;
+ unsigned long long opslimit;
+ size_t memlimit;
+ int alg;
if (sodium_init() < 0)
return FAIL;
@@ -878,25 +925,98 @@ crypt_sodium_init_(
sd_state = (sodium_state_T *)sodium_malloc(sizeof(sodium_state_T));
sodium_memzero(sd_state, sizeof(sodium_state_T));
- // derive a key from the password
- if (crypto_pwhash(dkey, sizeof(dkey), (const char *)key, STRLEN(key), salt,
- crypto_pwhash_OPSLIMIT_INTERACTIVE, crypto_pwhash_MEMLIMIT_INTERACTIVE,
- crypto_pwhash_ALG_DEFAULT) != 0)
+ if ((state->method_nr == CRYPT_M_SOD2 && !arg->cat_init_from_file)
+ || state->method_nr == CRYPT_M_SOD)
{
- // out of memory
- sodium_free(sd_state);
- return FAIL;
- }
- memcpy(sd_state->key, dkey, crypto_box_SEEDBYTES);
+ opslimit = crypto_pwhash_OPSLIMIT_INTERACTIVE;
+ memlimit = crypto_pwhash_MEMLIMIT_INTERACTIVE;
+ alg = crypto_pwhash_ALG_DEFAULT;
+
+#if 0
+ // For testing
+ if (state->method_nr == CRYPT_M_SOD2)
+ {
+ opslimit = crypto_pwhash_OPSLIMIT_MODERATE;
+ memlimit = crypto_pwhash_MEMLIMIT_MODERATE;
+ }
+#endif
+
+ // derive a key from the password
+ if (crypto_pwhash(dkey, sizeof(dkey), (const char *)key, STRLEN(key),
+ arg->cat_salt, opslimit, memlimit, alg) != 0)
+ {
+ // out of memory
+ sodium_free(sd_state);
+ return FAIL;
+ }
+ memcpy(sd_state->key, dkey, crypto_box_SEEDBYTES);
- retval += sodium_mlock(sd_state->key, crypto_box_SEEDBYTES);
- retval += sodium_mlock(key, STRLEN(key));
+ retval += sodium_mlock(sd_state->key, crypto_box_SEEDBYTES);
+ retval += sodium_mlock(key, STRLEN(key));
- if (retval < 0)
+ if (retval < 0)
+ {
+ emsg(_(e_encryption_sodium_mlock_failed));
+ sodium_free(sd_state);
+ return FAIL;
+ }
+ if (state->method_nr == CRYPT_M_SOD2)
+ {
+ memcpy(arg->cat_add, &opslimit, sizeof(opslimit));
+ arg->cat_add += sizeof(opslimit);
+
+ memcpy(arg->cat_add, &memlimit, sizeof(memlimit));
+ arg->cat_add += sizeof(memlimit);
+
+ memcpy(arg->cat_add, &alg, sizeof(alg));
+ arg->cat_add += sizeof(alg);
+ }
+ }
+ else
{
- emsg(_(e_encryption_sodium_mlock_failed));
- sodium_free(sd_state);
- return FAIL;
+ // Reading parameters from file
+ if (arg->cat_add_len
+ < (int)(sizeof(opslimit) + sizeof(memlimit) + sizeof(alg)))
+ {
+ sodium_free(sd_state);
+ return FAIL;
+ }
+
+ // derive the key from the file header
+ memcpy(&opslimit, arg->cat_add, sizeof(opslimit));
+ arg->cat_add += sizeof(opslimit);
+
+ memcpy(&memlimit, arg->cat_add, sizeof(memlimit));
+ arg->cat_add += sizeof(memlimit);
+
+ memcpy(&alg, arg->cat_add, sizeof(alg));
+ arg->cat_add += sizeof(alg);
+
+#ifdef FEAT_EVAL
+ crypt_sodium_report_hash_params(opslimit,
+ crypto_pwhash_OPSLIMIT_INTERACTIVE,
+ memlimit, crypto_pwhash_MEMLIMIT_INTERACTIVE,
+ alg, crypto_pwhash_ALG_DEFAULT);
+#endif
+
+ if (crypto_pwhash(dkey, sizeof(dkey), (const char *)key, STRLEN(key),
+ arg->cat_salt, opslimit, memlimit, alg) != 0)
+ {
+ // out of memory
+ sodium_free(sd_state);
+ return FAIL;
+ }
+ memcpy(sd_state->key, dkey, crypto_box_SEEDBYTES);
+
+ retval += sodium_mlock(sd_state->key, crypto_box_SEEDBYTES);
+ retval += sodium_mlock(key, STRLEN(key));
+
+ if (retval < 0)
+ {
+ emsg(_(e_encryption_sodium_mlock_failed));
+ sodium_free(sd_state);
+ return FAIL;
+ }
}
sd_state->count = 0;
state->method_state = sd_state;
@@ -1100,6 +1220,14 @@ crypt_sodium_buffer_decode(
sodium_state_T *sod_st = state->method_state;
unsigned char tag;
unsigned long long out_len;
+
+ if (sod_st->count == 0
+ && state->method_nr == CRYPT_M_SOD
+ && len > WRITEBUFSIZE
+ + crypto_secretstream_xchacha20poly1305_HEADERBYTES
+ + crypto_secretstream_xchacha20poly1305_ABYTES)
+ len -= cryptmethods[CRYPT_M_SOD2].add_len;
+
*buf_out = alloc_clear(len);
if (*buf_out == NULL)
{
@@ -1158,6 +1286,36 @@ crypt_sodium_randombytes_random(void)
{
return randombytes_random();
}
+
+#if defined(FEAT_EVAL) || defined(PROTO)
+ static void
+crypt_sodium_report_hash_params(
+ unsigned long long opslimit,
+ unsigned long long ops_def,
+ size_t memlimit,
+ size_t mem_def,
+ int alg,
+ int alg_def)
+{
+ if (p_verbose > 0)
+ {
+ verbose_enter();
+ if (opslimit != ops_def)
+ smsg(_("xchacha20v2: using custom opslimit \"%llu\" for Key derivation."), opslimit);
+ else
+ smsg(_("xchacha20v2: using default opslimit \"%llu\" for Key derivation."), opslimit);
+ if (memlimit != mem_def)
+ smsg(_("xchacha20v2: using custom memlimit \"%lu\" for Key derivation."), (unsigned long)memlimit);
+ else
+ smsg(_("xchacha20v2: using default memlimit \"%lu\" for Key derivation."), (unsigned long)memlimit);
+ if (alg != alg_def)
+ smsg(_("xchacha20v2: using custom algorithm \"%d\" for Key derivation."), alg);
+ else
+ smsg(_("xchacha20v2: using default algorithm \"%d\" for Key derivation."), alg);
+ verbose_leave();
+ }
+}
+#endif
# endif
#endif // FEAT_CRYPT
diff --git a/src/crypt_zip.c b/src/crypt_zip.c
index 91bbd7ba1..89e45951c 100644
--- a/src/crypt_zip.c
+++ b/src/crypt_zip.c
@@ -83,10 +83,7 @@ make_crc_tab(void)
crypt_zip_init(
cryptstate_T *state,
char_u *key,
- char_u *salt UNUSED,
- int salt_len UNUSED,
- char_u *seed UNUSED,
- int seed_len UNUSED)
+ crypt_arg_T *arg UNUSED)
{
char_u *p;
zip_state_T *zs;
diff --git a/src/fileio.c b/src/fileio.c
index 19664201e..8d3e6f5fa 100644
--- a/src/fileio.c
+++ b/src/fileio.c
@@ -218,6 +218,9 @@ readfile(
int using_b_fname;
static char *msg_is_a_directory = N_("is a directory");
int eof;
+#ifdef FEAT_SODIUM
+ int may_need_lseek = FALSE;
+#endif
au_did_filetype = FALSE; // reset before triggering any autocommands
@@ -1282,15 +1285,43 @@ retry:
*/
# ifdef FEAT_SODIUM
// Let the crypt layer work with a buffer size of 8192
+ //
+ // Sodium encryption requires a fixed block size to
+ // successfully decrypt. However, unfortunately the file
+ // header size changes between xchacha20 and xchacha20v2 by
+ // 'add_len' bytes.
+ // So we will now read the maximum header size + encryption
+ // metadata, but after determining to read an xchacha20
+ // encrypted file, we have to rewind the file descriptor by
+ // 'add_len' bytes in the second round.
+ //
+ // Be careful with changing it, it needs to stay the same
+ // for reading back previously encrypted files!
if (filesize == 0)
+ {
// set size to 8K + Sodium Crypt Metadata
size = WRITEBUFSIZE + crypt_get_max_header_len()
+ crypto_secretstream_xchacha20poly1305_HEADERBYTES
+ crypto_secretstream_xchacha20poly1305_ABYTES;
+ may_need_lseek = TRUE;
+ }
- else if (filesize > 0 && (curbuf->b_cryptstate != NULL &&
- curbuf->b_cryptstate->method_nr == CRYPT_M_SOD))
+ else if (filesize > 0 && (curbuf->b_cryptstate != NULL
+ && crypt_method_is_sodium(
+ curbuf->b_cryptstate->method_nr)))
+ {
size = WRITEBUFSIZE + crypto_secretstream_xchacha20poly1305_ABYTES;
+ // need to rewind by - add_len from CRYPT_M_SOD2 (see
+ // description above)
+ if (curbuf->b_cryptstate->method_nr == CRYPT_M_SOD
+ && !eof && may_need_lseek)
+ {
+ lseek(fd, crypt_get_header_len(
+ curbuf->b_cryptstate->method_nr)
+ - crypt_get_max_header_len(), SEEK_CUR);
+ may_need_lseek = FALSE;
+ }
+ }
# endif
eof = size;
size = read_eintr(fd, ptr, size);
diff --git a/src/memline.c b/src/memline.c
index ea08d324c..8a3bd1629 100644
--- a/src/memline.c
+++ b/src/memline.c
@@ -436,7 +436,7 @@ ml_set_mfp_crypt(buf_T *buf)
sha2_seed(buf->b_ml.ml_mfp->mf_seed, MF_SEED_LEN, NULL, 0);
}
#ifdef FEAT_SODIUM
- else if (method_nr == CRYPT_M_SOD)
+ else if (crypt_method_is_sodium(method_nr))
crypt_sodium_randombytes_buf(buf->b_ml.ml_mfp->mf_seed,
MF_SEED_LEN);
#endif
@@ -495,7 +495,7 @@ ml_set_crypt_key(
old_method = crypt_method_nr_from_name(old_cm);
// Swapfile encryption not supported by XChaCha20
- if (crypt_get_method_nr(buf) == CRYPT_M_SOD && *buf->b_p_key != NUL)
+ if (crypt_method_is_sodium(crypt_get_method_nr(buf)) && *buf->b_p_key != NUL)
{
// close the swapfile
mf_close_file(buf, TRUE);
@@ -5512,6 +5512,7 @@ ml_decrypt_data(
/*
* Prepare for encryption/decryption, using the key, seed and offset.
* Return an allocated cryptstate_T *.
+ * Note: Encryption not supported for SODIUM
*/
static cryptstate_T *
ml_crypt_prepare(memfile_T *mfp, off_T offset, int reading)
@@ -5520,21 +5521,23 @@ ml_crypt_prepare(memfile_T *mfp, off_T offset, int reading)
char_u salt[50];
int method_nr;
char_u *key;
- char_u *seed;
+ crypt_arg_T arg;
+ CLEAR_FIELD(arg);
if (reading && mfp->mf_old_key != NULL)
{
// Reading back blocks with the previous key/method/seed.
method_nr = mfp->mf_old_cm;
key = mfp->mf_old_key;
- seed = mfp->mf_old_seed;
+ arg.cat_seed = mfp->mf_old_seed;
}
else
{
method_nr = crypt_get_method_nr(buf);
key = buf->b_p_key;
- seed = mfp->mf_seed;
+ arg.cat_seed = mfp->mf_seed;
}
+
if (*key == NUL)
return NULL;
@@ -5543,14 +5546,24 @@ ml_crypt_prepare(memfile_T *mfp, off_T offset, int reading)
// For PKzip: Append the offset to the key, so that we use a different
// key for every block.
vim_snprintf((char *)salt, sizeof(salt), "%s%ld", key, (long)offset);
- return crypt_create(method_nr, salt, NULL, 0, NULL, 0);
+ arg.cat_seed = NULL;
+ arg.cat_init_from_file = FALSE;
+
+ return crypt_create(method_nr, salt, &arg);
}
// Using blowfish or better: add salt and seed. We use the byte offset
// of the block for the salt.
vim_snprintf((char *)salt, sizeof(salt), "%ld", (long)offset);
- return crypt_create(method_nr, key, salt, (int)STRLEN(salt),
- seed, MF_SEED_LEN);
+
+ arg.cat_salt = salt;
+ arg.cat_salt_len = (int)STRLEN(salt);
+ arg.cat_seed_len = MF_SEED_LEN;
+ arg.cat_add_len = 0;
+ arg.cat_add = NULL;
+ arg.cat_init_from_file = FALSE;
+
+ return crypt_create(method_nr, key, &arg);
}
#endif
diff --git a/src/option.c b/src/option.c
index 4d9da472f..1a9ba2534 100644
--- a/src/option.c
+++ b/src/option.c
@@ -4274,7 +4274,7 @@ did_set_undofile(optset_T *args)
&& !curbufIsChanged() && curbuf->b_ml.ml_mfp != NULL)
{
#ifdef FEAT_CRYPT
- if (crypt_get_method_nr(curbuf) == CRYPT_M_SOD)
+ if (crypt_method_is_sodium(crypt_get_method_nr(curbuf)))
continue;
#endif
u_compute_hash(hash);
diff --git a/src/optionstr.c b/src/optionstr.c
index 8aec38573..311b069d2 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -29,7 +29,7 @@ static char *(p_ff_values[]) = {FF_UNIX, FF_DOS, FF_MAC, NULL};
#ifdef FEAT_CRYPT
static char *(p_cm_values[]) = {"zip", "blowfish", "blowfish2",
# ifdef FEAT_SODIUM
- "xchacha20",
+ "xchacha20", "xchacha20v2",
# endif
NULL};
#endif
diff --git a/src/proto/blowfish.pro b/src/proto/blowfish.pro
index 6b2c45459..fbaa3dc59 100644
--- a/src/proto/blowfish.pro
+++ b/src/proto/blowfish.pro
@@ -1,6 +1,6 @@
/* blowfish.c */
void crypt_blowfish_encode(cryptstate_T *state, char_u *from, size_t len, char_u *to, int last);
void crypt_blowfish_decode(cryptstate_T *state, char_u *from, size_t len, char_u *to, int last);
-int crypt_blowfish_init(cryptstate_T *state, char_u *key, char_u *salt, int salt_len, char_u *seed, int seed_len);
+int crypt_blowfish_init(cryptstate_T *state, char_u *key, crypt_arg_T *arg);
int blowfish_self_test(void);
/* vim: set ft=c : */
diff --git a/src/proto/crypt.pro b/src/proto/crypt.pro
index 560e30b8d..c06782952 100644
--- a/src/proto/crypt.pro
+++ b/src/proto/crypt.pro
@@ -4,12 +4,13 @@ int crypt_method_nr_from_name(char_u *name);
int crypt_method_nr_from_magic(char *ptr, int len);
int crypt_works_inplace(cryptstate_T *state);
int crypt_get_method_nr(buf_T *buf);
+int crypt_method_is_sodium(int method);
int crypt_whole_undofile(int method_nr);
int crypt_get_header_len(int method_nr);
int crypt_get_max_header_len(void);
void crypt_set_cm_option(buf_T *buf, int method_nr);
int crypt_self_test(void);
-cryptstate_T *crypt_create(int method_nr, char_u *key, char_u *salt, int salt_len, char_u *seed, int seed_len);
+cryptstate_T *crypt_create(int method_nr, char_u *key, crypt_arg_T *crypt_arg);
cryptstate_T *crypt_create_from_header(int method_nr, char_u *key, char_u *header);
cryptstate_T *crypt_create_from_file(FILE *fp, char_u *key);
cryptstate_T *crypt_create_for_writing(int method_nr, char_u *key, char_u **header, int *header_len);
diff --git a/src/proto/crypt_zip.pro b/src/proto/crypt_zip.pro
index 626d9855b..eb29dbf76 100644
--- a/src/proto/crypt_zip.pro
+++ b/src/proto/crypt_zip.pro
@@ -1,5 +1,5 @@
/* crypt_zip.c */
-int crypt_zip_init(cryptstate_T *state, char_u *key, char_u *salt, int salt_len, char_u *seed, int seed_len);
+int crypt_zip_init(cryptstate_T *state, char_u *key, crypt_arg_T *arg);
void crypt_zip_encode(cryptstate_T *state, char_u *from, size_t len, char_u *to, int last);
void crypt_zip_decode(cryptstate_T *state, char_u *from, size_t len, char_u *to, int last);
/* vim: set ft=c : */
diff --git a/src/structs.h b/src/structs.h
index 55a859b29..7de0d9e6d 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -2771,11 +2771,24 @@ typedef struct {
# define CRYPT_M_BF 1
# define CRYPT_M_BF2 2
# define CRYPT_M_SOD 3
-# define CRYPT_M_COUNT 4 // number of crypt methods
+# define CRYPT_M_SOD2 4
+# define CRYPT_M_COUNT 5 // number of crypt methods
// Currently all crypt methods work inplace. If one is added that isn't then
// define this.
# define CRYPT_NOT_INPLACE 1
+
+// Struct for passing arguments down to the crypt_init functions
+typedef struct {
+ char_u *cat_salt;
+ int cat_salt_len;
+ char_u *cat_seed;
+ int cat_seed_len;
+ char_u *cat_add;
+ int cat_add_len;
+ int cat_init_from_file;
+} crypt_arg_T;
+
#endif
#ifdef FEAT_PROP_POPUP
diff --git a/src/testdir/test_crypt.vim b/src/testdir/test_crypt.vim
index d1c645f4c..a25a8802a 100644
--- a/src/testdir/test_crypt.vim
+++ b/src/testdir/test_crypt.vim
@@ -81,6 +81,11 @@ func Test_crypt_sodium()
call Crypt_uncrypt('xchacha20')
endfunc
+func Test_crypt_sodium_v2()
+ CheckFeature sodium
+ call Crypt_uncrypt('xchacha20v2')
+endfunc
+
func Uncrypt_stable(method, crypted_text, key, uncrypted_text)
split Xtest.txt
set bin noeol key= fenc=latin1
@@ -96,13 +101,15 @@ func Uncrypt_stable(method, crypted_text, key, uncrypted_text)
set key=
endfunc
-func Uncrypt_stable_xxd(method, hex, key, uncrypted_text)
+func Uncrypt_stable_xxd(method, hex, key, uncrypted_text, verbose)
if empty(s:xxd_cmd)
throw 'Skipped: xxd program missing'
endif
" use xxd to write the binary content
call system(s:xxd_cmd .. ' -r >Xtest.txt', a:hex)
- call feedkeys(":split Xtest.txt\<CR>" . a:key . "\<CR>", 'xt')
+ let cmd = (a:verbose ? ':verbose' : '') ..
+ \ ":split Xtest.txt\<CR>" . a:key . "\<CR>"
+ call feedkeys(cmd, 'xt')
call assert_equal(a:uncrypted_text, getline(1, len(a:uncrypted_text)))
bwipe!
call delete('Xtest.txt')
@@ -138,7 +145,40 @@ func Test_uncrypt_xchacha20()
\ '00000080: 72be 0136 84a1 d3 r..6...']
" the file should be in latin1 encoding, this makes sure that readfile()
" retries several times converting the multi-byte characters
- call Uncrypt_stable_xxd('xchacha20', hex, "sodium_crypt", ["abcdefghijklmnopqrstuvwxyzäöü", "ZZZ_äüöÄÜÖ_!@#$%^&*()_+=-`~"])
+ call Uncrypt_stable_xxd('xchacha20', hex, "sodium_crypt", ["abcdefghijklmnopqrstuvwxyzäöü", "ZZZ_äüöÄÜÖ_!@#$%^&*()_+=-`~"], 0)
+endfunc
+
+func Test_uncrypt_xchacha20v2_custom()
+ CheckFeature sodium
+ " Test, reading xchacha20v2 with custom encryption parameters
+ let hex = ['00000000: 5669 6d43 7279 7074 7e30 3521 934b f288 VimCrypt~05!.K..',
+ \ '00000010: 10ba 8bc9 25a0 8876 f85c f135 6fb8 518b ....%..v.\.5o.Q.',
+ \ '00000020: b133 9af1 0300 0000 0000 0000 0000 0010 .3..............',
+ \ '00000030: 0000 0000 0200 0000 b973 5f33 80e9 54fc .........s_3..T.',
+ \ '00000040: 138f ba3e 046b 3135 90b7 7783 5eac 7fe3 ...>.k15..w.^...',
+ \ '00000050: 0cd2 14df ed75 4b65 8763 8205 035c ec81 .....uKe.c...\..',
+ \ "00000060: a4cf 33d2 7507 ec38 ba62 a327 9068 d8ad ..3.u..8.b.'.h..",
+ \ '00000070: 2607 3fa6 f95d 7ea8 9799 f997 4820 0c &.?..]~.....H .']
+ call Uncrypt_stable_xxd('xchacha20v2', hex, "foobar", ["", "foo", "bar", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"], 1)
+ call assert_match('xchacha20v2: using custom \w\+ "\d\+" for Key derivation.', execute(':messages'))
+endfunc
+
+func Test_uncrypt_xchacha20v2()
+ CheckFeature sodium
+ " Test, reading xchacha20v2
+ let hex = [
+ \ '00000000: 5669 6d43 7279 7074 7e30 3521 9f20 4e14 VimCrypt~05!. N.',
+ \ '00000010: c7da c1bd 7dea 8fbc db6c 38e6 7a77 6fef ....}....l8.zwo.',
+ \ '00000020: 82dd 964b 0300 0000 0000 0000 0000 0010 ...K............',
+ \ '00000030: 0000 0000 0200 0000 a97c 2f00 0b9d 19eb .........|/.....',
+ \ '00000040: 1d92 1ea5 3f22 c179 4b3e 870a eb19 6380 ....?".yK>....c.',
+ \ '00000050: 63f8 222d b5d1 3c73 7be5 d580 47ea 44cc c."-..<s{...G.D.',
+ \ '00000060: 6c25 8078 3fd5 d836 c700 0122 bb30 7a59 l%.x?..6...".0zY',
+ \ '00000070: b184 2ae8 e7db 113a f732 938f 7a34 1333 ..*....:.2..z4.3',
+ \ '00000080: dc89 1491 51a0 67b9 0f3a b56c 1f9d 53b0 ....Q.g..:.l..S.',
+ \ '00000090: 2416 205a 8c4c 5fde 4dac 2611 8a48 24f0 $. Z.L_.M.&..H$.',
+ \ '000000a0: ba00 92c1 60 ....`']
+ call Uncrypt_stable_xxd('xchacha20v2', hex, "foo1234", ["abcdefghijklmnopqrstuvwxyzäöü", 'ZZZ_äüöÄÜÖ_!@#$%^&*()_+=-`~"'], 0)
endfunc
func Test_uncrypt_xchacha20_invalid()
@@ -165,7 +205,7 @@ func Test_uncrypt_xchacha20_2()
sp Xcrypt_sodium.txt
" Create a larger file, so that Vim will write in several blocks
- call setline(1, range(1,4000))
+ call setline(1, range(1, 4000))
call assert_equal(1, &swapfile)
set cryptmethod=xchacha20
call feedkeys(":X\<CR>sodium\<CR>sodium\<CR>", 'xt')
@@ -186,38 +226,73 @@ func Test_uncrypt_xchacha20_2()
bw!
call delete('Xcrypt_sodium.txt')
set cryptmethod&vim
+
endfunc
-func Test_uncrypt_xchacha20_3_persistent_undo()
+func Test_uncrypt_xchacha20v2_2()
CheckFeature sodium
- CheckFeature persistent_undo
- sp Xcrypt_sodium_undo.txt
- set cryptmethod=xchacha20 undofile
+ sp Xcrypt_sodium_v2.txt
+ " Create a larger file, so that Vim will write in several blocks
+ call setline(1, range(1, 4000))
+ call assert_equal(1, &swapfile)
+ set cryptmethod=xchacha20v2
call feedkeys(":X\<CR>sodium\<CR>sodium\<CR>", 'xt')
- call assert_equal(1, &undofile)
- let ufile=undofile(@%)
- call append(0, ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'])
- call cursor(1, 1)
-
- set undolevels=100
- normal dd
- set undolevels=100
- normal dd
- set undolevels=100
- normal dd
- set undolevels=100
+ " swapfile disabled
+ call assert_equal(0, &swapfile)
+ call assert_match("Note: Encryption of swapfile not supported, disabling swap file", execute(':messages'))
w!
- call assert_equal(0, &undofile)
+ " encrypted using xchacha20
+ call assert_match("\[xchachav2\]", execute(':messages'))
bw!
- call feedkeys(":sp Xcrypt_sodium_undo.txt\<CR>sodium\<CR>", 'xt')
- " should fail
- norm! u
- call assert_match('Already at oldest change', execute(':1mess'))
- call assert_fails('verbose rundo ' .. fnameescape(ufile), 'E822')
+ call feedkeys(":verbose :sp Xcrypt_sodium_v2.txt\<CR>sodium\<CR>", 'xt')
+ " successfully decrypted
+ call assert_equal(range(1, 4000)->map( {_, v -> string(v)}), getline(1,'$'))
+ call assert_match('xchacha20v2: using default \w\+ "\d\+" for Key derivation.', execute(':messages'))
+ set key=
+ w! ++ff=unix
+ " encryption removed (on MS-Windows the .* matches [unix])
+ call assert_match('"Xcrypt_sodium_v2.txt".*4000L, 18893B written', execute(':message'))
bw!
- set undolevels& cryptmethod& undofile&
- call delete('Xcrypt_sodium_undo.txt')
+ call delete('Xcrypt_sodium_v2.txt')
+ set cryptmethod&vim
+
+endfunc
+
+func Test_uncrypt_xchacha20_3_persistent_undo()
+ CheckFeature sodium
+ CheckFeature persistent_undo
+
+ for meth in ['xchacha20', 'xchacha20v2']
+
+ sp Xcrypt_sodium_undo.txt
+ exe "set cryptmethod=" .. meth .. " undofile"
+ call feedkeys(":X\<CR>sodium\<CR>sodium\<CR>", 'xt')
+ call assert_equal(1, &undofile)
+ let ufile=undofile(@%)
+ call append(0, ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'])
+ call cursor(1, 1)
+
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ normal dd
+ set undolevels=100
+ w!
+ call assert_equal(0, &undofile)
+ bw!
+ call feedkeys(":sp Xcrypt_sodium_undo.txt\<CR>sodium\<CR>", 'xt')
+ " should fail
+ norm! u
+ call assert_match('Already at oldest change', execute(':1mess'))
+ call assert_fails('verbose rundo ' .. fnameescape(ufile), 'E822')
+ bw!
+ set undolevels& cryptmethod& undofile&
+ call delete('Xcrypt_sodium_undo.txt')
+
+ endfor
endfunc
func Test_encrypt_xchacha20_missing()
@@ -226,6 +301,7 @@ func Test_encrypt_xchacha20_missing()
endif
sp Xcrypt_sodium_undo.txt
call assert_fails(':set cryptmethod=xchacha20', 'E474')
+ call assert_fails(':set cryptmethod=xchacha20v2', 'E474')
bw!
set cm&
endfunc
diff --git a/src/version.c b/src/version.c
index c17109c44..a87121f90 100644
--- a/src/version.c
+++ b/src/version.c
@@ -696,6 +696,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1481,
+/**/
1480,
/**/
1479,