New function mbedtls_cipher_finish_padded

New function `mbedtls_cipher_finish_padded()`, similar to
`mbedtls_cipher_finish()`, but reporting padding errors through a separate
output parameter. This makes it easier to avoid leaking the presence of a
padding error, especially through timing. Thus the new function is
recommended to defend against padding oracle attacks.

In this commit, implement this function naively, with timing that depends on
whether an error happened. A subsequent commit will make this function
constant-time.

Copy the test decrypt_test_vec and decrypt_test_vec_cf test cases into
variants that call `mbedtls_cipher_finish_padded()`.

Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
diff --git a/tests/suites/test_suite_cipher.function b/tests/suites/test_suite_cipher.function
index 8ae2234..fbc48b7 100644
--- a/tests/suites/test_suite_cipher.function
+++ b/tests/suites/test_suite_cipher.function
@@ -946,6 +946,179 @@
 }
 /* END_CASE */
 
+/* BEGIN_CASE */
+void decrypt_padded_test_vec(int cipher_id, int pad_mode, data_t *key,
+                             data_t *iv, data_t *cipher,
+                             data_t *clear, data_t *ad, data_t *tag,
+                             int expected_finish_result, int tag_result)
+{
+    unsigned char output[265];
+    mbedtls_cipher_context_t ctx;
+    size_t outlen, total_len;
+
+    mbedtls_cipher_init(&ctx);
+
+    memset(output, 0x00, sizeof(output));
+
+#if !defined(MBEDTLS_GCM_C) && !defined(MBEDTLS_CHACHAPOLY_C)
+    ((void) ad);
+    ((void) tag);
+#endif
+
+    /* Prepare context */
+    TEST_ASSERT(0 == mbedtls_cipher_setup(&ctx,
+                                          mbedtls_cipher_info_from_type(cipher_id)));
+    TEST_ASSERT(0 == mbedtls_cipher_setkey(&ctx, key->x, 8 * key->len, MBEDTLS_DECRYPT));
+#if defined(MBEDTLS_CIPHER_MODE_WITH_PADDING)
+    if (pad_mode != -1) {
+        TEST_ASSERT(0 == mbedtls_cipher_set_padding_mode(&ctx, pad_mode));
+    }
+#else
+    (void) pad_mode;
+#endif /* MBEDTLS_CIPHER_MODE_WITH_PADDING */
+    TEST_ASSERT(0 == mbedtls_cipher_set_iv(&ctx, iv->x, iv->len));
+    TEST_ASSERT(0 == mbedtls_cipher_reset(&ctx));
+#if defined(MBEDTLS_GCM_C) || defined(MBEDTLS_CHACHAPOLY_C)
+    int expected = (ctx.cipher_info->mode == MBEDTLS_MODE_GCM ||
+                    ctx.cipher_info->type == MBEDTLS_CIPHER_CHACHA20_POLY1305) ?
+                   0 : MBEDTLS_ERR_CIPHER_FEATURE_UNAVAILABLE;
+
+    TEST_EQUAL(expected, mbedtls_cipher_update_ad(&ctx, ad->x, ad->len));
+#endif
+
+    /* decode buffer and check tag->x */
+    total_len = 0;
+    TEST_ASSERT(0 == mbedtls_cipher_update(&ctx, cipher->x, cipher->len, output, &outlen));
+    total_len += outlen;
+
+    size_t invalid_padding = 42;
+    int actual_finish_result =
+        mbedtls_cipher_finish_padded(&ctx, output + outlen, &outlen,
+                                     &invalid_padding);
+    switch (expected_finish_result) {
+        case 0:
+            TEST_EQUAL(actual_finish_result, 0);
+            TEST_EQUAL(invalid_padding, 0);
+            break;
+        case MBEDTLS_ERR_CIPHER_INVALID_PADDING:
+            TEST_EQUAL(actual_finish_result, 0);
+            TEST_EQUAL(invalid_padding, SIZE_MAX);
+            break;
+        default:
+            TEST_EQUAL(actual_finish_result, expected_finish_result);
+            /* Check output parameter is set to the least-harmful value on error */
+            TEST_EQUAL(0, outlen);
+            break;
+    }
+    total_len += outlen;
+
+#if defined(MBEDTLS_GCM_C) || defined(MBEDTLS_CHACHAPOLY_C)
+    int tag_expected = (ctx.cipher_info->mode == MBEDTLS_MODE_GCM ||
+                        ctx.cipher_info->type == MBEDTLS_CIPHER_CHACHA20_POLY1305) ?
+                       tag_result : MBEDTLS_ERR_CIPHER_FEATURE_UNAVAILABLE;
+
+    TEST_EQUAL(tag_expected, mbedtls_cipher_check_tag(&ctx, tag->x, tag->len));
+#endif
+
+    /* check plaintext only if everything went fine */
+    if (0 == expected_finish_result && 0 == tag_result) {
+        TEST_ASSERT(total_len == clear->len);
+        TEST_ASSERT(0 == memcmp(output, clear->x, clear->len));
+    }
+
+exit:
+    mbedtls_cipher_free(&ctx);
+}
+/* END_CASE */
+
+/* BEGIN_CASE */
+void decrypt_padded_test_vec_cf(int cipher_id, int pad_mode, data_t *key,
+                                data_t *iv, data_t *cipher,
+                                data_t *clear, data_t *ad, data_t *tag,
+                                int expected_finish_result, int tag_result)
+{
+    unsigned char output[265];
+    mbedtls_cipher_context_t ctx;
+    size_t outlen, total_len;
+
+    mbedtls_cipher_init(&ctx);
+
+    memset(output, 0x00, sizeof(output));
+
+#if !defined(MBEDTLS_GCM_C) && !defined(MBEDTLS_CHACHAPOLY_C)
+    ((void) ad);
+    ((void) tag);
+#endif
+
+    TEST_CF_SECRET(key->x, key->len);
+    TEST_CF_SECRET(cipher->x, cipher->len);
+
+    /* Prepare context */
+    TEST_ASSERT(0 == mbedtls_cipher_setup(&ctx,
+                                          mbedtls_cipher_info_from_type(cipher_id)));
+    TEST_ASSERT(0 == mbedtls_cipher_setkey(&ctx, key->x, 8 * key->len, MBEDTLS_DECRYPT));
+#if defined(MBEDTLS_CIPHER_MODE_WITH_PADDING)
+    if (pad_mode != -1) {
+        TEST_ASSERT(0 == mbedtls_cipher_set_padding_mode(&ctx, pad_mode));
+    }
+#else
+    (void) pad_mode;
+#endif /* MBEDTLS_CIPHER_MODE_WITH_PADDING */
+    TEST_ASSERT(0 == mbedtls_cipher_set_iv(&ctx, iv->x, iv->len));
+    TEST_ASSERT(0 == mbedtls_cipher_reset(&ctx));
+#if defined(MBEDTLS_GCM_C) || defined(MBEDTLS_CHACHAPOLY_C)
+    int expected = (ctx.cipher_info->mode == MBEDTLS_MODE_GCM ||
+                    ctx.cipher_info->type == MBEDTLS_CIPHER_CHACHA20_POLY1305) ?
+                   0 : MBEDTLS_ERR_CIPHER_FEATURE_UNAVAILABLE;
+
+    TEST_EQUAL(expected, mbedtls_cipher_update_ad(&ctx, ad->x, ad->len));
+#endif
+
+    /* decode buffer and check tag->x */
+    total_len = 0;
+    TEST_ASSERT(0 == mbedtls_cipher_update(&ctx, cipher->x, cipher->len, output, &outlen));
+    total_len += outlen;
+
+    size_t invalid_padding = 42;
+    int actual_finish_result =
+        mbedtls_cipher_finish_padded(&ctx, output + outlen, &outlen,
+                                     &invalid_padding);
+    switch (expected_finish_result) {
+        case 0:
+            TEST_EQUAL(actual_finish_result, 0);
+            TEST_EQUAL(invalid_padding, 0);
+            break;
+        case MBEDTLS_ERR_CIPHER_INVALID_PADDING:
+            TEST_EQUAL(actual_finish_result, 0);
+            TEST_EQUAL(invalid_padding, SIZE_MAX);
+            break;
+        default:
+            TEST_EQUAL(actual_finish_result, expected_finish_result);
+            /* Check output parameter is set to the least-harmful value on error */
+            TEST_EQUAL(0, outlen);
+            break;
+    }
+    total_len += outlen;
+
+#if defined(MBEDTLS_GCM_C) || defined(MBEDTLS_CHACHAPOLY_C)
+    int tag_expected = (ctx.cipher_info->mode == MBEDTLS_MODE_GCM ||
+                        ctx.cipher_info->type == MBEDTLS_CIPHER_CHACHA20_POLY1305) ?
+                       tag_result : MBEDTLS_ERR_CIPHER_FEATURE_UNAVAILABLE;
+
+    TEST_EQUAL(tag_expected, mbedtls_cipher_check_tag(&ctx, tag->x, tag->len));
+#endif
+
+    /* check plaintext only if everything went fine */
+    if (0 == expected_finish_result && 0 == tag_result) {
+        TEST_CF_PUBLIC(output, sizeof(output));
+        TEST_MEMORY_COMPARE(output, total_len, clear->x, clear->len);
+    }
+
+exit:
+    mbedtls_cipher_free(&ctx);
+}
+/* END_CASE */
+
 /* BEGIN_CASE depends_on:MBEDTLS_CIPHER_MODE_AEAD */
 void auth_crypt_tv(int cipher_id, data_t *key, data_t *iv,
                    data_t *ad, data_t *cipher, data_t *tag,