// Integration tests for the CccCrypto public API. // // These exercise every bridge entry point through the clean Dart façade. // All tests require the native library, so they run as integration tests // via `flutter test integration_test/ccc_crypto_test.dart -d macos`. import 'dart:convert'; import 'dart:typed_data'; import 'package:ccc_cryptography/ccc_cryptography.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; /// Helper: hex string → Uint8List. Uint8List _hex(String hex) { final bytes = []; for (var i = 0; i < hex.length; i += 2) { bytes.add(int.parse(hex.substring(i, i + 2), radix: 16)); } return Uint8List.fromList(bytes); } /// Helper: Uint8List → lowercase hex string. String _toHex(Uint8List data) => data.map((b) => b.toRadixString(16).padLeft(2, '0')).join(); void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); setUpAll(() async { await CccCrypto.init(); }); // ═══════════════════════════════════════════════════════════════════════════ // 5.1 AEAD roundtrip — AES-256-GCM // ═══════════════════════════════════════════════════════════════════════════ test('5.1 AEAD roundtrip AES-256-GCM', () async { final key = Uint8List(32); // all zeros — fine for a test final nonce = Uint8List(12); final plaintext = Uint8List(1024); // 1 KB of zeros for (var i = 0; i < plaintext.length; i++) { plaintext[i] = i & 0xff; } final ciphertext = await CccCrypto.encryptAead( algorithm: CccAeadAlgorithm.aesGcm256, key: key, nonce: nonce, plaintext: plaintext, ); // Ciphertext must be longer than plaintext (appended auth tag). expect(ciphertext.length, greaterThan(plaintext.length)); final decrypted = await CccCrypto.decryptAead( algorithm: CccAeadAlgorithm.aesGcm256, key: key, nonce: nonce, ciphertext: ciphertext, ); expect(decrypted, equals(plaintext)); }); // ═══════════════════════════════════════════════════════════════════════════ // 5.2 AEAD roundtrip — ChaCha20-Poly1305 // ═══════════════════════════════════════════════════════════════════════════ test('5.2 AEAD roundtrip ChaCha20-Poly1305', () async { final key = Uint8List(32); final nonce = Uint8List(12); final plaintext = Uint8List(1024); for (var i = 0; i < plaintext.length; i++) { plaintext[i] = (i * 3) & 0xff; } final ciphertext = await CccCrypto.encryptAead( algorithm: CccAeadAlgorithm.chaCha20Poly1305, key: key, nonce: nonce, plaintext: plaintext, ); expect(ciphertext.length, greaterThan(plaintext.length)); final decrypted = await CccCrypto.decryptAead( algorithm: CccAeadAlgorithm.chaCha20Poly1305, key: key, nonce: nonce, ciphertext: ciphertext, ); expect(decrypted, equals(plaintext)); }); // ═══════════════════════════════════════════════════════════════════════════ // 5.3 AEAD tamper test — modifying ciphertext fails authentication // ═══════════════════════════════════════════════════════════════════════════ test('5.3 AEAD tamper test throws CccAuthenticationFailed', () async { final key = Uint8List(32); final nonce = Uint8List(12); final plaintext = Uint8List.fromList(utf8.encode('Hello, tamper test!')); final ciphertext = await CccCrypto.encryptAead( algorithm: CccAeadAlgorithm.aesGcm256, key: key, nonce: nonce, plaintext: plaintext, ); // Flip a byte in the ciphertext body (not the tag). final tampered = Uint8List.fromList(ciphertext); tampered[0] ^= 0xff; expect( () => CccCrypto.decryptAead( algorithm: CccAeadAlgorithm.aesGcm256, key: key, nonce: nonce, ciphertext: tampered, ), throwsA(isA()), ); }); // ═══════════════════════════════════════════════════════════════════════════ // 5.4 KDF known-vector — HKDF-SHA256 (RFC 5869 Test Case 1) // ═══════════════════════════════════════════════════════════════════════════ test('5.4 KDF HKDF-SHA256 RFC 5869 Test Case 1', () async { final ikm = _hex('0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b'); final salt = _hex('000102030405060708090a0b0c'); final info = _hex('f0f1f2f3f4f5f6f7f8f9'); const length = 42; final expectedOkm = _hex( '3cb25f25faacd57a90434f64d0362f2a' '2d2d0a90cf1a5a4c5db02d56ecc4c5bf' '34007208d5b887185865', ); final okm = await CccCrypto.deriveKey( algorithm: CccKdfAlgorithm.sha256, ikm: ikm, salt: salt, info: info, length: length, ); expect(okm.length, equals(length)); expect(_toHex(okm), equals(_toHex(expectedOkm))); }); // ═══════════════════════════════════════════════════════════════════════════ // 5.5 MAC roundtrip — HMAC-SHA256 compute then verify // ═══════════════════════════════════════════════════════════════════════════ test('5.5 MAC roundtrip HMAC-SHA256', () async { final key = Uint8List(32); for (var i = 0; i < key.length; i++) { key[i] = i & 0xff; } final data = Uint8List.fromList(utf8.encode('MAC roundtrip test data')); final mac = await CccCrypto.computeMac( algorithm: CccMacAlgorithm.hmacSha256, key: key, data: data, ); // HMAC-SHA256 produces 32-byte tag. expect(mac.length, equals(32)); final valid = await CccCrypto.verifyMac( algorithm: CccMacAlgorithm.hmacSha256, key: key, data: data, mac: mac, ); expect(valid, isTrue); }); // ═══════════════════════════════════════════════════════════════════════════ // 5.6 MAC tamper test — modified data fails verification // ═══════════════════════════════════════════════════════════════════════════ test('5.6 MAC tamper test returns false', () async { final key = Uint8List(32); final data = Uint8List.fromList(utf8.encode('original data')); final mac = await CccCrypto.computeMac( algorithm: CccMacAlgorithm.hmacSha256, key: key, data: data, ); // Modify the data after MAC was computed. final tamperedData = Uint8List.fromList(utf8.encode('tampered data')); final valid = await CccCrypto.verifyMac( algorithm: CccMacAlgorithm.hmacSha256, key: key, data: tamperedData, mac: mac, ); expect(valid, isFalse); }); // ═══════════════════════════════════════════════════════════════════════════ // 5.7 Hash known-vector — SHA-256 / SHA-384 / SHA-512 // ═══════════════════════════════════════════════════════════════════════════ test('5.7a Hash SHA-256 known vector', () async { final data = Uint8List.fromList(utf8.encode('abc')); final expected = _hex( 'ba7816bf8f01cfea414140de5dae2223' 'b00361a396177a9cb410ff61f20015ad', ); final digest = await CccCrypto.hash( algorithm: CccHashAlgorithm.sha256, data: data, ); expect(digest.length, equals(32)); expect(_toHex(digest), equals(_toHex(expected))); }); test('5.7b Hash SHA-384 known vector', () async { final data = Uint8List.fromList(utf8.encode('abc')); final expected = _hex( 'cb00753f45a35e8bb5a03d699ac65007' '272c32ab0eded1631a8b605a43ff5bed' '8086072ba1e7cc2358baeca134c825a7', ); final digest = await CccCrypto.hash( algorithm: CccHashAlgorithm.sha384, data: data, ); expect(digest.length, equals(48)); expect(_toHex(digest), equals(_toHex(expected))); }); test('5.7c Hash SHA-512 known vector', () async { final data = Uint8List.fromList(utf8.encode('abc')); final expected = _hex( 'ddaf35a193617abacc417349ae204131' '12e6fa4e89a97ea20a9eeee64b55d39a' '2192992a274fc1a836ba3c23a3feebbd' '454d4423643ce80e2a9ac94fa54ca49f', ); final digest = await CccCrypto.hash( algorithm: CccHashAlgorithm.sha512, data: data, ); expect(digest.length, equals(64)); expect(_toHex(digest), equals(_toHex(expected))); }); // ═══════════════════════════════════════════════════════════════════════════ // 5.8 KEM roundtrip — X25519 // ═══════════════════════════════════════════════════════════════════════════ test('5.8 KEM roundtrip X25519', () async { final kp = await CccCrypto.kemGenerateKeypair( algorithm: CccKemAlgorithm.x25519, ); expect(kp.publicKey.length, equals(32)); expect(kp.privateKey.length, equals(32)); final encap = await CccCrypto.kemEncapsulate( algorithm: CccKemAlgorithm.x25519, publicKey: kp.publicKey, ); expect(encap.sharedSecret.length, equals(32)); expect(encap.ciphertext.length, equals(32)); final decapSecret = await CccCrypto.kemDecapsulate( algorithm: CccKemAlgorithm.x25519, privateKey: kp.privateKey, ciphertext: encap.ciphertext, ); expect(decapSecret, equals(encap.sharedSecret)); }); // ═══════════════════════════════════════════════════════════════════════════ // 5.9 KEM roundtrip — X448 // ═══════════════════════════════════════════════════════════════════════════ test('5.9 KEM roundtrip X448', () async { final kp = await CccCrypto.kemGenerateKeypair( algorithm: CccKemAlgorithm.x448, ); expect(kp.publicKey.length, equals(56)); expect(kp.privateKey.length, equals(56)); final encap = await CccCrypto.kemEncapsulate( algorithm: CccKemAlgorithm.x448, publicKey: kp.publicKey, ); expect(encap.sharedSecret.length, equals(56)); expect(encap.ciphertext.length, equals(56)); final decapSecret = await CccCrypto.kemDecapsulate( algorithm: CccKemAlgorithm.x448, privateKey: kp.privateKey, ciphertext: encap.ciphertext, ); expect(decapSecret, equals(encap.sharedSecret)); }); // ═══════════════════════════════════════════════════════════════════════════ // 5.10 Provider catalog // ═══════════════════════════════════════════════════════════════════════════ test('5.10 Provider catalog', () async { final providers = CccCrypto.listProviders(); expect(providers, isNotEmpty); expect(providers, contains('wolfssl')); final catalog = await CccCrypto.getCapabilities(); expect(catalog.providerName, equals('wolfssl')); // Each category should have at least one algorithm. expect(catalog.aead, isNotEmpty); expect(catalog.kdf, isNotEmpty); expect(catalog.mac, isNotEmpty); expect(catalog.hash, isNotEmpty); expect(catalog.kem, isNotEmpty); // Check that availability and scores are populated. for (final algo in catalog.availableAead) { expect(algo.name, isNotEmpty); expect(algo.efficiencyScore, greaterThanOrEqualTo(0)); expect(algo.reliabilityScore, greaterThanOrEqualTo(0)); } }); // ═══════════════════════════════════════════════════════════════════════════ // 5.11 Self-test validation // ═══════════════════════════════════════════════════════════════════════════ test('5.11 Self-test validation', () async { final result = await CccCrypto.runSelfTest(); expect(result.providerName, equals('wolfssl')); expect(result.results, isNotEmpty); expect(result.allPassed, isTrue, reason: 'Failures: ${result.failures.map((r) => '${r.algoName}: ${r.errorMessage}').join(', ')}'); }); // ═══════════════════════════════════════════════════════════════════════════ // 5.12 Error handling — typed exceptions // ═══════════════════════════════════════════════════════════════════════════ test('5.12a Error: invalid key length throws CccInvalidKey', () async { // AES-256-GCM requires 32-byte key; provide 16 bytes. final shortKey = Uint8List(16); final nonce = Uint8List(12); final plaintext = Uint8List.fromList(utf8.encode('test')); expect( () => CccCrypto.encryptAead( algorithm: CccAeadAlgorithm.aesGcm256, key: shortKey, nonce: nonce, plaintext: plaintext, ), throwsA(isA()), ); }); test('5.12b Error: invalid nonce length throws CccException', () async { final key = Uint8List(32); // AES-GCM nonce should be 12 bytes; provide 8. final shortNonce = Uint8List(8); final plaintext = Uint8List.fromList(utf8.encode('test')); expect( () => CccCrypto.encryptAead( algorithm: CccAeadAlgorithm.aesGcm256, key: key, nonce: shortNonce, plaintext: plaintext, ), throwsA(isA()), ); }); // ═══════════════════════════════════════════════════════════════════════════ // 5.13 Algorithm ID mapping — enum discriminants match Rust repr(u32) // ═══════════════════════════════════════════════════════════════════════════ test('5.13 Algorithm ID mapping', () async { // Verify the catalog reports algorithm IDs matching Rust enum discriminants. final catalog = await CccCrypto.getCapabilities(); // AEAD: AesGcm256 = 12, ChaCha20Poly1305 = 13, XChaCha20Poly1305 = 14 final aeadIds = {for (final a in catalog.aead) a.name: a.id}; expect(aeadIds['AES-256-GCM'], equals(12)); expect(aeadIds['ChaCha20-Poly1305'], equals(13)); // Hash: Sha256 = 40, Sha384 = 41, Sha512 = 42 final hashIds = {for (final a in catalog.hash) a.name: a.id}; expect(hashIds['SHA-256'], equals(40)); expect(hashIds['SHA-384'], equals(41)); expect(hashIds['SHA-512'], equals(42)); // KEM: X25519 = 50, X448 = 51 final kemIds = {for (final a in catalog.kem) a.name: a.id}; expect(kemIds['X25519'], equals(50)); expect(kemIds['X448'], equals(51)); }); }