Update the built-in Chacha20-Poly1305 code to an RFC 7539 complaint version.
[tinc] / src / chacha-poly1305 / chachapoly.c
diff --git a/src/chacha-poly1305/chachapoly.c b/src/chacha-poly1305/chachapoly.c
new file mode 100644 (file)
index 0000000..9a6620c
--- /dev/null
@@ -0,0 +1,182 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 Grigori Goronzy <goronzy@kinoho.net>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <assert.h>
+
+#include "chachapoly.h"
+
+/**
+ * Constant-time memory compare. This should help to protect against
+ * side-channel attacks.
+ *
+ * \param av input 1
+ * \param bv input 2
+ * \param n bytes to compare
+ * \return 0 if inputs are equal
+ */
+static int memcmp_eq(const void *av, const void *bv, int n) {
+       const unsigned char *a = (const unsigned char *) av;
+       const unsigned char *b = (const unsigned char *) bv;
+       unsigned char res = 0;
+       int i;
+
+       for(i = 0; i < n; i++) {
+               res |= *a ^ *b;
+               a++;
+               b++;
+       }
+
+       return res;
+}
+
+/**
+ * Poly1305 tag generation. This concatenates a string according to the rules
+ * outlined in RFC 7539 and calculates the tag.
+ *
+ * \param poly_key 32 byte secret one-time key for poly1305
+ * \param ad associated data
+ * \param ad_len associated data length in bytes
+ * \param ct ciphertext
+ * \param ct_len ciphertext length in bytes
+ * \param tag pointer to 16 bytes for tag storage
+ */
+static void poly1305_get_tag(unsigned char *poly_key, const void *ad,
+                             int ad_len, const void *ct, int ct_len, unsigned char *tag) {
+       struct poly1305_context poly;
+       unsigned left_over;
+       uint64_t len;
+       unsigned char pad[16];
+
+       poly1305_init(&poly, poly_key);
+       memset(&pad, 0, sizeof(pad));
+
+       /* associated data and padding */
+       poly1305_update(&poly, ad, ad_len);
+       left_over = ad_len % 16;
+
+       if(left_over) {
+               poly1305_update(&poly, pad, 16 - left_over);
+       }
+
+       /* payload and padding */
+       poly1305_update(&poly, ct, ct_len);
+       left_over = ct_len % 16;
+
+       if(left_over) {
+               poly1305_update(&poly, pad, 16 - left_over);
+       }
+
+       /* lengths */
+       len = ad_len;
+       poly1305_update(&poly, (unsigned char *)&len, 8);
+       len = ct_len;
+       poly1305_update(&poly, (unsigned char *)&len, 8);
+
+       poly1305_finish(&poly, tag);
+}
+
+int chachapoly_init(struct chachapoly_ctx *ctx, const void *key, int key_len) {
+       assert(key_len == 128 || key_len == 256);
+
+       memset(ctx, 0, sizeof(*ctx));
+       chacha_keysetup(&ctx->cha_ctx, key, key_len);
+       return CHACHAPOLY_OK;
+}
+
+int chachapoly_crypt(struct chachapoly_ctx *ctx, const void *nonce,
+                     const void *ad, int ad_len, void *input, int input_len,
+                     void *output, void *tag, int tag_len, int encrypt) {
+       unsigned char poly_key[CHACHA_BLOCKLEN];
+       unsigned char calc_tag[POLY1305_TAGLEN];
+       const unsigned char one[4] = { 1, 0, 0, 0 };
+
+       /* initialize keystream and generate poly1305 key */
+       memset(poly_key, 0, sizeof(poly_key));
+       chacha_ivsetup(&ctx->cha_ctx, nonce, NULL);
+       chacha_encrypt_bytes(&ctx->cha_ctx, poly_key, poly_key, sizeof(poly_key));
+
+       /* check tag if decrypting */
+       if(encrypt == 0 && tag_len) {
+               poly1305_get_tag(poly_key, ad, ad_len, input, input_len, calc_tag);
+
+               if(memcmp_eq(calc_tag, tag, tag_len) != 0) {
+                       return CHACHAPOLY_INVALID_MAC;
+               }
+       }
+
+       /* crypt data */
+       chacha_ivsetup(&ctx->cha_ctx, nonce, one);
+       chacha_encrypt_bytes(&ctx->cha_ctx, (unsigned char *)input,
+                            (unsigned char *)output, input_len);
+
+       /* add tag if encrypting */
+       if(encrypt && tag_len) {
+               poly1305_get_tag(poly_key, ad, ad_len, output, input_len, calc_tag);
+               memcpy(tag, calc_tag, tag_len);
+       }
+
+       return CHACHAPOLY_OK;
+}
+
+int chachapoly_crypt_short(struct chachapoly_ctx *ctx, const void *nonce,
+                           const void *ad, int ad_len, void *input, int input_len,
+                           void *output, void *tag, int tag_len, int encrypt) {
+       unsigned char keystream[CHACHA_BLOCKLEN];
+       unsigned char calc_tag[POLY1305_TAGLEN];
+       int i;
+
+       assert(input_len <= 32);
+
+       /* initialize keystream and generate poly1305 key */
+       memset(keystream, 0, sizeof(keystream));
+       chacha_ivsetup(&ctx->cha_ctx, nonce, NULL);
+       chacha_encrypt_bytes(&ctx->cha_ctx, keystream, keystream,
+                            sizeof(keystream));
+
+       /* check tag if decrypting */
+       if(encrypt == 0 && tag_len) {
+               poly1305_get_tag(keystream, ad, ad_len, input, input_len, calc_tag);
+
+               if(memcmp_eq(calc_tag, tag, tag_len) != 0) {
+                       return CHACHAPOLY_INVALID_MAC;
+               }
+       }
+
+       /* crypt data */
+       for(i = 0; i < input_len; i++) {
+               ((unsigned char *)output)[i] =
+                       ((unsigned char *)input)[i] ^ keystream[32 + i];
+       }
+
+       /* add tag if encrypting */
+       if(encrypt && tag_len) {
+               poly1305_get_tag(keystream, ad, ad_len, output, input_len, calc_tag);
+               memcpy(tag, calc_tag, tag_len);
+       }
+
+       return CHACHAPOLY_OK;
+}