# HG changeset patch # User Bram Moolenaar # Date 1275252482 -7200 # Node ID 732cb7b3195607be513ecc08d65130993c2dcd88 # Parent 3d0a7beb0d75b4d4e52d6898df26f90ccd0b4b5c Crypt the text in the undo file if the file itself is crypted. diff --git a/runtime/compiler/perl.vim b/runtime/compiler/perl.vim --- a/runtime/compiler/perl.vim +++ b/runtime/compiler/perl.vim @@ -1,6 +1,6 @@ " Vim Compiler File " Compiler: Perl syntax checks (perl -Wc) -" Maintainer: Christian J. Robinson +" Maintainer: Christian J. Robinson " Last Change: 2006 Aug 13 if exists("current_compiler") diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt --- a/runtime/doc/editing.txt +++ b/runtime/doc/editing.txt @@ -1366,6 +1366,9 @@ this before writing the file. When read automatically to the method used when that file was written. You can change 'cryptmethod' before writing that file to change the method. +When writing an undo file, the same key and method will be used for the text +in the undo file. |persistent-undo|. + *E817* *E818* *E819* *E820* When encryption does not work properly, you would be able to write your text to a file and never be able to read it back. Therefore a test is performed to diff --git a/runtime/doc/todo.txt b/runtime/doc/todo.txt --- a/runtime/doc/todo.txt +++ b/runtime/doc/todo.txt @@ -1085,9 +1085,6 @@ Vim 7.3: - using NSIS 2.46: install on Windows 7 works, but no "Edit with Vim" menu. Use register_shell_extension()? (George Reilly, 2010 May 26) Ron's version: http://dev.ronware.org/p/vim/finfo?name=gvim.nsi -- Persistent undo bugs / fixes: - - Need to check all values for evil manipulation. -- Also crypt the undo file. - Also crypt the swap file, each block separately. Change mf_write() and mf_read(). How to get b_p_key to these functions? - Do profiling on sha256 code to find obvious bottlenecks. diff --git a/runtime/doc/undo.txt b/runtime/doc/undo.txt --- a/runtime/doc/undo.txt +++ b/runtime/doc/undo.txt @@ -225,6 +225,9 @@ after the undo file was written, to prev Undo files are normally saved in the same directory as the file. This can be changed with the 'undodir' option. +When the file is encrypted, the text in the undo file is also crypted. The +same key and method is used. |encryption| + You can also save and restore undo histories by using ":wundo" and ":rundo" respectively: *:wundo* *:rundo* diff --git a/runtime/indent/tf.vim b/runtime/indent/tf.vim --- a/runtime/indent/tf.vim +++ b/runtime/indent/tf.vim @@ -1,7 +1,7 @@ " Vim indent file " Language: tf (TinyFugue) -" Maintainer: Christian J. Robinson -" URL: http://www.infynity.spodzone.com/vim/indent/tf.vim +" Maintainer: Christian J. Robinson +" URL: http://christianrobinson.name/vim/indent/tf.vim " Last Change: 2002 May 29 " Only load this indent file when no other was loaded. diff --git a/src/fileio.c b/src/fileio.c --- a/src/fileio.c +++ b/src/fileio.c @@ -2827,7 +2827,7 @@ check_marks_read() } #endif -#ifdef FEAT_CRYPT +#if defined(FEAT_CRYPT) || defined(PROTO) /* * Get the crypt method used for a file from "ptr[len]", the magic text at the * start of the file. @@ -2926,7 +2926,129 @@ check_for_cryptkey(cryptkey, ptr, sizep, return cryptkey; } -#endif + +/* + * Check for magic number used for encryption. Applies to the current buffer. + * If found and decryption is possible returns OK; + */ + int +prepare_crypt_read(fp) + FILE *fp; +{ + int method; + char_u buffer[CRYPT_MAGIC_LEN + CRYPT_SEED_LEN_MAX + 2]; + + if (fread(buffer, CRYPT_MAGIC_LEN, 1, fp) != 1) + return FAIL; + method = get_crypt_method((char *)buffer, + CRYPT_MAGIC_LEN + CRYPT_SEED_LEN_MAX); + if (method < 0 || method != curbuf->b_p_cm) + return FAIL; + + if (method == 0) + crypt_init_keys(curbuf->b_p_key); + else + { + int seed_len = crypt_seed_len[method]; + + if (fread(buffer, seed_len, 1, fp) != 1) + return FAIL; + bf_key_init(curbuf->b_p_key); + bf_ofb_init(buffer, seed_len); + } + return OK; +} + +/* + * Prepare for writing encrypted bytes for buffer "buf". + * Returns a pointer to an allocated header of length "*lenp". + */ + char_u * +prepare_crypt_write(buf, lenp) + buf_T *buf; + int *lenp; +{ + char_u *header; + int seed_len = crypt_seed_len[buf->b_p_cm]; + + header = alloc_clear(CRYPT_MAGIC_LEN + CRYPT_SEED_LEN_MAX + 2); + if (header != NULL) + { + use_crypt_method = buf->b_p_cm; /* select pkzip or blowfish */ + vim_strncpy(header, (char_u *)crypt_magic[use_crypt_method], + CRYPT_MAGIC_LEN); + if (buf->b_p_cm == 0) + crypt_init_keys(buf->b_p_key); + else + { + /* Using blowfish, add seed. */ + sha2_seed(header + CRYPT_MAGIC_LEN, seed_len); /* create iv */ + bf_ofb_init(header + CRYPT_MAGIC_LEN, seed_len); + bf_key_init(buf->b_p_key); + } + } + *lenp = CRYPT_MAGIC_LEN + seed_len; + return header; +} + +/* + * Like fwrite() but crypt the bytes when 'key' is set. + * Returns 1 if successful. + */ + size_t +fwrite_crypt(buf, ptr, len, fp) + buf_T *buf; + char_u *ptr; + size_t len; + FILE *fp; +{ + char_u *copy; + char_u small_buf[100]; + int ztemp, t; + size_t i; + + if (*buf->b_p_key == NUL) + return fwrite(ptr, len, (size_t)1, fp); + if (len < 100) + copy = small_buf; /* no malloc()/free() for short strings */ + else + { + copy = lalloc(len, FALSE); + if (copy == NULL) + return 0; + } + for (i = 0; i < len; ++i) + { + ztemp = ptr[i]; + copy[i] = ZENCODE(ztemp, t); + } + i = fwrite(copy, len, (size_t)1, fp); + if (copy != small_buf) + vim_free(copy); + return i; +} + +/* + * Read a string of length "len" from "fd". + * When 'key' is set decrypt the bytes. + */ + char_u * +read_string_decrypt(buf, fd, len) + buf_T *buf; + FILE *fd; + int len; +{ + char_u *ptr; + char_u *p; + + ptr = read_string(fd, len); + if (ptr != NULL || *buf->b_p_key != NUL) + for (p = ptr; p < ptr + len; ++p) + ZDECODE(*p); + return ptr; +} + +#endif /* FEAT_CRYPT */ #ifdef UNIX static void @@ -4323,34 +4445,25 @@ restore_backup: #ifdef FEAT_CRYPT if (*buf->b_p_key && !filtering) { - char_u header[CRYPT_MAGIC_LEN + CRYPT_SEED_LEN_MAX + 2]; - int seed_len = crypt_seed_len[buf->b_p_cm]; - - use_crypt_method = buf->b_p_cm; /* select pkzip or blowfish */ - - vim_memset(header, 0, sizeof(header)); - vim_strncpy(header, (char_u *)crypt_magic[use_crypt_method], - CRYPT_MAGIC_LEN); - - if (buf->b_p_cm == 0) - crypt_init_keys(buf->b_p_key); + char_u *header; + int header_len; + + header = prepare_crypt_write(buf, &header_len); + if (header == NULL) + end = 0; else { - /* Using blowfish, add seed. */ - sha2_seed(header + CRYPT_MAGIC_LEN, seed_len); /* create iv */ - bf_ofb_init(header + CRYPT_MAGIC_LEN, seed_len); - bf_key_init(buf->b_p_key); - } - - /* Write magic number, so that Vim knows that this file is - * encrypted when reading it again. This also undergoes utf-8 to - * ucs-2/4 conversion when needed. */ - write_info.bw_buf = (char_u *)header; - write_info.bw_len = CRYPT_MAGIC_LEN + seed_len; - write_info.bw_flags = FIO_NOCONVERT; - if (buf_write_bytes(&write_info) == FAIL) - end = 0; - wb_flags |= FIO_ENCRYPTED; + /* Write magic number, so that Vim knows that this file is + * encrypted when reading it again. This also undergoes utf-8 to + * ucs-2/4 conversion when needed. */ + write_info.bw_buf = header; + write_info.bw_len = header_len; + write_info.bw_flags = FIO_NOCONVERT; + if (buf_write_bytes(&write_info) == FAIL) + end = 0; + wb_flags |= FIO_ENCRYPTED; + vim_free(header); + } } #endif @@ -5558,7 +5671,7 @@ buf_write_bytes(ip) for (i = 0; i < len; i++) { - ztemp = buf[i]; + ztemp = buf[i]; buf[i] = ZENCODE(ztemp, t); } } diff --git a/src/proto/fileio.pro b/src/proto/fileio.pro --- a/src/proto/fileio.pro +++ b/src/proto/fileio.pro @@ -2,6 +2,10 @@ void filemess __ARGS((buf_T *buf, char_u *name, char_u *s, int attr)); int readfile __ARGS((char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_skip, linenr_T lines_to_read, exarg_T *eap, int flags)); int prep_exarg __ARGS((exarg_T *eap, buf_T *buf)); +int prepare_crypt_read __ARGS((FILE *fp)); +char_u *prepare_crypt_write __ARGS((buf_T *buf, int *lenp)); +size_t fwrite_crypt __ARGS((buf_T *buf, char_u *ptr, size_t len, FILE *fp)); +char_u *read_string_decrypt __ARGS((buf_T *buf, FILE *fd, int len)); int check_file_readonly __ARGS((char_u *fname, int perm)); int buf_write __ARGS((buf_T *buf, char_u *fname, char_u *sfname, linenr_T start, linenr_T end, exarg_T *eap, int append, int forceit, int reset_changed, int filtering)); void msg_add_fname __ARGS((buf_T *buf, char_u *fname)); diff --git a/src/testdir/test72.in b/src/testdir/test72.in --- a/src/testdir/test72.in +++ b/src/testdir/test72.in @@ -49,6 +49,55 @@ dd:set ul=100 :e Xtestfile uuu:w >>test.out :" +:" And now with encryption, cryptmethod=0 +:e! Xtestfile +:set undofile cm=0 +ggdG +imonday +tuesday +wednesday +thursday +friday:set ul=100 +kkkdd:set ul=100 +dd:set ul=100 +dd:set ul=100 +:X +foobar +foobar +:w! +:bwipe! +:e Xtestfile +foobar +:set key= +uu:w >>test.out +:" +:" +:" With encryption, cryptmethod=1 +:e! Xtestfile +:set undofile cm=1 +ggdG +ijan +feb +mar +apr +jun:set ul=100 +kk0ifoo :set ul=100 +dd:set ul=100 +ibar :set ul=100 +:X +foobar +foobar +:w! +:bwipe! +:e Xtestfile +foobar +:set key= +/bar +:.w >>test.out +u:.w >>test.out +u:.w >>test.out +u:.w >>test.out +:" :" Rename the undo file so that it gets cleaned up. :call rename(".Xtestfile.un~", "Xtestundo") :qa! diff --git a/src/testdir/test72.ok b/src/testdir/test72.ok --- a/src/testdir/test72.ok +++ b/src/testdir/test72.ok @@ -7,3 +7,11 @@ seven eight nine ten +monday +wednesday +thursday +friday +bar apr +apr +foo mar +mar diff --git a/src/undo.c b/src/undo.c --- a/src/undo.c +++ b/src/undo.c @@ -103,9 +103,9 @@ static void u_freeentry __ARGS((u_entry_ static void corruption_error __ARGS((char *msg, char_u *file_name)); static void u_free_uhp __ARGS((u_header_T *uhp)); static int serialize_header __ARGS((FILE *fp, buf_T *buf, char_u *hash)); -static int serialize_uhp __ARGS((FILE *fp, u_header_T *uhp)); +static int serialize_uhp __ARGS((FILE *fp, buf_T *buf, u_header_T *uhp)); static u_header_T *unserialize_uhp __ARGS((FILE *fp, char_u *file_name)); -static int serialize_uep __ARGS((u_entry_T *uep, FILE *fp)); +static int serialize_uep __ARGS((FILE *fp, buf_T *buf, u_entry_T *uep)); static u_entry_T *unserialize_uep __ARGS((FILE *fp, int *error, char_u *file_name)); static void serialize_pos __ARGS((pos_T pos, FILE *fp)); static void unserialize_pos __ARGS((pos_T *pos, FILE *fp)); @@ -670,6 +670,7 @@ nomem: # define UF_ENTRY_MAGIC 0xf518 /* magic at start of entry */ # define UF_ENTRY_END_MAGIC 0x3581 /* magic after last entry */ # define UF_VERSION 1 /* 2-byte undofile version number */ +# define UF_VERSION_CRYPT 0x8001 /* idem, encrypted */ static char_u e_not_open[] = N_("E828: Cannot open undo file for writing: %s"); @@ -811,7 +812,28 @@ serialize_header(fp, buf, hash) /* Start writing, first the magic marker and undo info version. */ if (fwrite(UF_START_MAGIC, (size_t)UF_START_MAGIC_LEN, (size_t)1, fp) != 1) return FAIL; - put_bytes(fp, (long_u)UF_VERSION, 2); + + /* If the buffer is encrypted then all text bytes following will be + * encrypted. Numbers and other info is not crypted. */ +#ifdef FEAT_CRYPT + if (*buf->b_p_key) + { + char_u *header; + int header_len; + + put_bytes(fp, (long_u)UF_VERSION_CRYPT, 2); + header = prepare_crypt_write(buf, &header_len); + if (header == NULL) + return FAIL; + len = fwrite(header, (size_t)header_len, (size_t)1, fp); + vim_free(header); + if (len != 1) + return FAIL; + } + else +#endif + put_bytes(fp, (long_u)UF_VERSION, 2); + /* Write a hash of the buffer text, so that we can verify it is still the * same when reading the buffer text. */ @@ -822,7 +844,7 @@ serialize_header(fp, buf, hash) put_bytes(fp, (long_u)buf->b_ml.ml_line_count, 4); len = buf->b_u_line_ptr != NULL ? (int)STRLEN(buf->b_u_line_ptr) : 0; put_bytes(fp, (long_u)len, 4); - if (len > 0 && fwrite(buf->b_u_line_ptr, (size_t)len, (size_t)1, fp) != 1) + if (len > 0 && fwrite_crypt(buf, buf->b_u_line_ptr, (size_t)len, fp) != 1) return FAIL; put_bytes(fp, (long_u)buf->b_u_line_lnum, 4); put_bytes(fp, (long_u)buf->b_u_line_colnr, 4); @@ -841,8 +863,9 @@ serialize_header(fp, buf, hash) } static int -serialize_uhp(fp, uhp) +serialize_uhp(fp, buf, uhp) FILE *fp; + buf_T *buf; u_header_T *uhp; { int i; @@ -882,7 +905,7 @@ serialize_uhp(fp, uhp) for (uep = uhp->uh_entry; uep != NULL; uep = uep->ue_next) { put_bytes(fp, (long_u)UF_ENTRY_MAGIC, 2); - if (serialize_uep(uep, fp) == FAIL) + if (serialize_uep(fp, buf, uep) == FAIL) return FAIL; } put_bytes(fp, (long_u)UF_ENTRY_END_MAGIC, 2); @@ -971,9 +994,10 @@ unserialize_uhp(fp, file_name) * Serialize "uep" to "fp". */ static int -serialize_uep(uep, fp) +serialize_uep(fp, buf, uep) + FILE *fp; + buf_T *buf; u_entry_T *uep; - FILE *fp; { int i; size_t len; @@ -987,7 +1011,7 @@ serialize_uep(uep, fp) len = STRLEN(uep->ue_array[i]); if (put_bytes(fp, (long_u)len, 4) == FAIL) return FAIL; - if (len > 0 && fwrite(uep->ue_array[i], len, (size_t)1, fp) != 1) + if (len > 0 && fwrite_crypt(buf, uep->ue_array[i], len, fp) != 1) return FAIL; } return OK; @@ -1034,7 +1058,7 @@ unserialize_uep(fp, error, file_name) { line_len = get4c(fp); if (line_len >= 0) - line = read_string(fp, line_len); + line = read_string_decrypt(curbuf, fp, line_len); else { line = NULL; @@ -1333,7 +1357,7 @@ u_write_undo(name, forceit, buf, hash) #ifdef U_DEBUG ++headers_written; #endif - if (serialize_uhp(fp, uhp) == FAIL) + if (serialize_uhp(fp, buf, uhp) == FAIL) goto write_error; } @@ -1478,7 +1502,20 @@ u_read_undo(name, hash, orig_name) goto error; } version = get2c(fp); - if (version != UF_VERSION) + if (version == UF_VERSION_CRYPT) + { +#ifdef FEAT_CRYPT + if (prepare_crypt_read(fp) == FAIL) + { + EMSG2(_("E826: Undo file decryption failed: %s"), file_name); + goto error; + } +#else + EMSG2(_("E826: Undo file is encrypted: %s"), file_name); + goto error; +#endif + } + else if (version != UF_VERSION) { EMSG2(_("E824: Incompatible undo file: %s"), file_name); goto error; @@ -1510,7 +1547,7 @@ u_read_undo(name, hash, orig_name) if (str_len < 0) goto error; if (str_len > 0) - line_ptr = read_string(fp, str_len); + line_ptr = read_string_decrypt(curbuf, fp, str_len); line_lnum = (linenr_T)get4c(fp); line_colnr = (colnr_T)get4c(fp); if (line_lnum < 0 || line_colnr < 0) @@ -1634,10 +1671,6 @@ u_read_undo(name, hash, orig_name) curbuf->b_u_oldhead = old_idx < 0 ? NULL : uhp_table[old_idx]; curbuf->b_u_newhead = new_idx < 0 ? NULL : uhp_table[new_idx]; curbuf->b_u_curhead = cur_idx < 0 ? NULL : uhp_table[cur_idx]; -#ifdef U_DEBUG - if (curbuf->b_u_curhead != NULL) - corruption_error("curhead not NULL", file_name); -#endif curbuf->b_u_line_ptr = line_ptr; curbuf->b_u_line_lnum = line_lnum; curbuf->b_u_line_colnr = line_colnr;