def _ecc_decode_compressed_point(curve, compressed_point): """Decodes a compressed elliptic curve point as described in SEC-1 v2 section 2.3.4 http://www.secg.org/sec1-v2.pdf :param curve: Elliptic curve type to generate :type curve: cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve :param bytes compressed_point: Encoded compressed elliptic curve point :returns: X and Y coordinates from compressed point :rtype: tuple of longs :raises NotSupportedError: for non-prime curves, unsupported prime curves, and points at infinity """ if not compressed_point: raise NotSupportedError('Points at infinity are not allowed') yp_map = {b'\x02': 0, b'\x03': 1} X = compressed_point[1:] X = to_bytes(X) x = cryptography.utils.int_from_bytes(X, 'big') Y = compressed_point[0] # In Python3, bytes index calls return int values rather than strings if isinstance(Y, six.integer_types): Y = six.b(chr(Y)) elif isinstance(Y, six.string_types): Y = six.b(Y) yp = yp_map[Y] # If curve in prime field. if curve.name.startswith('secp'): try: params = _ECC_CURVE_PARAMETERS[curve.name] except KeyError: raise NotSupportedError( 'Curve {name} is not supported at this time'.format( name=curve.name)) alpha = (pow(x, 3, params.p) + (params.a * x % params.p) + params.b) % params.p # Only works for p % 4 == 3 at this time. # TODO: This is the case for all currently supported algorithms # This will need to be expanded if curves which do not match this are added. # Python-ecdsa has these algorithms implemented. Copy or reference? # https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm # Handbook of Applied Cryptography, algorithms 3.34 - 3.39 if params.p % 4 == 3: beta = pow(alpha, (params.p + 1) // 4, params.p) else: raise NotSupportedError( 'S not 1 :: Curve not supported at this time') if beta % 2 == yp: y = beta else: y = params.p - beta else: raise NotSupportedError( 'Non-prime curves are not supported at this time') return x, y
def _read_bytes(self, b): """Reads the requested number of bytes from a streaming message body. :param int b: Number of bytes to read :raises NotSupportedError: if content type is not supported """ _LOGGER.debug('%s bytes requested from stream with content type: %s', b, self.content_type) if b <= len(self.output_buffer) or self.source_stream.closed: _LOGGER.debug( 'No need to read from source stream or source stream closed') return if self.content_type == ContentType.FRAMED_DATA: _LOGGER.debug('Reading to framed body') self.output_buffer += self._read_bytes_to_framed_body(b) elif self.content_type == ContentType.NO_FRAMING: _LOGGER.debug('Reading to non-framed body') self.output_buffer += self._read_bytes_to_non_framed_body(b) else: raise NotSupportedError('Unsupported content type') # To maintain backwards compatibility, only enforce this if a CMM is provided by the caller. if self.config.key_provider is None and self.config.source_length is not None: # Enforce that if the caller provided a source length value, the total bytes encrypted # must not exceed that value. if self._bytes_encrypted > self.config.source_length: raise CustomMaximumValueExceeded( 'Bytes encrypted has exceeded stated source length estimate:\n{actual} > {estimated}' .format(actual=self._bytes_encrypted, estimated=self.config.source))
def deserialize_header(stream, max_encrypted_data_keys=None): # type: (IO, Union[int, None]) -> MessageHeader """Deserializes the header from a source stream :param stream: Source data stream :type stream: io.BytesIO :param max_encrypted_data_keys: Maximum number of encrypted keys to deserialize :type max_encrypted_data_keys: None or positive int :returns: Deserialized MessageHeader object :rtype: :class:`aws_encryption_sdk.structures.MessageHeader` and bytes :raises NotSupportedError: if unsupported data types are found :raises UnknownIdentityError: if unknown data types are found :raises SerializationError: if IV length does not match algorithm """ _LOGGER.debug("Starting header deserialization") tee = io.BytesIO() tee_stream = TeeStream(stream, tee) (version_id, ) = unpack_values(">B", tee_stream) version = _verified_version_from_id(version_id) header = {} header["version"] = version if version == SerializationVersion.V1: return _deserialize_header_v1(header, tee_stream, max_encrypted_data_keys), tee.getvalue() elif version == SerializationVersion.V2: return _deserialize_header_v2(header, tee_stream, max_encrypted_data_keys), tee.getvalue() else: raise NotSupportedError( "Unrecognized message format version: {}".format(version))
def _set_signature_type(self): """Ensures that the algorithm signature type is a known type and sets a reference value.""" try: cryptography.utils.verify_interface( ec.EllipticCurve, self.algorithm.signing_algorithm_info) return ec.EllipticCurve except cryptography.utils.InterfaceNotImplemented: raise NotSupportedError('Unsupported signing algorithm info')
def stream_length(self): """Returns the length of the source stream, determining it if not already known.""" if self._stream_length is None: try: current_position = self.source_stream.tell() self.source_stream.seek(0, 2) self._stream_length = self.source_stream.tell() self.source_stream.seek(current_position, 0) except Exception as error: # Catch-all for unknown issues encountered trying to seek for stream length raise NotSupportedError(error) return self._stream_length
def _verified_message_type_from_id(message_type_id): # type: (int) -> ObjectType """Load a message :class:`ObjectType` for the specified message type ID. :param int message_type_id: Message type ID :return: Message type :rtype: ObjectType :raises NotSupportedError: if unsupported message type ID is received """ try: return ObjectType(message_type_id) except ValueError as error: raise NotSupportedError("Unsupported type {} discovered in data stream".format(message_type_id), error)
def _verified_version_from_id(version_id): # type: (int) -> SerializationVersion """Load a message :class:`SerializationVersion` for the specified version ID. :param int version_id: Message format version ID :return: Message format version :rtype: SerializationVersion :raises NotSupportedError: if unsupported version ID is received """ try: return SerializationVersion(version_id) except ValueError as error: raise NotSupportedError("Unsupported version {}".format(version_id), error)
def generate_ecc_signing_key(algorithm): """Returns an ECC signing key. :param algorithm: Algorithm object which determines what signature to generate :type algorithm: aws_encryption_sdk.identifiers.Algorithm :returns: Generated signing key :raises NotSupportedError: if signing algorithm is not supported on this platform """ try: cryptography.utils.verify_interface(ec.EllipticCurve, algorithm.signing_algorithm_info) return ec.generate_private_key( curve=algorithm.signing_algorithm_info(), backend=default_backend()) except cryptography.utils.InterfaceNotImplemented: raise NotSupportedError('Unsupported signing algorithm info')
def _verified_algorithm_from_id(algorithm_id): # type: (int) -> AlgorithmSuite """Load a message :class:`AlgorithmSuite` for the specified algorithm suite ID. :param int algorithm_id: Algorithm suite ID :return: Algorithm suite :rtype: AlgorithmSuite :raises UnknownIdentityError: if unknown algorithm ID is received :raises NotSupportedError: if unsupported algorithm ID is received """ try: algorithm_suite = AlgorithmSuite.get_by_id(algorithm_id) except KeyError as error: raise UnknownIdentityError("Unknown algorithm {}".format(algorithm_id), error) if not algorithm_suite.allowed: raise NotSupportedError("Unsupported algorithm: {}".format(algorithm_suite)) return algorithm_suite
def _read_bytes(self, b): """Reads the requested number of bytes from a streaming message body. :param int b: Number of bytes to read :raises NotSupportedError: if content type is not supported """ if hasattr(self, "footer"): _LOGGER.debug("Source stream processing complete") return buffer_length = len(self.output_buffer) if 0 <= b <= buffer_length: _LOGGER.debug("%d bytes requested less than or equal to current output buffer size %d", b, buffer_length) return if self._header.content_type == ContentType.FRAMED_DATA: self.output_buffer += self._read_bytes_from_framed_body(b) elif self._header.content_type == ContentType.NO_FRAMING: self.output_buffer += self._read_bytes_from_non_framed_body(b) else: raise NotSupportedError("Unsupported content type")
def _read_bytes(self, b): """Reads the requested number of bytes from a streaming message body. :param int b: Number of bytes to read :raises NotSupportedError: if content type is not supported """ _LOGGER.debug('%s bytes requested from stream with content type: %s', b, self.content_type) if b <= len(self.output_buffer) or self.source_stream.closed: _LOGGER.debug( 'No need to read from source stream or source stream closed') return if self.content_type == ContentType.FRAMED_DATA: _LOGGER.debug('Reading to framed body') self.output_buffer += self._read_bytes_to_framed_body(b) elif self.content_type == ContentType.NO_FRAMING: _LOGGER.debug('Reading to non-framed body') self.output_buffer += self._read_bytes_to_non_framed_body(b) else: raise NotSupportedError('Unsupported content type')
def _read_bytes(self, b): """Reads the requested number of bytes from a streaming message body. :param int b: Number of bytes to read :raises NotSupportedError: if content type is not supported """ if self.source_stream.closed: _LOGGER.debug('Source stream closed') return if b <= len(self.output_buffer): _LOGGER.debug( '%s bytes requested less than or equal to current output buffer size %s', b, len(self.output_buffer)) return if self._header.content_type == ContentType.FRAMED_DATA: self.output_buffer += self._read_bytes_from_framed_body(b) elif self._header.content_type == ContentType.NO_FRAMING: self.output_buffer += self._read_bytes_from_non_framed_body(b) else: raise NotSupportedError('Unsupported content type')
def _ecc_encode_compressed_point(private_key): """Encodes a compressed elliptic curve point as described in SEC-1 v2 section 2.3.3 http://www.secg.org/sec1-v2.pdf :param private_key: Private key from which to extract point data :type private_key: cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey :returns: Encoded compressed elliptic curve point :rtype: bytes :raises NotSupportedError: for non-prime curves """ # key_size is in bits. Convert to bytes and round up byte_length = (private_key.curve.key_size + 7) // 8 public_numbers = private_key.public_key().public_numbers() y_map = [b'\x02', b'\x03'] # If curve in prime field. if private_key.curve.name.startswith('secp'): yp = public_numbers.y % 2 Y = y_map[yp] else: raise NotSupportedError( 'Non-prime curves are not supported at this time') return Y + cryptography.utils.int_to_bytes(public_numbers.x, byte_length)
def deserialize_header(stream): """Deserializes the header from a source stream :param stream: Source data stream :type stream: io.BytesIO :returns: Deserialized MessageHeader object :rtype: :class:`aws_encryption_sdk.structures.MessageHeader` and bytes :raises NotSupportedError: if unsupported data types are found :raises UnknownIdentityError: if unknown data types are found :raises SerializationError: if IV length does not match algorithm """ _LOGGER.debug('Starting header deserialization') tee = io.BytesIO() tee_stream = TeeStream(stream, tee) version_id, message_type_id = unpack_values('>BB', tee_stream) try: message_type = ObjectType(message_type_id) except ValueError as error: raise NotSupportedError( 'Unsupported type {} discovered in data stream'.format( message_type_id), error) try: version = SerializationVersion(version_id) except ValueError as error: raise NotSupportedError('Unsupported version {}'.format(version_id), error) header = {'version': version, 'type': message_type} algorithm_id, message_id, ser_encryption_context_length = unpack_values( '>H16sH', tee_stream) try: alg = Algorithm.get_by_id(algorithm_id) except KeyError as error: raise UnknownIdentityError('Unknown algorithm {}'.format(algorithm_id), error) if not alg.allowed: raise NotSupportedError('Unsupported algorithm: {}'.format(alg)) header['algorithm'] = alg header['message_id'] = message_id header['encryption_context'] = deserialize_encryption_context( tee_stream.read(ser_encryption_context_length)) (encrypted_data_key_count, ) = unpack_values('>H', tee_stream) encrypted_data_keys = set([]) for _ in range(encrypted_data_key_count): (key_provider_length, ) = unpack_values('>H', tee_stream) (key_provider_identifier, ) = unpack_values( '>{}s'.format(key_provider_length), tee_stream) (key_provider_information_length, ) = unpack_values('>H', tee_stream) (key_provider_information, ) = unpack_values( '>{}s'.format(key_provider_information_length), tee_stream) (encrypted_data_key_length, ) = unpack_values('>H', tee_stream) encrypted_data_key = tee_stream.read(encrypted_data_key_length) encrypted_data_keys.add( EncryptedDataKey(key_provider=MasterKeyInfo( provider_id=to_str(key_provider_identifier), key_info=key_provider_information), encrypted_data_key=encrypted_data_key)) header['encrypted_data_keys'] = encrypted_data_keys (content_type_id, ) = unpack_values('>B', tee_stream) try: content_type = ContentType(content_type_id) except ValueError as error: raise UnknownIdentityError( 'Unknown content type {}'.format(content_type_id), error) header['content_type'] = content_type (content_aad_length, ) = unpack_values('>I', tee_stream) if content_aad_length != 0: raise SerializationError( 'Content AAD length field is currently unused, its value must be always 0' ) header['content_aad_length'] = 0 (iv_length, ) = unpack_values('>B', tee_stream) if iv_length != alg.iv_len: raise SerializationError( 'Specified IV length ({length}) does not match algorithm IV length ({alg})' .format(length=iv_length, alg=alg)) header['header_iv_length'] = iv_length (frame_length, ) = unpack_values('>I', tee_stream) if content_type == ContentType.FRAMED_DATA and frame_length > MAX_FRAME_SIZE: raise SerializationError( 'Specified frame length larger than allowed maximum: {found} > {max}' .format(found=frame_length, max=MAX_FRAME_SIZE)) elif content_type == ContentType.NO_FRAMING and frame_length != 0: raise SerializationError( 'Non-zero frame length found for non-framed message') header['frame_length'] = frame_length return MessageHeader(**header), tee.getvalue()
def prepare_data_keys(key_provider, algorithm, encryption_context, plaintext_rostream, plaintext_length=None, data_key=None): """Prepares a DataKey to be used for encrypting message and list of EncryptedDataKey objects to be serialized into header. :param key_provider: Master Key Provider to use :type key_provider: aws_encryption_sdk.key_providers.base.MasterKeyProvider :param algorithm: Algorithm to use for encryption :type algorithm: aws_encryption_sdk.identifiers.Algorithm :param dict encryption_context: Encryption context to use when generating data key :param plaintext_stream: Source plaintext read-only stream :type plaintext_rostream: aws_encryption_sdk.internal.utils.ROStream :param int plaintext_length: Length of source plaintext (optional) :param data_key: Object containing data key to use (if not supplied, a new key will be generated) :type data_key: :class:`aws_encryption_sdk.structure.DataKey` or :class:`aws_encryption_sdk.structure.RawDataKey` :rtype: tuple containing :class:`aws_encryption_sdk.structure.RawDataKey` and set of :class:`aws_encryption_sdk.structure.EncryptedDataKey` :raises SerializationError: if primary master key is not a member of supplied MasterKeyProvider :raises NotSupportedError: if data_key is not a supported data type :raises MasterKeyProviderError: if no Master Keys are returned from key_provider """ primary_master_key, master_keys = key_provider.master_keys_for_encryption( encryption_context=encryption_context, plaintext_rostream=plaintext_rostream, plaintext_length=plaintext_length) if not master_keys: raise MasterKeyProviderError( 'No Master Keys available from Master Key Provider') if primary_master_key not in master_keys: raise MasterKeyProviderError( 'Primary Master Key not in provided Master Keys') encrypted_data_keys = set() encrypted_encryption_data_key = None if not data_key: encryption_data_key = primary_master_key.generate_data_key( algorithm, encryption_context) _LOGGER.debug('encryption data key generated from primary master key') elif isinstance(data_key, RawDataKey): encryption_data_key = data_key _LOGGER.debug('raw encryption data key provided') elif isinstance(data_key, DataKey): encryption_data_key = data_key _LOGGER.debug('full encryption data key provided') else: raise NotSupportedError('Unsupported data_key type: {}'.format( type(data_key))) _LOGGER.debug('encryption data key provider: %s', encryption_data_key.key_provider) for master_key in master_keys: encrypted_key = master_key.encrypt_data_key( data_key=encryption_data_key, algorithm=algorithm, encryption_context=encryption_context) encrypted_data_keys.add(encrypted_key) _LOGGER.debug('encryption key encrypted with master key: %s', master_key.key_provider) if master_key is primary_master_key: encrypted_encryption_data_key = encrypted_key # Normalize output to DataKey encryption_data_key = DataKey( key_provider=encryption_data_key.key_provider, data_key=encryption_data_key.data_key, encrypted_data_key=encrypted_encryption_data_key.encrypted_data_key) return encryption_data_key, encrypted_data_keys