Skip to content

Commit 38cde0c

Browse files
feat: Improvements to the message decryption process (2.x) (#213)
See GHSA-89v2-g37m-g3ff
1 parent 11d4c9d commit 38cde0c

File tree

13 files changed

+661
-27
lines changed

13 files changed

+661
-27
lines changed

CHANGELOG.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22
Changelog
33
*********
44

5+
2.2.0 -- 2021-05-27
6+
===================
7+
8+
Features
9+
--------
10+
* Improvements to the message decryption process
11+
12+
See https://github.com/aws/aws-encryption-sdk-cli/security/advisories/GHSA-89v2-g37m-g3ff.
13+
514
2.1.0 -- 2020-10-27
615
===================
716

README.rst

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ Required Prerequisites
4343
======================
4444

4545
* Python 2.7+ or 3.4+
46-
* aws-encryption-sdk >= 2.0.0
46+
* aws-encryption-sdk >= 2.2.0
4747

4848
Installation
4949
============
@@ -168,7 +168,7 @@ Metadata Contents
168168
`````````````````
169169
The metadata JSON contains the following fields:
170170

171-
* ``"mode"`` : ``"encrypt"``/``"decrypt"``
171+
* ``"mode"`` : ``"encrypt"``/``"decrypt"``/``"decrypt-unsigned"``
172172
* ``"input"`` : Full path to input file (or ``"<stdin>"`` if stdin)
173173
* ``"output"`` : Full path to output file (or ``"<stdout>"`` if stdout)
174174
* ``"header"`` : JSON representation of `message header data`_
@@ -342,7 +342,6 @@ Allowed parameters:
342342
* **max_messages_encrypted** : Determines how long each entry can remain in the cache, beginning when it was added.
343343
* **max_bytes_encrypted** : Specifies the maximum number of bytes that a cached data key can encrypt.
344344

345-
346345
Logging and Verbosity
347346
---------------------
348347
The ``-v`` argument allows you to tune the verbosity of the built-in logging to your desired level.

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
base64io>=1.0.1
2-
aws-encryption-sdk~=2.0
2+
aws-encryption-sdk~=2.2
33
setuptools
44
attrs>=17.1.0

src/aws_encryption_sdk_cli/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@ def process_cli_request(stream_args, parsed_args): # noqa: C901
185185
required_encryption_context=parsed_args.encryption_context,
186186
required_encryption_context_keys=parsed_args.required_encryption_context_keys,
187187
commitment_policy=commitment_policy,
188+
buffer_output=parsed_args.buffer,
189+
max_encrypted_data_keys=parsed_args.max_encrypted_data_keys,
188190
)
189191

190192
if parsed_args.input == "-":

src/aws_encryption_sdk_cli/internal/arg_parsing.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,14 @@ def _build_parser():
192192
"-d", "--decrypt", dest="action", action="store_const", const="decrypt", help="Decrypt data"
193193
)
194194
parser.add_dummy_redirect_argument("--decrypt")
195+
operating_action.add_argument(
196+
"--decrypt-unsigned",
197+
dest="action",
198+
action="store_const",
199+
const="decrypt-unsigned",
200+
help="Decrypt data and enforce messages are unsigned during decryption.",
201+
)
202+
parser.add_dummy_redirect_argument("--decrypt-unsigned")
195203

196204
# For each argument added to this group, a dummy redirect argument must
197205
# be added to the parent parser for each long form option string.
@@ -258,6 +266,10 @@ def _build_parser():
258266
),
259267
)
260268

269+
parser.add_argument(
270+
"-b", "--buffer", action="store_true", help="Buffer result in memory before releasing to output"
271+
)
272+
261273
parser.add_argument(
262274
"-i",
263275
"--input",
@@ -315,6 +327,13 @@ def _build_parser():
315327
),
316328
)
317329

330+
parser.add_argument(
331+
"--max-encrypted-data-keys",
332+
type=int,
333+
action=UniqueStoreAction,
334+
help="Maximum number of encrypted data keys to wrap (during encryption) or to unwrap (during decryption)",
335+
)
336+
318337
parser.add_argument(
319338
"--suffix",
320339
nargs="?",

src/aws_encryption_sdk_cli/internal/identifiers.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,14 @@
3131
"DEFAULT_MASTER_KEY_PROVIDER",
3232
"OperationResult",
3333
)
34-
__version__ = "2.1.0" # type: str
34+
__version__ = "2.2.0" # type: str
3535

3636
#: Suffix added to output files if specific output filename is not specified.
37-
OUTPUT_SUFFIX = {"encrypt": ".encrypted", "decrypt": ".decrypted"} # type: Dict[str, str]
37+
OUTPUT_SUFFIX = {
38+
"encrypt": ".encrypted",
39+
"decrypt": ".decrypted",
40+
"decrypt-unsigned": ".decrypted",
41+
} # type: Dict[str, str]
3842

3943
ALGORITHM_NAMES = {
4044
alg for alg in dir(aws_encryption_sdk.Algorithm) if not alg.startswith("_")

src/aws_encryption_sdk_cli/internal/io_handling.py

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from aws_encryption_sdk.materials_managers import CommitmentPolicy # noqa pylint: disable=unused-import
2525
from base64io import Base64IO
2626

27+
from aws_encryption_sdk_cli.exceptions import BadUserArgumentError
2728
from aws_encryption_sdk_cli.internal.identifiers import OUTPUT_SUFFIX, OperationResult
2829
from aws_encryption_sdk_cli.internal.logging_utils import LOGGER_NAME
2930
from aws_encryption_sdk_cli.internal.metadata import MetadataWriter, json_ready_header, json_ready_header_auth
@@ -142,6 +143,21 @@ def _output_dir(source_root, destination_root, source_dir):
142143
return os.path.join(destination_root, suffix)
143144

144145

146+
def _is_decrypt_mode(mode):
147+
# type: (str) -> bool
148+
"""
149+
Determines whether the provided mode does decryption
150+
151+
:param str filepath: Full file path to file in question
152+
:rtype: bool
153+
"""
154+
if mode in ("decrypt", "decrypt-unsigned"):
155+
return True
156+
if mode == "encrypt":
157+
return False
158+
raise BadUserArgumentError("Mode {mode} has not been implemented".format(mode=mode))
159+
160+
145161
@attr.s(hash=False, init=False)
146162
class IOHandler(object):
147163
"""Common handler for all IO operations. Holds common configuration values used for all
@@ -153,6 +169,7 @@ class IOHandler(object):
153169
:param bool no_overwrite: Should never overwrite existing files
154170
:param bool decode_input: Should input be base64 decoded before operation
155171
:param bool encode_output: Should output be base64 encoded after operation
172+
:param bool buffer_output: Should buffer entire output before releasing to destination
156173
:param dict required_encryption_context: Encryption context key-value pairs to require
157174
:param list required_encryption_context_keys: Encryption context keys to require
158175
"""
@@ -162,12 +179,13 @@ class IOHandler(object):
162179
no_overwrite = attr.ib(validator=attr.validators.instance_of(bool))
163180
decode_input = attr.ib(validator=attr.validators.instance_of(bool))
164181
encode_output = attr.ib(validator=attr.validators.instance_of(bool))
182+
buffer_output = attr.ib(validator=attr.validators.instance_of(bool))
165183
required_encryption_context = attr.ib(validator=attr.validators.instance_of(dict))
166184
required_encryption_context_keys = attr.ib(
167185
validator=attr.validators.instance_of(list)
168186
) # noqa pylint: disable=invalid-name
169187

170-
def __init__(
188+
def __init__( # noqa pylint: disable=too-many-arguments
171189
self,
172190
metadata_writer, # type: MetadataWriter
173191
interactive, # type: bool
@@ -177,6 +195,8 @@ def __init__(
177195
required_encryption_context, # type: Dict[str, str]
178196
required_encryption_context_keys, # type: List[str]
179197
commitment_policy, # type: CommitmentPolicy
198+
buffer_output,
199+
max_encrypted_data_keys, # type: Union[None, int]
180200
):
181201
# type: (...) -> None
182202
"""Workaround pending resolution of attrs/mypy interaction.
@@ -190,7 +210,11 @@ def __init__(
190210
self.encode_output = encode_output
191211
self.required_encryption_context = required_encryption_context
192212
self.required_encryption_context_keys = required_encryption_context_keys # pylint: disable=invalid-name
193-
self.client = aws_encryption_sdk.EncryptionSDKClient(commitment_policy=commitment_policy)
213+
self.buffer_output = buffer_output
214+
self.client = aws_encryption_sdk.EncryptionSDKClient(
215+
commitment_policy=commitment_policy,
216+
max_encrypted_data_keys=max_encrypted_data_keys,
217+
)
194218
attr.validate(self)
195219

196220
def _single_io_write(self, stream_args, source, destination_writer):
@@ -223,7 +247,7 @@ def _single_io_write(self, stream_args, source, destination_writer):
223247
else:
224248
metadata_kwargs["header_auth"] = json_ready_header_auth(header_auth)
225249

226-
if stream_args["mode"] == "decrypt":
250+
if _is_decrypt_mode(str(stream_args["mode"])):
227251
discovered_ec = handler.header.encryption_context
228252
missing_keys = set(self.required_encryption_context_keys).difference(set(discovered_ec.keys()))
229253
missing_pairs = set(self.required_encryption_context.items()).difference(set(discovered_ec.items()))
@@ -243,9 +267,12 @@ def _single_io_write(self, stream_args, source, destination_writer):
243267
return OperationResult.FAILED_VALIDATION
244268

245269
metadata.write_metadata(**metadata_kwargs)
246-
for chunk in handler:
247-
_destination.write(chunk)
248-
_destination.flush()
270+
if self.buffer_output:
271+
_destination.write(handler.read())
272+
else:
273+
for chunk in handler:
274+
_destination.write(chunk)
275+
_destination.flush()
249276
return OperationResult.SUCCESS
250277

251278
def process_single_operation(self, stream_args, source, destination):

test/integration/integration_test_utils.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def encrypt_args_template(metadata=False, caching=False, encode=False, decode=Fa
7272
return template
7373

7474

75-
def decrypt_args_template(metadata=False, encode=False, decode=False, discovery=True):
75+
def decrypt_args_template(metadata=False, encode=False, decode=False, discovery=True, buffer=False):
7676
template = "-d -i {source} -o {target} "
7777
if metadata:
7878
template += " {metadata}"
@@ -84,6 +84,17 @@ def decrypt_args_template(metadata=False, encode=False, decode=False, discovery=
8484
template += " --decode"
8585
if discovery:
8686
template += " --wrapping-keys discovery=true"
87+
if buffer:
88+
template += " --buffer"
89+
return template
90+
91+
92+
def decrypt_unsigned_args_template(metadata=False):
93+
template = "--decrypt-unsigned -i {source} -o {target} --wrapping-keys discovery=true"
94+
if metadata:
95+
template += " {metadata}"
96+
else:
97+
template += " -S"
8798
return template
8899

89100

0 commit comments

Comments
 (0)