Пример #1
0
class KeysManifest(object):
    """Keys Manifest handler.

    Described in AWS Crypto Tools Test Vector Framework feature #0002 Keys Manifest.

    :param int version: Version of this manifest
    :param dict keys: Mapping of key names to :class:`KeySpec`s
    """

    version = attr.ib(validator=membership_validator(SUPPORTED_VERSIONS))
    keys = attr.ib(validator=dictionary_validator(six.string_types, KeySpec))
    type_name = "keys"

    @classmethod
    def from_manifest_spec(cls, raw_manifest):
        # type: (KEYS_MANIFEST) -> KeysManifest
        """Load from a JSON keys manifest."""
        manifest_version = raw_manifest["manifest"]  # type: MANIFEST_VERSION
        validate_manifest_type(type_name=cls.type_name,
                               manifest_version=manifest_version,
                               supported_versions=SUPPORTED_VERSIONS)
        raw_key_specs = raw_manifest["keys"]  # type: Dict[str, KEY_SPEC]
        keys = {
            name: key_from_manifest_spec(key_spec)
            for name, key_spec in raw_key_specs.items()
        }
        return cls(version=raw_manifest["manifest"]["version"], keys=keys)

    def key(self, name):
        # type: (str) -> KeySpec
        """Provide the key with the specified name.

        :param str name: Key name
        :return: Specified key
        :rtype: KeySpec
        :raises ValueError: if key name is unknown
        """
        try:
            return self.keys[name]
        except KeyError:
            raise ValueError('Unknown key name: "{}"'.format(name))

    @property
    def manifest_spec(self):
        # type: () -> KEYS_MANIFEST
        """Build a keys manifest describing this manifest.

        :return: Manifest JSON
        :rtype: dict
        """
        return {
            "manifest": {
                "type": self.type_name,
                "version": self.version
            },
            "keys":
            {name: key.manifest_spec
             for name, key in self.keys.items()},
        }
Пример #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 MessageEncryptionManifest(object):
    """AWS Encryption SDK Encrypt Message manifest handler.

    Described in AWS Crypto Tools Test Vector Framework feature #0003 AWS Encryption SDK Encrypt Message.

    :param int version: Version of this manifest
    :param KeysManifest keys: Loaded keys
    :param dict plaintexts: Mapping of plaintext names to plaintext values
    :param dict tests: Mapping of test scenario names to :class:`EncryptTextScenario`s
    """

    version = attr.ib(validator=membership_validator(SUPPORTED_VERSIONS))
    keys = attr.ib(validator=attr.validators.instance_of(KeysManifest))
    plaintexts = attr.ib(
        validator=dictionary_validator(six.string_types, six.binary_type))
    tests = attr.ib(validator=dictionary_validator(
        six.string_types, MessageEncryptionTestScenario))
    type_name = "awses-encrypt"

    @staticmethod
    def _generate_plaintexts(plaintexts_specs):
        # type: (PLAINTEXTS_SPEC) -> Dict[str, bytes]
        """Generate required plaintext values.

        :param dict plaintexts_specs: Mapping of plaintext name to size in bytes
        :return: Mapping of plaintext name to randomly generated bytes
        :rtype: dict
        """
        return {
            name: os.urandom(size)
            for name, size in plaintexts_specs.items()
        }

    @classmethod
    def from_file(cls, input_file):
        # type: (IO) -> MessageEncryptionManifest
        """Load frome a file containing a full message encrypt manifest.

        :param file input_file: File object for file containing JSON manifest
        :return: Loaded manifest
        :rtype: MessageEncryptionManifest
        """
        raw_manifest = json.load(input_file)
        validate_manifest_type(type_name=cls.type_name,
                               manifest_version=raw_manifest["manifest"],
                               supported_versions=SUPPORTED_VERSIONS)

        parent_dir = os.path.abspath(os.path.dirname(input_file.name))
        reader = file_reader(parent_dir)
        raw_keys_manifest = json.loads(
            reader(raw_manifest["keys"]).decode(ENCODING))
        keys = KeysManifest.from_manifest_spec(raw_keys_manifest)
        plaintexts = cls._generate_plaintexts(raw_manifest["plaintexts"])
        tests = {}
        for name, scenario in raw_manifest["tests"].items():
            try:
                tests[name] = MessageEncryptionTestScenario.from_scenario(
                    scenario=scenario, keys=keys, plaintexts=plaintexts)
            except NotImplementedError:
                continue
        return cls(version=raw_manifest["manifest"]["version"],
                   keys=keys,
                   plaintexts=plaintexts,
                   tests=tests)

    def run_and_write_to_dir(self, target_directory, json_indent=None):
        # type: (str, Optional[int]) -> None
        """Process all known encrypt test scenarios and write the resulting data and manifests to disk.

        :param str target_directory: Directory in which to write all output
        :param int json_indent: Number of spaces to indent JSON files (optional: default is to write minified)
        """
        root_dir = os.path.abspath(target_directory)
        root_writer = file_writer(root_dir)

        root_writer(
            "keys.json",
            json.dumps(self.keys.manifest_spec,
                       indent=json_indent).encode(ENCODING))

        plaintext_writer = file_writer(os.path.join(root_dir, "plaintexts"))
        plaintext_uris = {
            name: plaintext_writer(name, plaintext)
            for name, plaintext in self.plaintexts.items()
        }

        ciphertext_writer = file_writer(os.path.join(root_dir, "ciphertexts"))

        test_scenarios = {
            name: scenario.run(ciphertext_writer,
                               plaintext_uris[scenario.plaintext_name])
            for name, scenario in self.tests.items()
        }

        decrypt_manifest = MessageDecryptionManifest(
            keys_uri="file://keys.json",
            keys=self.keys,
            test_scenarios=test_scenarios)

        root_writer(
            "manifest.json",
            json.dumps(decrypt_manifest.manifest_spec,
                       indent=json_indent).encode(ENCODING))
Пример #4
0
class MessageDecryptionManifest(object):
    # pylint: disable=too-many-arguments
    """AWS Encryption SDK Decrypt Message manifest handler.

    Described in AWS Crypto Tools Test Vector Framework feature #0003 AWS Encryption SDK Decrypt Message.

    :param str keys_uri:
    :param KeysManifest keys:
    :param dict test_scenarios:
    :param int version:
    :param str client_name:
    :param str client_version:
    """

    keys_uri = attr.ib(validator=attr.validators.instance_of(six.string_types))
    keys = attr.ib(validator=attr.validators.instance_of(KeysManifest))
    test_scenarios = attr.ib(default=attr.Factory(dict),
                             validator=dictionary_validator(
                                 six.string_types,
                                 MessageDecryptionTestScenario))
    version = attr.ib(default=CURRENT_VERSION,
                      validator=attr.validators.instance_of(int))
    client_name = attr.ib(default=CLIENT_NAME,
                          validator=attr.validators.instance_of(
                              six.string_types))
    client_version = attr.ib(default=aws_encryption_sdk.__version__,
                             validator=attr.validators.instance_of(
                                 six.string_types))
    type_name = "awses-decrypt"

    def __init__(
            self,
            keys_uri,  # type: str
            keys,  # type: KeysManifest
            test_scenarios=None,  # type: Optional[Dict[str, MessageDecryptionTestScenario]]
            version=CURRENT_VERSION,  # type: Optional[int]
            client_name=CLIENT_NAME,  # type: Optional[str]
            client_version=aws_encryption_sdk.
        __version__,  # type: Optional[str]
    ):  # noqa=D107
        # type: (...) -> None
        """Set initial values for the manifest."""
        # Workaround pending resolution of attrs/mypy interaction.
        # https://github.com/python/mypy/issues/2088
        # https://github.com/python-attrs/attrs/issues/215
        self.keys_uri = keys_uri
        self.keys = keys
        self.test_scenarios = test_scenarios
        self.version = version
        self.client_name = client_name
        self.client_version = client_version
        attr.validate(self)

    @property
    def manifest_spec(self):
        # type: () -> FULL_MESSAGE_DECRYPT_MANIFEST
        """Build a full message decrypt manifest describing this manifest.

        :return: Manifest JSON
        :rtype: dict
        """
        manifest_spec = {"type": self.type_name, "version": self.version}
        client_spec = {
            "name": self.client_name,
            "version": self.client_version
        }
        test_specs = {
            name: spec.scenario_spec
            for name, spec in self.test_scenarios.items()
        }
        return {
            "manifest": manifest_spec,
            "client": client_spec,
            "keys": self.keys_uri,
            "tests": test_specs
        }

    @classmethod
    def from_file(cls, input_file):
        # type: (IO) -> MessageDecryptionManifest
        """Load from a file containing a full message decrypt manifest.

        :param file input_file: File object for file containing JSON manifest
        :return: Loaded manifest
        :rtype: MessageDecryptionManifest
        """
        raw_manifest = json.load(input_file)
        validate_manifest_type(type_name=cls.type_name,
                               manifest_version=raw_manifest["manifest"],
                               supported_versions=SUPPORTED_VERSIONS)

        parent_dir = os.path.abspath(os.path.dirname(input_file.name))
        root_reader = file_reader(parent_dir)

        version = raw_manifest["manifest"]["version"]  # type: int
        keys_uri = raw_manifest["keys"]  # type: str

        raw_keys_manifest = json.loads(root_reader(keys_uri).decode(ENCODING))
        keys = KeysManifest.from_manifest_spec(raw_keys_manifest)

        client_name = raw_manifest["client"]["name"]  # type: str
        client_version = raw_manifest["client"]["version"]  # type: str
        raw_scenarios = raw_manifest[
            "tests"]  # type: Dict[str, DECRYPT_SCENARIO_SPEC]
        test_scenarios = {
            name: MessageDecryptionTestScenario.from_scenario(
                scenario=scenario,
                plaintext_reader=root_reader,
                ciphertext_reader=root_reader,
                keys=keys)
            for name, scenario in raw_scenarios.items()
        }

        return cls(
            keys_uri=keys_uri,
            keys=keys,
            test_scenarios=test_scenarios,
            version=version,
            client_name=client_name,
            client_version=client_version,
        )

    def run(self):
        # () -> None
        """Process all scenarios in this manifest."""
        for name, scenario in self.test_scenarios.items():
            scenario.run(name)
Пример #5
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
Пример #6
0
class MessageEncryptionManifest(object):
    """AWS Encryption SDK Encrypt Message manifest handler.

    Described in AWS Crypto Tools Test Vector Framework feature #0003 AWS Encryption SDK Encrypt Message.

    :param int version: Version of this manifest
    :param KeysManifest keys: Loaded keys
    :param dict plaintexts: Mapping of plaintext names to plaintext values
    :param dict tests: Mapping of test scenario names to :class:`EncryptTextScenario`s
    """

    version = attr.ib(validator=membership_validator(SUPPORTED_VERSIONS))
    keys = attr.ib(validator=attr.validators.instance_of(KeysManifest))
    plaintexts = attr.ib(
        validator=dictionary_validator(six.string_types, six.binary_type))
    tests = attr.ib(validator=dictionary_validator(
        six.string_types, MessageEncryptionTestScenario))
    type_name = "awses-encrypt"

    @staticmethod
    def _generate_plaintexts(plaintexts_specs):
        # type: (PLAINTEXTS_SPEC) -> Dict[str, bytes]
        """Generate required plaintext values.

        :param dict plaintexts_specs: Mapping of plaintext name to size in bytes
        :return: Mapping of plaintext name to randomly generated bytes
        :rtype: dict
        """
        return {
            name: os.urandom(size)
            for name, size in plaintexts_specs.items()
        }

    @classmethod
    def from_file(cls, input_file):
        # type: (IO) -> MessageEncryptionManifest
        """Load frome a file containing a full message encrypt manifest.

        :param file input_file: File object for file containing JSON manifest
        :return: Loaded manifest
        :rtype: MessageEncryptionManifest
        """
        raw_manifest = json.load(input_file)
        validate_manifest_type(type_name=cls.type_name,
                               manifest_version=raw_manifest["manifest"],
                               supported_versions=SUPPORTED_VERSIONS)

        parent_dir = os.path.abspath(os.path.dirname(input_file.name))
        reader = file_reader(parent_dir)
        raw_keys_manifest = json.loads(
            reader(raw_manifest["keys"]).decode(ENCODING))
        keys = KeysManifest.from_manifest_spec(raw_keys_manifest)
        plaintexts = cls._generate_plaintexts(raw_manifest["plaintexts"])
        tests = {}
        for name, scenario in raw_manifest["tests"].items():
            try:
                tests[name] = MessageEncryptionTestScenario.from_scenario(
                    scenario=scenario, keys=keys, plaintexts=plaintexts)
            except NotImplementedError:
                continue
        return cls(version=raw_manifest["manifest"]["version"],
                   keys=keys,
                   plaintexts=plaintexts,
                   tests=tests)

    def run(self):
        # () -> None
        """Process all scenarios in this manifest."""
        for _, scenario in self.tests.items():
            scenario.run()