tests/unit/test-crypto-cipher: Test AES-GCM mode
Exercise the new GCM mode and the setaad/gettag helpers with the canonical AES-GCM test vectors from the GCM specification (McGrew & Viega, also NIST SP 800-38D): AES-128 and AES-256, with and without associated data. Each vector is run through encrypt (checking the ciphertext and the generated tag) and decrypt (checking the recovered plaintext and the recomputed tag). Signed-off-by: Jamin Lin <jamin_lin@aspeedtech.com> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> Link: https://lore.kernel.org/qemu-devel/20260811060115.1849266-13-jamin_lin@aspeedtech.com Signed-off-by: Cédric Le Goater <clg@redhat.com>
Jamin Lin committed
Aug 11, 2026 at 06:01 UTC
69ddeb2fac7454daa6b5cd896cc91df96566ee25
1 file changed
+240
tests/unit/test-crypto-cipher.c
+240
@@ -810,6 +810,230 @@ static void test_cipher_short_plaintext(void)
810
qcrypto_cipher_free(cipher);
811
}
812
813
+typedef struct QCryptoCipherGcmTestData QCryptoCipherGcmTestData;
814
+struct QCryptoCipherGcmTestData {
815
+ const char *path;
816
+ QCryptoCipherAlgo alg;
817
+ const char *key;
818
+ const char *iv;
819
+ /* associated data, or NULL for none */
820
+ const char *aad;
821
+ const char *plaintext;
822
+ const char *ciphertext;
823
+ const char *tag;
824
+};
825
+
826
+/*
827
+ * AES-GCM test vectors from "The Galois/Counter Mode of Operation (GCM)"
828
+ * (McGrew & Viega, also NIST SP 800-38D), with a 96-bit IV and a 128-bit
829
+ * tag. Each entry's "Test case N" label is the numbered test case from that
830
+ * document (Appendix B / the GCM specification's test vectors).
831
+ */
832
+static QCryptoCipherGcmTestData gcm_test_data[] = {
833
+ {
834
+ /* Test case 2 */
835
+ .path = "/crypto/cipher/aes-gcm-128-2",
836
+ .alg = QCRYPTO_CIPHER_ALGO_AES_128,
837
+ .key = "00000000000000000000000000000000",
838
+ .iv = "000000000000000000000000",
839
+ .plaintext = "00000000000000000000000000000000",
840
+ .ciphertext = "0388dace60b6a392f328c2b971b2fe78",
841
+ .tag = "ab6e47d42cec13bdf53a67b21257bddf",
842
+ },
843
+ {
844
+ /* Test case 3 (no AAD) */
845
+ .path = "/crypto/cipher/aes-gcm-128-3",
846
+ .alg = QCRYPTO_CIPHER_ALGO_AES_128,
847
+ .key = "feffe9928665731c6d6a8f9467308308",
848
+ .iv = "cafebabefacedbaddecaf888",
849
+ .plaintext =
850
+ "d9313225f88406e5a55909c5aff5269a"
851
+ "86a7a9531534f7da2e4c303d8a318a72"
852
+ "1c3c0c95956809532fcf0e2449a6b525"
853
+ "b16aedf5aa0de657ba637b391aafd255",
854
+ .ciphertext =
855
+ "42831ec2217774244b7221b784d0d49c"
856
+ "e3aa212f2c02a4e035c17e2329aca12e"
857
+ "21d514b25466931c7d8f6a5aac84aa05"
858
+ "1ba30b396a0aac973d58e091473f5985",
859
+ .tag = "4d5c2af327cd64a62cf35abd2ba6fab4",
860
+ },
861
+ {
862
+ /* Test case 4 (with AAD) */
863
+ .path = "/crypto/cipher/aes-gcm-128-4",
864
+ .alg = QCRYPTO_CIPHER_ALGO_AES_128,
865
+ .key = "feffe9928665731c6d6a8f9467308308",
866
+ .iv = "cafebabefacedbaddecaf888",
867
+ .aad = "feedfacedeadbeeffeedfacedeadbeefabaddad2",
868
+ .plaintext =
869
+ "d9313225f88406e5a55909c5aff5269a"
870
+ "86a7a9531534f7da2e4c303d8a318a72"
871
+ "1c3c0c95956809532fcf0e2449a6b525"
872
+ "b16aedf5aa0de657ba637b39",
873
+ .ciphertext =
874
+ "42831ec2217774244b7221b784d0d49c"
875
+ "e3aa212f2c02a4e035c17e2329aca12e"
876
+ "21d514b25466931c7d8f6a5aac84aa05"
877
+ "1ba30b396a0aac973d58e091",
878
+ .tag = "5bc94fbc3221a5db94fae95ae7121a47",
879
+ },
880
+ {
881
+ /* Test case 15 (AES-256, no AAD) */
882
+ .path = "/crypto/cipher/aes-gcm-256-15",
883
+ .alg = QCRYPTO_CIPHER_ALGO_AES_256,
884
+ .key =
885
+ "feffe9928665731c6d6a8f9467308308"
886
+ "feffe9928665731c6d6a8f9467308308",
887
+ .iv = "cafebabefacedbaddecaf888",
888
+ .plaintext =
889
+ "d9313225f88406e5a55909c5aff5269a"
890
+ "86a7a9531534f7da2e4c303d8a318a72"
891
+ "1c3c0c95956809532fcf0e2449a6b525"
892
+ "b16aedf5aa0de657ba637b391aafd255",
893
+ .ciphertext =
894
+ "522dc1f099567d07f47f37a32a84427d"
895
+ "643a8cdcbfe5c0c97598a2bd2555d1aa"
896
+ "8cb08e48590dbb3da7b08b1056828838"
897
+ "c5f61e6393ba7a0abcc9f662898015ad",
898
+ .tag = "b094dac5d93471bdec1a502270e3cc6c",
899
+ },
900
+ {
901
+ /* Test case 16 (AES-256, with AAD) */
902
+ .path = "/crypto/cipher/aes-gcm-256-16",
903
+ .alg = QCRYPTO_CIPHER_ALGO_AES_256,
904
+ .key =
905
+ "feffe9928665731c6d6a8f9467308308"
906
+ "feffe9928665731c6d6a8f9467308308",
907
+ .iv = "cafebabefacedbaddecaf888",
908
+ .aad = "feedfacedeadbeeffeedfacedeadbeefabaddad2",
909
+ .plaintext =
910
+ "d9313225f88406e5a55909c5aff5269a"
911
+ "86a7a9531534f7da2e4c303d8a318a72"
912
+ "1c3c0c95956809532fcf0e2449a6b525"
913
+ "b16aedf5aa0de657ba637b39",
914
+ .ciphertext =
915
+ "522dc1f099567d07f47f37a32a84427d"
916
+ "643a8cdcbfe5c0c97598a2bd2555d1aa"
917
+ "8cb08e48590dbb3da7b08b1056828838"
918
+ "c5f61e6393ba7a0abcc9f662",
919
+ .tag = "76fc6ece0f4e1768cddf8853bb2d551b",
920
+ },
921
+};
922
+
923
+static void test_cipher_gcm(const void *opaque)
924
+{
925
+ const QCryptoCipherGcmTestData *data = opaque;
926
+ g_autofree uint8_t *key = NULL;
927
+ g_autofree uint8_t *iv = NULL;
928
+ g_autofree uint8_t *aad = NULL;
929
+ g_autofree uint8_t *ptext = NULL;
930
+ g_autofree uint8_t *ctext = NULL;
931
+ g_autofree uint8_t *tagexp = NULL;
932
+ g_autofree uint8_t *out = NULL;
933
+ uint8_t tag[16];
934
+ size_t nkey;
935
+ size_t niv;
936
+ size_t naad = 0;
937
+ size_t nptext;
938
+ size_t nctext;
939
+ size_t ntag;
940
+ QCryptoCipher *cipher;
941
+
942
+ nkey = unhex_string(data->key, &key);
943
+ niv = unhex_string(data->iv, &iv);
944
+ nptext = unhex_string(data->plaintext, &ptext);
945
+ nctext = unhex_string(data->ciphertext, &ctext);
946
+ ntag = unhex_string(data->tag, &tagexp);
947
+ if (data->aad) {
948
+ naad = unhex_string(data->aad, &aad);
949
+ }
950
+
951
+ g_assert_cmpint(nptext, ==, nctext);
952
+ g_assert_cmpint(ntag, ==, sizeof(tag));
953
+ out = g_new0(uint8_t, nptext);
954
+
955
+ /* Encrypt: plaintext -> ciphertext, then read back the tag. */
956
+ cipher = qcrypto_cipher_new(data->alg, QCRYPTO_CIPHER_MODE_GCM,
957
+ key, nkey, &error_abort);
958
+ g_assert(cipher != NULL);
959
+ g_assert(qcrypto_cipher_setiv(cipher, iv, niv, &error_abort) == 0);
960
+ if (naad) {
961
+ g_assert(qcrypto_cipher_setaad(cipher, aad, naad, &error_abort) == 0);
962
+ }
963
+ g_assert(qcrypto_cipher_encrypt(cipher, ptext, out, nptext,
964
+ &error_abort) == 0);
965
+ g_assert_cmpmem(out, nptext, ctext, nctext);
966
+ g_assert(qcrypto_cipher_gettag(cipher, tag, sizeof(tag),
967
+ &error_abort) == 0);
968
+ g_assert_cmpmem(tag, sizeof(tag), tagexp, ntag);
969
+ qcrypto_cipher_free(cipher);
970
+
971
+ /* Decrypt: ciphertext -> plaintext, recomputed tag must match. */
972
+ memset(out, 0, nptext);
973
+ cipher = qcrypto_cipher_new(data->alg, QCRYPTO_CIPHER_MODE_GCM,
974
+ key, nkey, &error_abort);
975
+ g_assert(cipher != NULL);
976
+ g_assert(qcrypto_cipher_setiv(cipher, iv, niv, &error_abort) == 0);
977
+ if (naad) {
978
+ g_assert(qcrypto_cipher_setaad(cipher, aad, naad, &error_abort) == 0);
979
+ }
980
+ g_assert(qcrypto_cipher_decrypt(cipher, ctext, out, nctext,
981
+ &error_abort) == 0);
982
+ g_assert_cmpmem(out, nctext, ptext, nptext);
983
+ g_assert(qcrypto_cipher_gettag(cipher, tag, sizeof(tag),
984
+ &error_abort) == 0);
985
+ g_assert_cmpmem(tag, sizeof(tag), tagexp, ntag);
986
+ qcrypto_cipher_free(cipher);
987
+}
988
+
989
+/*
990
+ * Corrupt one ciphertext byte and confirm the recomputed GCM tag no longer
991
+ * matches: the authentication tag must detect tampering.
992
+ */
993
+static void test_cipher_gcm_tamper(const void *opaque)
994
+{
995
+ const QCryptoCipherGcmTestData *data = opaque;
996
+ g_autofree uint8_t *key = NULL;
997
+ g_autofree uint8_t *iv = NULL;
998
+ g_autofree uint8_t *aad = NULL;
999
+ g_autofree uint8_t *ctext = NULL;
1000
+ g_autofree uint8_t *tagexp = NULL;
1001
+ g_autofree uint8_t *out = NULL;
1002
+ uint8_t tag[16];
1003
+ size_t nkey;
1004
+ size_t niv;
1005
+ size_t naad = 0;
1006
+ size_t nctext;
1007
+ size_t ntag;
1008
+ QCryptoCipher *cipher;
1009
+
1010
+ nkey = unhex_string(data->key, &key);
1011
+ niv = unhex_string(data->iv, &iv);
1012
+ nctext = unhex_string(data->ciphertext, &ctext);
1013
+ ntag = unhex_string(data->tag, &tagexp);
1014
+ if (data->aad) {
1015
+ naad = unhex_string(data->aad, &aad);
1016
+ }
1017
+ out = g_new0(uint8_t, nctext);
1018
+
1019
+ /* Flip one ciphertext bit before decrypting. */
1020
+ ctext[0] ^= 0x01;
1021
+
1022
+ cipher = qcrypto_cipher_new(data->alg, QCRYPTO_CIPHER_MODE_GCM,
1023
+ key, nkey, &error_abort);
1024
+ g_assert(cipher != NULL);
1025
+ g_assert(qcrypto_cipher_setiv(cipher, iv, niv, &error_abort) == 0);
1026
+ if (naad) {
1027
+ g_assert(qcrypto_cipher_setaad(cipher, aad, naad, &error_abort) == 0);
1028
+ }
1029
+ g_assert(qcrypto_cipher_decrypt(cipher, ctext, out, nctext,
1030
+ &error_abort) == 0);
1031
+ g_assert(qcrypto_cipher_gettag(cipher, tag, sizeof(tag),
1032
+ &error_abort) == 0);
1033
+ g_assert(memcmp(tag, tagexp, ntag) != 0);
1034
+ qcrypto_cipher_free(cipher);
1035
+}
1036
+
1037
int main(int argc, char **argv)
1038
{
1039
size_t i;
@@ -828,6 +1052,22 @@ int main(int argc, char **argv)
1052
}
1053
}
1054
1055
+ for (i = 0; i < G_N_ELEMENTS(gcm_test_data); i++) {
1056
+ if (qcrypto_cipher_supports(gcm_test_data[i].alg,
1057
+ QCRYPTO_CIPHER_MODE_GCM)) {
1058
+ g_autofree char *tamper = g_strdup_printf("%s/tamper",
1059
+ gcm_test_data[i].path);
1060
+
1061
+ g_test_add_data_func(gcm_test_data[i].path, &gcm_test_data[i],
1062
+ test_cipher_gcm);
1063
+ g_test_add_data_func(tamper, &gcm_test_data[i],
1064
+ test_cipher_gcm_tamper);
1065
+ } else {
1066
+ g_printerr("# skip unsupported %s:gcm\n",
1067
+ QCryptoCipherAlgo_str(gcm_test_data[i].alg));
1068
+ }
1069
+ }
1070
+
1071
if (qcrypto_cipher_supports(QCRYPTO_CIPHER_ALGO_AES_256,
1072
QCRYPTO_CIPHER_MODE_CBC)) {
1073
g_test_add_func("/crypto/cipher/null-iv",