Exemplo n.º 1
0
class AwsKmsKeySpec(KeySpec):
    """AWS KMS key specification.

    :param bool encrypt: Key can be used to encrypt
    :param bool decrypt: Key can be used to decrypt
    :param str type_name: Master key type name (must be "aws-kms")
    :param str key_id: Master key ID
    """

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

    type_name = attr.ib(validator=membership_validator(("aws-kms", )))

    def __init__(self, encrypt, decrypt, type_name, key_id):  # noqa=D107
        # type: (bool, bool, str, str) -> None
        # Workaround pending resolution of attrs/mypy interaction.
        # https://github.com/python/mypy/issues/2088
        # https://github.com/python-attrs/attrs/issues/215
        self.type_name = type_name
        super(AwsKmsKeySpec, self).__init__(encrypt, decrypt, key_id)

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

        :return: Key specification JSON
        :rtype: dict
        """
        return {
            "encrypt": self.encrypt,
            "decrypt": self.decrypt,
            "type": self.type_name,
            "key-id": arn_from_key_id(self.key_id),
        }
Exemplo n.º 2
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()},
        }
Exemplo n.º 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))
Exemplo n.º 4
0
class ManualKeySpec(KeySpec):
    # pylint: disable=too-many-arguments
    """Manual key specification.

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

    :param str key_id: Master key ID
    :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 str material: Raw material encoded
    """

    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=attr.validators.instance_of(six.string_types))

    def __init__(
            self,
            key_id,  # type: str
            encrypt,  # type: bool
            decrypt,  # type: bool
            algorithm,  # type: str
            type_name,  # type: str
            bits,  # type: int
            encoding,  # type: str
            material,  # type: Iterable[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
        super(ManualKeySpec, self).__init__(encrypt, decrypt, key_id)

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

        :return: Binary key material
        :rtype: bytes
        """
        raw_material = 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,
            "material": self.material,
            "key-id": self.key_id,
        }
class MasterKeySpec(object):
    """AWS Encryption SDK master key specification utilities.

    Described in AWS Crypto Tools Test Vector Framework features #0003 and #0004.

    :param str type_name: Master key type name
    :param str key_name: Name of key in keys spec
    :param str provider_id: Master key provider ID
    :param str encryption_algorithm: Wrapping key encryption algorithm (required for raw master keys)
    :param str padding_algorithm: Wrapping key padding algorithm (required for raw master keys)
    :param str padding_hash: Wrapping key padding hash (required for raw master keys)
    """

    type_name = attr.ib(validator=membership_validator(KNOWN_TYPES))
    key_name = attr.ib(validator=attr.validators.instance_of(six.string_types))
    provider_id = attr.ib(validator=attr.validators.optional(
        attr.validators.instance_of(six.string_types)))
    encryption_algorithm = attr.ib(validator=attr.validators.optional(
        membership_validator(KNOWN_ALGORITHMS)))
    padding_algorithm = attr.ib(validator=attr.validators.optional(
        membership_validator(KNOWN_PADDING)))
    padding_hash = attr.ib(validator=attr.validators.optional(
        membership_validator(KNOWN_PADDING_HASH)))

    def __attrs_post_init__(self):
        # type: () -> None
        """Verify that known types all have loaders and that all required parameters are provided."""
        if set(KNOWN_TYPES) != set(self._MASTER_KEY_LOADERS.keys()):
            raise NotImplementedError(
                "Gap found between known master key types and available master key loaders."
            )

        if self.type_name == "raw":
            if None in (self.provider_id, self.encryption_algorithm):
                raise ValueError(
                    "Provider ID and encryption algorithm are both required for raw keys"
                )

            if self.encryption_algorithm == "rsa" and self.padding_algorithm is None:
                raise ValueError(
                    "Padding algorithm is required for raw RSA keys")

            if self.padding_algorithm == "oaep-mgf1" and self.padding_hash is None:
                raise ValueError(
                    'Padding hash must be specified if padding algorithm is "oaep-mgf1"'
                )

    @classmethod
    def from_scenario(cls, spec):
        # type: (MASTER_KEY_SPEC) -> MasterKeySpec
        """Load from a master key specification.

        :param dict spec: Master key specification JSON
        :return: Loaded master key specification
        :rtype: MasterKeySpec
        """
        return cls(
            type_name=spec["type"],
            key_name=spec["key"],
            provider_id=spec.get("provider-id"),
            encryption_algorithm=spec.get("encryption-algorithm"),
            padding_algorithm=spec.get("padding-algorithm"),
            padding_hash=spec.get("padding-hash"),
        )

    def _wrapping_algorithm(self, key_bits):
        # type: (int) -> WrappingAlgorithm
        """Determine the correct wrapping algorithm if this is a raw master key.

        :param key_bits: Key size in bits
        :return: Correct wrapping algorithm
        :rtype: WrappingAlgorithm
        :raises TypeError: if this is not a raw master key specification
        """
        if not self.type_name == "raw":
            raise TypeError("This is not a raw master key")

        key_spec_values = [self.encryption_algorithm]
        if self.encryption_algorithm == "aes":
            key_spec_values.append(str(key_bits))

        elif self.encryption_algorithm == "rsa":
            key_spec_values.append(self.padding_algorithm)

            if self.padding_hash is not None:
                key_spec_values.append(self.padding_hash)

        key_spec_name = "/".join(key_spec_values)

        if key_spec_name in _NOT_YET_IMPLEMENTED:
            raise NotImplementedError('Key spec "{}" is not yet available.')

        return _RAW_WRAPPING_KEY_ALGORITHMS[key_spec_name]

    def _wrapping_key(self, key_spec):
        # type: (KeySpec) -> WrappingKey
        """Build the  correct wrapping key if this is a raw master key.

        :param KeySpec key_spec: Key specification to use with this master key
        :return: Wrapping key to use
        :rtype: WrappingKey
        :raises TypeError: if this is not a raw master key specification
        """
        if not self.type_name == "raw":
            raise TypeError("This is not a raw master key")

        algorithm = self._wrapping_algorithm(key_spec.bits)
        material = key_spec.raw_material
        key_type = _RAW_ENCRYPTION_KEY_TYPE[key_spec.type_name]
        return WrappingKey(wrapping_algorithm=algorithm,
                           wrapping_key=material,
                           wrapping_key_type=key_type)

    def _raw_master_key_from_spec(self, key_spec):
        # type: (KeySpec) -> RawMasterKey
        """Build a raw master key using this specification.

        :param KeySpec key_spec: Key specification to use with this master key
        :return: Raw master key based on this specification
        :rtype: RawMasterKey
        :raises TypeError: if this is not a raw master key specification
        """
        if not self.type_name == "raw":
            raise TypeError("This is not a raw master key")

        wrapping_key = self._wrapping_key(key_spec)
        return RawMasterKey(provider_id=self.provider_id,
                            key_id=key_spec.key_id,
                            wrapping_key=wrapping_key)

    def _kms_master_key_from_spec(self, key_spec):
        # type: (KeySpec) -> KMSMasterKey
        """Build an AWS KMS master key using this specification.

        :param KeySpec key_spec: Key specification to use with this master key
        :return: AWS KMS master key based on this specification
        :rtype: KMSMasterKey
        :raises TypeError: if this is not an AWS KMS master key specification
        """
        if not self.type_name == "aws-kms":
            raise TypeError("This is not an AWS KMS master key")

        return KMS_MASTER_KEY_PROVIDER.master_key(key_id=key_spec.key_id)

    _MASTER_KEY_LOADERS = {
        "aws-kms": _kms_master_key_from_spec,
        "raw": _raw_master_key_from_spec
    }

    def master_key(self, keys):
        # type: (KeysManifest) -> MasterKeyProvider
        """Build a master key using this specification.

        :param KeysManifest keys: Loaded key materials
        """
        key_spec = keys.key(self.key_name)
        key_loader = self._MASTER_KEY_LOADERS[self.type_name]
        return key_loader(self, key_spec)

    @property
    def scenario_spec(self):
        # type: () -> MASTER_KEY_SPEC
        """Build a master key specification describing this master key.

        :return: Master key specification JSON
        :rtype: dict
        """
        spec = {"type": self.type_name, "key": self.key_name}

        if self.type_name != "aws-kms":
            spec.update({
                "provider-id": self.provider_id,
                "encryption-algorithm": self.encryption_algorithm,
                "padding-algorithm": self.padding_algorithm,
            })
            if self.padding_hash is not None:
                spec["padding-hash"] = self.padding_hash

        return spec
Exemplo n.º 6
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,
        }
Exemplo n.º 7
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()