Add output parameter to mbedtls_gcm_finish

Alternative implementations of GCM may delay the output of partial
blocks from mbedtls_gcm_update(). Add an output parameter to
mbedtls_gcm_finish() to allow such implementations to pass the final
partial block back to the caller. With the software implementation,
this final output is always empty.

Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
diff --git a/ChangeLog.d/gcm-update.txt b/ChangeLog.d/gcm-update.txt
index 4511fec..d384551 100644
--- a/ChangeLog.d/gcm-update.txt
+++ b/ChangeLog.d/gcm-update.txt
@@ -1,3 +1,13 @@
+API changes
+   * The interface of the GCM module has changed to remove restrictions on
+     how the input to multipart operations is broken down. mbedtls_gcm_finish()
+     now takes an extra output parameter for the last partial output block.
+     The software implementation always produces the full output at each
+     call to mbedtls_gcm_update(), but alternative implementations activated
+     by MBEDTLS_GCM_ALT may delay partial blocks to the next call to
+     mbedtls_gcm_update() or mbedtls_gcm_finish().
+     These changes are backward compatible for users of the cipher API.
+
 Features
    * The multi-part GCM interface (mbedtls_gcm_update() or
      mbedtls_cipher_update()) no longer requires the size of partial inputs to
diff --git a/include/mbedtls/gcm.h b/include/mbedtls/gcm.h
index 8a26ebb..951ee00 100644
--- a/include/mbedtls/gcm.h
+++ b/include/mbedtls/gcm.h
@@ -282,13 +282,23 @@
  *                  buffer of at least \p tag_len Bytes.
  * \param tag_len   The length of the tag to generate. This must be at least
  *                  four.
+ * \param output    The buffer for the final output.
+ *                  This must be a writable buffer of at least \p output_len
+ *                  bytes.
+ *                  With the built-in implementation, there is no final
+ *                  output and this can be \p NULL.
+ *                  Alternative implementations may return a partial block
+ *                  of output.
+ * \param output_len  The size of the \p output buffer in bytes.
+ *                  With the built-in implementation, this can be \c 0.
+ *                  Alternative implementations may require a 15-byte buffer.
  *
  * \return          \c 0 on success.
  * \return          #MBEDTLS_ERR_GCM_BAD_INPUT on failure.
  */
 int mbedtls_gcm_finish( mbedtls_gcm_context *ctx,
-                unsigned char *tag,
-                size_t tag_len );
+                        unsigned char *output, size_t output_len,
+                        unsigned char *tag, size_t tag_len );
 
 /**
  * \brief           This function clears a GCM context and the underlying
diff --git a/library/cipher.c b/library/cipher.c
index c88d666..63eaba8 100644
--- a/library/cipher.c
+++ b/library/cipher.c
@@ -1101,6 +1101,7 @@
 #if defined(MBEDTLS_GCM_C)
     if( MBEDTLS_MODE_GCM == ctx->cipher_info->mode )
         return( mbedtls_gcm_finish( (mbedtls_gcm_context *) ctx->cipher_ctx,
+                                    NULL, 0,
                                     tag, tag_len ) );
 #endif
 
@@ -1153,6 +1154,7 @@
 
         if( 0 != ( ret = mbedtls_gcm_finish(
                        (mbedtls_gcm_context *) ctx->cipher_ctx,
+                       NULL, 0,
                        check_tag, tag_len ) ) )
         {
             return( ret );
diff --git a/library/gcm.c b/library/gcm.c
index b3105d8..de766bc 100644
--- a/library/gcm.c
+++ b/library/gcm.c
@@ -471,8 +471,8 @@
 }
 
 int mbedtls_gcm_finish( mbedtls_gcm_context *ctx,
-                unsigned char *tag,
-                size_t tag_len )
+                        unsigned char *output, size_t output_len,
+                        unsigned char *tag, size_t tag_len )
 {
     unsigned char work_buf[16];
     size_t i;
@@ -482,6 +482,11 @@
     GCM_VALIDATE_RET( ctx != NULL );
     GCM_VALIDATE_RET( tag != NULL );
 
+    /* We never pass any output in finish(). The output parameter exists only
+     * for the sake of alternative implementations. */
+    (void) output;
+    (void) output_len;
+
     orig_len = ctx->len * 8;
     orig_add_len = ctx->add_len * 8;
 
@@ -541,7 +546,7 @@
     if( ( ret = mbedtls_gcm_update( ctx, length, input, output ) ) != 0 )
         return( ret );
 
-    if( ( ret = mbedtls_gcm_finish( ctx, tag, tag_len ) ) != 0 )
+    if( ( ret = mbedtls_gcm_finish( ctx, NULL, 0, tag, tag_len ) ) != 0 )
         return( ret );
 
     return( 0 );
@@ -979,7 +984,7 @@
                     goto exit;
             }
 
-            ret = mbedtls_gcm_finish( &ctx, tag_buf, 16 );
+            ret = mbedtls_gcm_finish( &ctx, NULL, 0, tag_buf, 16 );
             if( ret != 0 )
                 goto exit;
 
@@ -1039,7 +1044,7 @@
                     goto exit;
             }
 
-            ret = mbedtls_gcm_finish( &ctx, tag_buf, 16 );
+            ret = mbedtls_gcm_finish( &ctx, NULL, 0, tag_buf, 16 );
             if( ret != 0 )
                 goto exit;
 
diff --git a/tests/suites/test_suite_gcm.function b/tests/suites/test_suite_gcm.function
index d4dce93..965d154 100644
--- a/tests/suites/test_suite_gcm.function
+++ b/tests/suites/test_suite_gcm.function
@@ -41,7 +41,7 @@
     output = NULL;
 
     ASSERT_ALLOC( output, tag->len );
-    TEST_EQUAL( 0, mbedtls_gcm_finish( ctx, output, tag->len ) );
+    TEST_EQUAL( 0, mbedtls_gcm_finish( ctx, NULL, 0, output, tag->len ) );
     ASSERT_COMPARE( output, tag->len, tag->x, tag->len );
     mbedtls_free( output );
     output = NULL;
@@ -326,10 +326,10 @@
     /* mbedtls_gcm_finish() */
     TEST_INVALID_PARAM_RET(
         MBEDTLS_ERR_GCM_BAD_INPUT,
-        mbedtls_gcm_finish( NULL, valid_buffer, valid_len ) );
+        mbedtls_gcm_finish( NULL, NULL, 0, valid_buffer, valid_len ) );
     TEST_INVALID_PARAM_RET(
         MBEDTLS_ERR_GCM_BAD_INPUT,
-        mbedtls_gcm_finish( &ctx, NULL, valid_len ) );
+        mbedtls_gcm_finish( &ctx, NULL, 0, NULL, valid_len ) );
 
 exit:
     mbedtls_gcm_free( &ctx );