wpsharks / s2member

s2Member® Framework (membership management for WordPress®).
64 stars 36 forks source link

AWS4 Authentication Protocol Required by Some Regions #440

Closed jaswrks closed 9 years ago

jaswrks commented 9 years ago

Amazon S3 supports Signature Version 4, a protocol for authenticating inbound API requests to AWS services, in all AWS regions. At this time, existing AWS regions continue to support the previous protocol, Signature Version 2. Any new regions after January 30, 2014 will support only Signature Version 4 and therefore all requests to those regions must be made with Signature Version 4. Note that Signature Version 4 requires the request body to be signed for added security. This requirement creates additional computation load that is not required in Signature Version 2.

Referencing: http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html

jaswrks commented 9 years ago

Referencing: http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html

jaswrks commented 9 years ago

Holy hell! I dunno what the folks at AWS are thinking! Their signature algo is just ridiculous. haha

All of this just to get a signature for the Authorization header. Unbelievable! Gotta love the ridiculous attempt at making it more complex by nesting a date key inside a region key, inside a date/region key, just to get the key that you should sign with. On top of that there are a whole bunch of other stars that need to fall inline. Have they never heard of oAuth?

~ Ugh, 4 hours I'll never get back. It's working now though. I'll merge it in shortly :-)

This is one article I hope I don't need to read again anytime soon. http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html


        /**
         * Creates an Amazon S3 AWS4-HMAC-SHA256 signature.
         *
         * @package s2Member\Files
         * @since 150108
         *
         * @param string $string Input string/data, to be signed by this routine.
         *
         * @return string An AWS4-HMAC-SHA256 signature for Amazon S3.
         */
        public static function amazon_s34_sign($string = '')
        {
            $s3c = array(); // Initialize config. keys.
            foreach($GLOBALS['WS_PLUGIN__']['s2member']['o'] as $option => $option_value)
                if(preg_match('/^amazon_s3_files_/', $option) && ($option = preg_replace('/^amazon_s3_files_/', '', $option)))
                    $s3c[$option] = $option_value;

            $s3_date_key                = c_ws_plugin__s2member_utils_strings::hmac_sha256_sign(date('Ymd'), 'AWS4'.$s3c['secret_key'], TRUE);
            $s3_date_region_key         = c_ws_plugin__s2member_utils_strings::hmac_sha256_sign($s3c['bucket_region'], $s3_date_key, TRUE);
            $s3_date_region_service_key = c_ws_plugin__s2member_utils_strings::hmac_sha256_sign('s3', $s3_date_region_key, TRUE);
            $s3_signing_key             = c_ws_plugin__s2member_utils_strings::hmac_sha256_sign('aws4_request', $s3_date_region_service_key, TRUE);

            return c_ws_plugin__s2member_utils_strings::hmac_sha256_sign((string)$string, $s3_signing_key);
        }

        /**
         * Creates an Amazon S3 AWS4-HMAC-SHA256 signature/authorization header.
         *
         * @package s2Member\Files
         * @since 150108
         *
         * @param string $s3_date The date header; e.g. `YYYYMMDD'T'HHMMSS'Z'`.
         * @param string $s3_domain The API endpoint domain; e.g. `[bucket].s3.amazonaws.com`.
         * @param string $s3_location The API endpoint URI; e.g. `/?acl`.
         * @param string $s3_method The request method; e.g. `GET`, `PUT`, `POST`, etc.
         * @param array  $s3_headers An associative array of all headers.
         * @param string $s3_body Any input data sent with the request.
         *
         * @return string An AWS4-HMAC-SHA256 signature/authorization header for Amazon S3.
         */
        public static function amazon_s34_authorization($s3_date = '',
                                                        $s3_domain = 's3.amazonaws.com',
                                                        $s3_location = '/', $s3_method = 'GET',
                                                        $s3_headers = array(), $s3_body = '')
        {
            $s3_date     = trim((string)$s3_date);
            $s3_domain   = trim(strtolower((string)$s3_domain));
            $s3_location = trim((string)$s3_location);
            $s3_method   = trim(strtoupper((string)$s3_method));
            $s3_headers  = (array)$s3_headers;
            $s3_body     = trim((string)$s3_body);

            $s3c = array(); // Initialize config. keys.
            foreach($GLOBALS['WS_PLUGIN__']['s2member']['o'] as $option => $option_value)
                if(preg_match('/^amazon_s3_files_/', $option) && ($option = preg_replace('/^amazon_s3_files_/', '', $option)))
                    $s3c[$option] = $option_value;

            $s3_iso8601_date   = date('Ymd\THis\Z');
            $s3_location_parts = parse_url($s3_location);
            $s3_canonical_path = !empty($s3_location_parts['path']) ? '/'.ltrim($s3_location_parts['path'], '/') : '/';
            $s3_scope          = date('Ymd').'/'.$s3c['bucket_region'].'/s3/aws4_request';

            $s3_canonical_query = ''; // Initialize.
            wp_parse_str((string)@$s3_location_parts['query'], $query_args);
            ksort($query_args, SORT_STRING);

            foreach($query_args as $_key => $_value)
                $s3_canonical_query .= '&'.c_ws_plugin__s2member_utils_strings::urldecode_ur_chars_deep(rawurlencode($_key)).
                                       '='.c_ws_plugin__s2member_utils_strings::urldecode_ur_chars_deep(rawurlencode($_value));
            $s3_canonical_query = ltrim($s3_canonical_query, '&');
            unset($_key, $_value); // Housekeeping.

            $s3_canonical_headers     = '';
            $s3_canonical_header_keys = array();
            ksort($s3_headers, SORT_STRING);

            foreach($s3_headers as $_key => $_value)
                if(is_string($_key) && ($_key = strtolower($_key)))
                    if(in_array($_key, array('host', 'content-type'), TRUE) || stripos($_key, 'X-Amz-') === 0)
                    {
                        $s3_canonical_headers .= strtolower($_key).':'.trim($_value)."\n";
                        $s3_canonical_header_keys[] = strtolower($_key);
                    }
            unset($_key, $_value); // Housekeeping.

            $s3_canonicial_request = $s3_method."\n".
                                     $s3_canonical_path."\n".
                                     $s3_canonical_query."\n".
                                     $s3_canonical_headers."\n".
                                     implode(';', $s3_canonical_header_keys)."\n".
                                     hash('sha256', $s3_body);
            $s3_string_to_sign     = 'AWS4-HMAC-SHA256'."\n".
                                     $s3_date."\n".
                                     $s3_scope."\n".
                                     hash('sha256', $s3_canonicial_request);
            $s3_signature          = self::amazon_s34_sign($s3_string_to_sign);

            $s3_authorization_header_signature = 'AWS4-HMAC-SHA256 Credential='.$s3c['access_key'].'/'.$s3_scope.','.
                                                 'SignedHeaders='.implode(';', $s3_canonical_header_keys).','.
                                                 'Signature='.$s3_signature;

            return $s3_authorization_header_signature;
        }
jaswrks commented 9 years ago

ha ~ I'm deleting 38 CloudFront Distros I created while testing this. That's funny :smile:

jaswrks commented 9 years ago

Reopening this issue. It's come to my attention that digital signatures leading to specific resources may also require AWS4-HMAC-SHA256 in some regions.

jaswrks commented 9 years ago

Next Release Changelog:

jaswrks commented 9 years ago

This issue was resolved in the release of s2Member v150203. Please see: http://www.s2member.com/kb/v150203/ If there are any follow-ups needed, please open a new issue and we will investigate. Thanks!