예제 #1
0
class ChangeEDKProviderInfoTamperingMethod(TamperingMethod):
    """Tampering method that changes the provider info on all EDKs."""

    new_provider_infos = attr.ib(validator=iterable_validator(list, six.string_types))

    def __init__(self, new_provider_infos):
        """Create a new instance for a given new provider info value."""
        self.new_provider_infos = new_provider_infos

    @classmethod
    def from_values_spec(cls, values_spec):
        """Load from a tampering parameters specification"""
        return ChangeEDKProviderInfoTamperingMethod(values_spec)

    # pylint: disable=R0201
    def run_scenario_with_tampering(self, ciphertext_writer, generation_scenario, _plaintext_uri):
        """
        Run a given scenario, tampering with the input or the result.

        return: a list of (ciphertext, result) pairs.
        """
        master_key_provider = generation_scenario.encryption_scenario.master_key_provider_fn()

        # Use a caching CMM to avoid generating a new data key every time.
        cache = LocalCryptoMaterialsCache(10)
        caching_cmm = CachingCryptoMaterialsManager(
            master_key_provider=master_key_provider,
            cache=cache,
            max_age=60.0,
            max_messages_encrypted=100,
        )
        return [
            self.run_scenario_with_new_provider_info(
                ciphertext_writer, generation_scenario, caching_cmm, new_provider_info
            )
            for new_provider_info in self.new_provider_infos
        ]

    def run_scenario_with_new_provider_info(
        self, ciphertext_writer, generation_scenario, materials_manager, new_provider_info
    ):
        """Run with tampering for a specific new provider info value"""
        tampering_materials_manager = ProviderInfoChangingCryptoMaterialsManager(materials_manager, new_provider_info)
        ciphertext_to_decrypt = generation_scenario.encryption_scenario.run(tampering_materials_manager)
        expected_result = MessageDecryptionTestResult.expect_error(
            "Incorrect encrypted data key provider info: " + new_provider_info
        )
        return generation_scenario.decryption_test_scenario_pair(
            ciphertext_writer, ciphertext_to_decrypt, expected_result
        )
예제 #2
0
class MessageEncryptionTestScenario(object):
    """Data class for a single full message decrypt test scenario.

    Handles serialization and deserialization to and from manifest specs.

    :param str plaintext_name: Identifying name of plaintext
    :param bytes plaintext: Binary plaintext data
    :param AlgorithmSuite algorithm: Algorithm suite to use
    :param int frame_size: Frame size to use
    :param dict encryption_context: Encryption context to use
    :param master_key_specs: Iterable of loaded master key specifications
    :type master_key_specs: iterable of :class:`MasterKeySpec`
    :param MasterKeyProvider master_key_provider:
    """

    plaintext_name = attr.ib(
        validator=attr.validators.instance_of(six.string_types))
    plaintext = attr.ib(validator=attr.validators.instance_of(six.binary_type))
    algorithm = attr.ib(validator=attr.validators.instance_of(AlgorithmSuite))
    frame_size = attr.ib(validator=attr.validators.instance_of(int))
    encryption_context = attr.ib(
        validator=dictionary_validator(six.string_types, six.string_types))
    master_key_specs = attr.ib(
        validator=iterable_validator(list, MasterKeySpec))
    master_key_provider = attr.ib(
        validator=attr.validators.instance_of(MasterKeyProvider))

    @classmethod
    def from_scenario(cls, scenario, keys, plaintexts):
        # type: (ENCRYPT_SCENARIO_SPEC, KeysManifest, Dict[str, bytes]) -> MessageEncryptionTestScenario
        """Load from a scenario specification.

        :param dict scenario: Scenario specification JSON
        :param KeysManifest keys: Loaded keys
        :param dict plaintexts: Mapping of plaintext names to plaintext values
        :return: Loaded test scenario
        :rtype: MessageEncryptionTestScenario
        """
        algorithm = algorithm_suite_from_string_id(scenario["algorithm"])
        master_key_specs = [
            MasterKeySpec.from_scenario(spec)
            for spec in scenario["master-keys"]
        ]
        master_key_provider = master_key_provider_from_master_key_specs(
            keys, master_key_specs)

        return cls(
            plaintext_name=scenario["plaintext"],
            plaintext=plaintexts[scenario["plaintext"]],
            algorithm=algorithm,
            frame_size=scenario["frame-size"],
            encryption_context=scenario["encryption-context"],
            master_key_specs=master_key_specs,
            master_key_provider=master_key_provider,
        )

    @property
    def scenario_spec(self):
        # type: () -> ENCRYPT_SCENARIO_SPEC
        """Build a scenario specification describing this test scenario.

        :return: Scenario specification JSON
        :rtype: dict
        """
        return {
            "plaintext": self.plaintext_name,
            "algorithm": binascii.hexlify(self.algorithm.id_as_bytes()),
            "frame-size": self.frame_size,
            "encryption-context": self.encryption_context,
            "master-keys":
            [spec.scenario_spec for spec in self.master_key_specs],
        }

    def run(self, ciphertext_writer, plaintext_uri):
        """Run this scenario, writing the resulting ciphertext with ``ciphertext_writer`` and returning
        a :class:`MessageDecryptionTestScenario` that describes the matching decrypt scenario.

        :param callable ciphertext_writer: Callable that will write the requested named ciphertext and
            return a URI locating the written data
        :param str plaintext_uri: URI locating the written plaintext data for this scenario
        :return: Decrypt test scenario that describes the generated scenario
        :rtype: MessageDecryptionTestScenario
        """
        ciphertext, _header = aws_encryption_sdk.encrypt(
            source=self.plaintext,
            algorithm=self.algorithm,
            frame_length=self.frame_size,
            encryption_context=self.encryption_context,
            key_provider=self.master_key_provider,
        )

        ciphertext_name = str(uuid.uuid4())
        ciphertext_uri = ciphertext_writer(ciphertext_name, ciphertext)

        return MessageDecryptionTestScenario(
            plaintext_uri=plaintext_uri,
            plaintext=self.plaintext,
            ciphertext_uri=ciphertext_uri,
            ciphertext=ciphertext,
            master_key_specs=self.master_key_specs,
            master_key_provider=self.master_key_provider,
        )
예제 #3
0
class MessageDecryptionTestScenario(object):
    # pylint: disable=too-many-arguments
    """Data class for a single full message decrypt test scenario.

    Handles serialization and deserialization to and from manifest specs.

    :param str plaintext_uri: URI locating plaintext data
    :param bytes plaintext: Binary plaintext data
    :param str ciphertext_uri: URI locating ciphertext data
    :param bytes ciphertext: Binary ciphertext data
    :param master_key_specs: Iterable of master key specifications
    :type master_key_specs: iterable of :class:`MasterKeySpec`
    :param MasterKeyProvider master_key_provider:
    :param str description: Description of test scenario (optional)
    """

    # pylint: disable=too-few-public-methods

    plaintext_uri = attr.ib(
        validator=attr.validators.instance_of(six.string_types))
    plaintext = attr.ib(validator=attr.validators.instance_of(six.binary_type))
    ciphertext_uri = attr.ib(
        validator=attr.validators.instance_of(six.string_types))
    ciphertext = attr.ib(
        validator=attr.validators.instance_of(six.binary_type))
    master_key_specs = attr.ib(
        validator=iterable_validator(list, MasterKeySpec))
    master_key_provider = attr.ib(
        validator=attr.validators.instance_of(MasterKeyProvider))
    description = attr.ib(default=None,
                          validator=attr.validators.optional(
                              attr.validators.instance_of(six.string_types)))

    def __init__(
            self,
            plaintext_uri,  # type: str
            plaintext,  # type: bytes
            ciphertext_uri,  # type: str
            ciphertext,  # type: bytes
            master_key_specs,  # type: Iterable[MasterKeySpec]
            master_key_provider,  # type: MasterKeyProvider
            description=None,  # type: Optional[str]
    ):  # noqa=D107
        # type: (...) -> None
        """Set initial values for the test scenario."""
        # Workaround pending resolution of attrs/mypy interaction.
        # https://github.com/python/mypy/issues/2088
        # https://github.com/python-attrs/attrs/issues/215
        self.plaintext_uri = plaintext_uri
        self.plaintext = plaintext
        self.ciphertext_uri = ciphertext_uri
        self.ciphertext = ciphertext
        self.master_key_specs = master_key_specs
        self.master_key_provider = master_key_provider
        self.description = description
        attr.validate(self)

    @classmethod
    def from_scenario(
            cls,
            scenario,  # type: DECRYPT_SCENARIO_SPEC
            plaintext_reader,  # type: Callable[[str], bytes]
            ciphertext_reader,  # type: Callable[[str], bytes]
            keys,  # type: KeysManifest
    ):
        # type: (...) -> MessageDecryptionTestScenario
        """Load from a scenario specification.

        :param dict scenario: Scenario specification JSON
        :param plaintext_reader: URI-handling data reader for reading plaintext
        :param ciphertext_reader: URI-handling data reader for reading ciphertext
        :param KeysManifest keys: Loaded keys
        :return: Loaded test scenario
        :rtype: MessageDecryptionTestScenario
        """
        raw_master_key_specs = scenario[
            "master-keys"]  # type: Iterable[MASTER_KEY_SPEC]
        master_key_specs = [
            MasterKeySpec.from_scenario(spec) for spec in raw_master_key_specs
        ]
        master_key_provider = master_key_provider_from_master_key_specs(
            keys, master_key_specs)

        return cls(
            plaintext_uri=scenario["plaintext"],
            plaintext=plaintext_reader(scenario["plaintext"]),
            ciphertext_uri=scenario["ciphertext"],
            ciphertext=ciphertext_reader(scenario["ciphertext"]),
            master_key_specs=master_key_specs,
            master_key_provider=master_key_provider,
            description=scenario.get("description"),
        )

    @property
    def scenario_spec(self):
        # type: () -> DECRYPT_SCENARIO_SPEC
        """Build a scenario specification describing this test scenario.

        :return: Scenario specification JSON
        :rtype: dict
        """
        spec = {
            "plaintext": self.plaintext_uri,
            "ciphertext": self.ciphertext_uri,
            "master-keys":
            [spec.scenario_spec for spec in self.master_key_specs],
        }
        if self.description is not None:
            spec["description"] = self.description
        return spec

    def run(self, name):
        """Run this test scenario

        :param str name: Descriptive name for this scenario to use in any logging or errors
        """
        client = aws_encryption_sdk.EncryptionSDKClient(
            commitment_policy=CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT)
        plaintext, _header = client.decrypt(
            source=self.ciphertext, key_provider=self.master_key_provider)
        if plaintext != self.plaintext:
            raise ValueError(
                "Decrypted plaintext does not match expected value for scenario '{}'"
                .format(name))
예제 #4
0
class ManualKeySpec(KeySpec):
    """Manual key specification.

    Allowed values described in AWS Crypto Tools Test Vector Framework feature #0002 Keys Manifest.

    :param bool encrypt: Key can be used to encrypt
    :param bool decrypt: Key can be used to decrypt
    :param str algorithm: Algorithm to use with key
    :param str type_name: Key type
    :param int bits: Key length in bits
    :param str encoding: Encoding used to encode key material
    :param material: Raw material encoded, then split into lines separated by ``line_separator``
    :type material: list of str
    :param str line_separator: Character with which to separate members of ``material``
        before decoding (optional: default is empty string)
    """

    algorithm = attr.ib(validator=membership_validator(KEY_ALGORITHMS))
    type_name = attr.ib(validator=membership_validator(KEY_TYPES))
    bits = attr.ib(validator=attr.validators.instance_of(int))
    encoding = attr.ib(validator=membership_validator(KEY_ENCODINGS))
    material = attr.ib(validator=iterable_validator(list, six.string_types))
    line_separator = attr.ib(default="",
                             validator=attr.validators.instance_of(
                                 six.string_types))

    def __init__(
            self,
            encrypt,  # type: bool
            decrypt,  # type: bool
            algorithm,  # type: str
            type_name,  # type: str
            bits,  # type: int
            encoding,  # type: str
            material,  # type: Iterable[str]
            line_separator="",  # type: Optional[str]
    ):  # noqa=D107
        # type: (...) -> None
        # Workaround pending resolution of attrs/mypy interaction.
        # https://github.com/python/mypy/issues/2088
        # https://github.com/python-attrs/attrs/issues/215
        self.algorithm = algorithm
        self.type_name = type_name
        self.bits = bits
        self.encoding = encoding
        self.material = material
        self.line_separator = line_separator
        super(ManualKeySpec, self).__init__(encrypt, decrypt)

    @property
    def raw_material(self):
        # type: () -> bytes
        """Provide the raw binary material for this key.

        :return: Binary key material
        :rtype: bytes
        """
        raw_material = self.line_separator.join(self.material).encode(ENCODING)
        if self.encoding == "base64":
            return base64.b64decode(raw_material)

        return raw_material

    @property
    def manifest_spec(self):
        # type: () -> MANUAL_KEY_SPEC
        """Build a key specification describing this key specification.

        :return: Key specification JSON
        :rtype: dict
        """
        return {
            "encrypt": self.encrypt,
            "decrypt": self.decrypt,
            "algorithm": self.algorithm,
            "type": self.type_name,
            "bits": self.bits,
            "encoding": self.encoding,
            "line-separator": self.line_separator,
            "material": self.material,
        }
예제 #5
0
class MessageDecryptionTestScenarioGenerator(object):
    # pylint: disable=too-many-instance-attributes
    """Data class for a single full message decrypt test scenario.

    Handles serialization and deserialization to and from manifest specs.

    :param MessageEncryptionTestScenario encryption_scenario: Encryption parameters
    :param tampering_method: Optional method used to tamper with the ciphertext
    :type tampering_method: :class:`TamperingMethod`
    :param decryption_method:
    :param decryption_master_key_specs: Iterable of master key specifications
    :type decryption_master_key_specs: iterable of :class:`MasterKeySpec`
    :param Callable decryption_master_key_provider_fn:
    :param result:
    """

    encryption_scenario = attr.ib(validator=attr.validators.instance_of(MessageEncryptionTestScenario))
    tampering_method = attr.ib(validator=attr.validators.optional(attr.validators.instance_of(TamperingMethod)))
    decryption_method = attr.ib(validator=attr.validators.optional(attr.validators.instance_of(DecryptionMethod)))
    decryption_master_key_specs = attr.ib(validator=iterable_validator(list, MasterKeySpec))
    decryption_master_key_provider_fn = attr.ib(validator=attr.validators.is_callable())
    result = attr.ib(validator=attr.validators.optional(attr.validators.instance_of(MessageDecryptionTestResult)))

    @classmethod
    def from_scenario(cls, scenario, keys, plaintexts):
        """Load from a scenario specification.

        :param dict scenario: Scenario specification JSON
        :param KeysManifest keys: Loaded keys
        :param dict plaintexts: Mapping of plaintext names to plaintext values
        :return: Loaded test scenario
        :rtype: MessageDecryptionTestScenarioGenerator
        """
        encryption_scenario_spec = scenario["encryption-scenario"]
        encryption_scenario = MessageEncryptionTestScenario.from_scenario(encryption_scenario_spec, keys, plaintexts)
        tampering = scenario.get("tampering")
        tampering_method = TamperingMethod.from_tampering_spec(tampering)
        decryption_method_spec = scenario.get("decryption-method")
        decryption_method = DecryptionMethod(decryption_method_spec) if decryption_method_spec else None
        if "decryption-master-keys" in scenario:
            decryption_master_key_specs = [
                MasterKeySpec.from_scenario(spec) for spec in scenario["decryption-master-keys"]
            ]

            def decryption_master_key_provider_fn():
                return master_key_provider_from_master_key_specs(keys, decryption_master_key_specs)

        else:
            decryption_master_key_specs = encryption_scenario.master_key_specs
            decryption_master_key_provider_fn = encryption_scenario.master_key_provider_fn
        result_spec = scenario.get("result")
        result = MessageDecryptionTestResult.from_result_spec(result_spec, None) if result_spec else None

        return cls(
            encryption_scenario=encryption_scenario,
            tampering_method=tampering_method,
            decryption_method=decryption_method,
            decryption_master_key_specs=decryption_master_key_specs,
            decryption_master_key_provider_fn=decryption_master_key_provider_fn,
            result=result,
        )

    def run(self, ciphertext_writer, plaintext_uri):
        """Run this scenario, writing the resulting ciphertext with ``ciphertext_writer`` and returning
        a :class:`MessageDecryptionTestScenario` that describes the matching decrypt scenario.

        :param callable ciphertext_writer: Callable that will write the requested named ciphertext and
            return a URI locating the written data
        :param str plaintext_uri: URI locating the written plaintext data for this scenario
        :return: Decrypt test scenario that describes the generated scenario
        :rtype: MessageDecryptionTestScenario
        """
        return dict(self.tampering_method.run_scenario_with_tampering(ciphertext_writer, self, plaintext_uri))

    def decryption_test_scenario_pair(self, ciphertext_writer, ciphertext_to_decrypt, expected_result):
        """Create a new (name, decryption scenario) pair"""
        ciphertext_name = str(uuid.uuid4())
        ciphertext_uri = ciphertext_writer(ciphertext_name, ciphertext_to_decrypt)

        return (
            ciphertext_name,
            MessageDecryptionTestScenario(
                ciphertext_uri=ciphertext_uri,
                ciphertext=ciphertext_to_decrypt,
                master_key_specs=self.decryption_master_key_specs,
                master_key_provider_fn=self.decryption_master_key_provider_fn,
                decryption_method=self.decryption_method,
                result=expected_result,
            ),
        )
예제 #6
0
class MessageEncryptionTestScenario(object):
    # pylint: disable=too-many-instance-attributes
    """Data class for a single full message decrypt test scenario.

    Handles serialization and deserialization to and from manifest specs.

    :param str plaintext_name: Identifying name of plaintext
    :param bytes plaintext: Binary plaintext data
    :param AlgorithmSuite algorithm: Algorithm suite to use
    :param int frame_size: Frame size to use
    :param dict encryption_context: Encryption context to use
    :param master_key_specs: Iterable of loaded master key specifications
    :type master_key_specs: iterable of :class:`MasterKeySpec`
    :param Callable master_key_provider_fn:
    """

    plaintext_name = attr.ib(
        validator=attr.validators.instance_of(six.string_types))
    plaintext = attr.ib(validator=attr.validators.instance_of(six.binary_type))
    algorithm = attr.ib(validator=attr.validators.instance_of(AlgorithmSuite))
    frame_size = attr.ib(validator=attr.validators.instance_of(int))
    encryption_context = attr.ib(
        validator=dictionary_validator(six.string_types, six.string_types))
    master_key_specs = attr.ib(
        validator=iterable_validator(list, MasterKeySpec))
    master_key_provider_fn = attr.ib(validator=attr.validators.is_callable())

    @classmethod
    def from_scenario(cls, scenario, keys, plaintexts):
        # type: (ENCRYPT_SCENARIO_SPEC, KeysManifest, Dict[str, bytes]) -> MessageEncryptionTestScenario
        """Load from a scenario specification.

        :param dict scenario: Scenario specification JSON
        :param KeysManifest keys: Loaded keys
        :param dict plaintexts: Mapping of plaintext names to plaintext values
        :return: Loaded test scenario
        :rtype: MessageEncryptionTestScenario
        """
        algorithm = algorithm_suite_from_string_id(scenario["algorithm"])
        master_key_specs = [
            MasterKeySpec.from_scenario(spec)
            for spec in scenario["master-keys"]
        ]

        def master_key_provider_fn():
            return master_key_provider_from_master_key_specs(
                keys, master_key_specs)

        return cls(
            plaintext_name=scenario["plaintext"],
            plaintext=plaintexts[scenario["plaintext"]],
            algorithm=algorithm,
            frame_size=scenario["frame-size"],
            encryption_context=scenario["encryption-context"],
            master_key_specs=master_key_specs,
            master_key_provider_fn=master_key_provider_fn,
        )

    def run(self, materials_manager=None):
        """Run this scenario, writing the resulting ciphertext with ``ciphertext_writer`` and returning
        a :class:`MessageDecryptionTestScenario` that describes the matching decrypt scenario.

        :param callable ciphertext_writer: Callable that will write the requested named ciphertext and
            return a URI locating the written data
        :param str plaintext_uri: URI locating the written plaintext data for this scenario
        :return: Decrypt test scenario that describes the generated scenario
        :rtype: MessageDecryptionTestScenario
        """
        commitment_policy = CommitmentPolicy.FORBID_ENCRYPT_ALLOW_DECRYPT
        if self.algorithm.is_committing():
            commitment_policy = CommitmentPolicy.REQUIRE_ENCRYPT_ALLOW_DECRYPT

        client = aws_encryption_sdk.EncryptionSDKClient(
            commitment_policy=commitment_policy)
        encrypt_kwargs = dict(
            source=self.plaintext,
            algorithm=self.algorithm,
            frame_length=self.frame_size,
            encryption_context=self.encryption_context,
        )
        if materials_manager:
            encrypt_kwargs["materials_manager"] = materials_manager
        else:
            encrypt_kwargs["key_provider"] = self.master_key_provider_fn()
        ciphertext, _header = client.encrypt(**encrypt_kwargs)
        return ciphertext