Skip to content

Commit

Permalink
AwsSigningConfig.signed_body_value is now a string instead of an enum (
Browse files Browse the repository at this point in the history
…#172)

This allows precalculated hashes to be used.
  • Loading branch information
graebm authored Aug 19, 2020
1 parent 5f6be49 commit f3362bd
Show file tree
Hide file tree
Showing 11 changed files with 118 additions and 53 deletions.
2 changes: 1 addition & 1 deletion aws-common-runtime/aws-c-common
Submodule aws-c-common updated 44 files
+1 −1 include/aws/common/atomics_gnu.inl
+43 −6 include/aws/common/byte_buf.h
+1 −1 include/aws/common/byte_order.inl
+1 −0 include/aws/common/logging.h
+31 −0 include/aws/common/private/xml_parser_impl.h
+17 −1 include/aws/common/string.h
+108 −0 include/aws/common/xml_parser.h
+125 −26 source/byte_buf.c
+1 −0 source/common.c
+3 −3 source/ring_buffer.c
+10 −0 source/string.c
+455 −0 source/xml_parser.c
+14 −0 tests/CMakeLists.txt
+74 −0 tests/byte_buf_test.c
+50 −0 tests/cursor_test.c
+21 −0 tests/split_test.c
+472 −0 tests/xml_parser_test.c
+1 −1 verification/cbmc/aws-templates-for-cbmc-proofs
+1 −2 verification/cbmc/proofs/Makefile-project-defines
+4 −4 verification/cbmc/proofs/aws_byte_buf_write_be16/cbmc-batch.yaml
+4 −4 verification/cbmc/proofs/aws_byte_buf_write_be64/cbmc-batch.yaml
+4 −4 verification/cbmc/proofs/aws_byte_cursor_compare_lexical/cbmc-batch.yaml
+4 −4 verification/cbmc/proofs/aws_byte_cursor_read_be16/cbmc-batch.yaml
+4 −4 verification/cbmc/proofs/aws_byte_cursor_read_be64/cbmc-batch.yaml
+2 −2 verification/cbmc/proofs/aws_hash_iter_delete/aws_hash_iter_delete_harness.c
+4 −4 verification/cbmc/proofs/aws_hash_iter_delete/cbmc-batch.yaml
+1 −1 verification/cbmc/proofs/aws_hash_iter_done/aws_hash_iter_done_harness.c
+4 −4 verification/cbmc/proofs/aws_hash_iter_done/cbmc-batch.yaml
+1 −1 verification/cbmc/proofs/aws_linked_list_back/Makefile
+1 −1 verification/cbmc/proofs/aws_linked_list_begin/Makefile
+1 −1 verification/cbmc/proofs/aws_linked_list_rend/Makefile
+3 −2 verification/cbmc/proofs/aws_ring_buffer_acquire/aws_ring_buffer_acquire_harness.c
+4 −4 verification/cbmc/proofs/aws_ring_buffer_acquire/cbmc-batch.yaml
+5 −4 verification/cbmc/proofs/aws_ring_buffer_acquire_up_to/aws_ring_buffer_acquire_up_to_harness.c
+4 −4 verification/cbmc/proofs/aws_ring_buffer_acquire_up_to/cbmc-batch.yaml
+2 −1 verification/cbmc/proofs/aws_ring_buffer_buf_belongs_to_pool/aws_ring_buffer_buf_belongs_to_pool_harness.c
+4 −4 verification/cbmc/proofs/aws_ring_buffer_buf_belongs_to_pool/cbmc-batch.yaml
+2 −1 verification/cbmc/proofs/aws_ring_buffer_release/aws_ring_buffer_release_harness.c
+4 −4 verification/cbmc/proofs/aws_ring_buffer_release/cbmc-batch.yaml
+4 −4 verification/cbmc/proofs/memset_using_uint64/cbmc-batch.yaml
+6 −6 verification/cbmc/proofs/memset_using_uint64/memset_using_uint64_harness.c
+4 −2 verification/cbmc/sources/make_common_data_structures.c
+3 −2 verification/cbmc/stubs/memcmp_override.c
+2 −2 verification/cbmc/stubs/memset_override.c
55 changes: 36 additions & 19 deletions awscrt/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,24 +208,32 @@ class AwsSignatureType(IntEnum):
"""


class AwsSignedBodyValueType(IntEnum):
"""Controls what goes in the canonical request's body value."""
class AwsSignedBodyValue:
"""
Values for use with :attr:`AwsSigningConfig.signed_body_value`.
Some services use special values (e.g. "UNSIGNED-PAYLOAD") when the body
is not being signed in the usual way.
"""

EMPTY_SHA256 = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
"""The SHA-256 of the empty string."""

EMPTY = 0
"""Use the SHA-256 of the empty string."""
UNSIGNED_PAYLOAD = 'UNSIGNED-PAYLOAD'
"""Unsigned payload option (not accepted by all services)"""

PAYLOAD = 1
"""Use the SHA-256 of the actual payload."""
STREAMING_AWS4_HMAC_SHA256_PAYLOAD = 'STREAMING-AWS4-HMAC-SHA256-PAYLOAD'
"""Each payload chunk will be signed (not accepted by all services)"""

UNSIGNED_PAYLOAD = 2
"""Use the literal string "UNSIGNED-PAYLOAD"."""
STREAMING_AWS4_HMAC_SHA256_EVENTS = 'STREAMING-AWS4-HMAC-SHA256-EVENTS'
"""Each event will be signed (not accepted by all services)"""


class AwsSignedBodyHeaderType(IntEnum):
"""
Controls if signing adds a header containing the canonical request's signed body value.
See :class:`AwsSignedBodyValueType`.
See :attr:`AwsSigningConfig.signed_body_value`.
"""

NONE = 0
Expand Down Expand Up @@ -280,9 +288,13 @@ class AwsSigningConfig(NativeResource):
should_normalize_uri_path (bool): Whether the resource paths are
normalized when building the canonical request.
signed_body_value_type (AwsSignedBodyValueType): Controls what goes in
the canonical request's body value. Default is to use the SHA-256
of the actual payload.
signed_body_value (Optional[str]): If set, this value is used as the
canonical request's body value. Typically, this is the SHA-256
of the payload, written as lowercase hex. If this has been
precalculated, it can be set here. Special values used by certain
services can also be set (see :class:`AwsSignedBodyValue`). If `None`
is passed (the default), the typical value will be calculated from
the payload during signing.
signed_body_header_type (AwsSignedBodyHeaderType): Controls if signing
adds a header containing the canonical request's signed body value.
Expand All @@ -308,7 +320,7 @@ class AwsSigningConfig(NativeResource):
'should_sign_header',
'use_double_uri_encode',
'should_normalize_uri_path',
'signed_body_value_type',
'signed_body_value',
'signed_body_header_type',
'expiration_in_seconds',
'omit_session_token',
Expand All @@ -324,7 +336,7 @@ def __init__(self,
should_sign_header=None,
use_double_uri_encode=True,
should_normalize_uri_path=True,
signed_body_value_type=AwsSignedBodyValueType.PAYLOAD,
signed_body_value=None,
signed_body_header_type=AwsSignedBodyHeaderType.NONE,
expiration_in_seconds=None,
omit_session_token=False,
Expand All @@ -336,7 +348,7 @@ def __init__(self,
assert isinstance_str(region)
assert isinstance_str(service)
assert callable(should_sign_header) or should_sign_header is None
assert isinstance(signed_body_value_type, AwsSignedBodyValueType)
assert signed_body_value is None or (isinstance(signed_body_value, str) and len(signed_body_value) > 0)
assert isinstance(signed_body_header_type, AwsSignedBodyHeaderType)
assert expiration_in_seconds is None or expiration_in_seconds > 0

Expand Down Expand Up @@ -379,7 +391,7 @@ def should_sign_header_wrapper(name):
should_sign_header_wrapper,
use_double_uri_encode,
should_normalize_uri_path,
signed_body_value_type,
signed_body_value,
signed_body_header_type,
expiration_in_seconds,
omit_session_token)
Expand Down Expand Up @@ -463,11 +475,16 @@ def should_normalize_uri_path(self):
return _awscrt.signing_config_get_should_normalize_uri_path(self._binding)

@property
def signed_body_value_type(self):
def signed_body_value(self):
"""
AwsSignedBodyValueType: Controls what goes in the canonical request's body value.
Optional[str]: What to use as the canonical request's body value.
If `None` is set (the default), a value will be calculated from
the payload during signing. Typically, this is the SHA-256 of the
payload, written as lowercase hex. If this has been precalculated,
it can be set here. Special values used by certain services can also
be set (see :class:`AwsSignedBodyValue`).
"""
return AwsSignedBodyValueType(_awscrt.signing_config_get_signed_body_value_type(self._binding))
return _awscrt.signing_config_get_signed_body_value(self._binding)

@property
def signed_body_header_type(self):
Expand Down
20 changes: 20 additions & 0 deletions docsrc/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build

# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
6 changes: 6 additions & 0 deletions make-docs.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -e
pushd `dirname $0`/docsrc > /dev/null
make html
cp -a build/html/. ../docs
popd > /dev/null
2 changes: 1 addition & 1 deletion source/auth.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ PyObject *aws_py_signing_config_get_service(PyObject *self, PyObject *args);
PyObject *aws_py_signing_config_get_date(PyObject *self, PyObject *args);
PyObject *aws_py_signing_config_get_use_double_uri_encode(PyObject *self, PyObject *args);
PyObject *aws_py_signing_config_get_should_normalize_uri_path(PyObject *self, PyObject *args);
PyObject *aws_py_signing_config_get_signed_body_value_type(PyObject *self, PyObject *args);
PyObject *aws_py_signing_config_get_signed_body_value(PyObject *self, PyObject *args);
PyObject *aws_py_signing_config_get_signed_body_header_type(PyObject *self, PyObject *args);
PyObject *aws_py_signing_config_get_expiration_in_seconds(PyObject *self, PyObject *args);
PyObject *aws_py_signing_config_get_omit_session_token(PyObject *self, PyObject *args);
Expand Down
46 changes: 23 additions & 23 deletions source/auth_signing_config.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,13 @@ PyObject *aws_py_signing_config_new(PyObject *self, PyObject *args) {
PyObject *py_should_sign_header_fn;
PyObject *py_use_double_uri_encode;
PyObject *py_should_normalize_uri_path;
int signed_body_value_type;
struct aws_byte_cursor signed_body_value;
int signed_body_header_type;
uint64_t expiration_in_seconds;
PyObject *py_omit_session_token;
if (!PyArg_ParseTuple(
args,
"iiOs#s#OdOOOiiKO",
"iiOs#s#OdOOOz#iKO",
&algorithm,
&signature_type,
&py_credentials_provider,
Expand All @@ -97,7 +97,8 @@ PyObject *aws_py_signing_config_new(PyObject *self, PyObject *args) {
&py_should_sign_header_fn,
&py_use_double_uri_encode,
&py_should_normalize_uri_path,
&signed_body_value_type,
&signed_body_value.ptr,
&signed_body_value.len,
&signed_body_header_type,
&expiration_in_seconds,
&py_omit_session_token)) {
Expand All @@ -123,9 +124,11 @@ PyObject *aws_py_signing_config_new(PyObject *self, PyObject *args) {
binding->native.config_type = AWS_SIGNING_CONFIG_AWS;
binding->native.algorithm = algorithm;
binding->native.signature_type = signature_type;
binding->native.region = region;
binding->native.service = service;
binding->native.flags.use_double_uri_encode = PyObject_IsTrue(py_use_double_uri_encode);
binding->native.flags.should_normalize_uri_path = PyObject_IsTrue(py_should_normalize_uri_path);
binding->native.signed_body_value = signed_body_value_type;
binding->native.signed_body_value = signed_body_value;
binding->native.signed_body_header = signed_body_header_type;
binding->native.expiration_in_seconds = expiration_in_seconds;
binding->native.flags.omit_session_token = PyObject_IsTrue(py_omit_session_token);
Expand All @@ -138,26 +141,17 @@ PyObject *aws_py_signing_config_new(PyObject *self, PyObject *args) {
binding->py_credentials_provider = py_credentials_provider;
Py_INCREF(binding->py_credentials_provider);

/* strings: service, region */
size_t total_string_len;
if (aws_add_size_checked(region.len, service.len, &total_string_len)) {
PyErr_SetAwsLastError();
/* backup strings */
if (aws_byte_buf_init_cache_and_update_cursors(
&binding->string_storage,
aws_py_get_allocator(),
&binding->native.region,
&binding->native.service,
&binding->native.signed_body_value,
NULL)) {
goto error;
}

if (aws_byte_buf_init(&binding->string_storage, aws_py_get_allocator(), total_string_len)) {
PyErr_SetAwsLastError();
goto error;
}

binding->native.region.ptr = binding->string_storage.buffer + binding->string_storage.len;
binding->native.region.len = region.len;
aws_byte_buf_write_from_whole_cursor(&binding->string_storage, region);

binding->native.service.ptr = binding->string_storage.buffer + binding->string_storage.len;
binding->native.service.len = service.len;
aws_byte_buf_write_from_whole_cursor(&binding->string_storage, service);

/* date: store original datetime python object so user doesn't see different timezones after set/get */
aws_date_time_init_epoch_secs(&binding->native.date, timestamp);
binding->py_date = py_date;
Expand Down Expand Up @@ -275,13 +269,19 @@ PyObject *aws_py_signing_config_get_should_normalize_uri_path(PyObject *self, Py
return PyBool_FromLong(binding->native.flags.should_normalize_uri_path);
}

PyObject *aws_py_signing_config_get_signed_body_value_type(PyObject *self, PyObject *args) {
PyObject *aws_py_signing_config_get_signed_body_value(PyObject *self, PyObject *args) {
struct config_binding *binding = s_common_get(self, args);
if (!binding) {
return NULL;
}

return PyLong_FromLong(binding->native.signed_body_value);
/* In C layer, 0 length cursor indicates "defaults please".
* In Python layer, None indicates "defaults please". */
if (binding->native.signed_body_value.len == 0) {
Py_RETURN_NONE;
}

return PyString_FromAwsByteCursor(&binding->native.signed_body_value);
}

PyObject *aws_py_signing_config_get_signed_body_header_type(PyObject *self, PyObject *args) {
Expand Down
2 changes: 1 addition & 1 deletion source/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,7 @@ static PyMethodDef s_module_methods[] = {
AWS_PY_METHOD_DEF(signing_config_get_date, METH_VARARGS),
AWS_PY_METHOD_DEF(signing_config_get_use_double_uri_encode, METH_VARARGS),
AWS_PY_METHOD_DEF(signing_config_get_should_normalize_uri_path, METH_VARARGS),
AWS_PY_METHOD_DEF(signing_config_get_signed_body_value_type, METH_VARARGS),
AWS_PY_METHOD_DEF(signing_config_get_signed_body_value, METH_VARARGS),
AWS_PY_METHOD_DEF(signing_config_get_signed_body_header_type, METH_VARARGS),
AWS_PY_METHOD_DEF(signing_config_get_expiration_in_seconds, METH_VARARGS),
AWS_PY_METHOD_DEF(signing_config_get_omit_session_token, METH_VARARGS),
Expand Down
32 changes: 27 additions & 5 deletions test/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from io import BytesIO
import os
from test import NativeResourceTest, TIMEOUT
import time

EXAMPLE_ACCESS_KEY_ID = 'example_access_key_id'
EXAMPLE_SECRET_ACCESS_KEY = 'example_secret_access_key'
Expand Down Expand Up @@ -117,7 +118,7 @@ def should_sign_header(name):

use_double_uri_encode = False
should_normalize_uri_path = False
signed_body_value_type = awscrt.auth.AwsSignedBodyValueType.EMPTY
signed_body_value = awscrt.auth.AwsSignedBodyValue.EMPTY_SHA256
signed_body_header_type = awscrt.auth.AwsSignedBodyHeaderType.X_AMZ_CONTENT_SHA_256
expiration_in_seconds = 123
omit_session_token = True
Expand All @@ -131,7 +132,7 @@ def should_sign_header(name):
should_sign_header=should_sign_header,
use_double_uri_encode=use_double_uri_encode,
should_normalize_uri_path=should_normalize_uri_path,
signed_body_value_type=signed_body_value_type,
signed_body_value=signed_body_value,
signed_body_header_type=signed_body_header_type,
expiration_in_seconds=expiration_in_seconds,
omit_session_token=omit_session_token)
Expand All @@ -145,7 +146,7 @@ def should_sign_header(name):
self.assertIs(should_sign_header, cfg.should_sign_header)
self.assertEqual(use_double_uri_encode, cfg.use_double_uri_encode)
self.assertEqual(should_normalize_uri_path, cfg.should_normalize_uri_path)
self.assertIs(signed_body_value_type, cfg.signed_body_value_type)
self.assertEqual(signed_body_value, cfg.signed_body_value)
self.assertIs(signed_body_header_type, cfg.signed_body_header_type)
self.assertEqual(expiration_in_seconds, cfg.expiration_in_seconds)
self.assertEqual(omit_session_token, cfg.omit_session_token)
Expand All @@ -168,7 +169,7 @@ def test_replace(self):
should_sign_header=lambda x: False,
use_double_uri_encode=False,
should_normalize_uri_path=False,
signed_body_value_type=awscrt.auth.AwsSignedBodyValueType.EMPTY,
signed_body_value=awscrt.auth.AwsSignedBodyValue.EMPTY_SHA256,
signed_body_header_type=awscrt.auth.AwsSignedBodyHeaderType.X_AMZ_CONTENT_SHA_256,
expiration_in_seconds=123,
omit_session_token=True)
Expand Down Expand Up @@ -198,7 +199,7 @@ def _replace_attr(name, value):
_replace_attr('should_sign_header', lambda x: True)
_replace_attr('use_double_uri_encode', True)
_replace_attr('should_normalize_uri_path', True)
_replace_attr('signed_body_value_type', awscrt.auth.AwsSignedBodyValueType.PAYLOAD)
_replace_attr('signed_body_value', awscrt.auth.AwsSignedBodyValue.UNSIGNED_PAYLOAD)
_replace_attr('signed_body_header_type', awscrt.auth.AwsSignedBodyHeaderType.NONE)
_replace_attr('expiration_in_seconds', 987)
_replace_attr('omit_session_token', False)
Expand All @@ -210,6 +211,27 @@ def _replace_attr(name, value):

self.assertEqual(orig_cfg.should_sign_header, new_cfg.should_sign_header)

def test_special_defaults(self):
credentials_provider = awscrt.auth.AwsCredentialsProvider.new_static(
EXAMPLE_ACCESS_KEY_ID, EXAMPLE_SECRET_ACCESS_KEY)

config = awscrt.auth.AwsSigningConfig(
algorithm=awscrt.auth.AwsSigningAlgorithm.V4,
signature_type=awscrt.auth.AwsSignatureType.HTTP_REQUEST_QUERY_PARAMS,
credentials_provider=credentials_provider,
region='us-west-1',
service='aws-suborbital-ion-cannon')

# for things in the C layer where zeroed values mean "defaults please",
# the python layer chooses to use None.
# make sure None is coming back and not zeroes or empty strings.
self.assertIsNone(config.signed_body_value)
self.assertIsNone(config.expiration_in_seconds)

# if no date specified, now should be used.
# check that config.date has something very close to now.
self.assertAlmostEqual(time.time(), config.date.timestamp(), delta=2.0)


SIGV4TEST_ACCESS_KEY_ID = 'AKIDEXAMPLE'
SIGV4TEST_SECRET_ACCESS_KEY = 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY'
Expand Down

0 comments on commit f3362bd

Please sign in to comment.