diff src/fileio.c @ 6122:18ac55444b37 v7.4.399

updated for version 7.4.399 Problem: Encryption implementation is messy. Blowfish encryption has a weakness. Solution: Refactor the encryption, store the state in an allocated struct instead of using a save/restore mechanism. Introduce the "blowfish2" method, which does not have the weakness and encrypts the whole undo file. (largely by David Leadbeater)
author Bram Moolenaar <bram@vim.org>
date Sun, 10 Aug 2014 13:38:34 +0200
parents f7bc601823e5
children 86fb698a38d5
line wrap: on
line diff
--- a/src/fileio.c
+++ b/src/fileio.c
@@ -24,20 +24,6 @@
 #define BUFSIZE		8192	/* size of normal write buffer */
 #define SMBUFSIZE	256	/* size of emergency write buffer */
 
-#ifdef FEAT_CRYPT
-/* crypt_magic[0] is pkzip crypt, crypt_magic[1] is sha2+blowfish */
-static char	*crypt_magic[] = {"VimCrypt~01!", "VimCrypt~02!"};
-static char	crypt_magic_head[] = "VimCrypt~";
-# define CRYPT_MAGIC_LEN	12		/* must be multiple of 4! */
-
-/* For blowfish, after the magic header, we store 8 bytes of salt and then 8
- * bytes of seed (initialisation vector). */
-static int	crypt_salt_len[] = {0, 8};
-static int	crypt_seed_len[] = {0, 8};
-#define CRYPT_SALT_LEN_MAX 8
-#define CRYPT_SEED_LEN_MAX 8
-#endif
-
 /* Is there any system that doesn't have access()? */
 #define USE_MCH_ACCESS
 
@@ -55,7 +41,6 @@ static char_u *readfile_charconvert __AR
 static void check_marks_read __ARGS((void));
 #endif
 #ifdef FEAT_CRYPT
-static int crypt_method_from_magic __ARGS((char *ptr, int len));
 static char_u *check_for_cryptkey __ARGS((char_u *cryptkey, char_u *ptr, long *sizep, off_t *filesizep, int newfile, char_u *fname, int *did_ask));
 #endif
 #ifdef UNIX
@@ -116,6 +101,9 @@ struct bw_info
 #ifdef HAS_BW_FLAGS
     int		bw_flags;	/* FIO_ flags */
 #endif
+#ifdef FEAT_CRYPT
+    buf_T	*bw_buffer;	/* buffer being written */
+#endif
 #ifdef FEAT_MBYTE
     char_u	bw_rest[CONV_RESTLEN]; /* not converted bytes */
     int		bw_restlen;	/* nr of bytes in bw_rest[] */
@@ -250,7 +238,6 @@ readfile(fname, sfname, from, lines_to_s
 #ifdef FEAT_CRYPT
     char_u	*cryptkey = NULL;
     int		did_ask_for_key = FALSE;
-    int		crypt_method_used;
 #endif
 #ifdef FEAT_PERSISTENT_UNDO
     context_sha256_T sha_ctx;
@@ -966,13 +953,6 @@ retry:
 #endif
     }
 
-#ifdef FEAT_CRYPT
-    if (cryptkey != NULL)
-	/* Need to reset the state, but keep the key, don't want to ask for it
-	 * again. */
-	crypt_pop_state();
-#endif
-
     /*
      * When retrying with another "fenc" and the first time "fileformat"
      * will be reset.
@@ -1175,6 +1155,15 @@ retry:
 	if (read_undo_file)
 	    sha256_start(&sha_ctx);
 #endif
+#ifdef FEAT_CRYPT
+	if (curbuf->b_cryptstate != NULL)
+	{
+	    /* Need to free the state, but keep the key, don't want to ask for
+	     * it again. */
+	    crypt_free_state(curbuf->b_cryptstate);
+	    curbuf->b_cryptstate = NULL;
+	}
+#endif
     }
 
     while (!error && !got_int)
@@ -1339,6 +1328,76 @@ retry:
 		    size = read_eintr(fd, ptr, size);
 		}
 
+#ifdef FEAT_CRYPT
+		/*
+		 * At start of file: Check for magic number of encryption.
+		 */
+		if (filesize == 0 && size > 0)
+		    cryptkey = check_for_cryptkey(cryptkey, ptr, &size,
+						  &filesize, newfile, sfname,
+						  &did_ask_for_key);
+		/*
+		 * Decrypt the read bytes.  This is done before checking for
+		 * EOF because the crypt layer may be buffering.
+		 */
+		if (cryptkey != NULL && size > 0)
+		{
+		    if (crypt_works_inplace(curbuf->b_cryptstate))
+		    {
+			crypt_decode_inplace(curbuf->b_cryptstate, ptr, size);
+		    }
+		    else
+		    {
+			char_u	*newptr = NULL;
+			int	decrypted_size;
+
+			decrypted_size = crypt_decode_alloc(
+				    curbuf->b_cryptstate, ptr, size, &newptr);
+
+			/* If the crypt layer is buffering, not producing
+			 * anything yet, need to read more. */
+			if (size > 0 && decrypted_size == 0)
+			    continue;
+
+			if (linerest == 0)
+			{
+			    /* Simple case: reuse returned buffer (may be
+			     * NULL, checked later). */
+			    new_buffer = newptr;
+			}
+			else
+			{
+			    long_u	new_size;
+
+			    /* Need new buffer to add bytes carried over. */
+			    new_size = (long_u)(decrypted_size + linerest + 1);
+			    new_buffer = lalloc(new_size, FALSE);
+			    if (new_buffer == NULL)
+			    {
+				do_outofmem_msg(new_size);
+				error = TRUE;
+				break;
+			    }
+
+			    mch_memmove(new_buffer, buffer, linerest);
+			    if (newptr != NULL)
+				mch_memmove(new_buffer + linerest, newptr,
+							      decrypted_size);
+			}
+
+			if (new_buffer != NULL)
+			{
+			    vim_free(buffer);
+			    buffer = new_buffer;
+			    new_buffer = NULL;
+			    line_start = buffer;
+			    ptr = buffer + linerest;
+			}
+			size = decrypted_size;
+		    }
+		}
+#endif
+
 		if (size <= 0)
 		{
 		    if (size < 0)		    /* read error */
@@ -1403,21 +1462,6 @@ retry:
 		    }
 #endif
 		}
-
-#ifdef FEAT_CRYPT
-		/*
-		 * At start of file: Check for magic number of encryption.
-		 */
-		if (filesize == 0)
-		    cryptkey = check_for_cryptkey(cryptkey, ptr, &size,
-					&filesize, newfile, sfname,
-					&did_ask_for_key);
-		/*
-		 * Decrypt the read bytes.
-		 */
-		if (cryptkey != NULL && size > 0)
-		    crypt_decode(ptr, size);
-#endif
 	    }
 	    skip_read = FALSE;
 
@@ -1430,10 +1474,9 @@ retry:
 	     */
 	    if ((filesize == 0
 # ifdef FEAT_CRYPT
-		   || (filesize == (CRYPT_MAGIC_LEN
-					   + crypt_salt_len[use_crypt_method]
-					   + crypt_seed_len[use_crypt_method])
-							  && cryptkey != NULL)
+		   || (cryptkey != NULL
+			&& filesize == crypt_get_header_len(
+						 crypt_get_method_nr(curbuf)))
 # endif
 		       )
 		    && (fio_flags == FIO_UCSBOM
@@ -2262,15 +2305,15 @@ failed:
 	save_file_ff(curbuf);		/* remember the current file format */
 
 #ifdef FEAT_CRYPT
-    crypt_method_used = use_crypt_method;
-    if (cryptkey != NULL)
-    {
-	crypt_pop_state();
-	if (cryptkey != curbuf->b_p_key)
-	    free_crypt_key(cryptkey);
-	/* don't set cryptkey to NULL, it's used below as a flag that
-	 * encryption was used */
-    }
+    if (curbuf->b_cryptstate != NULL)
+    {
+	crypt_free_state(curbuf->b_cryptstate);
+	curbuf->b_cryptstate = NULL;
+    }
+    if (cryptkey != NULL && cryptkey != curbuf->b_p_key)
+	crypt_free_key(cryptkey);
+    /* Don't set cryptkey to NULL, it's used below as a flag that
+     * encryption was used. */
 #endif
 
 #ifdef FEAT_MBYTE
@@ -2457,10 +2500,7 @@ failed:
 #ifdef FEAT_CRYPT
 	    if (cryptkey != NULL)
 	    {
-		if (crypt_method_used == 1)
-		    STRCAT(IObuff, _("[blowfish]"));
-		else
-		    STRCAT(IObuff, _("[crypted]"));
+		crypt_append_msg(curbuf);
 		c = TRUE;
 	    }
 #endif
@@ -2489,9 +2529,7 @@ failed:
 #ifdef FEAT_CRYPT
 	    if (cryptkey != NULL)
 		msg_add_lines(c, (long)linecnt, filesize
-			- CRYPT_MAGIC_LEN
-			- crypt_salt_len[use_crypt_method]
-			- crypt_seed_len[use_crypt_method]);
+			 - crypt_get_header_len(crypt_get_method_nr(curbuf)));
 	    else
 #endif
 		msg_add_lines(c, (long)linecnt, filesize);
@@ -2882,33 +2920,6 @@ check_marks_read()
 
 #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.
- * Returns -1 when no encryption used.
- */
-    static int
-crypt_method_from_magic(ptr, len)
-    char  *ptr;
-    int   len;
-{
-    int i;
-
-    for (i = 0; i < (int)(sizeof(crypt_magic) / sizeof(crypt_magic[0])); i++)
-    {
-	if (len < (CRYPT_MAGIC_LEN + crypt_salt_len[i] + crypt_seed_len[i]))
-	    continue;
-	if (memcmp(ptr, crypt_magic[i], CRYPT_MAGIC_LEN) == 0)
-	    return i;
-    }
-
-    i = (int)STRLEN(crypt_magic_head);
-    if (len >= i && memcmp(ptr, crypt_magic_head, i) == 0)
-	EMSG(_("E821: File is encrypted with unknown method"));
-
-    return -1;
-}
-
-/*
  * Check for magic number used for encryption.  Applies to the current buffer.
  * If found, the magic number is removed from ptr[*sizep] and *sizep and
  * *filesizep are updated.
@@ -2924,7 +2935,7 @@ check_for_cryptkey(cryptkey, ptr, sizep,
     char_u	*fname;		/* file name to display */
     int		*did_ask;	/* flag: whether already asked for key */
 {
-    int method = crypt_method_from_magic((char *)ptr, *sizep);
+    int method = crypt_method_nr_from_magic((char *)ptr, *sizep);
     int b_p_ro = curbuf->b_p_ro;
 
     if (method >= 0)
@@ -2933,9 +2944,7 @@ check_for_cryptkey(cryptkey, ptr, sizep,
 	 * Avoids accidentally overwriting the file with garbage. */
 	curbuf->b_p_ro = TRUE;
 
-	set_crypt_method(curbuf, method);
-	if (method > 0)
-	    (void)blowfish_self_test();
+	crypt_set_cm_option(curbuf, method);
 	if (cryptkey == NULL && !*did_ask)
 	{
 	    if (*curbuf->b_p_key)
@@ -2948,7 +2957,7 @@ check_for_cryptkey(cryptkey, ptr, sizep,
 		 * Happens when retrying to detect encoding. */
 		smsg((char_u *)_(need_key_msg), fname);
 		msg_scroll = TRUE;
-		cryptkey = get_crypt_key(newfile, FALSE);
+		cryptkey = crypt_get_key(newfile, FALSE);
 		*did_ask = TRUE;
 
 		/* check if empty key entered */
@@ -2963,24 +2972,18 @@ check_for_cryptkey(cryptkey, ptr, sizep,
 
 	if (cryptkey != NULL)
 	{
-	    int seed_len = crypt_seed_len[method];
-	    int salt_len = crypt_salt_len[method];
-
-	    crypt_push_state();
-	    use_crypt_method = method;
-	    if (method == 0)
-		crypt_init_keys(cryptkey);
-	    else
-	    {
-		bf_key_init(cryptkey, ptr + CRYPT_MAGIC_LEN, salt_len);
-		bf_cfb_init(ptr + CRYPT_MAGIC_LEN + salt_len, seed_len);
-	    }
-
-	    /* Remove magic number from the text */
-	    *filesizep += CRYPT_MAGIC_LEN + salt_len + seed_len;
-	    *sizep -= CRYPT_MAGIC_LEN + salt_len + seed_len;
-	    mch_memmove(ptr, ptr + CRYPT_MAGIC_LEN + salt_len + seed_len,
-							      (size_t)*sizep);
+	    int header_len;
+
+	    curbuf->b_cryptstate = crypt_create_from_header(
+						       method, cryptkey, ptr);
+	    crypt_set_cm_option(curbuf, method);
+
+	    /* Remove cryptmethod specific header from the text. */
+	    header_len = crypt_get_header_len(method);
+	    *filesizep += header_len;
+	    *sizep -= header_len;
+	    mch_memmove(ptr, ptr + header_len, (size_t)*sizep);
+
 	    /* Restore the read-only flag. */
 	    curbuf->b_p_ro = b_p_ro;
 	}
@@ -2992,85 +2995,6 @@ check_for_cryptkey(cryptkey, ptr, sizep,
 
     return cryptkey;
 }
-
-/*
- * 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_SALT_LEN_MAX
-						    + CRYPT_SEED_LEN_MAX + 2];
-
-    if (fread(buffer, CRYPT_MAGIC_LEN, 1, fp) != 1)
-	return FAIL;
-    method = crypt_method_from_magic((char *)buffer,
-					CRYPT_MAGIC_LEN +
-					CRYPT_SEED_LEN_MAX +
-					CRYPT_SALT_LEN_MAX);
-    if (method < 0 || method != get_crypt_method(curbuf))
-	return FAIL;
-
-    crypt_push_state();
-    if (method == 0)
-	crypt_init_keys(curbuf->b_p_key);
-    else
-    {
-	int salt_len = crypt_salt_len[method];
-	int seed_len = crypt_seed_len[method];
-
-	if (fread(buffer, salt_len + seed_len, 1, fp) != 1)
-	    return FAIL;
-	bf_key_init(curbuf->b_p_key, buffer, salt_len);
-	bf_cfb_init(buffer + salt_len, seed_len);
-    }
-    return OK;
-}
-
-/*
- * Prepare for writing encrypted bytes for buffer "buf".
- * Returns a pointer to an allocated header of length "*lenp".
- * When out of memory returns NULL.
- * Otherwise calls crypt_push_state(), call crypt_pop_state() later.
- */
-    char_u *
-prepare_crypt_write(buf, lenp)
-    buf_T *buf;
-    int   *lenp;
-{
-    char_u  *header;
-    int	    seed_len = crypt_seed_len[get_crypt_method(buf)];
-    int     salt_len = crypt_salt_len[get_crypt_method(buf)];
-    char_u  *salt;
-    char_u  *seed;
-
-    header = alloc_clear(CRYPT_MAGIC_LEN + CRYPT_SALT_LEN_MAX
-						    + CRYPT_SEED_LEN_MAX + 2);
-    if (header != NULL)
-    {
-	crypt_push_state();
-	use_crypt_method = get_crypt_method(buf);  /* select zip or blowfish */
-	vim_strncpy(header, (char_u *)crypt_magic[use_crypt_method],
-							     CRYPT_MAGIC_LEN);
-	if (use_crypt_method == 0)
-	    crypt_init_keys(buf->b_p_key);
-	else
-	{
-	    /* Using blowfish, add salt and seed. */
-	    salt = header + CRYPT_MAGIC_LEN;
-	    seed = salt + salt_len;
-	    sha2_seed(salt, salt_len, seed, seed_len);
-	    bf_key_init(buf->b_p_key, salt, salt_len);
-	    bf_cfb_init(seed, seed_len);
-	}
-    }
-    *lenp = CRYPT_MAGIC_LEN + salt_len + seed_len;
-    return header;
-}
-
 #endif  /* FEAT_CRYPT */
 
 #ifdef UNIX
@@ -3224,9 +3148,6 @@ buf_write(buf, fname, sfname, start, end
     int		    write_undo_file = FALSE;
     context_sha256_T sha_ctx;
 #endif
-#ifdef FEAT_CRYPT
-    int		    crypt_method_used;
-#endif
 
     if (fname == NULL || *fname == NUL)	/* safety check */
 	return FAIL;
@@ -3262,6 +3183,9 @@ buf_write(buf, fname, sfname, start, end
     write_info.bw_iconv_fd = (iconv_t)-1;
 # endif
 #endif
+#ifdef FEAT_CRYPT
+    write_info.bw_buffer = buf;
+#endif
 
     /* After writing a file changedtick changes but we don't want to display
      * the line. */
@@ -4505,17 +4429,17 @@ restore_backup:
 #ifdef FEAT_CRYPT
     if (*buf->b_p_key != NUL && !filtering)
     {
-	char_u *header;
-	int    header_len;
-
-	header = prepare_crypt_write(buf, &header_len);
-	if (header == NULL)
+	char_u		*header;
+	int		header_len;
+
+	buf->b_cryptstate = crypt_create_for_writing(crypt_get_method_nr(buf),
+					  buf->b_p_key, &header, &header_len);
+	if (buf->b_cryptstate == NULL || header == NULL)
 	    end = 0;
 	else
 	{
-	    /* 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 magic number, so that Vim knows how this file is
+	     * encrypted when reading it back. */
 	    write_info.bw_buf = header;
 	    write_info.bw_len = header_len;
 	    write_info.bw_flags = FIO_NOCONVERT;
@@ -4769,11 +4693,12 @@ restore_backup:
 	mch_set_acl(wfname, acl);
 #endif
 #ifdef FEAT_CRYPT
-    crypt_method_used = use_crypt_method;
-    if (wb_flags & FIO_ENCRYPTED)
-	crypt_pop_state();
-#endif
-
+    if (buf->b_cryptstate != NULL)
+    {
+	crypt_free_state(buf->b_cryptstate);
+	buf->b_cryptstate = NULL;
+    }
+#endif
 
 #if defined(FEAT_MBYTE) && defined(FEAT_EVAL)
     if (wfname != fname)
@@ -4924,10 +4849,7 @@ restore_backup:
 #ifdef FEAT_CRYPT
 	if (wb_flags & FIO_ENCRYPTED)
 	{
-	    if (crypt_method_used == 1)
-		STRCAT(IObuff, _("[blowfish]"));
-	    else
-		STRCAT(IObuff, _("[crypted]"));
+	    crypt_append_msg(buf);
 	    c = TRUE;
 	}
 #endif
@@ -5740,8 +5662,26 @@ buf_write_bytes(ip)
 #endif /* FEAT_MBYTE */
 
 #ifdef FEAT_CRYPT
-    if (flags & FIO_ENCRYPTED)		/* encrypt the data */
-	crypt_encode(buf, len, buf);
+    if (flags & FIO_ENCRYPTED)
+    {
+	/* Encrypt the data. Do it in-place if possible, otherwise use an
+	 * allocated buffer. */
+	if (crypt_works_inplace(ip->bw_buffer->b_cryptstate))
+	{
+	    crypt_encode_inplace(ip->bw_buffer->b_cryptstate, buf, len);
+	}
+	else
+	{
+	    char_u *outbuf;
+
+	    len = crypt_encode_alloc(curbuf->b_cryptstate, buf, len, &outbuf);
+	    if (len == 0)
+		return OK;  /* Crypt layer is buffering, will flush later. */
+	    wlen = write_eintr(ip->bw_fd, outbuf, len);
+	    vim_free(outbuf);
+	    return (wlen < len) ? FAIL : OK;
+	}
+    }
 #endif
 
     wlen = write_eintr(ip->bw_fd, buf, len);