From f573c6e1ed58d46d694c802eaf5ae3662a952744 Mon Sep 17 00:00:00 2001 From: Christian Brabandt Date: Sun, 20 Jun 2021 14:02:16 +0200 Subject: patch 8.2.3022: available encryption methods are not strong enough Problem: Available encryption methods are not strong enough. Solution: Add initial support for xchaha20. (Christian Brabandt, closes #8394) --- .github/workflows/ci.yml | 3 +- runtime/doc/eval.txt | 1 + runtime/doc/options.txt | 17 ++ runtime/doc/various.txt | 1 + src/INSTALLpc.txt | 3 + src/Make_cyg_ming.mak | 15 +- src/Make_mvc.mak | 29 +- src/auto/configure | 66 +++++ src/blowfish.c | 7 +- src/bufwrite.c | 19 +- src/config.h.in | 1 + src/configure.ac | 37 +++ src/crypt.c | 385 +++++++++++++++++++++++++-- src/crypt_zip.c | 6 +- src/errors.h | 19 ++ src/evalfunc.c | 7 + src/feature.h | 7 + src/fileio.c | 40 ++- src/memline.c | 38 ++- src/option.c | 4 + src/optionstr.c | 6 +- src/proto/blowfish.pro | 4 +- src/proto/crypt.pro | 14 +- src/proto/crypt_zip.pro | 4 +- src/structs.h | 5 +- src/testdir/samples/crypt_sodium_invalid.txt | Bin 0 -> 16504 bytes src/testdir/test_crypt.vim | 127 ++++++++- src/undo.c | 12 +- src/version.c | 7 + 29 files changed, 820 insertions(+), 64 deletions(-) create mode 100644 src/testdir/samples/crypt_sodium_invalid.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9127455bc..e569b4239 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -71,7 +71,8 @@ jobs: cscope \ libgtk2.0-dev \ desktop-file-utils \ - libtool-bin + libtool-bin \ + libsodium-dev - name: Install clang-11 if: matrix.compiler == 'clang' diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index dd2d24232..4620623c6 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -11971,6 +11971,7 @@ scrollbind Compiled with 'scrollbind' support. (always true) showcmd Compiled with 'showcmd' support. signs Compiled with |:sign| support. smartindent Compiled with 'smartindent' support. +sodium Compiled with libsodium for better crypt support sound Compiled with sound support, e.g. `sound_playevent()` spell Compiled with spell checking support |spell|. startuptime Compiled with |--startuptime| support. diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index e600dd91d..19ddd9cb5 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -2384,6 +2384,23 @@ A jump table for the options with a short description can be found at |Q_op|. you write the file the encrypted bytes will be different. The whole undo file is encrypted, not just the pieces of text. + *E1193* *E1194* *E1195* *E1196* + *E1197* *E1198* *E1199* *E1200* *E1201* + xchacha20 XChaCha20 Cipher with Poly1305 Message Authentication + Code. Medium strong till strong encryption. + Encryption is provided by the libsodium library, it + requires Vim to be built with |+sodium| + It adds a seed and a message authentication code (MAC) + to the file. This needs at least a Vim 8.2.3022 to + read the encrypted file. + Encryption of swap files is not supported, therefore + no swap file will be used when xchacha20 encryption is + enabled. + Encryption of undo files is not yet supported, + therefore no undo file will currently be written. + CURRENTLY EXPERIMENTAL: Files written with this method + might have to be read back with the same version of + Vim if the binary format changes later. You should use "blowfish2", also to re-encrypt older files. diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt index eb45922cc..0ad12e804 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -444,6 +444,7 @@ m *+ruby/dyn* Ruby interface |ruby-dynamic| |/dyn| T *+scrollbind* |'scrollbind'| B *+signs* |:sign| N *+smartindent* |'smartindent'| +B *+sodium* compiled with libsodium for better encryption support B *+sound* |sound_playevent()|, |sound_playfile()| functions, etc. N *+spell* spell checking support, see |spell| N *+startuptime* |--startuptime| argument diff --git a/src/INSTALLpc.txt b/src/INSTALLpc.txt index b5c1a0b58..d5516c891 100644 --- a/src/INSTALLpc.txt +++ b/src/INSTALLpc.txt @@ -322,6 +322,9 @@ MSYS2 has its own git package, and you can also install it via pacman: $ pacman -S git +For enabling libsodium support, you also need to install the package + + $ pacman -S mingw-w64-x86_64-libsodium 2.3. Keep the build environment up-to-date diff --git a/src/Make_cyg_ming.mak b/src/Make_cyg_ming.mak index c33200b33..e93165dbf 100644 --- a/src/Make_cyg_ming.mak +++ b/src/Make_cyg_ming.mak @@ -41,6 +41,9 @@ DEBUG=no # set to yes to measure code coverage COVERAGE=no +# better encryption support using libsodium +#SODIUM=yes + # set to SIZE for size, SPEED for speed, MAXSPEED for maximum optimization OPTIMIZE=MAXSPEED @@ -517,6 +520,10 @@ CXXFLAGS = -std=gnu++11 WINDRES_FLAGS = EXTRA_LIBS = +ifdef SODIUM +DEFINES += -DHAVE_SODIUM +endif + ifdef GETTEXT DEFINES += -DHAVE_GETTEXT -DHAVE_LOCALE_H GETTEXTINCLUDE = $(GETTEXT)/include @@ -660,6 +667,10 @@ DEFINES += -DFEAT_DIRECTX_COLOR_EMOJI endif endif +ifeq ($(SODIUM),yes) +SODIUMLIB = -lsodium +endif + # Only allow XPM for a GUI build. ifeq (yes, $(GUI)) @@ -1064,7 +1075,7 @@ $(EXEOBJC): | $(OUTDIR) ifeq ($(VIMDLL),yes) $(TARGET): $(OBJ) - $(LINK) $(CFLAGS) $(LFLAGS) -o $@ $(OBJ) $(LIB) -lole32 -luuid -lgdi32 $(LUA_LIB) $(MZSCHEME_LIBDIR) $(MZSCHEME_LIB) $(PYTHONLIB) $(PYTHON3LIB) $(RUBYLIB) + $(LINK) $(CFLAGS) $(LFLAGS) -o $@ $(OBJ) $(LIB) -lole32 -luuid -lgdi32 $(LUA_LIB) $(MZSCHEME_LIBDIR) $(MZSCHEME_LIB) $(PYTHONLIB) $(PYTHON3LIB) $(RUBYLIB) $(SODIUMLIB) $(GVIMEXE): $(EXEOBJG) $(VIMDLLBASE).dll $(CC) -L. $(EXELFLAGS) -mwindows -o $@ $(EXEOBJG) -l$(VIMDLLBASE) @@ -1073,7 +1084,7 @@ $(VIMEXE): $(EXEOBJC) $(VIMDLLBASE).dll $(CC) -L. $(EXELFLAGS) -o $@ $(EXEOBJC) -l$(VIMDLLBASE) else $(TARGET): $(OBJ) - $(LINK) $(CFLAGS) $(LFLAGS) -o $@ $(OBJ) $(LIB) -lole32 -luuid $(LUA_LIB) $(MZSCHEME_LIBDIR) $(MZSCHEME_LIB) $(PYTHONLIB) $(PYTHON3LIB) $(RUBYLIB) + $(LINK) $(CFLAGS) $(LFLAGS) -o $@ $(OBJ) $(LIB) -lole32 -luuid $(LUA_LIB) $(MZSCHEME_LIBDIR) $(MZSCHEME_LIB) $(PYTHONLIB) $(PYTHON3LIB) $(RUBYLIB) $(SODIUMLIB) endif upx: exes diff --git a/src/Make_mvc.mak b/src/Make_mvc.mak index 42b6f8ddf..c61bb27ab 100644 --- a/src/Make_mvc.mak +++ b/src/Make_mvc.mak @@ -41,6 +41,9 @@ # # Sound support: SOUND=yes (default is yes) # +# Sodium support: SODIUM=[Path to Sodium directory] +# You need to install the msvc package from https://download.libsodium.org/libsodium/releases/ +# # DLL support (EXPERIMENTAL): VIMDLL=yes (default is no) # Creates vim{32,64}.dll, and stub gvim.exe and vim.exe. # The shared codes between the GUI and the console are built into @@ -372,6 +375,26 @@ SOUND = no ! endif !endif +!ifndef SODIUM +SODIUM = no +!endif + +!if "$(SODIUM)" != "no" +! if "$(CPU)" == "AMD64" +SOD_LIB = $(SODIUM)\x64\Release\v140\dynamic +! elseif "$(CPU)" == "i386" +SOD_LIB = $(SODIUM)\x86\Release\v140\dynamic +! else +SODIUM = no +! endif +!endif + +!if "$(SODIUM)" != "no" +SOD_INC = -I $(SODIUM)\include +SOD_DEFS = -DFEAT_SODIUM +SOD_LIB = $(SOD_LIB)\libsodium.lib +!endif + !ifndef NETBEANS NETBEANS = $(GUI) !endif @@ -491,7 +514,7 @@ CON_LIB = $(CON_LIB) /DELAYLOAD:comdlg32.dll /DELAYLOAD:ole32.dll DelayImp.lib CFLAGS = -c /W3 /GF /nologo $(CVARS) -I. -Iproto -DHAVE_PATHDEF -DWIN32 \ $(CSCOPE_DEFS) $(TERM_DEFS) $(SOUND_DEFS) $(NETBEANS_DEFS) $(CHANNEL_DEFS) \ - $(NBDEBUG_DEFS) $(XPM_DEFS) \ + $(NBDEBUG_DEFS) $(XPM_DEFS) $(SOD_DEFS) \ $(DEFINES) -DWINVER=$(WINVER) -D_WIN32_WINNT=$(WINVER) #>>>>> end of choices @@ -703,7 +726,7 @@ CFLAGS = $(CFLAGS) $(CFLAGS_DEPR) INCL = vim.h alloc.h ascii.h ex_cmds.h feature.h errors.h globals.h \ keymap.h macros.h option.h os_dos.h os_win32.h proto.h regexp.h \ - spell.h structs.h term.h beval.h $(NBDEBUG_INCL) + spell.h structs.h term.h beval.h $(NBDEBUG_INCL) $(SOD_INC) OBJ = \ $(OUTDIR)\arabic.obj \ @@ -1282,7 +1305,7 @@ conflags = $(conflags) /map /mapinfo:lines LINKARGS1 = $(linkdebug) $(conflags) LINKARGS2 = $(CON_LIB) $(GUI_LIB) $(NODEFAULTLIB) $(LIBC) $(OLE_LIB) user32.lib \ $(LUA_LIB) $(MZSCHEME_LIB) $(PERL_LIB) $(PYTHON_LIB) $(PYTHON3_LIB) $(RUBY_LIB) \ - $(TCL_LIB) $(SOUND_LIB) $(NETBEANS_LIB) $(XPM_LIB) $(LINK_PDB) + $(TCL_LIB) $(SOUND_LIB) $(NETBEANS_LIB) $(XPM_LIB) $(SOD_LIB) $(LINK_PDB) # Report link time code generation progress if used. !ifdef NODEBUG diff --git a/src/auto/configure b/src/auto/configure index 7ecc40ca5..cd678fd9f 100755 --- a/src/auto/configure +++ b/src/auto/configure @@ -839,6 +839,7 @@ with_motif_lib with_tlib enable_largefile enable_canberra +enable_libsodium enable_acl enable_gpm enable_sysmouse @@ -1513,6 +1514,7 @@ Optional Features: --disable-desktop-database-update update disabled --disable-largefile omit support for large files --disable-canberra Do not use libcanberra. + --disable-libsodium Do not use libsodium. --disable-acl No check for ACL support. --disable-gpm Don't use gpm (Linux mouse daemon). --disable-sysmouse Don't use sysmouse (mouse in *BSD console). @@ -13005,6 +13007,70 @@ rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking --enable-libsodium argument" >&5 +$as_echo_n "checking --enable-libsodium argument... " >&6; } +# Check whether --enable-libsodium was given. +if test "${enable_libsodium+set}" = set; then : + enableval=$enable_libsodium; +else + enable_libsodium="maybe" +fi + + +if test "$enable_libsodium" = "maybe"; then + if test "$features" = "big" -o "$features" = "huge"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: Defaulting to yes" >&5 +$as_echo "Defaulting to yes" >&6; } + enable_libsodium="yes" + else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: Defaulting to no" >&5 +$as_echo "Defaulting to no" >&6; } + enable_libsodium="no" + fi +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $enable_libsodium" >&5 +$as_echo "$enable_libsodium" >&6; } +fi +if test "$enable_libsodium" = "yes"; then + if test "x$PKG_CONFIG" != "xno"; then + libsodium_lib=`$PKG_CONFIG --libs libsodium 2>/dev/null` + libsodium_cflags=`$PKG_CONFIG --cflags libsodium 2>/dev/null` + fi + if test "x$libsodium_lib" = "x"; then + libsodium_lib=-lsodium + libsodium_cflags= + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for libcanberra" >&5 +$as_echo_n "checking for libcanberra... " >&6; } + ac_save_CFLAGS="$CFLAGS" + ac_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $libsodium_cflags" + LIBS="$LIBS $libsodium_lib" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + + # include + +int +main () +{ + + printf("%d", sodium_init()); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; }; $as_echo "#define HAVE_SODIUM 1" >>confdefs.h + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no; try installing libsodium-dev" >&5 +$as_echo "no; try installing libsodium-dev" >&6; }; CFLAGS="$ac_save_CFLAGS"; LIBS="$ac_save_LIBS" +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for st_blksize" >&5 $as_echo_n "checking for st_blksize... " >&6; } diff --git a/src/blowfish.c b/src/blowfish.c index 342bcc406..4502a1c5d 100644 --- a/src/blowfish.c +++ b/src/blowfish.c @@ -596,7 +596,8 @@ crypt_blowfish_encode( cryptstate_T *state, char_u *from, size_t len, - char_u *to) + char_u *to, + int last UNUSED) { bf_state_T *bfs = state->method_state; size_t i; @@ -619,7 +620,8 @@ crypt_blowfish_decode( cryptstate_T *state, char_u *from, size_t len, - char_u *to) + char_u *to, + int last UNUSED) { bf_state_T *bfs = state->method_state; size_t i; @@ -680,5 +682,4 @@ blowfish_self_test(void) } return OK; } - #endif // FEAT_CRYPT diff --git a/src/bufwrite.c b/src/bufwrite.c index c7c832cff..c91bcd995 100644 --- a/src/bufwrite.c +++ b/src/bufwrite.c @@ -30,6 +30,7 @@ struct bw_info int bw_flags; // FIO_ flags #ifdef FEAT_CRYPT buf_T *bw_buffer; // buffer being written + int bw_finish; // finish encrypting #endif char_u bw_rest[CONV_RESTLEN]; // not converted bytes int bw_restlen; // nr of bytes in bw_rest[] @@ -493,14 +494,14 @@ buf_write_bytes(struct bw_info *ip) if (crypt_works_inplace(ip->bw_buffer->b_cryptstate)) { # endif - crypt_encode_inplace(ip->bw_buffer->b_cryptstate, buf, len); + crypt_encode_inplace(ip->bw_buffer->b_cryptstate, buf, len, ip->bw_finish); # ifdef CRYPT_NOT_INPLACE } else { char_u *outbuf; - len = crypt_encode_alloc(curbuf->b_cryptstate, buf, len, &outbuf); + len = crypt_encode_alloc(curbuf->b_cryptstate, buf, len, &outbuf, ip->bw_finish); if (len == 0) return OK; // Crypt layer is buffering, will flush later. wlen = write_eintr(ip->bw_fd, outbuf, len); @@ -724,6 +725,7 @@ buf_write( #endif #ifdef FEAT_CRYPT write_info.bw_buffer = buf; + write_info.bw_finish = FALSE; #endif // After writing a file changedtick changes but we don't want to display @@ -2015,6 +2017,13 @@ restore_backup: ++s; if (++len != bufsize) continue; +#ifdef FEAT_CRYPT + if (write_info.bw_fd > 0 && lnum == end + && (write_info.bw_flags & FIO_ENCRYPTED) + && *buf->b_p_key != NUL && !filtering + && *ptr == NUL) + write_info.bw_finish = TRUE; + #endif if (buf_write_bytes(&write_info) == FAIL) { end = 0; // write error: break loop @@ -2118,6 +2127,12 @@ restore_backup: if (len > 0 && end > 0) { write_info.bw_len = len; +#ifdef FEAT_CRYPT + if (write_info.bw_fd > 0 && lnum >= end + && (write_info.bw_flags & FIO_ENCRYPTED) + && *buf->b_p_key != NUL && !filtering) + write_info.bw_finish = TRUE; + #endif if (buf_write_bytes(&write_info) == FAIL) end = 0; // write error nchars += len; diff --git a/src/config.h.in b/src/config.h.in index fbf4b2449..0808cc358 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -208,6 +208,7 @@ #undef HAVE_STRPTIME #undef HAVE_STRTOL #undef HAVE_CANBERRA +#undef HAVE_SODIUM #undef HAVE_ST_BLKSIZE #undef HAVE_SYSCONF #undef HAVE_SYSCTL diff --git a/src/configure.ac b/src/configure.ac index 9810ea1fc..84b54dbf0 100644 --- a/src/configure.ac +++ b/src/configure.ac @@ -3767,6 +3767,43 @@ if test "$enable_canberra" = "yes"; then AC_MSG_RESULT(no; try installing libcanberra-dev); CFLAGS="$ac_save_CFLAGS"; LIBS="$ac_save_LIBS") fi +AC_MSG_CHECKING(--enable-libsodium argument) +AC_ARG_ENABLE(libsodium, + [ --disable-libsodium Do not use libsodium.], + , [enable_libsodium="maybe"]) + +if test "$enable_libsodium" = "maybe"; then + if test "$features" = "big" -o "$features" = "huge"; then + AC_MSG_RESULT(Defaulting to yes) + enable_libsodium="yes" + else + AC_MSG_RESULT(Defaulting to no) + enable_libsodium="no" + fi +else + AC_MSG_RESULT($enable_libsodium) +fi +if test "$enable_libsodium" = "yes"; then + if test "x$PKG_CONFIG" != "xno"; then + libsodium_lib=`$PKG_CONFIG --libs libsodium 2>/dev/null` + libsodium_cflags=`$PKG_CONFIG --cflags libsodium 2>/dev/null` + fi + if test "x$libsodium_lib" = "x"; then + libsodium_lib=-lsodium + libsodium_cflags= + fi + AC_MSG_CHECKING(for libcanberra) + ac_save_CFLAGS="$CFLAGS" + ac_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $libsodium_cflags" + LIBS="$LIBS $libsodium_lib" + AC_TRY_LINK([ + # include + ], [ + printf("%d", sodium_init()); ], + AC_MSG_RESULT(yes); AC_DEFINE(HAVE_SODIUM), + AC_MSG_RESULT(no; try installing libsodium-dev); CFLAGS="$ac_save_CFLAGS"; LIBS="$ac_save_LIBS") +fi dnl fstatfs() can take 2 to 4 arguments, try to use st_blksize if possible AC_MSG_CHECKING(for st_blksize) diff --git a/src/crypt.c b/src/crypt.c index 0164f1ce9..35d4e14dc 100644 --- a/src/crypt.c +++ b/src/crypt.c @@ -12,6 +12,10 @@ */ #include "vim.h" +#ifdef FEAT_SODIUM +# include +#endif + #if defined(FEAT_CRYPT) || defined(PROTO) /* * Optional encryption support. @@ -33,7 +37,7 @@ typedef struct { char *name; // encryption name as used in 'cryptmethod' 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 salt + int seed_len; // length of seed, or 0 when not using seed #ifdef CRYPT_NOT_INPLACE int works_inplace; // encryption/decryption can be done in-place #endif @@ -49,16 +53,16 @@ typedef struct { // Function pointers for encoding/decoding from one buffer into another. // Optional, however, these or the _buffer ones should be configured. void (*encode_fn)(cryptstate_T *state, char_u *from, size_t len, - char_u *to); + char_u *to, int last); void (*decode_fn)(cryptstate_T *state, char_u *from, size_t len, - char_u *to); + char_u *to, int last); // Function pointers for encoding and decoding, can buffer data if needed. // Optional (however, these or the above should be configured). long (*encode_buffer_fn)(cryptstate_T *state, char_u *from, size_t len, - char_u **newptr); + char_u **newptr, int last); long (*decode_buffer_fn)(cryptstate_T *state, char_u *from, size_t len, - char_u **newptr); + char_u **newptr, int last); // Function pointers for in-place encoding and decoding, used for // crypt_*_inplace(). "from" and "to" arguments will be equal. @@ -68,9 +72,9 @@ typedef struct { // padding to files). // This method is used for swap and undo files which have a rigid format. void (*encode_inplace_fn)(cryptstate_T *state, char_u *p1, size_t len, - char_u *p2); + char_u *p2, int last); void (*decode_inplace_fn)(cryptstate_T *state, char_u *p1, size_t len, - char_u *p2); + char_u *p2, int last); } cryptmethod_T; // index is method_nr of cryptstate_T, CRYPT_M_* @@ -126,10 +130,41 @@ static cryptmethod_T cryptmethods[CRYPT_M_COUNT] = { crypt_blowfish_encode, crypt_blowfish_decode, }, + // XChaCha20 using libsodium + { + "xchacha20", + "VimCrypt~04!", +#ifdef FEAT_SODIUM + crypto_pwhash_argon2id_SALTBYTES, // 16 +#else + 16, +#endif + 8, +#ifdef CRYPT_NOT_INPLACE + FALSE, +#endif + FALSE, + NULL, + crypt_sodium_init, + crypt_sodium_encode, crypt_sodium_decode, + crypt_sodium_buffer_encode, crypt_sodium_buffer_decode, + crypt_sodium_encode, crypt_sodium_decode, + }, + // NOTE: when adding a new method, use some random bytes for the magic key, // to avoid that a text file is recognized as encrypted. }; +#ifdef FEAT_SODIUM +typedef struct { + size_t count; + unsigned char key[crypto_box_SEEDBYTES]; + // 32, same as crypto_secretstream_xchacha20poly1305_KEYBYTES + crypto_secretstream_xchacha20poly1305_state + state; +} sodium_state_T; +#endif + #define CRYPT_MAGIC_LEN 12 // cannot change static char crypt_magic_head[] = "VimCrypt~"; @@ -260,7 +295,7 @@ crypt_create( state->method_nr = method_nr; if (cryptmethods[method_nr].init_fn( - state, key, salt, salt_len, seed, seed_len) == FAIL) + state, key, salt, salt_len, seed, seed_len) == FAIL) { vim_free(state); return NULL; @@ -365,9 +400,16 @@ crypt_create_for_writing( // TODO: Should this be crypt method specific? (Probably not worth // it). sha2_seed is pretty bad for large amounts of entropy, so make // that into something which is suitable for anything. - sha2_seed(salt, salt_len, seed, seed_len); +#ifdef FEAT_SODIUM + if (sodium_init() >= 0) + { + randombytes_buf(salt, salt_len); + randombytes_buf(seed, seed_len); + } + else +#endif + sha2_seed(salt, salt_len, seed, seed_len); } - state = crypt_create(method_nr, key, salt, salt_len, seed, seed_len); if (state == NULL) VIM_CLEAR(*header); @@ -380,7 +422,15 @@ crypt_create_for_writing( void crypt_free_state(cryptstate_T *state) { - vim_free(state->method_state); +#ifdef FEAT_SODIUM + if (state->method_nr == CRYPT_M_SOD) + { + sodium_memzero(state->method_state, sizeof(sodium_state_T)); + sodium_free(state->method_state); + } + else +#endif + vim_free(state->method_state); vim_free(state); } @@ -395,21 +445,22 @@ crypt_encode_alloc( cryptstate_T *state, char_u *from, size_t len, - char_u **newptr) + char_u **newptr, + int last) { cryptmethod_T *method = &cryptmethods[state->method_nr]; if (method->encode_buffer_fn != NULL) // Has buffer function, pass through. - return method->encode_buffer_fn(state, from, len, newptr); + return method->encode_buffer_fn(state, from, len, newptr, last); if (len == 0) // Not buffering, just return EOF. return (long)len; - *newptr = alloc(len); + *newptr = alloc(len + 50); if (*newptr == NULL) return -1; - method->encode_fn(state, from, len, *newptr); + method->encode_fn(state, from, len, *newptr, last); return (long)len; } @@ -423,13 +474,14 @@ crypt_decode_alloc( cryptstate_T *state, char_u *ptr, long len, - char_u **newptr) + char_u **newptr, + int last) { cryptmethod_T *method = &cryptmethods[state->method_nr]; if (method->decode_buffer_fn != NULL) // Has buffer function, pass through. - return method->decode_buffer_fn(state, ptr, len, newptr); + return method->decode_buffer_fn(state, ptr, len, newptr, last); if (len == 0) // Not buffering, just return EOF. @@ -438,7 +490,7 @@ crypt_decode_alloc( *newptr = alloc(len); if (*newptr == NULL) return -1; - method->decode_fn(state, ptr, len, *newptr); + method->decode_fn(state, ptr, len, *newptr, last); return len; } #endif @@ -451,9 +503,10 @@ crypt_encode( cryptstate_T *state, char_u *from, size_t len, - char_u *to) + char_u *to, + int last) { - cryptmethods[state->method_nr].encode_fn(state, from, len, to); + cryptmethods[state->method_nr].encode_fn(state, from, len, to, last); } #if 0 // unused @@ -465,9 +518,10 @@ crypt_decode( cryptstate_T *state, char_u *from, size_t len, - char_u *to) + char_u *to, + int last) { - cryptmethods[state->method_nr].decode_fn(state, from, len, to); + cryptmethods[state->method_nr].decode_fn(state, from, len, to, last); } #endif @@ -478,9 +532,11 @@ crypt_decode( crypt_encode_inplace( cryptstate_T *state, char_u *buf, - size_t len) + size_t len, + int last) { - cryptmethods[state->method_nr].encode_inplace_fn(state, buf, len, buf); + cryptmethods[state->method_nr].encode_inplace_fn(state, buf, len, + buf, last); } /* @@ -490,9 +546,11 @@ crypt_encode_inplace( crypt_decode_inplace( cryptstate_T *state, char_u *buf, - size_t len) + size_t len, + int last) { - cryptmethods[state->method_nr].decode_inplace_fn(state, buf, len, buf); + cryptmethods[state->method_nr].decode_inplace_fn(state, buf, len, + buf, last); } /* @@ -523,6 +581,19 @@ crypt_check_method(int method) msg_scroll = TRUE; msg(_("Warning: Using a weak encryption method; see :help 'cm'")); } + if (method == CRYPT_M_SOD) + { + // encryption uses padding and MAC, that does not work very well with + // swap and undo files, so disable them + mf_close_file(curbuf, TRUE); // remove the swap file + set_option_value((char_u *)"swf", 0, NULL, OPT_LOCAL); +#ifdef FEAT_PERSISTENT_UNDO + set_option_value((char_u *)"udf", 0, NULL, OPT_LOCAL); +#endif + + msg_scroll = TRUE; + msg(_("Note: Encryption of swapfile not supported, disabling swap- and undofile")); + } } void @@ -610,4 +681,266 @@ crypt_append_msg( } } + int +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) +{ +# ifdef FEAT_SODIUM + // crypto_box_SEEDBYTES == crypto_secretstream_xchacha20poly1305_KEYBYTES + unsigned char dkey[crypto_box_SEEDBYTES]; // 32 + sodium_state_T *sd_state; + + if (sodium_init() < 0) + return FAIL; + + 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) + { + // out of memory + sodium_free(sd_state); + return FAIL; + } + memcpy(sd_state->key, dkey, crypto_box_SEEDBYTES); + sd_state->count = 0; + state->method_state = sd_state; + + return OK; +# else + emsg(e_libsodium_not_built_in); + return FAIL; +# endif +} + +/* + * Encrypt "from[len]" into "to[len]". + * "from" and "to" can be equal to encrypt in place. + * Call needs to ensure that there is enough space in to (for the header) + */ + void +crypt_sodium_encode( + cryptstate_T *state UNUSED, + char_u *from UNUSED, + size_t len UNUSED, + char_u *to UNUSED, + int last UNUSED) +{ +# ifdef FEAT_SODIUM + // crypto_box_SEEDBYTES == crypto_secretstream_xchacha20poly1305_KEYBYTES + sodium_state_T *sod_st = state->method_state; + unsigned char tag = last + ? crypto_secretstream_xchacha20poly1305_TAG_FINAL : 0; + + if (sod_st->count == 0) + { + if (len <= crypto_secretstream_xchacha20poly1305_HEADERBYTES) + { + emsg(e_libsodium_cannot_encrypt_header); + return; + } + crypto_secretstream_xchacha20poly1305_init_push(&sod_st->state, + to, sod_st->key); + to += crypto_secretstream_xchacha20poly1305_HEADERBYTES; + } + + if (sod_st->count && len <= crypto_secretstream_xchacha20poly1305_ABYTES) + { + emsg(e_libsodium_cannot_encrypt_buffer); + return; + } + + crypto_secretstream_xchacha20poly1305_push(&sod_st->state, to, NULL, + from, len, NULL, 0, tag); + + sod_st->count++; +# endif +} + +/* TODO: Unused + * Decrypt "from[len]" into "to[len]". + * "from" and "to" can be equal to encrypt in place. + */ + void +crypt_sodium_decode( + cryptstate_T *state UNUSED, + char_u *from UNUSED, + size_t len UNUSED, + char_u *to UNUSED, + int last UNUSED) +{ +# ifdef FEAT_SODIUM + // crypto_box_SEEDBYTES == crypto_secretstream_xchacha20poly1305_KEYBYTES + sodium_state_T *sod_st = state->method_state; + unsigned char tag; + unsigned long long buf_len; + char_u *p1 = from; + char_u *p2 = to; + char_u *buf_out; + + if (sod_st->count == 0 + && len <= crypto_secretstream_xchacha20poly1305_HEADERBYTES) + { + emsg(e_libsodium_cannot_decrypt_header); + return; + } + + buf_out = (char_u *)alloc(len); + + if (buf_out == NULL) + { + emsg(e_libsodium_cannot_allocate_buffer); + return; + } + if (sod_st->count == 0) + { + if (crypto_secretstream_xchacha20poly1305_init_pull( + &sod_st->state, from, sod_st->key) != 0) + { + emsg(e_libsodium_decryption_failed_header_incomplete); + goto fail; + } + + from += crypto_secretstream_xchacha20poly1305_HEADERBYTES; + len -= crypto_secretstream_xchacha20poly1305_HEADERBYTES; + + if (p1 == p2) + to += crypto_secretstream_xchacha20poly1305_HEADERBYTES; + } + + if (sod_st->count && len <= crypto_secretstream_xchacha20poly1305_ABYTES) + { + emsg(e_libsodium_cannot_decrypt_buffer); + return; + } + if (crypto_secretstream_xchacha20poly1305_pull(&sod_st->state, + buf_out, &buf_len, &tag, from, len, NULL, 0) != 0) + { + emsg(e_libsodium_decription_failed); + goto fail; + } + sod_st->count++; + + if (tag == crypto_secretstream_xchacha20poly1305_TAG_FINAL && !last) + { + emsg(e_libsodium_decyption_failed_premature); + goto fail; + } + if (p1 == p2) + mch_memmove(p2, buf_out, buf_len); + +fail: + vim_free(buf_out); +# endif +} + +/* + * Encrypt "from[len]" into "to[len]". + * "from" and "to" can be equal to encrypt in place. + */ + long +crypt_sodium_buffer_encode( + cryptstate_T *state UNUSED, + char_u *from UNUSED, + size_t len UNUSED, + char_u **buf_out UNUSED, + int last UNUSED) +{ +# ifdef FEAT_SODIUM + // crypto_box_SEEDBYTES == crypto_secretstream_xchacha20poly1305_KEYBYTES + unsigned long long out_len; + char_u *ptr; + unsigned char tag = last + ? crypto_secretstream_xchacha20poly1305_TAG_FINAL : 0; + int length; + sodium_state_T *sod_st = state->method_state; + int first = (sod_st->count == 0); + + length = len + crypto_secretstream_xchacha20poly1305_ABYTES + + (first ? crypto_secretstream_xchacha20poly1305_HEADERBYTES : 0); + *buf_out = alloc_clear(length); + if (*buf_out == NULL) + { + emsg(e_libsodium_cannot_allocate_buffer); + return -1; + } + ptr = *buf_out; + + if (first) + { + crypto_secretstream_xchacha20poly1305_init_push(&sod_st->state, + ptr, sod_st->key); + ptr += crypto_secretstream_xchacha20poly1305_HEADERBYTES; + } + + crypto_secretstream_xchacha20poly1305_push(&sod_st->state, ptr, + &out_len, from, len, NULL, 0, tag); + + sod_st->count++; + return out_len + (first + ? crypto_secretstream_xchacha20poly1305_HEADERBYTES : 0); +# else + return -1; +# endif +} + +/* + * Decrypt "from[len]" into "to[len]". + * "from" and "to" can be equal to encrypt in place. + */ + long +crypt_sodium_buffer_decode( + cryptstate_T *state UNUSED, + char_u *from UNUSED, + size_t len UNUSED, + char_u **buf_out UNUSED, + int last UNUSED) +{ +# ifdef FEAT_SODIUM + // crypto_box_SEEDBYTES == crypto_secretstream_xchacha20poly1305_KEYBYTES + sodium_state_T *sod_st = state->method_state; + unsigned char tag; + unsigned long long out_len; + *buf_out = alloc_clear(len); + if (*buf_out == NULL) + { + emsg(e_libsodium_cannot_allocate_buffer); + return -1; + } + + if (sod_st->count == 0) + { + if (crypto_secretstream_xchacha20poly1305_init_pull(&sod_st->state, + from, sod_st->key) != 0) + { + emsg(e_libsodium_decryption_failed_header_incomplete); + return -1; + } + from += crypto_secretstream_xchacha20poly1305_HEADERBYTES; + len -= crypto_secretstream_xchacha20poly1305_HEADERBYTES; + sod_st->count++; + } + if (crypto_secretstream_xchacha20poly1305_pull(&sod_st->state, + *buf_out, &out_len, &tag, from, len, NULL, 0) != 0) + { + emsg(e_libsodium_decription_failed); + return -1; + } + + if (tag == crypto_secretstream_xchacha20poly1305_TAG_FINAL && !last) + emsg(e_libsodium_decyption_failed_premature); + return (long) out_len; +# else + return -1; +# endif +} + #endif // FEAT_CRYPT diff --git a/src/crypt_zip.c b/src/crypt_zip.c index 42abe9350..b11d7a329 100644 --- a/src/crypt_zip.c +++ b/src/crypt_zip.c @@ -114,7 +114,8 @@ crypt_zip_encode( cryptstate_T *state, char_u *from, size_t len, - char_u *to) + char_u *to, + int last UNUSED) { zip_state_T *zs = state->method_state; size_t i; @@ -137,7 +138,8 @@ crypt_zip_decode( cryptstate_T *state, char_u *from, size_t len, - char_u *to) + char_u *to, + int last UNUSED) { zip_state_T *zs = state->method_state; size_t i; diff --git a/src/errors.h b/src/errors.h index 63e2659f7..cb93efd90 100644 --- a/src/errors.h +++ b/src/errors.h @@ -427,3 +427,22 @@ EXTERN char e_call_to_function_that_failed_to_compile_str[] INIT(= N_("E1191: Call to function that failed to compile: %s")); EXTERN char e_empty_function_name[] INIT(= N_("E1192: Empty function name")); +// libsodium +EXTERN char e_libsodium_not_built_in[] + INIT(= N_("E1193: cryptmethod xchacha20 not built into this Vim")); +EXTERN char e_libsodium_cannot_encrypt_header[] + INIT(= N_("E1194: Cannot encrypt header, not enough space")); +EXTERN char e_libsodium_cannot_encrypt_buffer[] + INIT(= N_("E1195: Cannot encrypt buffer, not enough space")); +EXTERN char e_libsodium_cannot_decrypt_header[] + INIT(= N_("E1196: Cannot decrypt header, not enough space")); +EXTERN char e_libsodium_cannot_allocate_buffer[] + INIT(= N_("E1197: Cannot allocate_buffer for encryption")); +EXTERN char e_libsodium_decryption_failed_header_incomplete[] + INIT(= N_("E1198: Decryption failed: Header incomplete!")); +EXTERN char e_libsodium_cannot_decrypt_buffer[] + INIT(= N_("E1199: Cannot decrypt buffer, not enough space")); +EXTERN char e_libsodium_decription_failed[] + INIT(= N_("E1200: Decryption failed: corrupted chunk!")); +EXTERN char e_libsodium_decyption_failed_premature[] + INIT(= N_("E1201: Decryption failed: pre-mature end of file!")); diff --git a/src/evalfunc.c b/src/evalfunc.c index 97f7b6107..9267117da 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -5070,6 +5070,13 @@ f_has(typval_T *argvars, typval_T *rettv) 1 #else 0 +#endif + }, + {"sodium", +#ifdef FEAT_SODIUM + 1 +#else + 0 #endif }, {"sound", diff --git a/src/feature.h b/src/feature.h index 4df73d1cb..2ae739aa8 100644 --- a/src/feature.h +++ b/src/feature.h @@ -593,6 +593,13 @@ # define FEAT_SOUND_CANBERRA #endif +/* + * libsodium - add cryptography support + */ +#if defined(HAVE_SODIUM) && defined(FEAT_BIG) +# define FEAT_SODIUM +#endif + // There are two ways to use XPM. #if (defined(HAVE_XM_XPMP_H) && defined(FEAT_GUI_MOTIF)) \ || defined(HAVE_X11_XPM_H) diff --git a/src/fileio.c b/src/fileio.c index 91c02bfcd..e05dfa3a5 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -13,6 +13,10 @@ #include "vim.h" +#ifdef FEAT_SODIUM +# include +#endif + #if defined(__TANDEM) # include // for SSIZE_MAX #endif @@ -148,6 +152,8 @@ readfile( char_u *p; off_T filesize = 0; int skip_read = FALSE; + off_T filesize_disk = 0; // file size read from disk + off_T filesize_count = 0; // counter #ifdef FEAT_CRYPT char_u *cryptkey = NULL; int did_ask_for_key = FALSE; @@ -215,6 +221,7 @@ readfile( int using_b_ffname; int using_b_fname; static char *msg_is_a_directory = N_("is a directory"); + int eof; au_did_filetype = FALSE; // reset before triggering any autocommands @@ -405,6 +412,7 @@ readfile( { buf_store_time(curbuf, &st, fname); curbuf->b_mtime_read = curbuf->b_mtime; + filesize_disk = st.st_size; #ifdef UNIX /* * Use the protection bits of the original file for the swap file. @@ -1080,6 +1088,7 @@ retry: { linerest = 0; filesize = 0; + filesize_count = 0; skip_count = lines_to_skip; read_count = lines_to_read; conv_restlen = 0; @@ -1263,7 +1272,23 @@ retry: /* * Read bytes from the file. */ +# ifdef FEAT_SODIUM + // Let the crypt layer work with a buffer size of 8192 + if (filesize == 0) + // set size to 8K + Sodium Crypt Metadata + size = WRITEBUFSIZE + 36 + + crypto_secretstream_xchacha20poly1305_HEADERBYTES + + crypto_secretstream_xchacha20poly1305_ABYTES; + + else if (filesize > 0 && (curbuf->b_cryptstate != NULL && + curbuf->b_cryptstate->method_nr == CRYPT_M_SOD)) + size = WRITEBUFSIZE + crypto_secretstream_xchacha20poly1305_ABYTES; +# endif + eof = size; size = read_eintr(fd, ptr, size); + filesize_count += size; + // hit end of file + eof = (size < eof || filesize_count == filesize_disk); } #ifdef FEAT_CRYPT @@ -1285,7 +1310,8 @@ retry: if (crypt_works_inplace(curbuf->b_cryptstate)) { # endif - crypt_decode_inplace(curbuf->b_cryptstate, ptr, size); + crypt_decode_inplace(curbuf->b_cryptstate, ptr, + size, eof); # ifdef CRYPT_NOT_INPLACE } else @@ -1294,8 +1320,16 @@ retry: int decrypted_size; decrypted_size = crypt_decode_alloc( - curbuf->b_cryptstate, ptr, size, &newptr); + curbuf->b_cryptstate, ptr, size, + &newptr, eof); + if (decrypted_size < 0) + { + // error message already given + error = TRUE; + vim_free(newptr); + break; + } // If the crypt layer is buffering, not producing // anything yet, need to read more. if (decrypted_size == 0) @@ -1325,6 +1359,7 @@ retry: if (newptr != NULL) mch_memmove(new_buffer + linerest, newptr, decrypted_size); + vim_free(newptr); } if (new_buffer != NULL) @@ -1334,6 +1369,7 @@ retry: new_buffer = NULL; line_start = buffer; ptr = buffer + linerest; + real_size = size; } size = decrypted_size; } diff --git a/src/memline.c b/src/memline.c index 58582c9d6..3b09a7dd4 100644 --- a/src/memline.c +++ b/src/memline.c @@ -48,6 +48,11 @@ # include #endif +// for randombytes_buf +#ifdef FEAT_SODIUM +# include +#endif + #if defined(SASC) || defined(__amigaos4__) # include // for Open() and Close() #endif @@ -64,12 +69,14 @@ typedef struct pointer_entry PTR_EN; // block/line-count pair #define BLOCK0_ID1_C0 'c' // block 0 id 1 'cm' 0 #define BLOCK0_ID1_C1 'C' // block 0 id 1 'cm' 1 #define BLOCK0_ID1_C2 'd' // block 0 id 1 'cm' 2 +#define BLOCK0_ID1_C3 'S' // block 0 id 1 'cm' 3 - but not actually used #if defined(FEAT_CRYPT) static int id1_codes[] = { BLOCK0_ID1_C0, // CRYPT_M_ZIP BLOCK0_ID1_C1, // CRYPT_M_BF BLOCK0_ID1_C2, // CRYPT_M_BF2 + BLOCK0_ID1_C3, // CRYPT_M_SOD - Unused! }; #endif @@ -426,11 +433,15 @@ ml_set_mfp_crypt(buf_T *buf) { int method_nr = crypt_get_method_nr(buf); - if (method_nr > CRYPT_M_ZIP) + if (method_nr > CRYPT_M_ZIP && method_nr < CRYPT_M_SOD) { // Generate a seed and store it in the memfile. sha2_seed(buf->b_ml.ml_mfp->mf_seed, MF_SEED_LEN, NULL, 0); } +#ifdef FEAT_SODIUM + else if (method_nr == CRYPT_M_SOD) + randombytes_buf(buf->b_ml.ml_mfp->mf_seed, MF_SEED_LEN); + #endif } } @@ -447,7 +458,7 @@ ml_set_b0_crypt(buf_T *buf, ZERO_BL *b0p) int method_nr = crypt_get_method_nr(buf); b0p->b0_id[1] = id1_codes[method_nr]; - if (method_nr > CRYPT_M_ZIP) + if (method_nr > CRYPT_M_ZIP && method_nr < CRYPT_M_SOD) { // Generate a seed and store it in block 0 and in the memfile. sha2_seed(&b0p->b0_seed, MF_SEED_LEN, NULL, 0); @@ -482,10 +493,17 @@ ml_set_crypt_key( int top; int old_method; - if (mfp == NULL) + if (mfp == NULL || mfp->mf_fd < 0) return; // no memfile yet, nothing to do old_method = crypt_method_nr_from_name(old_cm); + if (old_method == CRYPT_M_SOD || crypt_get_method_nr(buf) == CRYPT_M_SOD) + { + // close the swapfile + mf_close_file(buf, TRUE); + buf->b_p_swf = FALSE; + return; + } // First make sure the swapfile is in a consistent state, using the old // key and method. { @@ -911,7 +929,8 @@ ml_check_b0_id(ZERO_BL *b0p) || (b0p->b0_id[1] != BLOCK0_ID1 && b0p->b0_id[1] != BLOCK0_ID1_C0 && b0p->b0_id[1] != BLOCK0_ID1_C1 - && b0p->b0_id[1] != BLOCK0_ID1_C2) + && b0p->b0_id[1] != BLOCK0_ID1_C2 + && b0p->b0_id[1] != BLOCK0_ID1_C3) ) return FAIL; return OK; @@ -2402,7 +2421,9 @@ ml_sync_all(int check_file, int check_char) FOR_ALL_BUFFERS(buf) { - if (buf->b_ml.ml_mfp == NULL || buf->b_ml.ml_mfp->mf_fname == NULL) + if (buf->b_ml.ml_mfp == NULL + || buf->b_ml.ml_mfp->mf_fname == NULL + || buf->b_ml.ml_mfp->mf_fd < 0) continue; // no file ml_flush_line(buf); // flush buffered line @@ -5320,7 +5341,8 @@ ml_encrypt_data( mch_memmove(new_data, dp, head_end - (char_u *)dp); // Encrypt the text. - crypt_encode(state, text_start, text_len, new_data + dp->db_txt_start); + crypt_encode(state, text_start, text_len, new_data + dp->db_txt_start, + FALSE); crypt_free_state(state); // Clear the gap. @@ -5360,7 +5382,7 @@ ml_decrypt_data( if (state != NULL) { // Decrypt the text in place. - crypt_decode_inplace(state, text_start, text_len); + crypt_decode_inplace(state, text_start, text_len, FALSE); crypt_free_state(state); } } @@ -5407,7 +5429,7 @@ ml_crypt_prepare(memfile_T *mfp, off_T offset, int reading) // 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); + seed, MF_SEED_LEN); } #endif diff --git a/src/option.c b/src/option.c index 535213c19..07bb71e4d 100644 --- a/src/option.c +++ b/src/option.c @@ -2713,6 +2713,10 @@ set_bool_option( || (opt_flags & OPT_GLOBAL) || opt_flags == 0) && !curbufIsChanged() && curbuf->b_ml.ml_mfp != NULL) { +#ifdef FEAT_CRYPT + if (crypt_get_method_nr(curbuf) == CRYPT_M_SOD) + continue; +#endif u_compute_hash(hash); u_read_undo(NULL, hash, curbuf->b_fname); } diff --git a/src/optionstr.c b/src/optionstr.c index f332294ed..47aee35be 100644 --- a/src/optionstr.c +++ b/src/optionstr.c @@ -24,7 +24,11 @@ static char *(p_bo_values[]) = {"all", "backspace", "cursor", "complete", static char *(p_nf_values[]) = {"bin", "octal", "hex", "alpha", "unsigned", NULL}; static char *(p_ff_values[]) = {FF_UNIX, FF_DOS, FF_MAC, NULL}; #ifdef FEAT_CRYPT -static char *(p_cm_values[]) = {"zip", "blowfish", "blowfish2", NULL}; +static char *(p_cm_values[]) = {"zip", "blowfish", "blowfish2", + # ifdef FEAT_SODIUM + "xchacha20", + # endif + NULL}; #endif static char *(p_cmp_values[]) = {"internal", "keepascii", NULL}; static char *(p_dy_values[]) = {"lastline", "truncate", "uhex", NULL}; diff --git a/src/proto/blowfish.pro b/src/proto/blowfish.pro index 5998fb358..6b2c45459 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); -void crypt_blowfish_decode(cryptstate_T *state, char_u *from, size_t len, char_u *to); +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 blowfish_self_test(void); /* vim: set ft=c : */ diff --git a/src/proto/crypt.pro b/src/proto/crypt.pro index e8ba18897..2e5803920 100644 --- a/src/proto/crypt.pro +++ b/src/proto/crypt.pro @@ -1,6 +1,7 @@ /* crypt.c */ 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_whole_undofile(int method_nr); int crypt_get_header_len(int method_nr); @@ -11,12 +12,19 @@ cryptstate_T *crypt_create_from_header(int method_nr, char_u *key, char_u *heade 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); void crypt_free_state(cryptstate_T *state); -void crypt_encode(cryptstate_T *state, char_u *from, size_t len, char_u *to); -void crypt_encode_inplace(cryptstate_T *state, char_u *buf, size_t len); -void crypt_decode_inplace(cryptstate_T *state, char_u *buf, size_t len); +long crypt_encode_alloc(cryptstate_T *state, char_u *from, size_t len, char_u **newptr, int last); +long crypt_decode_alloc(cryptstate_T *state, char_u *ptr, long len, char_u **newptr, int last); +void crypt_encode(cryptstate_T *state, char_u *from, size_t len, char_u *to, int last); +void crypt_encode_inplace(cryptstate_T *state, char_u *buf, size_t len, int last); +void crypt_decode_inplace(cryptstate_T *state, char_u *buf, size_t len, int last); void crypt_free_key(char_u *key); void crypt_check_method(int method); void crypt_check_current_method(void); char_u *crypt_get_key(int store, int twice); void crypt_append_msg(buf_T *buf); +int crypt_sodium_init(cryptstate_T *state, char_u *key, char_u *salt, int salt_len, char_u *seed, int seed_len); +void crypt_sodium_encode(cryptstate_T *state, char_u *from, size_t len, char_u *to, int last); +void crypt_sodium_decode(cryptstate_T *state, char_u *from, size_t len, char_u *to, int last); +long crypt_sodium_buffer_encode(cryptstate_T *state, char_u *from, size_t len, char_u **buf_out, int last); +long crypt_sodium_buffer_decode(cryptstate_T *state, char_u *from, size_t len, char_u **buf_out, int last); /* vim: set ft=c : */ diff --git a/src/proto/crypt_zip.pro b/src/proto/crypt_zip.pro index 868b131f0..626d9855b 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); -void crypt_zip_encode(cryptstate_T *state, char_u *from, size_t len, char_u *to); -void crypt_zip_decode(cryptstate_T *state, char_u *from, size_t len, char_u *to); +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 26ab315fe..d62b10f37 100644 --- a/src/structs.h +++ b/src/structs.h @@ -2513,11 +2513,12 @@ typedef struct { # define CRYPT_M_ZIP 0 # define CRYPT_M_BF 1 # define CRYPT_M_BF2 2 -# define CRYPT_M_COUNT 3 // number of crypt methods +# define CRYPT_M_SOD 3 +# define CRYPT_M_COUNT 4 // 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 +# define CRYPT_NOT_INPLACE 1 #endif #ifdef FEAT_PROP_POPUP diff --git a/src/testdir/samples/crypt_sodium_invalid.txt b/src/testdir/samples/crypt_sodium_invalid.txt new file mode 100644 index 000000000..35e31b5ac Binary files /dev/null and b/src/testdir/samples/crypt_sodium_invalid.txt differ diff --git a/src/testdir/test_crypt.vim b/src/testdir/test_crypt.vim index f386593ce..63bf8a5b2 100644 --- a/src/testdir/test_crypt.vim +++ b/src/testdir/test_crypt.vim @@ -22,6 +22,11 @@ func Test_head_only_3() call Common_head_only('VimCrypt~03!abc') endfunc +func Test_head_only_4() + CheckFeature sodium + call Common_head_only('VimCrypt~04!abc') +endfunc + func Crypt_uncrypt(method) exe "set cryptmethod=" . a:method " If the blowfish test fails 'cryptmethod' will be 'zip' now. @@ -55,6 +60,11 @@ func Test_crypt_blowfish2() call Crypt_uncrypt('blowfish2') endfunc +func Test_crypt_sodium() + CheckFeature sodium + call Crypt_uncrypt('xchacha20') +endfunc + func Uncrypt_stable(method, crypted_text, key, uncrypted_text) split Xtest.txt set bin noeol key= fenc=latin1 @@ -70,6 +80,16 @@ func Uncrypt_stable(method, crypted_text, key, uncrypted_text) set key= endfunc +func Uncrypt_stable_xxd(method, hex, key, uncrypted_text) + " use xxd to write the binary content + call system('xxd -r >Xtest.txt', a:hex) + call feedkeys(":split Xtest.txt\" . a:key . "\", 'xt') + call assert_equal(a:uncrypted_text, getline(1, len(a:uncrypted_text))) + bwipe! + call delete('Xtest.txt') + set key= +endfunc + func Test_uncrypt_zip() call Uncrypt_stable('zip', "VimCrypt~01!\u0006\u001clV'\u00de}Mg\u00a0\u00ea\u00a3V\u00a9\u00e7\u0007E#3\u008e2U\u00e9\u0097", "foofoo", ["1234567890", "aábbccddeëff"]) endfunc @@ -78,10 +98,115 @@ func Test_uncrypt_blowfish() call Uncrypt_stable('blowfish', "VimCrypt~02!k)\u00be\u0017\u0097#\u0016\u00ddS\u009c\u00f5=\u00ba\u00e0\u00c8#\u00a5M\u00b4\u0086J\u00c3A\u00cd\u00a5M\u00b4\u0086!\u0080\u0015\u009b\u00f5\u000f\u00e1\u00d2\u0019\u0082\u0016\u0098\u00f7\u000d\u00da", "barbar", ["asdfasdfasdf", "0001112223333"]) endfunc -func Test_uncrypt_blowfish2() +func Test_uncrypt_blowfish2a() call Uncrypt_stable('blowfish', "VimCrypt~03!\u001e\u00d1N\u00e3;\u00d3\u00c0\u00a0^C)\u0004\u00f7\u007f.\u00b6\u00abF\u000eS\u0019\u00e0\u008b6\u00d2[T\u00cb\u00a7\u0085\u00d8\u00be9\u000b\u00812\u000bQ\u00b3\u00cc@\u0097\u000f\u00df\u009a\u00adIv\u00aa.\u00d8\u00c9\u00ee\u009e`\u00bd$\u00af%\u00d0", "barburp", ["abcdefghijklmnopqrstuvwxyz", "!@#$%^&*()_+=-`~"]) endfunc +func Test_uncrypt_blowfish2() + call Uncrypt_stable('blowfish2', "VimCrypt~03!\u001e\u00d1N\u00e3;\u00d3\u00c0\u00a0^C)\u0004\u00f7\u007f.\u00b6\u00abF\u000eS\u0019\u00e0\u008b6\u00d2[T\u00cb\u00a7\u0085\u00d8\u00be9\u000b\u00812\u000bQ\u00b3\u00cc@\u0097\u000f\u00df\u009a\u00adIv\u00aa.\u00d8\u00c9\u00ee\u009e`\u00bd$\u00af%\u00d0", "barburp", ["abcdefghijklmnopqrstuvwxyz", "!@#$%^&*()_+=-`~"]) +endfunc + +func Test_uncrypt_xchacha20() + CheckFeature sodium + let hex=['00000000: 5669 6d43 7279 7074 7e30 3421 6b7d e607 vimCrypt~04!k}..', + \ '00000010: 4ea4 e99f 923e f67f 7b59 a80d 3bca 2f06 N....>..{Y..;./.', + \ '00000020: fa11 b951 8d09 0dc9 470f e7cf 8b90 4310 ...Q....G.....C.', + \ '00000030: 653b b83b e493 378b 0390 0e38 f912 626b e;.;..7....8..bk', + \ '00000040: a02e 4697 0254 2625 2d8e 3a0b 784b e89c ..F..T&%-.:.xK..', + \ '00000050: 0c67 a975 3c17 9319 8ffd 1463 7783 a1f3 .g.u<......cw...', + \ '00000060: d917 dcb3 8b3e ecd7 c7d4 086b 6059 7ead .....>.....k`Y~.', + \ '00000070: 9b07 f96b 5c1b 4d08 cd91 f208 5221 7484 ...k\.M.....R!t.', + \ '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_äüöÄÜÖ_!@#$%^&*()_+=-`~"]) +endfunc + +func Test_uncrypt_xchacha20_invalid() + CheckFeature sodium + " load an invalid encrypted file and verify it can be decrypted with an + " error message + try + call feedkeys(":split samples/crypt_sodium_invalid.txt\sodium\", 'xt') + call assert_false(1, 'should not happen') + catch + call assert_exception('pre-mature') + endtry + call assert_match("Note: Encryption of swapfile not supported, disabling swap- and undofile", execute(':5messages')) + + call assert_equal(0, &swapfile) + call assert_equal("xchacha20", &cryptmethod) + call assert_equal('311111111111111111111111', getline('$')) + bw! +endfunc + +func Test_uncrypt_xchacha20_2() + CheckFeature sodium + sp Xcrypt_sodium.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=xchacha20 + call feedkeys(":X\sodium\sodium\", 'xt') + " swapfile disabled + call assert_equal(0, &swapfile) + call assert_match("Note: Encryption of swapfile not supported, disabling swap- and undofile", execute(':messages')) + w! + " encrypted using xchacha20 + call assert_match("\[xchacha20\]", execute(':messages')) + bw! + call feedkeys(":sp Xcrypt_sodium.txt\sodium\", 'xt') + " successfully decrypted + call assert_equal(range(1, 4000)->map( {_, v -> string(v)}), getline(1,'$')) + set key= + w! + " enryption removed + call assert_match('"Xcrypt_sodium.txt" 4000L, 18893B written', execute(':message')) + bw! + call delete('Xcrypt_sodium.txt') + set cryptmethod&vim +endfunc + +func Test_uncrypt_xchacha20_3_persistent_undo() + CheckFeature sodium + CheckFeature persistent_undo + sp Xcrypt_sodium_undo.txt + set cryptmethod=xchacha20 undofile + call feedkeys(":X\sodium\sodium\", 'xt') + call assert_equal(0, &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! + 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') +endfunc + +func Test_encrypt_xchacha20_missing() + if has("sodium") + return + endif + sp Xcrypt_sodium_undo.txt + call assert_fails(':set cryptmethod=xchacha20', 'E474') + bw! + set cm& +endfunc + func Test_uncrypt_unknown_method() split Xuncrypt_unknown.txt set bin noeol key= fenc=latin1 diff --git a/src/undo.c b/src/undo.c index 12f3e968f..4b1158a69 100644 --- a/src/undo.c +++ b/src/undo.c @@ -963,7 +963,9 @@ undo_flush(bufinfo_T *bi) { if (bi->bi_buffer != NULL && bi->bi_state != NULL && bi->bi_used > 0) { - crypt_encode_inplace(bi->bi_state, bi->bi_buffer, bi->bi_used); + // Last parameter is only used for sodium encryption and that + // explicitly disables encryption of undofiles. + crypt_encode_inplace(bi->bi_state, bi->bi_buffer, bi->bi_used, FALSE); if (fwrite(bi->bi_buffer, bi->bi_used, (size_t)1, bi->bi_fp) != 1) return FAIL; bi->bi_used = 0; @@ -995,7 +997,9 @@ fwrite_crypt(bufinfo_T *bi, char_u *ptr, size_t len) if (copy == NULL) return 0; } - crypt_encode(bi->bi_state, ptr, len, copy); + // Last parameter is only used for sodium encryption and that + // explicitly disables encryption of undofiles. + crypt_encode(bi->bi_state, ptr, len, copy, TRUE); i = fwrite(copy, len, (size_t)1, bi->bi_fp); if (copy != small_buf) vim_free(copy); @@ -1129,7 +1133,7 @@ undo_read(bufinfo_T *bi, char_u *buffer, size_t size) } bi->bi_avail = n; bi->bi_used = 0; - crypt_decode_inplace(bi->bi_state, bi->bi_buffer, bi->bi_avail); + crypt_decode_inplace(bi->bi_state, bi->bi_buffer, bi->bi_avail, FALSE); } n = size_todo; if (n > bi->bi_avail - bi->bi_used) @@ -1176,7 +1180,7 @@ read_string_decrypt(bufinfo_T *bi, int len) ptr[len] = NUL; #ifdef FEAT_CRYPT if (bi->bi_state != NULL && bi->bi_buffer == NULL) - crypt_decode_inplace(bi->bi_state, ptr, len); + crypt_decode_inplace(bi->bi_state, ptr, len, FALSE); #endif } return ptr; diff --git a/src/version.c b/src/version.c index d217fe615..646481612 100644 --- a/src/version.c +++ b/src/version.c @@ -553,6 +553,11 @@ static char *(features[]) = #else "-smartindent", #endif +#ifdef FEAT_SODIUM + "+sodium", +#else + "-sodium", +#endif #ifdef FEAT_SOUND "+sound", #else @@ -750,6 +755,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 3022, /**/ 3021, /**/ -- cgit v1.2.1