diff --git a/api/lib/uploader/uploader_utils.dart b/api/lib/uploader/uploader_utils.dart index c8eb268..2f2dac9 100644 --- a/api/lib/uploader/uploader_utils.dart +++ b/api/lib/uploader/uploader_utils.dart @@ -57,7 +57,7 @@ class UploaderUtils { paramsMap['timestamp'] = (DateTime.now().millisecondsSinceEpoch / 1000).toString(); paramsMap['signature'] = - Utils.apiSignRequest(paramsMap, config.apiSecret!); + Utils.apiSignRequest(paramsMap, config.apiSecret!, signatureVersion: cloudinary.config.cloudConfig.signatureVersion); paramsMap['api_key'] = config.apiKey; if (paramsMap['unsigned'] != null) { paramsMap.remove('unsigned'); diff --git a/api/lib/uploader/utils.dart b/api/lib/uploader/utils.dart index fac3cce..e04b45b 100644 --- a/api/lib/uploader/utils.dart +++ b/api/lib/uploader/utils.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:math'; +import 'package:cloudinary_url_gen/config/cloud_config.dart'; import 'package:convert/convert.dart'; import 'package:crypto/crypto.dart'; @@ -10,33 +11,19 @@ class Utils { 'filename', ]; - static String apiSignRequest( - Map paramsMap, String apiSecret) { - List paramsArr = []; - paramsMap.removeWhere((key, value) => value == null); - paramsMap.removeWhere( - (key, value) => value == null || _excludeKeys.contains(key)); - var sortedParams = paramsMap.keys.whereType().toList()..sort(); - for (var key in sortedParams) { - var value = paramsMap[key]; - String? paramValue; - if (value is List) { - if (value.isNotEmpty) { - paramValue = value.toString(); //.join(','); - } else { - continue; - } - } else { - if (value != null) { - paramValue = value.toString(); - } - } - if (paramValue != null) { - paramsArr.add('$key=${paramValue.replaceAll(r'\', '')}'); + static String apiSignRequest(Map paramsMap, String apiSecret, {int? signatureVersion = defaultSignatureVersion}) { + paramsMap.removeWhere((key, value) => value == null || _excludeKeys.contains(key)); + + String queryString = (paramsMap.keys.whereType().toList()..sort()) + .where((key) => paramsMap[key] is List ? (paramsMap[key] as List).isNotEmpty : paramsMap[key] != null) + .map((key) { + var value = paramsMap[key].toString().replaceAll(r'\', ''); + if (signatureVersion == 2) { + value = value.replaceAll('&', '%26'); } - } - var toSign = '${paramsArr.join('&')}$apiSecret'; - return hex.encode(sha1.convert(utf8.encode(toSign)).bytes); + return '$key=$value'; + }).join('&'); + return hex.encode(sha1.convert(utf8.encode(queryString + apiSecret)).bytes); } static bool isRemoteUrl(String value) { diff --git a/api/pubspec.yaml b/api/pubspec.yaml index 2b042aa..69aa6b4 100644 --- a/api/pubspec.yaml +++ b/api/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: crypto: ^3.0.3 convert: ^3.1.1 http_parser: ^4.0.2 - cloudinary_url_gen: ^1.7.0 + cloudinary_url_gen: ^1.8.0 dev_dependencies: lints: ^3.0.0 diff --git a/api/test/uploader_test.dart b/api/test/uploader_test.dart index fca7a85..552fc67 100644 --- a/api/test/uploader_test.dart +++ b/api/test/uploader_test.dart @@ -549,6 +549,40 @@ void main() { var result = resultOrThrow(response?.data); assert(result.playbackUrl != null); }); + + test('Test signature with escaping characters', () { + const cloudName = 'dn6ot3ged'; + const secret = 'hdcixPpR2iKERPwqvH6sHdK9cyac'; + + final paramsWithAmpersand = { + 'cloud_name': cloudName, + 'timestamp': 1568810420, + 'notification_url': 'https://fake.com/callback?a=1&tags=hello,world' + }; + + final signatureWithAmpersand = Utils.apiSignRequest(paramsWithAmpersand, secret); + + final paramsSmuggled = { + 'cloud_name': cloudName, + 'timestamp': 1568810420, + 'notification_url': 'https://fake.com/callback?a=1', + 'tags': 'hello,world' + }; + + final signatureSmuggled = Utils.apiSignRequest(paramsSmuggled, secret); + + expect(signatureWithAmpersand, isNot(equals(signatureSmuggled)), + reason: 'Signatures should be different to prevent parameter smuggling'); + + const expectedSignature = '4fdf465dd89451cc1ed8ec5b3e314e8a51695704'; + expect(signatureWithAmpersand, equals(expectedSignature)); + + const expectedSmuggledSignature = '7b4e3a539ff1fa6e6700c41b3a2ee77586a025f9'; + expect(signatureSmuggled, equals(expectedSmuggledSignature)); + + final versionOneSignature = Utils.apiSignRequest(paramsSmuggled, secret, signatureVersion: 1); + expect(versionOneSignature, equals(signatureSmuggled)); + }); } validateSignature(UploadResult result) {