Skip to content

Commit

Permalink
Merge pull request #9136 from aws/cryptography-to-crt
Browse files Browse the repository at this point in the history
Use AWS CRT for cryptographic functionality in CloudTrail, CloudFront, and EC2 customizations
  • Loading branch information
kdaily authored Dec 17, 2024
2 parents a940ef5 + 1381d10 commit 0872094
Show file tree
Hide file tree
Showing 18 changed files with 222 additions and 126 deletions.
5 changes: 5 additions & 0 deletions .changes/next-release/enhancement-cloudfront-36119.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "enhancement",
"category": "``cloudfront``",
"description": "Replace cryptographic functions from ``cryptography`` with ``awscrt`` for the ``sign`` command."
}
5 changes: 5 additions & 0 deletions .changes/next-release/enhancement-cloudtrail-54686.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "enhancement",
"category": "``cloudtrail``",
"description": "Replace cryptographic functions from ``cryptography`` with ``awscrt`` for the ``validate-logs`` and ``verify-query-results`` commands."
}
5 changes: 5 additions & 0 deletions .changes/next-release/enhancement-ec2-67140.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "enhancement",
"category": "``ec2``",
"description": "Replace cryptographic functions from ``cryptography`` with ``awscrt`` for the ``get-password-data`` command."
}
14 changes: 7 additions & 7 deletions awscli/customizations/cloudfront.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,12 @@
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
import hashlib
import sys
import time
import random

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from awscrt.crypto import RSA, RSASignatureAlgorithm

from botocore.utils import parse_to_aware_datetime
from botocore.signers import CloudFrontSigner
Expand Down Expand Up @@ -258,9 +256,11 @@ def _run_main(self, args, parsed_globals):

class RSASigner(object):
def __init__(self, private_key):
backend = default_backend()
key_bytes = private_key.encode('utf8')
self.priv_key = load_pem_private_key(key_bytes, None, backend)
self.priv_key = RSA.new_private_key_from_pem_data(key_bytes)

def sign(self, message):
return self.priv_key.sign(message, PKCS1v15(), hashes.SHA1())
return self.priv_key.sign(
RSASignatureAlgorithm.PKCS1_5_SHA1,
hashlib.sha1(message).digest()
)
28 changes: 15 additions & 13 deletions awscli/customizations/cloudtrail/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,7 @@
from datetime import datetime, timedelta
from dateutil import tz, parser

import cryptography
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
from cryptography.hazmat.primitives.serialization import load_der_public_key
from awscrt.crypto import RSA, RSASignatureAlgorithm

from awscli.customizations.cloudtrail.utils import get_trail_by_arn, \
get_account_id_from_arn, PublicKeyProvider
Expand Down Expand Up @@ -555,18 +551,24 @@ def validate(self, bucket, key, public_key, digest_data, inflated_digest):
"""
try:
decoded_key = base64.b64decode(public_key)
public_key = load_der_public_key(decoded_key, default_backend())
to_sign = self._create_string_to_sign(digest_data, inflated_digest)
signature_bytes = binascii.unhexlify(digest_data['_signature'])
hashing = hashes.SHA256()
public_key.verify(signature_bytes, to_sign, PKCS1v15(), hashing)
except ValueError:
public_key = RSA.new_public_key_from_der_data(decoded_key)
except RuntimeError:
raise DigestError(
('Digest file\ts3://%s/%s\tINVALID: Unable to load PKCS #1 key'
' with fingerprint %s')
% (bucket, key, digest_data['digestPublicKeyFingerprint']))
except cryptography.exceptions.InvalidSignature:
# Don't display the stack trace of a signature exception to avoid

to_sign = self._create_string_to_sign(digest_data, inflated_digest)
signature_bytes = binascii.unhexlify(digest_data['_signature'])

result = public_key.verify(
signature_algorithm=RSASignatureAlgorithm.PKCS1_5_SHA256,
digest=hashlib.sha256(to_sign).digest(),
signature=signature_bytes
)
if not result:
# The previous implementation caught a cryptography.exceptions.InvalidSignature
# exception here and re-raised the DigestSignatureError to avoid the stack trace
# leaking any information about the key.
raise DigestSignatureError(bucket, key)

Expand Down
31 changes: 14 additions & 17 deletions awscli/customizations/cloudtrail/verifyqueryresults.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,10 @@
from abc import ABC, abstractmethod
from os import path

import cryptography
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
from cryptography.hazmat.primitives.serialization import load_der_public_key
from awscli.customizations.exceptions import ParamValidationError

from awscrt.crypto import RSA, RSASignatureAlgorithm

from awscli.customizations.commands import BasicCommand
from awscli.customizations.cloudtrail.utils import parse_date, PublicKeyProvider

Expand Down Expand Up @@ -64,17 +61,17 @@ def validate(self, public_key_base64, sign_file):
:param sign_file: Dict of sign file data returned when JSON
decoding a manifest.
"""
try:
public_key = self._load_public_key(public_key_base64)
signature_bytes = binascii.unhexlify(sign_file["hashSignature"])
hashing = hashes.SHA256()
public_key.verify(
signature_bytes,
self._create_string_to_sign(sign_file),
PKCS1v15(),
hashing,
)
except cryptography.exceptions.InvalidSignature:
public_key = self._load_public_key(public_key_base64)
signature_bytes = binascii.unhexlify(sign_file["hashSignature"])
result = public_key.verify(
signature_algorithm=RSASignatureAlgorithm.PKCS1_5_SHA256,
digest=hashlib.sha256(self._create_string_to_sign(sign_file)).digest(),
signature=signature_bytes
)
if not result:
# The previous implementation caught a cryptography.exceptions.InvalidSignature
# exception here and re-raised the DigestSignatureError to avoid the stack trace
# leaking any information about the key.
raise ValidationError("Invalid signature in sign file")

def _create_string_to_sign(self, sign_file):
Expand All @@ -88,7 +85,7 @@ def _create_string_to_sign(self, sign_file):
def _load_public_key(self, public_key_base64):
try:
decoded_key = base64.b64decode(public_key_base64)
return load_der_public_key(decoded_key, default_backend())
return RSA.new_public_key_from_der_data(decoded_key)
except ValueError:
raise ValidationError(
f"Sign file invalid, unable to load PKCS #1 key: {public_key_base64}"
Expand Down
20 changes: 6 additions & 14 deletions awscli/customizations/ec2/decryptpassword.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,15 @@
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
import base64
import logging
import os
import base64

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
from cryptography.hazmat.primitives.serialization import load_pem_private_key

from awscrt.crypto import RSA, RSASignatureAlgorithm
from botocore import model

from awscli.arguments import BaseCLIArgument


logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -85,8 +80,6 @@ def add_to_params(self, parameters, value):
path = os.path.expanduser(path)
if os.path.isfile(path):
self._key_path = path
endpoint_prefix = \
self._operation_model.service_model.endpoint_prefix
service_id = self._operation_model.service_model.service_id
event = 'after-call.%s.%s' % (service_id.hyphenize(),
self._operation_model.name)
Expand All @@ -100,8 +93,8 @@ def add_to_params(self, parameters, value):
def _decrypt_password_data(self, parsed, **kwargs):
"""
This handler gets called after the GetPasswordData command has been
executed. It is called with the and the ``parsed`` data. It checks to
see if a private launch key was specified on the command. If it was,
executed. It is called with the ``parsed`` data. It checks to
see if a private launch key was specified on the command. If it was,
it tries to use that private key to decrypt the password data and
replace it in the returned data dictionary.
"""
Expand All @@ -113,10 +106,9 @@ def _decrypt_password_data(self, parsed, **kwargs):
try:
with open(self._key_path, 'rb') as pk_file:
pk_bytes = pk_file.read()
backend = default_backend()
private_key = load_pem_private_key(pk_bytes, None, backend)
private_key = RSA.new_private_key_from_pem_data(pk_bytes)
value = base64.b64decode(value)
value = private_key.decrypt(value, PKCS1v15())
value = private_key.decrypt(RSASignatureAlgorithm.PKCS1_5_SHA256, value)
logger.debug(parsed)
parsed['PasswordData'] = value.decode('utf-8')
logger.debug(parsed)
Expand Down
17 changes: 8 additions & 9 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@
import botocore.validate
from botocore.exceptions import ClientError, WaiterError

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import Encoding, \
PublicFormat, load_pem_private_key
from awscrt.crypto import RSA

import prompt_toolkit
import prompt_toolkit.buffer
Expand Down Expand Up @@ -688,14 +686,15 @@ def _wait_for_key(self, bucket_name, key_name, extra_params=None,
waiter.wait(**params)

class PublicPrivateKeyLoader:
def load_private_key_and_generate_public_key(private_key_path):
def load_private_key_and_public_key(private_key_path, public_key_path):
with open(private_key_path, 'rb') as f:
private_key_byte_input = f.read()

private_key = load_pem_private_key(private_key_byte_input, None,
default_backend())
public_key = private_key.public_key()
pub_bytes = public_key.public_bytes(Encoding.DER, PublicFormat.PKCS1)
public_key_b64 = base64.b64encode(pub_bytes)
private_key = RSA.new_private_key_from_pem_data(private_key_byte_input)

with open(public_key_path, 'rb') as f:
public_key_byte_input = f.read()

public_key_b64 = base64.b64encode(public_key_byte_input)

return public_key_b64, private_key
20 changes: 18 additions & 2 deletions tests/functional/cloudfront/test_sign.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,18 +64,34 @@ def test_canned_policy(self):
cmdline = (
self.prefix + '--private-key file://' + self.private_key_file +
' --date-less-than 2016-1-1')
expected_signature = (
"UiEmtMsInU-gXoa1O7-bTRJmZ~ocphB0ONMxyEHs2r8Y9dwzeB~DkbgzPMX3jbdb"
"wIwVX3f4VcY4HBLdPSkbF~D6KbUlxPw1ju8mlXeu2C436XxZdrJrrJaiEDaTpKsl"
"Xpn9ngaCzVfCVPfkC3a0NBWBySi5ezCG2yzb0c-djNgI1wkogwtmtZuOxAoKF1sR"
"TyFX9ZitUiUIl~65nkJ94s~GGwxzTf1kMi7Wdm~9rFrJpx0O7nJEBy5O578s2UHr"
"ejtwyedUR5BqXTkgu~A51NcjAN9LErATV7SVBYicoZ76AOfB-TKay7g6-MWCK6-T"
"-4Q5x6XH4yzII3JpbCmVwA__"
)
expected_params = {
'Key-Pair-Id': ['my_id'],
'Expires': ['1451606400'], 'Signature': [mock.ANY]}
'Expires': ['1451606400'], 'Signature': [expected_signature]}
self.assertDesiredUrl(
self.run_cmd(cmdline)[0], 'http://example.com/hi', expected_params)

def test_custom_policy(self):
cmdline = (
self.prefix + '--private-key file://' + self.private_key_file +
' --date-less-than 2016-1-1 --ip-address 12.34.56.78')
expected_signature = (
"Vw-WG18WJJXim7YSGWS-zW~XmFB9MjCDOvgC~2Gz-1wiMQzCrXzYYbSE7-aF6JGO"
"Ob5ewArpMqmu2g5mohnqgieZX1NY6IOteDoXYgqaNj1DafHWQD6UJ3IKVfkxISU9"
"OmFPoG7H~VSPWEzOxdjOqdIPvAU2pW2mJ5oWu2aL62s0VVtLGCAm-DahiSQisl0J"
"bzpPyG1pofvPbT75qc71r9uiqSAbPjUF5nmLCZazVnFjDkj3zIgMRYa5aV54VDa6"
"-wEizzmjQ3-m6UMoYgcGHQXEjoFIWTfpZvbZBYkmK9lk3d16cgvaHafTJ-CPegn1"
"bKxfgNEjSAoPWS0OvBkRmg__"
)
expected_params = {
'Key-Pair-Id': ['my_id'],
'Policy': [mock.ANY], 'Signature': [mock.ANY]}
'Policy': [mock.ANY], 'Signature': [expected_signature]}
self.assertDesiredUrl(
self.run_cmd(cmdline)[0], 'http://example.com/hi', expected_params)
17 changes: 17 additions & 0 deletions tests/functional/cloudtrail/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,20 @@
# distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.

import os

def get_private_key_path():
return os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"test_resource",
"sample_private_key.pem",
)


def get_public_key_path():
return os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"test_resource",
"sample_public_key.der",
)
32 changes: 25 additions & 7 deletions tests/functional/cloudtrail/test_resource/sample_private_key.pem
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----
Binary file not shown.
Loading

0 comments on commit 0872094

Please sign in to comment.