diff --git a/lib/src/impl_ffi/impl_ffi.aescbc.dart b/lib/src/impl_ffi/impl_ffi.aescbc.dart index fa4a2127..27a84479 100644 --- a/lib/src/impl_ffi/impl_ffi.aescbc.dart +++ b/lib/src/impl_ffi/impl_ffi.aescbc.dart @@ -42,7 +42,7 @@ Stream _aesCbcEncryptOrDecrypt( final ivSize = ssl.EVP_CIPHER_iv_length(cipher); if (iv.length != ivSize) { - throw ArgumentError.value(iv, 'iv', 'must be $ivSize bytes'); + throw operationError('iv must be $ivSize bytes'); } final ctx = scope.createEVP_CIPHER_CTX(); diff --git a/lib/src/testing/regression/aes_cbc_invalid_iv.dart b/lib/src/testing/regression/aes_cbc_invalid_iv.dart new file mode 100644 index 00000000..4fa1e10b --- /dev/null +++ b/lib/src/testing/regression/aes_cbc_invalid_iv.dart @@ -0,0 +1,62 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:webcrypto/webcrypto.dart'; + +import '../utils/utils.dart'; + +List<({String name, Future Function() test})> tests() => [ + for (final ivLength in [15, 17]) + ( + name: 'AES-CBC rejects IVs with $ivLength bytes', + test: () async { + final key = await AesCbcSecretKey.importRawKey(List.filled(16, 0)); + const plaintext = [1, 2, 3]; + final ciphertext = await key.encryptBytes( + plaintext, + List.filled(16, 0), + ); + final iv = List.filled(ivLength, 0); + + await _expectOperationError( + () => key.encryptBytes(plaintext, iv), + 'Expected encryption with a $ivLength-byte IV to be rejected', + ); + await _expectOperationError( + () => key.decryptBytes(ciphertext, iv), + 'Expected decryption with a $ivLength-byte IV to be rejected', + ); + await _expectOperationError( + () => key.encryptStream(Stream.value(plaintext), iv).drain(), + 'Expected stream encryption with a $ivLength-byte IV to be rejected', + ); + await _expectOperationError( + () => key.decryptStream(Stream.value(ciphertext), iv).drain(), + 'Expected stream decryption with a $ivLength-byte IV to be rejected', + ); + }, + ), +]; + +Future _expectOperationError( + Future Function() callback, + String message, +) async { + try { + await callback(); + } on OperationError { + return; + } + check(false, message); +} diff --git a/lib/src/testing/testing.dart b/lib/src/testing/testing.dart index 7099328c..ff9ee058 100644 --- a/lib/src/testing/testing.dart +++ b/lib/src/testing/testing.dart @@ -30,6 +30,7 @@ import 'webcrypto/rsassapkcs1v15.dart' as rsassapkcs1v15; // Other test files, that don't use TestRunner import 'webcrypto/random.dart' as random; import 'webcrypto/digest.dart' as digest; +import 'regression/aes_cbc_invalid_iv.dart' as aes_cbc_invalid_iv; import 'regression/issue_302_hmac_jwk_length.dart' as issue_302_hmac_jwk_length; import 'regression/aes_gcm_invalid_tag_length.dart' as aes_gcm_invalid_tag_length; @@ -67,6 +68,7 @@ void runAllTests( for (final r in _testRunners) ...r.tests(), ...random.tests(), ...digest.tests(), + ...aes_cbc_invalid_iv.tests(), ...issue_302_hmac_jwk_length.tests(), ...aes_gcm_invalid_tag_length.tests(), ...issue_60_trailing_bytes.tests(),