summaryrefslogtreecommitdiffstats
path: root/src/crypt.c
diff options
context:
space:
mode:
authorChristian Brabandt <cb@256bit.org>2021-06-20 14:02:16 +0200
committerBram Moolenaar <Bram@vim.org>2021-06-20 14:02:16 +0200
commitf573c6e1ed58d46d694c802eaf5ae3662a952744 (patch)
tree9d3ccf4402f322b9d1baf130696a6b0e600ae693 /src/crypt.c
parent208f0b48b2c616b29f377a1408290111ed2663f7 (diff)
patch 8.2.3022: available encryption methods are not strong enoughv8.2.3022
Problem: Available encryption methods are not strong enough. Solution: Add initial support for xchaha20. (Christian Brabandt, closes #8394)
Diffstat (limited to 'src/crypt.c')
-rw-r--r--src/crypt.c385
1 files changed, 359 insertions, 26 deletions
diff --git a/src/crypt.c b/src/crypt.c
index 0164f1ce9d..35d4e14dc3 100644
--- a/src/crypt.c
+++ b/src/crypt.c
@@ -12,6 +12,10 @@
*/
#include "vim.h"
+#ifdef FEAT_SODIUM
+# include <sodium.h>
+#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