chachapoly: split crypt_and_mac() to match GCM API

In addition to making the APIs of the various AEAD modules more consistent
with each other, it's useful to have an auth_decrypt() function so that we can
safely check the tag ourselves, as the user might otherwise do it in an
insecure way (or even forget to do it altogether).
diff --git a/include/mbedtls/chachapoly.h b/include/mbedtls/chachapoly.h
index 810675d..e7413b3 100644
--- a/include/mbedtls/chachapoly.h
+++ b/include/mbedtls/chachapoly.h
@@ -31,6 +31,8 @@
 
 #define MBEDTLS_ERR_CHACHAPOLY_BAD_INPUT_DATA -0x00047 /**< Invalid input parameter(s). */
 #define MBEDTLS_ERR_CHACHAPOLY_BAD_STATE      -0x00049 /**< The requested operation is not permitted in the current state */
+#define MBEDTLS_ERR_CHACHAPOLY_AUTH_FAILED    -0x00049 /**< Authenticated decryption failed: data was not authentic. */
+
 
 #ifdef __cplusplus
 extern "C" {
@@ -192,37 +194,64 @@
                                unsigned char mac[16] );
 
 /**
- * \brief               Encrypt or decrypt data, and produce a MAC with ChaCha20-Poly1305.
+ * \brief               Encrypt or decrypt data, and produce a MAC (tag) with ChaCha20-Poly1305.
  *
- * \param key           The 256-bit (32 bytes) encryption key to use.
- * \param nonce         The 96-bit (12 bytes) nonce/IV to use.
+ * \param ctx           The ChachaPoly context.
  * \param mode          Specifies whether the data in the \p input buffer is to
  *                      be encrypted or decrypted. If there is no data to encrypt
  *                      or decrypt (i.e. \p ilen is 0) then the value of this
  *                      parameter does not matter.
- * \param aad_len       The length (in bytes) of the AAD data to process.
+ * \param length        The length (in bytes) of the data to encrypt or decrypt.
+ * \param nonce         The 96-bit (12 bytes) nonce/IV to use.
  * \param aad           Buffer containing the additional authenticated data (AAD).
  *                      This pointer can be NULL if aad_len == 0.
- * \param ilen          The length (in bytes) of the data to encrypt or decrypt.
+ * \param aad_len       The length (in bytes) of the AAD data to process.
  * \param input         Buffer containing the data to encrypt or decrypt.
  *                      This pointer can be NULL if ilen == 0.
  * \param output        Buffer to where the encrypted or decrypted data is written.
  *                      This pointer can be NULL if ilen == 0.
- * \param mac           Buffer to where the computed 128-bit (16 bytes) MAC is written.
+ * \param tag           Buffer to where the computed 128-bit (16 bytes) MAC is written.
  *
  * \return              MBEDTLS_ERR_CHACHAPOLY_BAD_INPUT_DATA is returned
  *                      if one or more of the required parameters are NULL.
  *                      Otherwise, 0 is returned to indicate success.
  */
-int mbedtls_chachapoly_crypt_and_mac( const unsigned char key[32],
-                                      const unsigned char nonce[12],
+int mbedtls_chachapoly_crypt_and_tag( mbedtls_chachapoly_context *ctx,
                                       mbedtls_chachapoly_mode_t mode,
-                                      size_t aad_len,
+                                      size_t length,
+                                      const unsigned char nonce[12],
                                       const unsigned char *aad,
-                                      size_t ilen,
+                                      size_t aad_len,
                                       const unsigned char *input,
                                       unsigned char *output,
-                                      unsigned char mac[16] );
+                                      unsigned char tag[16] );
+
+/**
+ * \brief           Decrypt data and check a MAC (tag) with ChaCha20-Poly1305.
+ *
+ * \param ctx       The ChachaPoly context.
+ * \param length    The length of the input and output data.
+ * \param nonce     The nonce / initialization vector.
+ * \param aad       The buffer holding the additional authenticated data.
+ * \param aad_len   The length of the additional authenticated data.
+ * \param tag       The buffer holding the tag.
+ * \param input     The buffer holding the input data.
+ * \param output    The buffer for holding the output data.
+ *
+ * \return              MBEDTLS_ERR_CHACHAPOLY_BAD_INPUT_DATA is returned
+ *                      if one or more of the required parameters are NULL.
+ *                      MBEDTLS_ERR_CHACHAPOLY_AUTH_FAILED if the tag does not
+ *                      match.
+ *                      Otherwise, 0 is returned to indicate success.
+ */
+int mbedtls_chachapoly_auth_decrypt( mbedtls_chachapoly_context *ctx,
+                                     size_t length,
+                                     const unsigned char nonce[12],
+                                     const unsigned char *aad,
+                                     size_t aad_len,
+                                     const unsigned char tag[16],
+                                     const unsigned char *input,
+                                     unsigned char *output );
 
 /**
  * \brief               Checkup routine
diff --git a/library/chachapoly.c b/library/chachapoly.c
index 35ae99e..0dba5ed 100644
--- a/library/chachapoly.c
+++ b/library/chachapoly.c
@@ -295,44 +295,70 @@
     return( 0 );
 }
 
-int mbedtls_chachapoly_crypt_and_mac ( const unsigned char key[32],
-                                       const unsigned char nonce[12],
-                                       mbedtls_chachapoly_mode_t mode,
-                                       size_t aad_len,
-                                       const unsigned char *aad,
-                                       size_t ilen,
-                                       const unsigned char *input,
-                                       unsigned char *output,
-                                       unsigned char mac[16] )
+int mbedtls_chachapoly_crypt_and_tag( mbedtls_chachapoly_context *ctx,
+                                      mbedtls_chachapoly_mode_t mode,
+                                      size_t length,
+                                      const unsigned char nonce[12],
+                                      const unsigned char *aad,
+                                      size_t aad_len,
+                                      const unsigned char *input,
+                                      unsigned char *output,
+                                      unsigned char tag[16] )
 {
-    mbedtls_chachapoly_context ctx;
     int result;
 
-    mbedtls_chachapoly_init( &ctx );
-
-    result = mbedtls_chachapoly_setkey( &ctx, key );
+    result = mbedtls_chachapoly_starts( ctx, nonce, mode );
     if ( result != 0 )
         goto cleanup;
 
-    result = mbedtls_chachapoly_starts( &ctx, nonce, mode );
-    if ( result != 0 )
-        goto cleanup;
-
-    result = mbedtls_chachapoly_update_aad( &ctx, aad_len, aad );
+    result = mbedtls_chachapoly_update_aad( ctx, aad_len, aad );
     if ( result != 0 )
             goto cleanup;
 
-    result = mbedtls_chachapoly_update( &ctx, ilen, input, output );
+    result = mbedtls_chachapoly_update( ctx, length, input, output );
     if ( result != 0 )
             goto cleanup;
 
-    result = mbedtls_chachapoly_finish( &ctx, mac );
+    result = mbedtls_chachapoly_finish( ctx, tag );
 
 cleanup:
-    mbedtls_chachapoly_free( &ctx );
     return( result );
 }
 
+int mbedtls_chachapoly_auth_decrypt( mbedtls_chachapoly_context *ctx,
+                                     size_t length,
+                                     const unsigned char nonce[12],
+                                     const unsigned char *aad,
+                                     size_t aad_len,
+                                     const unsigned char tag[16],
+                                     const unsigned char *input,
+                                     unsigned char *output )
+{
+    int ret;
+    unsigned char check_tag[16];
+    size_t i;
+    int diff;
+
+    if( ( ret = mbedtls_chachapoly_crypt_and_tag( ctx,
+                        MBEDTLS_CHACHAPOLY_DECRYPT, length, nonce,
+                        aad, aad_len, input, output, check_tag ) ) != 0 )
+    {
+        return( ret );
+    }
+
+    /* Check tag in "constant-time" */
+    for( diff = 0, i = 0; i < sizeof( check_tag ); i++ )
+        diff |= tag[i] ^ check_tag[i];
+
+    if( diff != 0 )
+    {
+        mbedtls_zeroize( output, length );
+        return( MBEDTLS_ERR_CHACHAPOLY_AUTH_FAILED );
+    }
+
+    return( 0 );
+}
+
 #endif /* MBEDTLS_CHACHAPOLY_ALT */
 
 #if defined(MBEDTLS_SELF_TEST)
@@ -425,6 +451,7 @@
 
 int mbedtls_chachapoly_self_test( int verbose )
 {
+    mbedtls_chachapoly_context ctx;
     unsigned i;
     int result;
     unsigned char output[200];
@@ -437,12 +464,24 @@
             mbedtls_printf( "  ChaCha20-Poly1305 test %u ", i );
         }
 
-        result = mbedtls_chachapoly_crypt_and_mac( test_key[i],
-                                                   test_nonce[i],
+        mbedtls_chachapoly_init( &ctx );
+
+        result = mbedtls_chachapoly_setkey( &ctx, test_key[i] );
+        if ( result != 0 )
+        {
+            if ( verbose != 0 )
+            {
+                mbedtls_printf( "setkey() error code: %i\n", result );
+            }
+            return( -1 );
+        }
+
+        result = mbedtls_chachapoly_crypt_and_tag( &ctx,
                                                    MBEDTLS_CHACHAPOLY_ENCRYPT,
-                                                   test_aad_len[i],
-                                                   test_aad[i],
                                                    test_input_len[i],
+                                                   test_nonce[i],
+                                                   test_aad[i],
+                                                   test_aad_len[i],
                                                    test_input[i],
                                                    output,
                                                    mac );
@@ -450,7 +489,7 @@
         {
             if ( verbose != 0 )
             {
-                mbedtls_printf( "error code: %i\n", result );
+                mbedtls_printf( "crypt_and_tag() error code: %i\n", result );
             }
             return( -1 );
         }
@@ -473,6 +512,8 @@
             return( -1 );
         }
 
+        mbedtls_chachapoly_free( &ctx );
+
         if ( verbose != 0 )
         {
             mbedtls_printf( "passed\n" );
diff --git a/tests/suites/test_suite_chachapoly.function b/tests/suites/test_suite_chachapoly.function
index fb1a738..b205c4c 100644
--- a/tests/suites/test_suite_chachapoly.function
+++ b/tests/suites/test_suite_chachapoly.function
@@ -24,6 +24,7 @@
     size_t key_len;
     size_t nonce_len;
     size_t mac_len;
+    mbedtls_chachapoly_context ctx;
 
     memset( key_str,     0x00, 32    );
     memset( nonce_str,   0x00, 12    );
@@ -43,14 +44,21 @@
     TEST_ASSERT( nonce_len == 12 );
     TEST_ASSERT( mac_len   == 16 );
 
-    mbedtls_chachapoly_crypt_and_mac( key_str, nonce_str,
+    mbedtls_chachapoly_init( &ctx );
+
+    mbedtls_chachapoly_setkey( &ctx, key_str );
+
+    mbedtls_chachapoly_crypt_and_tag( &ctx,
                                       MBEDTLS_CHACHAPOLY_ENCRYPT,
-                                      aad_len, aad_str,
-                                      input_len, input_str, output,
-                                      mac );
+                                      input_len, nonce_str,
+                                      aad_str, aad_len,
+                                      input_str, output, mac );
 
     TEST_ASSERT( memcmp( output_str, output, output_len ) == 0 );
     TEST_ASSERT( memcmp( mac_str, mac, 16U ) == 0 );
+
+exit:
+    mbedtls_chachapoly_free( &ctx );
 }
 /* END_CASE */
 
@@ -64,13 +72,14 @@
     unsigned char output_str[10000];
     unsigned char mac_str[16];
     unsigned char output[10000];
-    unsigned char mac[16];
     size_t input_len;
     size_t output_len;
     size_t aad_len;
     size_t key_len;
     size_t nonce_len;
     size_t mac_len;
+    int ret;
+    mbedtls_chachapoly_context ctx;
 
     memset( key_str,     0x00, 32    );
     memset( nonce_str,   0x00, 12    );
@@ -90,14 +99,20 @@
     TEST_ASSERT( nonce_len == 12 );
     TEST_ASSERT( mac_len   == 16 );
 
-    mbedtls_chachapoly_crypt_and_mac( key_str, nonce_str,
-                                      MBEDTLS_CHACHAPOLY_DECRYPT,
-                                      aad_len, aad_str,
-                                      input_len, input_str, output,
-                                      mac );
+    mbedtls_chachapoly_init( &ctx );
 
+    mbedtls_chachapoly_setkey( &ctx, key_str );
+
+    ret = mbedtls_chachapoly_auth_decrypt( &ctx,
+                                           input_len, nonce_str,
+                                           aad_str, aad_len,
+                                           mac_str, input_str, output );
+
+    TEST_ASSERT( ret == 0 );
     TEST_ASSERT( memcmp( output_str, output, output_len ) == 0 );
-    TEST_ASSERT( memcmp( mac_str, mac, 16U ) == 0 );
+
+exit:
+    mbedtls_chachapoly_free( &ctx );
 }
 /* END_CASE */