From 92724b8d518839fb5db9068204ebccbf67edc487 Mon Sep 17 00:00:00 2001 From: Justin Boswell Date: Wed, 28 Jul 2021 12:54:37 -0700 Subject: [PATCH] Implemented SigV4A (#40) * Implemented should_sign_header callback * Added SigV4A to SigningAlgorithm * Added SigV4A test * Added debugging info to README * Fixed memory leak from argument vectors * Fixed memory leak of stream read buffers * Added support for callables as option values * Added should_sign_header support and test * Fixed ELG type bug exposed by aws-crt-ffi change * Updated to aws-crt-ffi v0.2.0 --- README.md | 20 +++ crt/aws-crt-ffi | 2 +- ext/awscrt.c | 1 + ext/awscrt.stub.php | 2 + ext/awscrt_arginfo.h | 182 ++++++++++++++------------ ext/crt.c | 29 ++-- ext/event_loop.c | 2 +- ext/http.c | 2 +- ext/php_aws_crt.h | 59 +++++---- ext/php_util.c | 33 +++++ ext/signing.c | 53 ++++++++ ext/stream.c | 2 +- src/AWS/CRT/Auth/SigningAlgorithm.php | 3 +- src/AWS/CRT/Auth/SigningConfigAWS.php | 6 +- src/AWS/CRT/CRT.php | 4 + src/AWS/CRT/Options.php | 4 + tests/SigningTest.php | 89 ++++++++++++- 17 files changed, 361 insertions(+), 132 deletions(-) create mode 100644 ext/php_util.c diff --git a/README.md b/README.md index 9c691aa..d942eb0 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,26 @@ C:\php-sdk\php-\vc15\x64\php-src $ nmake test-awscrt ``` +## Debugging +Using [PHPBrew](https://github.com/phpbrew/phpbrew) to build/manage multiple versions of PHP is helpful. + +Note: You must use a debug build of PHP to debug native extensions. +See the [PHP Internals Book](https://www.phpinternalsbook.com/php7/build_system/building_php.html) for more info + +```shell +# PHP 8 example +$ phpbrew install --stdout -j 8 8.0 +default -- CFLAGS=-Wno-error --disable-cgi --enable-debug +# PHP 5.5 example +$ phpbrew install --stdout -j 8 5.5 +default -openssl -mbstring -- CFLAGS="-w -Wno-error" --enable-debug --with-zlib=/usr/local/opt/zlib +$ phpbrew switch php-8.0.6 # or whatever version is current, it'll be at the end of the build output +$ phpize +$ ./configure +$ make CMAKE_BUILD_TYPE=Debug +``` + +Ensure that the php you launch from your debugger is the result of `which php`, not just +the system default php. + ## Security See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. diff --git a/crt/aws-crt-ffi b/crt/aws-crt-ffi index 105be1d..a7c2c91 160000 --- a/crt/aws-crt-ffi +++ b/crt/aws-crt-ffi @@ -1 +1 @@ -Subproject commit 105be1d1ce027d748c8834493533186300075160 +Subproject commit a7c2c91a1009ab859f562761826593d44f11aca7 diff --git a/ext/awscrt.c b/ext/awscrt.c index c72b758..16a8e46 100644 --- a/ext/awscrt.c +++ b/ext/awscrt.c @@ -13,3 +13,4 @@ #include "stream.c" // #include "hash.c" #include "crc.c" +#include "php_util.c" diff --git a/ext/awscrt.stub.php b/ext/awscrt.stub.php index b7f7a10..dafa6d3 100644 --- a/ext/awscrt.stub.php +++ b/ext/awscrt.stub.php @@ -105,6 +105,8 @@ function aws_crt_signing_config_aws_set_expiration_in_seconds(int $config, int $ function aws_crt_signing_config_aws_set_date(int $config, int $timestamp): void {} +function aws_crt_signing_config_aws_set_should_sign_header_fn(int $config, object $should_sign_header): void {} + function aws_crt_signable_new_from_http_request(int $http_message): int {} function aws_crt_signable_new_from_chunk(int $input_stream, string $previous_signature): int {} diff --git a/ext/awscrt_arginfo.h b/ext/awscrt_arginfo.h index 811492a..f5c9812 100644 --- a/ext/awscrt_arginfo.h +++ b/ext/awscrt_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 36d4e512f02a12c10689eb9b0941bcd7837fbf1d */ + * Stub hash: 9b39b5b4523538d8b64b2558e11dfafb209c8ea8 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_aws_crt_last_error, 0, 0, 0) ZEND_END_ARG_INFO() @@ -195,6 +195,11 @@ ZEND_ARG_INFO(0, config) ZEND_ARG_INFO(0, timestamp) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO_EX(arginfo_aws_crt_signing_config_aws_set_should_sign_header_fn, 0, 0, 2) +ZEND_ARG_INFO(0, config) +ZEND_ARG_INFO(0, should_sign_header) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_INFO_EX(arginfo_aws_crt_signable_new_from_http_request, 0, 0, 1) ZEND_ARG_INFO(0, http_message) ZEND_END_ARG_INFO() @@ -285,6 +290,7 @@ ZEND_FUNCTION(aws_crt_signing_config_aws_set_signed_body_value); ZEND_FUNCTION(aws_crt_signing_config_aws_set_signed_body_header_type); ZEND_FUNCTION(aws_crt_signing_config_aws_set_expiration_in_seconds); ZEND_FUNCTION(aws_crt_signing_config_aws_set_date); +ZEND_FUNCTION(aws_crt_signing_config_aws_set_should_sign_header_fn); ZEND_FUNCTION(aws_crt_signable_new_from_http_request); ZEND_FUNCTION(aws_crt_signable_new_from_chunk); ZEND_FUNCTION(aws_crt_signable_new_from_canonical_request); @@ -295,86 +301,98 @@ ZEND_FUNCTION(aws_crt_sign_request_aws); ZEND_FUNCTION(aws_crt_crc32); ZEND_FUNCTION(aws_crt_crc32c); -static const zend_function_entry ext_functions[] = - {ZEND_FE(aws_crt_last_error, arginfo_aws_crt_last_error) ZEND_FE(aws_crt_error_name, arginfo_aws_crt_error_name) ZEND_FE( +static const zend_function_entry ext_functions + [] = {ZEND_FE(aws_crt_last_error, arginfo_aws_crt_last_error) ZEND_FE(aws_crt_error_name, arginfo_aws_crt_error_name) ZEND_FE( aws_crt_error_str, arginfo_aws_crt_error_str) ZEND_FE(aws_crt_error_debug_str, arginfo_aws_crt_error_debug_str) - ZEND_FE(aws_crt_event_loop_group_options_new, arginfo_aws_crt_event_loop_group_options_new) ZEND_FE( - aws_crt_event_loop_group_options_release, - arginfo_aws_crt_event_loop_group_options_release) ZEND_FE(aws_crt_event_loop_group_options_set_max_threads, arginfo_aws_crt_event_loop_group_options_set_max_threads) - ZEND_FE(aws_crt_event_loop_group_new, arginfo_aws_crt_event_loop_group_new) ZEND_FE( - aws_crt_event_loop_group_release, - arginfo_aws_crt_event_loop_group_release) ZEND_FE(aws_crt_input_stream_options_new, arginfo_aws_crt_input_stream_options_new) - ZEND_FE(aws_crt_input_stream_options_release, arginfo_aws_crt_input_stream_options_release) ZEND_FE( - aws_crt_input_stream_options_set_user_data, - arginfo_aws_crt_input_stream_options_set_user_data) ZEND_FE(aws_crt_input_stream_new, arginfo_aws_crt_input_stream_new) - ZEND_FE(aws_crt_input_stream_release, arginfo_aws_crt_input_stream_release) ZEND_FE( - aws_crt_input_stream_seek, - arginfo_aws_crt_input_stream_seek) ZEND_FE(aws_crt_input_stream_read, arginfo_aws_crt_input_stream_read) - ZEND_FE(aws_crt_input_stream_eof, arginfo_aws_crt_input_stream_eof) ZEND_FE( - aws_crt_input_stream_get_length, - arginfo_aws_crt_input_stream_get_length) - ZEND_FE(aws_crt_http_message_new_from_blob, arginfo_aws_crt_http_message_new_from_blob) ZEND_FE( - aws_crt_http_message_to_blob, - arginfo_aws_crt_http_message_to_blob) - ZEND_FE(aws_crt_http_message_release, arginfo_aws_crt_http_message_release) ZEND_FE( - aws_crt_credentials_options_new, - arginfo_aws_crt_credentials_options_new) - ZEND_FE(aws_crt_credentials_options_release, arginfo_aws_crt_credentials_options_release) ZEND_FE( - aws_crt_credentials_options_set_access_key_id, - arginfo_aws_crt_credentials_options_set_access_key_id) - ZEND_FE( - aws_crt_credentials_options_set_secret_access_key, - arginfo_aws_crt_credentials_options_set_secret_access_key) - ZEND_FE( - aws_crt_credentials_options_set_session_token, - arginfo_aws_crt_credentials_options_set_session_token) - ZEND_FE( - aws_crt_credentials_options_set_expiration_timepoint_seconds, - arginfo_aws_crt_credentials_options_set_expiration_timepoint_seconds) - ZEND_FE(aws_crt_credentials_new, arginfo_aws_crt_credentials_new) ZEND_FE( - aws_crt_credentials_release, - arginfo_aws_crt_credentials_release) - ZEND_FE( - aws_crt_credentials_provider_release, - arginfo_aws_crt_credentials_provider_release) - ZEND_FE( - aws_crt_credentials_provider_static_options_new, - arginfo_aws_crt_credentials_provider_static_options_new) - ZEND_FE( - aws_crt_credentials_provider_static_options_release, - arginfo_aws_crt_credentials_provider_static_options_release) - ZEND_FE( - aws_crt_credentials_provider_static_options_set_access_key_id, - arginfo_aws_crt_credentials_provider_static_options_set_access_key_id) - ZEND_FE( - aws_crt_credentials_provider_static_options_set_secret_access_key, - arginfo_aws_crt_credentials_provider_static_options_set_secret_access_key) - ZEND_FE( - aws_crt_credentials_provider_static_options_set_session_token, - arginfo_aws_crt_credentials_provider_static_options_set_session_token) - ZEND_FE( - aws_crt_credentials_provider_static_new, - arginfo_aws_crt_credentials_provider_static_new) - ZEND_FE( - aws_crt_signing_config_aws_new, - arginfo_aws_crt_signing_config_aws_new) - ZEND_FE( - aws_crt_signing_config_aws_release, - arginfo_aws_crt_signing_config_aws_release) - ZEND_FE( - aws_crt_signing_config_aws_set_algorithm, - arginfo_aws_crt_signing_config_aws_set_algorithm) ZEND_FE(aws_crt_signing_config_aws_set_signature_type, arginfo_aws_crt_signing_config_aws_set_signature_type) ZEND_FE(aws_crt_signing_config_aws_set_credentials_provider, arginfo_aws_crt_signing_config_aws_set_credentials_provider) ZEND_FE(aws_crt_signing_config_aws_set_region, arginfo_aws_crt_signing_config_aws_set_region) ZEND_FE(aws_crt_signing_config_aws_set_service, arginfo_aws_crt_signing_config_aws_set_service) ZEND_FE(aws_crt_signing_config_aws_set_use_double_uri_encode, arginfo_aws_crt_signing_config_aws_set_use_double_uri_encode) ZEND_FE(aws_crt_signing_config_aws_set_should_normalize_uri_path, arginfo_aws_crt_signing_config_aws_set_should_normalize_uri_path) ZEND_FE(aws_crt_signing_config_aws_set_omit_session_token, arginfo_aws_crt_signing_config_aws_set_omit_session_token) ZEND_FE(aws_crt_signing_config_aws_set_signed_body_value, arginfo_aws_crt_signing_config_aws_set_signed_body_value) ZEND_FE(aws_crt_signing_config_aws_set_signed_body_header_type, arginfo_aws_crt_signing_config_aws_set_signed_body_header_type) ZEND_FE(aws_crt_signing_config_aws_set_expiration_in_seconds, arginfo_aws_crt_signing_config_aws_set_expiration_in_seconds) ZEND_FE(aws_crt_signing_config_aws_set_date, arginfo_aws_crt_signing_config_aws_set_date) ZEND_FE(aws_crt_signable_new_from_http_request, arginfo_aws_crt_signable_new_from_http_request) ZEND_FE(aws_crt_signable_new_from_chunk, arginfo_aws_crt_signable_new_from_chunk) ZEND_FE(aws_crt_signable_new_from_canonical_request, arginfo_aws_crt_signable_new_from_canonical_request) ZEND_FE(aws_crt_signable_release, arginfo_aws_crt_signable_release) ZEND_FE(aws_crt_signing_result_release, arginfo_aws_crt_signing_result_release) - ZEND_FE( - aws_crt_signing_result_apply_to_http_request, - arginfo_aws_crt_signing_result_apply_to_http_request) - ZEND_FE( - aws_crt_sign_request_aws, - arginfo_aws_crt_sign_request_aws) - ZEND_FE( - aws_crt_crc32, - arginfo_aws_crt_crc32) - ZEND_FE( - aws_crt_crc32c, - arginfo_aws_crt_crc32c) - ZEND_FE_END}; + ZEND_FE(aws_crt_event_loop_group_options_new, arginfo_aws_crt_event_loop_group_options_new) ZEND_FE( + aws_crt_event_loop_group_options_release, + arginfo_aws_crt_event_loop_group_options_release) ZEND_FE(aws_crt_event_loop_group_options_set_max_threads, arginfo_aws_crt_event_loop_group_options_set_max_threads) + ZEND_FE(aws_crt_event_loop_group_new, arginfo_aws_crt_event_loop_group_new) ZEND_FE( + aws_crt_event_loop_group_release, + arginfo_aws_crt_event_loop_group_release) ZEND_FE(aws_crt_input_stream_options_new, arginfo_aws_crt_input_stream_options_new) + ZEND_FE(aws_crt_input_stream_options_release, arginfo_aws_crt_input_stream_options_release) ZEND_FE( + aws_crt_input_stream_options_set_user_data, + arginfo_aws_crt_input_stream_options_set_user_data) ZEND_FE(aws_crt_input_stream_new, arginfo_aws_crt_input_stream_new) + ZEND_FE(aws_crt_input_stream_release, arginfo_aws_crt_input_stream_release) ZEND_FE( + aws_crt_input_stream_seek, + arginfo_aws_crt_input_stream_seek) ZEND_FE(aws_crt_input_stream_read, arginfo_aws_crt_input_stream_read) + ZEND_FE(aws_crt_input_stream_eof, arginfo_aws_crt_input_stream_eof) ZEND_FE( + aws_crt_input_stream_get_length, + arginfo_aws_crt_input_stream_get_length) ZEND_FE(aws_crt_http_message_new_from_blob, arginfo_aws_crt_http_message_new_from_blob) + ZEND_FE(aws_crt_http_message_to_blob, arginfo_aws_crt_http_message_to_blob) ZEND_FE( + aws_crt_http_message_release, + arginfo_aws_crt_http_message_release) ZEND_FE(aws_crt_credentials_options_new, arginfo_aws_crt_credentials_options_new) + ZEND_FE(aws_crt_credentials_options_release, arginfo_aws_crt_credentials_options_release) ZEND_FE( + aws_crt_credentials_options_set_access_key_id, + arginfo_aws_crt_credentials_options_set_access_key_id) + ZEND_FE( + aws_crt_credentials_options_set_secret_access_key, + arginfo_aws_crt_credentials_options_set_secret_access_key) + ZEND_FE(aws_crt_credentials_options_set_session_token, arginfo_aws_crt_credentials_options_set_session_token) ZEND_FE( + aws_crt_credentials_options_set_expiration_timepoint_seconds, + arginfo_aws_crt_credentials_options_set_expiration_timepoint_seconds) + ZEND_FE(aws_crt_credentials_new, arginfo_aws_crt_credentials_new) ZEND_FE( + aws_crt_credentials_release, + arginfo_aws_crt_credentials_release) + ZEND_FE(aws_crt_credentials_provider_release, arginfo_aws_crt_credentials_provider_release) ZEND_FE( + aws_crt_credentials_provider_static_options_new, + arginfo_aws_crt_credentials_provider_static_options_new) + ZEND_FE( + aws_crt_credentials_provider_static_options_release, + arginfo_aws_crt_credentials_provider_static_options_release) + ZEND_FE( + aws_crt_credentials_provider_static_options_set_access_key_id, + arginfo_aws_crt_credentials_provider_static_options_set_access_key_id) + ZEND_FE( + aws_crt_credentials_provider_static_options_set_secret_access_key, + arginfo_aws_crt_credentials_provider_static_options_set_secret_access_key) + ZEND_FE( + aws_crt_credentials_provider_static_options_set_session_token, + arginfo_aws_crt_credentials_provider_static_options_set_session_token) + ZEND_FE( + aws_crt_credentials_provider_static_new, + arginfo_aws_crt_credentials_provider_static_new) + ZEND_FE( + aws_crt_signing_config_aws_new, + arginfo_aws_crt_signing_config_aws_new) + ZEND_FE( + aws_crt_signing_config_aws_release, + arginfo_aws_crt_signing_config_aws_release) + ZEND_FE( + aws_crt_signing_config_aws_set_algorithm, + arginfo_aws_crt_signing_config_aws_set_algorithm) + ZEND_FE( + aws_crt_signing_config_aws_set_signature_type, + arginfo_aws_crt_signing_config_aws_set_signature_type) + ZEND_FE( + aws_crt_signing_config_aws_set_credentials_provider, + arginfo_aws_crt_signing_config_aws_set_credentials_provider) + ZEND_FE( + aws_crt_signing_config_aws_set_region, + arginfo_aws_crt_signing_config_aws_set_region) + ZEND_FE( + aws_crt_signing_config_aws_set_service, + arginfo_aws_crt_signing_config_aws_set_service) + ZEND_FE(aws_crt_signing_config_aws_set_use_double_uri_encode, arginfo_aws_crt_signing_config_aws_set_use_double_uri_encode) ZEND_FE(aws_crt_signing_config_aws_set_should_normalize_uri_path, arginfo_aws_crt_signing_config_aws_set_should_normalize_uri_path) ZEND_FE(aws_crt_signing_config_aws_set_omit_session_token, arginfo_aws_crt_signing_config_aws_set_omit_session_token) ZEND_FE(aws_crt_signing_config_aws_set_signed_body_value, arginfo_aws_crt_signing_config_aws_set_signed_body_value) ZEND_FE(aws_crt_signing_config_aws_set_signed_body_header_type, arginfo_aws_crt_signing_config_aws_set_signed_body_header_type) ZEND_FE(aws_crt_signing_config_aws_set_expiration_in_seconds, arginfo_aws_crt_signing_config_aws_set_expiration_in_seconds) ZEND_FE(aws_crt_signing_config_aws_set_date, arginfo_aws_crt_signing_config_aws_set_date) ZEND_FE(aws_crt_signing_config_aws_set_should_sign_header_fn, arginfo_aws_crt_signing_config_aws_set_should_sign_header_fn) ZEND_FE(aws_crt_signable_new_from_http_request, arginfo_aws_crt_signable_new_from_http_request) ZEND_FE(aws_crt_signable_new_from_chunk, arginfo_aws_crt_signable_new_from_chunk) ZEND_FE( + aws_crt_signable_new_from_canonical_request, + arginfo_aws_crt_signable_new_from_canonical_request) + ZEND_FE( + aws_crt_signable_release, + arginfo_aws_crt_signable_release) + ZEND_FE( + aws_crt_signing_result_release, + arginfo_aws_crt_signing_result_release) + ZEND_FE( + aws_crt_signing_result_apply_to_http_request, + arginfo_aws_crt_signing_result_apply_to_http_request) + ZEND_FE( + aws_crt_sign_request_aws, + arginfo_aws_crt_sign_request_aws) + ZEND_FE( + aws_crt_crc32, + arginfo_aws_crt_crc32) + ZEND_FE( + aws_crt_crc32c, + arginfo_aws_crt_crc32c) + ZEND_FE_END}; diff --git a/ext/crt.c b/ext/crt.c index 0a37d40..766aa82 100644 --- a/ext/crt.c +++ b/ext/crt.c @@ -51,11 +51,7 @@ zval aws_php_invoke_callback(zval *callback, const char *arg_types, ...) { case 's': { const char *buf = va_arg(va, const char *); const size_t len = va_arg(va, size_t); -#if AWS_PHP_AT_LEAST_7 - ZVAL_STRINGL(&stack[arg_idx], buf, len); -#else - ZVAL_STRINGL(&stack[arg_idx], buf, len, 0); -#endif + aws_php_zval_stringl(&stack[arg_idx], buf, len); break; } /* other primitives */ @@ -129,17 +125,22 @@ zval aws_php_invoke_callback(zval *callback, const char *arg_types, ...) { #endif /* Clean up arguments */ - for (int arg_idx = 0; arg_idx < num_args; ++arg_idx) { -#if !AWS_PHP_AT_LEAST_7 - zval_ptr_dtor(args[arg_idx]); -#else - zval_ptr_dtor(&stack[arg_idx]); +#if AWS_PHP_AT_LEAST_7 + zend_fcall_info_args_clear(&fci, 1); #endif - } return retval; } +void aws_php_zval_stringl(zval *val, const char *str, size_t len) { + AWS_FATAL_ASSERT(val != NULL); +#if AWS_PHP_AT_LEAST_7 + ZVAL_STRINGL(val, str, len); +#else + ZVAL_STRINGL(val, str, len, 1); +#endif +} + aws_php_thread_queue s_aws_php_main_thread_queue; bool aws_php_is_main_thread(void) { @@ -290,7 +291,7 @@ PHP_FUNCTION(aws_crt_error_str) { zend_ulong error_code = 0; aws_php_parse_parameters("l", &error_code); - RETURN_STRING(aws_crt_error_str(error_code)); + XRETURN_STRING(aws_crt_error_str(error_code)); } /* aws_crt_error_name(int error_code) */ @@ -298,7 +299,7 @@ PHP_FUNCTION(aws_crt_error_name) { zend_ulong error_code = 0; aws_php_parse_parameters("l", &error_code); - RETURN_STRING(aws_crt_error_name(error_code)); + XRETURN_STRING(aws_crt_error_name(error_code)); } /* aws_crt_error_debug_str(int error_code) */ @@ -306,5 +307,5 @@ PHP_FUNCTION(aws_crt_error_debug_str) { zend_ulong error_code = 0; aws_php_parse_parameters("l", &error_code); - RETURN_STRING(aws_crt_error_debug_str(error_code)); + XRETURN_STRING(aws_crt_error_debug_str(error_code)); } diff --git a/ext/event_loop.c b/ext/event_loop.c index 7e78bef..cd8d42f 100644 --- a/ext/event_loop.c +++ b/ext/event_loop.c @@ -43,6 +43,6 @@ PHP_FUNCTION(aws_crt_event_loop_group_release) { aws_php_parse_parameters("l", &php_elg); - struct aws_event_loop_group *elg = (void *)php_elg; + aws_crt_event_loop_group *elg = (void *)php_elg; aws_crt_event_loop_group_release(elg); } diff --git a/ext/http.c b/ext/http.c index c76e9fd..3f429ae 100644 --- a/ext/http.c +++ b/ext/http.c @@ -23,7 +23,7 @@ PHP_FUNCTION(aws_crt_http_message_to_blob) { aws_crt_http_message *message = (void *)php_msg; aws_crt_buf blob; aws_crt_http_message_to_blob(message, &blob); - RETURN_STRINGL((const char *)blob.blob, blob.length); + XRETURN_STRINGL((const char *)blob.blob, blob.length); } PHP_FUNCTION(aws_crt_http_message_release) { diff --git a/ext/php_aws_crt.h b/ext/php_aws_crt.h index 80f5119..9393a0b 100644 --- a/ext/php_aws_crt.h +++ b/ext/php_aws_crt.h @@ -48,21 +48,16 @@ ZEND_EXTERN_MODULE_GLOBALS(awscrt) #if AWS_PHP_AT_LEAST_7 /* PHP 7 takes a zval*, PHP5 takes a zval** */ # define AWS_PHP_STREAM_FROM_ZVAL(s, z) php_stream_from_zval(s, z) -#else /* PHP 5.5-5.6, 7.0-7.1 */ -/* PHP 7.2+ always duplicate string return values */ -# undef RETURN_STRING -# define RETURN_STRING(s) \ - { \ - RETVAL_STRING(s, 1); \ - return; \ - } -# undef RETURN_STRINGL -# define RETURN_STRINGL(s, l) \ - { \ - RETVAL_STRINGL(s, l, 1); \ - return; \ - } +#define XRETURN_STRINGL RETURN_STRINGL +#define XRETURN_STRING RETURN_STRING +#define XRETVAL_STRINGL RETVAL_STRINGL +#define XRETVAL_STRING RETVAL_STRING +#else /* PHP 5.5-5.6 */ # define AWS_PHP_STREAM_FROM_ZVAL(s, z) php_stream_from_zval(s, &z) +#define XRETURN_STRINGL(s, l) RETURN_STRINGL(s, l, 1) +#define XRETURN_STRING(s) RETURN_STRING(s, 1) +#define XRETVAL_STRINGL(s, l) RETVAL_STRINGL(s, l, 1) +#define XRETVAL_STRING(s) RETVAL_STRING(s, 1) #endif /* PHP 5.x */ #include "api.h" @@ -100,29 +95,39 @@ ZEND_EXTERN_MODULE_GLOBALS(awscrt) * throws an exception resulting from argument parsing, notes the current function name in the exception */ #define aws_php_argparse_fail() \ -do { \ - aws_php_throw_exception("Failed to parse arguments to %s", __func__); \ -} while (0) + do { \ + aws_php_throw_exception("Failed to parse arguments to %s", __func__); \ + } while (0) /** * calls zend_parse_parameters() with the arguments and throws an exception if parsing fails */ #define aws_php_parse_parameters(type_spec, ...) \ -do { \ - if (zend_parse_parameters(ZEND_NUM_ARGS(), type_spec, __VA_ARGS__) == FAILURE) { \ - aws_php_argparse_fail(); \ - } \ -} while (0) + do { \ + if (zend_parse_parameters(ZEND_NUM_ARGS(), type_spec, __VA_ARGS__) == FAILURE) { \ + aws_php_argparse_fail(); \ + } \ + } while (0) /** * calls zend_parse_parameters_none() and throws an exception if parsing fails */ #define aws_php_parse_parameters_none() \ -do { \ - if (zend_parse_parameters_none() == FAILURE) { \ - aws_php_argparse_fail(); \ - } \ -} while (0) + do { \ + if (zend_parse_parameters_none() == FAILURE) { \ + aws_php_argparse_fail(); \ + } \ + } while (0) + +/* PHP/Zend utility functions to work across PHP versions */ +zval *aws_php_zval_new(void); +void aws_php_zval_dtor(void *zval_ptr); +bool aws_php_zval_as_bool(zval *z); +void aws_php_zval_copy(zval *dest, zval *src); +/** + * Replacement for ZVAL_STRINGL that is PHP version agnostic + */ +void aws_php_zval_stringl(zval *val, const char *str, size_t len); /* Thread queue functions for managing PHP's optional threading situation */ typedef struct _aws_php_task { diff --git a/ext/php_util.c b/ext/php_util.c new file mode 100644 index 0000000..8e7bc30 --- /dev/null +++ b/ext/php_util.c @@ -0,0 +1,33 @@ + +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include "php_aws_crt.h" + +zval *aws_php_zval_new(void) { + return emalloc(sizeof(zval)); +} + +void aws_php_zval_dtor(void *zval_ptr) { + zval *z = zval_ptr; + zval_dtor(z); + efree(z); +} + +bool aws_php_zval_as_bool(zval *z) { +#if AWS_PHP_AT_LEAST_7 + return (Z_TYPE_P(z) == IS_TRUE); +#else + return (Z_TYPE_P(z) == IS_BOOL && Z_LVAL_P(z) != 0); +#endif +} + +void aws_php_zval_copy(zval *dest, zval *src) { +#if AWS_PHP_AT_LEAST_7 + ZVAL_COPY(dest, src); +#else + ZVAL_COPY_VALUE(dest, src); +#endif +} diff --git a/ext/signing.c b/ext/signing.c index 124da0c..702319a 100644 --- a/ext/signing.c +++ b/ext/signing.c @@ -151,6 +151,59 @@ PHP_FUNCTION(aws_crt_signing_config_aws_set_date) { aws_crt_signing_config_aws_set_date(signing_config, php_timestamp); } +typedef struct _should_sign_header_data { + zval *should_sign_header; + zval *header_name; + bool result; +} should_sign_header_data; + +static void should_sign_header_task(void *data) { + should_sign_header_data *task = data; + zval result = aws_php_invoke_callback(task->should_sign_header, "z", task->header_name); + task->result = aws_php_zval_as_bool(&result); + zval_dtor(&result); +} + +static bool aws_php_should_sign_header(const char *header_name, size_t header_length, void *user_data) { + zval php_header_name; + aws_php_zval_stringl(&php_header_name, header_name, header_length); + + should_sign_header_data task_data = { + .should_sign_header = user_data, + .header_name = &php_header_name, + .result = false, + }; + + aws_php_task task = { + .callback = should_sign_header_task, + .data = &task_data, + }; + + aws_php_thread_queue_push(&s_aws_php_main_thread_queue, task); + aws_php_thread_queue_yield(&s_aws_php_main_thread_queue); + + zval_dtor(&php_header_name); + + return task_data.result; +} + +PHP_FUNCTION(aws_crt_signing_config_aws_set_should_sign_header_fn) { + zend_ulong php_signing_config = 0; + zval *php_should_sign_header = NULL; + + aws_php_parse_parameters("lz", &php_signing_config, &php_should_sign_header); + + aws_crt_signing_config_aws *signing_config = (void *)php_signing_config; + + /* copy/retain PHP callback, add as user data for signing_config resource */ + zval *should_sign_header = aws_php_zval_new(); + aws_php_zval_copy(should_sign_header, php_should_sign_header); + aws_crt_resource_set_user_data(signing_config, should_sign_header, aws_php_zval_dtor); + + aws_crt_signing_config_aws_set_should_sign_header_fn( + signing_config, aws_php_should_sign_header, should_sign_header); +} + PHP_FUNCTION(aws_crt_signable_new_from_http_request) { zend_ulong php_http_message = 0; diff --git a/ext/stream.c b/ext/stream.c index 9412992..414146b 100644 --- a/ext/stream.c +++ b/ext/stream.c @@ -121,7 +121,7 @@ PHP_FUNCTION(aws_crt_input_stream_read) { aws_crt_input_stream *stream = (void *)php_stream; uint8_t *buf = emalloc(length); int ret = aws_crt_input_stream_read(stream, buf, length); - RETURN_STRINGL((const char *)buf, length); + XRETVAL_STRINGL((const char *)buf, length); efree(buf); } diff --git a/src/AWS/CRT/Auth/SigningAlgorithm.php b/src/AWS/CRT/Auth/SigningAlgorithm.php index 4d3358d..dd11059 100644 --- a/src/AWS/CRT/Auth/SigningAlgorithm.php +++ b/src/AWS/CRT/Auth/SigningAlgorithm.php @@ -7,4 +7,5 @@ namespace AWS\CRT\Auth; class SigningAlgorithm { const SIGv4 = 0; -} \ No newline at end of file + const SIGv4_ASYMMETRIC = 1; +} diff --git a/src/AWS/CRT/Auth/SigningConfigAWS.php b/src/AWS/CRT/Auth/SigningConfigAWS.php index f16ebd9..8a73df9 100644 --- a/src/AWS/CRT/Auth/SigningConfigAWS.php +++ b/src/AWS/CRT/Auth/SigningConfigAWS.php @@ -23,7 +23,8 @@ class SigningConfigAWS extends NativeResource { 'signed_body_value' => null, 'signed_body_header_type' => SignedBodyHeaderType::NONE, 'expiration_in_seconds' => 0, - 'date' => time() + 'date' => time(), + 'should_sign_header' => null, ]; } @@ -57,6 +58,9 @@ class SigningConfigAWS extends NativeResource { self::$crt->signing_config_aws_set_expiration_in_seconds( $sc, $options->expiration_in_seconds->asInt()); self::$crt->signing_config_aws_set_date($sc, $options->date->asInt()); + if ($should_sign_header = $options->should_sign_header->asCallable()) { + self::$crt->signing_config_aws_set_should_sign_header_fn($sc, $should_sign_header); + } } function __destruct() diff --git a/src/AWS/CRT/CRT.php b/src/AWS/CRT/CRT.php index 5410496..b7f99ad 100644 --- a/src/AWS/CRT/CRT.php +++ b/src/AWS/CRT/CRT.php @@ -317,6 +317,10 @@ final class CRT { self::$impl->aws_crt_signing_config_aws_set_date($signing_config, $timestamp); } + function signing_config_aws_set_should_sign_header_fn($signing_config, $should_sign_header_fn) { + self::$impl->aws_crt_signing_config_aws_set_should_sign_header_fn($signing_config, $should_sign_header_fn); + } + function signable_new_from_http_request($http_message) { return self::$impl->aws_crt_signable_new_from_http_request($http_message); } diff --git a/src/AWS/CRT/Options.php b/src/AWS/CRT/Options.php index f362cc5..363a396 100644 --- a/src/AWS/CRT/Options.php +++ b/src/AWS/CRT/Options.php @@ -34,6 +34,10 @@ final class OptionValue { public function asArray() { return is_array($this->value) ? $this->value : (!empty($this->value) ? [$this->value] : []); } + + public function asCallable() { + return is_callable($this->value) ? $this->value : null; + } } final class Options { diff --git a/tests/SigningTest.php b/tests/SigningTest.php index ac84b9d..31429d7 100644 --- a/tests/SigningTest.php +++ b/tests/SigningTest.php @@ -61,10 +61,13 @@ final class SigningTest extends CrtTestCase { const SIGV4TEST_SESSION_TOKEN = null; const SIGV4TEST_SERVICE = 'service'; const SIGV4TEST_REGION = 'us-east-1'; - public function testSigv4HeaderSigning() { + private static function SIGV4TEST_DATE() { + return mktime(12, 36, 0, 8, 30, 2015); + } + + public function testShouldSignHeader() { $this->skipFFI(); - $date = mktime(12, 36, 0, 8, 30, 2015); $credentials_provider = new StaticCredentialsProvider([ 'access_key_id' => self::SIGV4TEST_ACCESS_KEY_ID, 'secret_access_key' => self::SIGV4TEST_SECRET_ACCESS_KEY, @@ -76,7 +79,48 @@ final class SigningTest extends CrtTestCase { 'credentials_provider' => $credentials_provider, 'region' => self::SIGV4TEST_REGION, 'service' => self::SIGV4TEST_SERVICE, - 'date' => $date, + 'date' => self::SIGV4TEST_DATE(), + 'should_sign_header' => function($header) { + return strtolower($header) != 'x-do-not-sign'; + } + ]); + $http_request = new Request('GET', '/', [], [ + 'Host' => 'example.amazonaws.com', + 'X-Do-Not-Sign' => 'DO NOT SIGN THIS']); + $this->assertNotNull($http_request, "Unable to create HttpRequest for signing"); + $signable = Signable::fromHttpRequest($http_request); + $this->assertNotNull($signable, "Unable to create signable from HttpRequest"); + + Signing::signRequestAws( + $signable, $signing_config, + function($signing_result, $error_code) use (&$http_request) { + $this->assertEquals(0, $error_code); + $signing_result->applyToHttpRequest($http_request); + } + ); + + // This signature value is computed without the X-Do-Not-Sign header above + $headers = $http_request->headers(); + $this->assertEquals( + 'AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5fa00fa31553b73ebf1942676e86291e8372ff2a2260956d9b8aae1d763fbf31', + $headers->get('Authorization')); + } + + public function testSigv4HeaderSigning() { + $this->skipFFI(); + + $credentials_provider = new StaticCredentialsProvider([ + 'access_key_id' => self::SIGV4TEST_ACCESS_KEY_ID, + 'secret_access_key' => self::SIGV4TEST_SECRET_ACCESS_KEY, + 'session_token' => self::SIGV4TEST_SESSION_TOKEN, + ]); + $signing_config = new SigningConfigAWS([ + 'algorithm' => SigningAlgorithm::SIGv4, + 'signature_type' => SignatureType::HTTP_REQUEST_HEADERS, + 'credentials_provider' => $credentials_provider, + 'region' => self::SIGV4TEST_REGION, + 'service' => self::SIGV4TEST_SERVICE, + 'date' => self::SIGV4TEST_DATE(), ]); $http_request = new Request('GET', '/', [], ['Host' => 'example.amazonaws.com']); $this->assertNotNull($http_request, "Unable to create HttpRequest for signing"); @@ -97,4 +141,43 @@ final class SigningTest extends CrtTestCase { $headers->get('Authorization')); $this->assertEquals('20150830T123600Z', $headers->get('X-Amz-Date')); } + + public function testSigV4aHeaderSigning() { + $this->skipFFI(); + + $credentials_provider = new StaticCredentialsProvider([ + 'access_key_id' => self::SIGV4TEST_ACCESS_KEY_ID, + 'secret_access_key' => self::SIGV4TEST_SECRET_ACCESS_KEY, + 'session_token' => self::SIGV4TEST_SESSION_TOKEN, + ]); + $signing_config = new SigningConfigAWS([ + 'algorithm' => SigningAlgorithm::SIGv4_ASYMMETRIC, + 'signature_type' => SignatureType::HTTP_REQUEST_HEADERS, + 'credentials_provider' => $credentials_provider, + 'region' => self::SIGV4TEST_REGION, + 'service' => self::SIGV4TEST_SERVICE, + 'date' => self::SIGV4TEST_DATE(), + ]); + + $http_request = new Request('GET', '/', [], ['Host' => 'example.amazonaws.com']); + $this->assertNotNull($http_request, "Unable to create HttpRequest for signing"); + $signable = Signable::fromHttpRequest($http_request); + $this->assertNotNull($signable, "Unable to create signable from HttpRequest"); + + Signing::signRequestAws( + $signable, $signing_config, + function($signing_result, $error_code) use (&$http_request) { + $this->assertEquals(0, $error_code); + $signing_result->applyToHttpRequest($http_request); + } + ); + + $headers = $http_request->headers(); + $auth_header_value = $headers->get('Authorization'); + $this->assertNotNull($auth_header_value); + $this->assertStringStartsWith( + 'AWS4-ECDSA-P256-SHA256 Credential=AKIDEXAMPLE/20150830/service/aws4_request, SignedHeaders=host;x-amz-date;x-amz-region-set, Signature=', + $auth_header_value); + $this->assertEquals('20150830T123600Z', $headers->get('X-Amz-Date')); + } }