@samitouri / QOSamiQemu / commits / 69ddeb2fac

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",