lum_ccc_fplugin/example/integration_test/ccc_crypto_test.dart

415 lines
17 KiB
Dart

// 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 = <int>[];
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<CccAuthenticationFailed>()),
);
});
// ═══════════════════════════════════════════════════════════════════════════
// 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<CccException>()),
);
});
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<CccException>()),
);
});
// ═══════════════════════════════════════════════════════════════════════════
// 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));
});
}