From aae583441bcdbb0bfef3b8a1d193e04ae09ca95d Mon Sep 17 00:00:00 2001 From: Christian Brabandt Date: Sun, 23 Apr 2023 17:50:22 +0100 Subject: 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) --- src/blowfish.c | 11 +- src/buffer.c | 4 +- src/crypt.c | 276 +++++++++++++++++++++++++++++++++++---------- src/crypt_zip.c | 5 +- src/fileio.c | 35 +++++- src/memline.c | 29 +++-- src/option.c | 2 +- src/optionstr.c | 2 +- src/proto/blowfish.pro | 2 +- src/proto/crypt.pro | 3 +- src/proto/crypt_zip.pro | 2 +- src/structs.h | 15 ++- src/testdir/test_crypt.vim | 132 +++++++++++++++++----- src/version.c | 2 + 14 files changed, 404 insertions(+), 116 deletions(-) (limited to 'src') 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 @@ -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); } +/* + * 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\" . a:key . "\", 'xt') + let cmd = (a:verbose ? ':verbose' : '') .. + \ ":split Xtest.txt\" . a:key . "\" + 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."-..sodium\sodium\", '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\sodium\sodium\", '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\sodium\", '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\sodium\", '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\sodium\sodium\", '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\sodium\", '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 @@ -695,6 +695,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1481, /**/ 1480, /**/ -- cgit v1.2.1