1
0
forked from aniani/vim

patch 9.0.1481: decrypting with libsodium may fail if the library changes

Problem:    Decrypting with libsodium may fail if the library changes.
Solution:   Add parameters used to the encrypted file header. (Christian
            Brabandt, closes #12279)
This commit is contained in:
Christian Brabandt 2023-04-23 17:50:22 +01:00 committed by Bram Moolenaar
parent dcd40cfca0
commit aae583441b
16 changed files with 422 additions and 121 deletions

View File

@ -1533,9 +1533,10 @@ To disable the encryption, reset the 'key' option to an empty value: >
You can use the 'cryptmethod' option to select the type of encryption, use one You can use the 'cryptmethod' option to select the type of encryption, use one
of these: > of these: >
:setlocal cm=zip " weak method, backwards compatible :setlocal cm=zip " weak method, backwards compatible
:setlocal cm=blowfish " method with flaws :setlocal cm=blowfish " method with flaws, do not use
:setlocal cm=blowfish2 " medium strong method :setlocal cm=blowfish2 " medium strong method
:setlocal cm=xchacha20v2 " medium strong method using libsodium
Do this before writing the file. When reading an encrypted file it will be Do this before writing the file. When reading an encrypted file it will be
set automatically to the method used when that file was written. You can set automatically to the method used when that file was written. You can

View File

@ -2511,12 +2511,14 @@ A jump table for the options with a short description can be found at |Q_op|.
*pkzip* *pkzip*
zip PkZip compatible method. A weak kind of encryption. zip PkZip compatible method. A weak kind of encryption.
Backwards compatible with Vim 7.2 and older. Backwards compatible with Vim 7.2 and older.
Only use if you need to be backwards compatible.
*blowfish* *blowfish*
blowfish Blowfish method. Medium strong encryption but it has blowfish Blowfish method. Medium strong encryption but it has
an implementation flaw. Requires Vim 7.3 or later, an implementation flaw. Requires Vim 7.3 or later,
files can NOT be read by Vim 7.2 and older. This adds files can NOT be read by Vim 7.2 and older. This adds
a "seed" to the file, every time you write the file a "seed" to the file, every time you write the file
the encrypted bytes will be different. the encrypted bytes will be different.
Obsolete, please do no longer use.
*blowfish2* *blowfish2*
blowfish2 Blowfish method. Medium strong encryption. Requires blowfish2 Blowfish method. Medium strong encryption. Requires
Vim 7.4.401 or later, files can NOT be read by Vim 7.3 Vim 7.4.401 or later, files can NOT be read by Vim 7.3
@ -2538,11 +2540,21 @@ A jump table for the options with a short description can be found at |Q_op|.
enabled. enabled.
Encryption of undo files is not yet supported, Encryption of undo files is not yet supported,
therefore no undo file will currently be written. therefore no undo file will currently be written.
CURRENTLY EXPERIMENTAL: Files written with this method CAREFUL: Files written with this method might have to
be read back with the same version of Vim if the
binary format changes later.
Obsolete, please do no longer use.
xchacha20v2 Same algorithm as with "xchacha20" that correctly
stores the key derivation parameters together with the
encrypted file. Should work better in case the
parameters in the libsodium library ever change.
STILL EXPERIMENTAL: Files written with this method
might have to be read back with the same version of might have to be read back with the same version of
Vim if the binary format changes later. Vim if the binary format changes later.
You should use "blowfish2", also to re-encrypt older files. You should use "blowfish2", also to re-encrypt older files. The
"xchacha20" method provides better encryption, but it does not work
with all versions of Vim.
When reading an encrypted file 'cryptmethod' will be set automatically When reading an encrypted file 'cryptmethod' will be set automatically
to the detected method of the file being read. Thus if you write it to the detected method of the file being read. Thus if you write it

View File

@ -641,11 +641,8 @@ crypt_blowfish_decode(
int int
crypt_blowfish_init( crypt_blowfish_init(
cryptstate_T *state, cryptstate_T *state,
char_u* key, char_u *key,
char_u* salt, crypt_arg_T *arg)
int salt_len,
char_u* seed,
int seed_len)
{ {
bf_state_T *bfs = ALLOC_CLEAR_ONE(bf_state_T); bf_state_T *bfs = ALLOC_CLEAR_ONE(bf_state_T);
@ -660,8 +657,8 @@ crypt_blowfish_init(
if (blowfish_self_test() == FAIL) if (blowfish_self_test() == FAIL)
return FAIL; return FAIL;
bf_key_init(bfs, key, salt, salt_len); bf_key_init(bfs, key, arg->cat_salt, arg->cat_salt_len);
bf_cfb_init(bfs, seed, seed_len); bf_cfb_init(bfs, arg->cat_seed, arg->cat_seed_len);
return OK; return OK;
} }

View File

@ -2362,8 +2362,8 @@ free_buf_options(
#endif #endif
#ifdef FEAT_CRYPT #ifdef FEAT_CRYPT
# ifdef FEAT_SODIUM # ifdef FEAT_SODIUM
if ((buf->b_p_key != NULL) && (*buf->b_p_key != NUL) && if (buf->b_p_key != NULL && *buf->b_p_key != NUL
(crypt_get_method_nr(buf) == CRYPT_M_SOD)) && crypt_method_is_sodium(crypt_get_method_nr(buf)))
crypt_sodium_munlock(buf->b_p_key, STRLEN(buf->b_p_key)); crypt_sodium_munlock(buf->b_p_key, STRLEN(buf->b_p_key));
# endif # endif
clear_string_option(&buf->b_p_key); clear_string_option(&buf->b_p_key);

View File

@ -34,6 +34,8 @@ typedef struct {
char *magic; // magic bytes stored in file header char *magic; // magic bytes stored in file header
int salt_len; // length of salt, or 0 when not using salt 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 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 #ifdef CRYPT_NOT_INPLACE
int works_inplace; // encryption/decryption can be done in-place int works_inplace; // encryption/decryption can be done in-place
#endif #endif
@ -44,7 +46,7 @@ typedef struct {
// Function pointer for initializing encryption/decryption. // Function pointer for initializing encryption/decryption.
int (* init_fn)(cryptstate_T *state, char_u *key, 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. // Function pointers for encoding/decoding from one buffer into another.
// Optional, however, these or the _buffer ones should be configured. // Optional, however, these or the _buffer ones should be configured.
@ -73,9 +75,12 @@ typedef struct {
char_u *p2, int last); char_u *p2, int last);
} cryptmethod_T; } 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_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); 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_* // index is method_nr of cryptstate_T, CRYPT_M_*
static cryptmethod_T cryptmethods[CRYPT_M_COUNT] = { static cryptmethod_T cryptmethods[CRYPT_M_COUNT] = {
@ -85,6 +90,7 @@ static cryptmethod_T cryptmethods[CRYPT_M_COUNT] = {
"VimCrypt~01!", "VimCrypt~01!",
0, 0,
0, 0,
0,
#ifdef CRYPT_NOT_INPLACE #ifdef CRYPT_NOT_INPLACE
TRUE, TRUE,
#endif #endif
@ -102,6 +108,7 @@ static cryptmethod_T cryptmethods[CRYPT_M_COUNT] = {
"VimCrypt~02!", "VimCrypt~02!",
8, 8,
8, 8,
0,
#ifdef CRYPT_NOT_INPLACE #ifdef CRYPT_NOT_INPLACE
TRUE, TRUE,
#endif #endif
@ -119,6 +126,7 @@ static cryptmethod_T cryptmethods[CRYPT_M_COUNT] = {
"VimCrypt~03!", "VimCrypt~03!",
8, 8,
8, 8,
0,
#ifdef CRYPT_NOT_INPLACE #ifdef CRYPT_NOT_INPLACE
TRUE, TRUE,
#endif #endif
@ -130,7 +138,7 @@ static cryptmethod_T cryptmethods[CRYPT_M_COUNT] = {
crypt_blowfish_encode, crypt_blowfish_decode, crypt_blowfish_encode, crypt_blowfish_decode,
}, },
// XChaCha20 using libsodium // XChaCha20 using libsodium; implementation issues
{ {
"xchacha20", "xchacha20",
"VimCrypt~04!", "VimCrypt~04!",
@ -140,6 +148,29 @@ static cryptmethod_T cryptmethods[CRYPT_M_COUNT] = {
16, 16,
#endif #endif
8, 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 #ifdef CRYPT_NOT_INPLACE
FALSE, FALSE,
#endif #endif
@ -369,6 +400,15 @@ crypt_get_method_nr(buf_T *buf)
return crypt_method_nr_from_name(*buf->b_p_cm == NUL ? p_cm : buf->b_p_cm); return crypt_method_nr_from_name(*buf->b_p_cm == NUL ? p_cm : buf->b_p_cm);
} }
/*
* 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 * Return TRUE when the buffer uses an encryption method that encrypts the
* whole undo file, not only the text. * whole undo file, not only the text.
@ -387,7 +427,8 @@ crypt_get_header_len(int method_nr)
{ {
return CRYPT_MAGIC_LEN return CRYPT_MAGIC_LEN
+ cryptmethods[method_nr].salt_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( crypt_create(
int method_nr, int method_nr,
char_u *key, char_u *key,
char_u *salt, crypt_arg_T *crypt_arg)
int salt_len,
char_u *seed,
int seed_len)
{ {
cryptstate_T *state = ALLOC_ONE(cryptstate_T); cryptstate_T *state = ALLOC_ONE(cryptstate_T);
@ -456,8 +494,7 @@ crypt_create(
return state; return state;
state->method_nr = method_nr; state->method_nr = method_nr;
if (cryptmethods[method_nr].init_fn( if (cryptmethods[method_nr].init_fn(state, key, crypt_arg) == FAIL)
state, key, salt, salt_len, seed, seed_len) == FAIL)
{ {
vim_free(state); vim_free(state);
return NULL; return NULL;
@ -476,17 +513,22 @@ crypt_create_from_header(
char_u *key, char_u *key,
char_u *header) char_u *header)
{ {
char_u *salt = NULL; crypt_arg_T arg;
char_u *seed = NULL;
int salt_len = cryptmethods[method_nr].salt_len;
int seed_len = cryptmethods[method_nr].seed_len;
if (salt_len > 0) CLEAR_FIELD(arg);
salt = header + CRYPT_MAGIC_LEN; arg.cat_init_from_file = TRUE;
if (seed_len > 0)
seed = header + CRYPT_MAGIC_LEN + salt_len;
return crypt_create(method_nr, key, salt, salt_len, seed, seed_len); 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 *header_len)
{ {
int len = crypt_get_header_len(method_nr); int len = crypt_get_header_len(method_nr);
char_u *salt = NULL; crypt_arg_T arg;
char_u *seed = NULL;
int salt_len = cryptmethods[method_nr].salt_len;
int seed_len = cryptmethods[method_nr].seed_len;
cryptstate_T *state; 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_len = len;
*header = alloc(len); *header = alloc(len);
if (*header == NULL) if (*header == NULL)
return NULL; return NULL;
mch_memmove(*header, cryptmethods[method_nr].magic, CRYPT_MAGIC_LEN); 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) if (arg.cat_salt_len > 0)
salt = *header + CRYPT_MAGIC_LEN; arg.cat_salt = *header + CRYPT_MAGIC_LEN;
if (seed_len > 0) if (arg.cat_seed_len > 0)
seed = *header + CRYPT_MAGIC_LEN + salt_len; 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 // TODO: Should this be crypt method specific? (Probably not worth
// it). sha2_seed is pretty bad for large amounts of entropy, so make // it). sha2_seed is pretty bad for large amounts of entropy, so make
@ -565,16 +612,16 @@ crypt_create_for_writing(
#ifdef FEAT_SODIUM #ifdef FEAT_SODIUM
if (sodium_init() >= 0) if (sodium_init() >= 0)
{ {
if (salt_len > 0) if (arg.cat_salt_len > 0)
randombytes_buf(salt, salt_len); randombytes_buf(arg.cat_salt, arg.cat_salt_len);
if (seed_len > 0) if (arg.cat_seed_len > 0)
randombytes_buf(seed, seed_len); randombytes_buf(arg.cat_seed, arg.cat_seed_len);
} }
else else
#endif #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) if (state == NULL)
VIM_CLEAR(*header); VIM_CLEAR(*header);
return state; return state;
@ -587,7 +634,7 @@ crypt_create_for_writing(
crypt_free_state(cryptstate_T *state) crypt_free_state(cryptstate_T *state)
{ {
#ifdef FEAT_SODIUM #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, sodium_munlock(((sodium_state_T *)state->method_state)->key,
crypto_box_SEEDBYTES); crypto_box_SEEDBYTES);
@ -742,7 +789,7 @@ crypt_free_key(char_u *key)
void void
crypt_check_method(int method) crypt_check_method(int method)
{ {
if (method < CRYPT_M_BF2) if (method < CRYPT_M_BF2 || method == CRYPT_M_SOD)
{ {
msg_scroll = TRUE; msg_scroll = TRUE;
msg(_("Warning: Using a weak encryption method; see :help 'cm'")); msg(_("Warning: Using a weak encryption method; see :help 'cm'"));
@ -754,7 +801,7 @@ crypt_check_method(int method)
crypt_check_swapfile_curbuf(void) crypt_check_swapfile_curbuf(void)
{ {
int method = crypt_get_method_nr(curbuf); 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 // encryption uses padding and MAC, that does not work very well with
// swap and undo files, so disable them // 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 // 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) if (msg_didout)
msg_putchar('\n'); msg_putchar('\n');
@ -861,16 +908,16 @@ crypt_append_msg(
crypt_sodium_init_( crypt_sodium_init_(
cryptstate_T *state UNUSED, cryptstate_T *state UNUSED,
char_u *key UNUSED, char_u *key UNUSED,
char_u *salt UNUSED, crypt_arg_T *arg UNUSED)
int salt_len UNUSED,
char_u *seed UNUSED,
int seed_len UNUSED)
{ {
# ifdef FEAT_SODIUM # ifdef FEAT_SODIUM
// crypto_box_SEEDBYTES == crypto_secretstream_xchacha20poly1305_KEYBYTES // crypto_box_SEEDBYTES == crypto_secretstream_xchacha20poly1305_KEYBYTES
unsigned char dkey[crypto_box_SEEDBYTES]; // 32 unsigned char dkey[crypto_box_SEEDBYTES]; // 32
sodium_state_T *sd_state; sodium_state_T *sd_state;
int retval = 0; int retval = 0;
unsigned long long opslimit;
size_t memlimit;
int alg;
if (sodium_init() < 0) if (sodium_init() < 0)
return FAIL; return FAIL;
@ -878,25 +925,98 @@ crypt_sodium_init_(
sd_state = (sodium_state_T *)sodium_malloc(sizeof(sodium_state_T)); sd_state = (sodium_state_T *)sodium_malloc(sizeof(sodium_state_T));
sodium_memzero(sd_state, sizeof(sodium_state_T)); sodium_memzero(sd_state, sizeof(sodium_state_T));
// derive a key from the password if ((state->method_nr == CRYPT_M_SOD2 && !arg->cat_init_from_file)
if (crypto_pwhash(dkey, sizeof(dkey), (const char *)key, STRLEN(key), salt, || state->method_nr == CRYPT_M_SOD)
crypto_pwhash_OPSLIMIT_INTERACTIVE, crypto_pwhash_MEMLIMIT_INTERACTIVE,
crypto_pwhash_ALG_DEFAULT) != 0)
{ {
// out of memory opslimit = crypto_pwhash_OPSLIMIT_INTERACTIVE;
sodium_free(sd_state); memlimit = crypto_pwhash_MEMLIMIT_INTERACTIVE;
return FAIL; 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));
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);
}
} }
memcpy(sd_state->key, dkey, crypto_box_SEEDBYTES); else
retval += sodium_mlock(sd_state->key, crypto_box_SEEDBYTES);
retval += sodium_mlock(key, STRLEN(key));
if (retval < 0)
{ {
emsg(_(e_encryption_sodium_mlock_failed)); // Reading parameters from file
sodium_free(sd_state); if (arg->cat_add_len
return FAIL; < (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; sd_state->count = 0;
state->method_state = sd_state; state->method_state = sd_state;
@ -1100,6 +1220,14 @@ crypt_sodium_buffer_decode(
sodium_state_T *sod_st = state->method_state; sodium_state_T *sod_st = state->method_state;
unsigned char tag; unsigned char tag;
unsigned long long out_len; 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); *buf_out = alloc_clear(len);
if (*buf_out == NULL) if (*buf_out == NULL)
{ {
@ -1158,6 +1286,36 @@ crypt_sodium_randombytes_random(void)
{ {
return randombytes_random(); 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
#endif // FEAT_CRYPT #endif // FEAT_CRYPT

View File

@ -83,10 +83,7 @@ make_crc_tab(void)
crypt_zip_init( crypt_zip_init(
cryptstate_T *state, cryptstate_T *state,
char_u *key, char_u *key,
char_u *salt UNUSED, crypt_arg_T *arg UNUSED)
int salt_len UNUSED,
char_u *seed UNUSED,
int seed_len UNUSED)
{ {
char_u *p; char_u *p;
zip_state_T *zs; zip_state_T *zs;

View File

@ -218,6 +218,9 @@ readfile(
int using_b_fname; int using_b_fname;
static char *msg_is_a_directory = N_("is a directory"); static char *msg_is_a_directory = N_("is a directory");
int eof; int eof;
#ifdef FEAT_SODIUM
int may_need_lseek = FALSE;
#endif
au_did_filetype = FALSE; // reset before triggering any autocommands au_did_filetype = FALSE; // reset before triggering any autocommands
@ -1282,15 +1285,43 @@ retry:
*/ */
# ifdef FEAT_SODIUM # ifdef FEAT_SODIUM
// Let the crypt layer work with a buffer size of 8192 // 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) if (filesize == 0)
{
// set size to 8K + Sodium Crypt Metadata // set size to 8K + Sodium Crypt Metadata
size = WRITEBUFSIZE + crypt_get_max_header_len() size = WRITEBUFSIZE + crypt_get_max_header_len()
+ crypto_secretstream_xchacha20poly1305_HEADERBYTES + crypto_secretstream_xchacha20poly1305_HEADERBYTES
+ crypto_secretstream_xchacha20poly1305_ABYTES; + crypto_secretstream_xchacha20poly1305_ABYTES;
may_need_lseek = TRUE;
}
else if (filesize > 0 && (curbuf->b_cryptstate != NULL && else if (filesize > 0 && (curbuf->b_cryptstate != NULL
curbuf->b_cryptstate->method_nr == CRYPT_M_SOD)) && crypt_method_is_sodium(
curbuf->b_cryptstate->method_nr)))
{
size = WRITEBUFSIZE + crypto_secretstream_xchacha20poly1305_ABYTES; 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 # endif
eof = size; eof = size;
size = read_eintr(fd, ptr, size); size = read_eintr(fd, ptr, size);

View File

@ -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); sha2_seed(buf->b_ml.ml_mfp->mf_seed, MF_SEED_LEN, NULL, 0);
} }
#ifdef FEAT_SODIUM #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, crypt_sodium_randombytes_buf(buf->b_ml.ml_mfp->mf_seed,
MF_SEED_LEN); MF_SEED_LEN);
#endif #endif
@ -495,7 +495,7 @@ ml_set_crypt_key(
old_method = crypt_method_nr_from_name(old_cm); old_method = crypt_method_nr_from_name(old_cm);
// Swapfile encryption not supported by XChaCha20 // 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 // close the swapfile
mf_close_file(buf, TRUE); mf_close_file(buf, TRUE);
@ -5512,6 +5512,7 @@ ml_decrypt_data(
/* /*
* Prepare for encryption/decryption, using the key, seed and offset. * Prepare for encryption/decryption, using the key, seed and offset.
* Return an allocated cryptstate_T *. * Return an allocated cryptstate_T *.
* Note: Encryption not supported for SODIUM
*/ */
static cryptstate_T * static cryptstate_T *
ml_crypt_prepare(memfile_T *mfp, off_T offset, int reading) 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]; char_u salt[50];
int method_nr; int method_nr;
char_u *key; char_u *key;
char_u *seed; crypt_arg_T arg;
CLEAR_FIELD(arg);
if (reading && mfp->mf_old_key != NULL) if (reading && mfp->mf_old_key != NULL)
{ {
// Reading back blocks with the previous key/method/seed. // Reading back blocks with the previous key/method/seed.
method_nr = mfp->mf_old_cm; method_nr = mfp->mf_old_cm;
key = mfp->mf_old_key; key = mfp->mf_old_key;
seed = mfp->mf_old_seed; arg.cat_seed = mfp->mf_old_seed;
} }
else else
{ {
method_nr = crypt_get_method_nr(buf); method_nr = crypt_get_method_nr(buf);
key = buf->b_p_key; key = buf->b_p_key;
seed = mfp->mf_seed; arg.cat_seed = mfp->mf_seed;
} }
if (*key == NUL) if (*key == NUL)
return NULL; 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 // For PKzip: Append the offset to the key, so that we use a different
// key for every block. // key for every block.
vim_snprintf((char *)salt, sizeof(salt), "%s%ld", key, (long)offset); 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 // Using blowfish or better: add salt and seed. We use the byte offset
// of the block for the salt. // of the block for the salt.
vim_snprintf((char *)salt, sizeof(salt), "%ld", (long)offset); 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 #endif

View File

@ -4274,7 +4274,7 @@ did_set_undofile(optset_T *args)
&& !curbufIsChanged() && curbuf->b_ml.ml_mfp != NULL) && !curbufIsChanged() && curbuf->b_ml.ml_mfp != NULL)
{ {
#ifdef FEAT_CRYPT #ifdef FEAT_CRYPT
if (crypt_get_method_nr(curbuf) == CRYPT_M_SOD) if (crypt_method_is_sodium(crypt_get_method_nr(curbuf)))
continue; continue;
#endif #endif
u_compute_hash(hash); u_compute_hash(hash);

View File

@ -29,7 +29,7 @@ static char *(p_ff_values[]) = {FF_UNIX, FF_DOS, FF_MAC, NULL};
#ifdef FEAT_CRYPT #ifdef FEAT_CRYPT
static char *(p_cm_values[]) = {"zip", "blowfish", "blowfish2", static char *(p_cm_values[]) = {"zip", "blowfish", "blowfish2",
# ifdef FEAT_SODIUM # ifdef FEAT_SODIUM
"xchacha20", "xchacha20", "xchacha20v2",
# endif # endif
NULL}; NULL};
#endif #endif

View File

@ -1,6 +1,6 @@
/* blowfish.c */ /* blowfish.c */
void crypt_blowfish_encode(cryptstate_T *state, char_u *from, size_t len, char_u *to, int last); 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); 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); int blowfish_self_test(void);
/* vim: set ft=c : */ /* vim: set ft=c : */

View File

@ -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_method_nr_from_magic(char *ptr, int len);
int crypt_works_inplace(cryptstate_T *state); int crypt_works_inplace(cryptstate_T *state);
int crypt_get_method_nr(buf_T *buf); int crypt_get_method_nr(buf_T *buf);
int crypt_method_is_sodium(int method);
int crypt_whole_undofile(int method_nr); int crypt_whole_undofile(int method_nr);
int crypt_get_header_len(int method_nr); int crypt_get_header_len(int method_nr);
int crypt_get_max_header_len(void); int crypt_get_max_header_len(void);
void crypt_set_cm_option(buf_T *buf, int method_nr); void crypt_set_cm_option(buf_T *buf, int method_nr);
int crypt_self_test(void); 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_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_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); cryptstate_T *crypt_create_for_writing(int method_nr, char_u *key, char_u **header, int *header_len);

View File

@ -1,5 +1,5 @@
/* crypt_zip.c */ /* 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_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); void crypt_zip_decode(cryptstate_T *state, char_u *from, size_t len, char_u *to, int last);
/* vim: set ft=c : */ /* vim: set ft=c : */

View File

@ -2771,11 +2771,24 @@ typedef struct {
# define CRYPT_M_BF 1 # define CRYPT_M_BF 1
# define CRYPT_M_BF2 2 # define CRYPT_M_BF2 2
# define CRYPT_M_SOD 3 # 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 // Currently all crypt methods work inplace. If one is added that isn't then
// define this. // define this.
# define CRYPT_NOT_INPLACE 1 # 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 #endif
#ifdef FEAT_PROP_POPUP #ifdef FEAT_PROP_POPUP

View File

@ -81,6 +81,11 @@ func Test_crypt_sodium()
call Crypt_uncrypt('xchacha20') call Crypt_uncrypt('xchacha20')
endfunc endfunc
func Test_crypt_sodium_v2()
CheckFeature sodium
call Crypt_uncrypt('xchacha20v2')
endfunc
func Uncrypt_stable(method, crypted_text, key, uncrypted_text) func Uncrypt_stable(method, crypted_text, key, uncrypted_text)
split Xtest.txt split Xtest.txt
set bin noeol key= fenc=latin1 set bin noeol key= fenc=latin1
@ -96,13 +101,15 @@ func Uncrypt_stable(method, crypted_text, key, uncrypted_text)
set key= set key=
endfunc 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) if empty(s:xxd_cmd)
throw 'Skipped: xxd program missing' throw 'Skipped: xxd program missing'
endif endif
" use xxd to write the binary content " use xxd to write the binary content
call system(s:xxd_cmd .. ' -r >Xtest.txt', a:hex) 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))) call assert_equal(a:uncrypted_text, getline(1, len(a:uncrypted_text)))
bwipe! bwipe!
call delete('Xtest.txt') call delete('Xtest.txt')
@ -138,7 +145,40 @@ func Test_uncrypt_xchacha20()
\ '00000080: 72be 0136 84a1 d3 r..6...'] \ '00000080: 72be 0136 84a1 d3 r..6...']
" the file should be in latin1 encoding, this makes sure that readfile() " the file should be in latin1 encoding, this makes sure that readfile()
" retries several times converting the multi-byte characters " 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 endfunc
func Test_uncrypt_xchacha20_invalid() func Test_uncrypt_xchacha20_invalid()
@ -165,7 +205,7 @@ func Test_uncrypt_xchacha20_2()
sp Xcrypt_sodium.txt sp Xcrypt_sodium.txt
" Create a larger file, so that Vim will write in several blocks " 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) call assert_equal(1, &swapfile)
set cryptmethod=xchacha20 set cryptmethod=xchacha20
call feedkeys(":X\<CR>sodium\<CR>sodium\<CR>", 'xt') call feedkeys(":X\<CR>sodium\<CR>sodium\<CR>", 'xt')
@ -186,38 +226,73 @@ func Test_uncrypt_xchacha20_2()
bw! bw!
call delete('Xcrypt_sodium.txt') call delete('Xcrypt_sodium.txt')
set cryptmethod&vim set cryptmethod&vim
endfunc
func Test_uncrypt_xchacha20v2_2()
CheckFeature sodium
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')
" swapfile disabled
call assert_equal(0, &swapfile)
call assert_match("Note: Encryption of swapfile not supported, disabling swap file", execute(':messages'))
w!
" encrypted using xchacha20
call assert_match("\[xchachav2\]", execute(':messages'))
bw!
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!
call delete('Xcrypt_sodium_v2.txt')
set cryptmethod&vim
endfunc endfunc
func Test_uncrypt_xchacha20_3_persistent_undo() func Test_uncrypt_xchacha20_3_persistent_undo()
CheckFeature sodium CheckFeature sodium
CheckFeature persistent_undo CheckFeature persistent_undo
sp Xcrypt_sodium_undo.txt for meth in ['xchacha20', 'xchacha20v2']
set cryptmethod=xchacha20 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 sp Xcrypt_sodium_undo.txt
normal dd exe "set cryptmethod=" .. meth .. " undofile"
set undolevels=100 call feedkeys(":X\<CR>sodium\<CR>sodium\<CR>", 'xt')
normal dd call assert_equal(1, &undofile)
set undolevels=100 let ufile=undofile(@%)
normal dd call append(0, ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'])
set undolevels=100 call cursor(1, 1)
w!
call assert_equal(0, &undofile) set undolevels=100
bw! normal dd
call feedkeys(":sp Xcrypt_sodium_undo.txt\<CR>sodium\<CR>", 'xt') set undolevels=100
" should fail normal dd
norm! u set undolevels=100
call assert_match('Already at oldest change', execute(':1mess')) normal dd
call assert_fails('verbose rundo ' .. fnameescape(ufile), 'E822') set undolevels=100
bw! w!
set undolevels& cryptmethod& undofile& call assert_equal(0, &undofile)
call delete('Xcrypt_sodium_undo.txt') 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 endfunc
func Test_encrypt_xchacha20_missing() func Test_encrypt_xchacha20_missing()
@ -226,6 +301,7 @@ func Test_encrypt_xchacha20_missing()
endif endif
sp Xcrypt_sodium_undo.txt sp Xcrypt_sodium_undo.txt
call assert_fails(':set cryptmethod=xchacha20', 'E474') call assert_fails(':set cryptmethod=xchacha20', 'E474')
call assert_fails(':set cryptmethod=xchacha20v2', 'E474')
bw! bw!
set cm& set cm&
endfunc endfunc

View File

@ -695,6 +695,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
1481,
/**/ /**/
1480, 1480,
/**/ /**/