def register_key_manager(cls, key_manager: km_module.KeyManager, new_key_allowed: bool = True) -> None: """Tries to register a key_manager for the given key_manager.key_type(). Args: key_manager: A KeyManager object new_key_allowed: If new_key_allowed is true, users can generate new keys with this manager using Registry.new_key() """ key_managers = cls._key_managers type_url = key_manager.key_type() primitive_class = key_manager.primitive_class() if not key_manager.does_support(type_url): raise tink_error.TinkError( 'The manager does not support its own type {}.'.format( type_url)) if type_url in key_managers: existing, existing_new_key = key_managers[type_url] if (type(existing) != type(key_manager) or # pylint: disable=unidiomatic-typecheck existing.primitive_class() != primitive_class): raise tink_error.TinkError( 'A manager for type {} has been already registered.'. format(type_url)) else: if not existing_new_key and new_key_allowed: raise tink_error.TinkError( ('A manager for type {} has been already registered ' 'with forbidden new key operation.').format(type_url)) key_managers[type_url] = (existing, new_key_allowed) else: key_managers[type_url] = (key_manager, new_key_allowed)
def verify_mac(self, mac_value: bytes, data: bytes) -> None: if len(mac_value) <= crypto_format.NON_RAW_PREFIX_SIZE: # This also rejects raw MAC with size of 4 bytes or fewer. Those MACs are # clearly insecure, thus should be discouraged. raise tink_error.TinkError('tag too short') prefix = mac_value[:crypto_format.NON_RAW_PREFIX_SIZE] mac_no_prefix = mac_value[crypto_format.NON_RAW_PREFIX_SIZE:] for entry in self._primitive_set.primitive_from_identifier(prefix): try: if entry.output_prefix_type == tink_pb2.LEGACY: entry.primitive.verify_mac(mac_no_prefix, data + crypto_format.LEGACY_START_BYTE) else: entry.primitive.verify_mac(mac_no_prefix, data) # If there is no exception, the MAC is valid and we can return. return except tink_error.TinkError as e: logging.info('tag prefix matches a key, but cannot verify: %s', e) # No 'non-raw' key matched, so let's try the raw keys (if any exist). for entry in self._primitive_set.raw_primitives(): try: entry.primitive.verify_mac(mac_value, data) # If there is no exception, the MAC is valid and we can return. return except tink_error.TinkError as e: pass raise tink_error.TinkError('invalid MAC')
def read_encrypted(self) -> tink_pb2.EncryptedKeyset: if not self._serialized_keyset: raise tink_error.TinkError('No keyset found') try: encrypted_keyset = tink_pb2.EncryptedKeyset() encrypted_keyset.ParseFromString(self._serialized_keyset) return encrypted_keyset except message.DecodeError as e: raise tink_error.TinkError(e)
def _validate_key(key: tink_pb2.Keyset.Key): """Raises tink_error.TinkError if key is not valid.""" if not key.HasField('key_data'): raise tink_error.TinkError('key {} has no key data'.format(key.key_id)) if key.output_prefix_type == tink_pb2.UNKNOWN_PREFIX: raise tink_error.TinkError('key {} has unknown prefix'.format( key.key_id)) if key.status == tink_pb2.UNKNOWN_STATUS: raise tink_error.TinkError('key {} has unknown status'.format( key.key_id))
def public_key_data( cls, private_key_data: tink_pb2.KeyData) -> tink_pb2.KeyData: """Generates a new key for the specified key_template.""" if (private_key_data.key_material_type != tink_pb2.KeyData.ASYMMETRIC_PRIVATE): raise tink_error.TinkError('The keyset contains a non-private key') key_mgr = cls.key_manager(private_key_data.type_url) if not isinstance(key_mgr, km_module.PrivateKeyManager): raise tink_error.TinkError( 'manager for key type {} is not a PrivateKeyManager'.format( private_key_data.type_url)) return key_mgr.public_key_data(private_key_data)
def _assert_no_secret_key_material(keyset: tink_pb2.Keyset): for key in keyset.key: if key.key_data.key_material_type in ( tink_pb2.KeyData.UNKNOWN_KEYMATERIAL, tink_pb2.KeyData.SYMMETRIC, tink_pb2.KeyData.ASYMMETRIC_PRIVATE): raise tink_error.TinkError('keyset contains secret key material')
def _key_manager_internal( cls, type_url: Text) -> Tuple[km_module.KeyManager, bool]: """Returns a key manager, new_key_allowed pair for the given type_url.""" if type_url not in cls._key_managers: raise tink_error.TinkError( 'No manager for type {} has been registered.'.format(type_url)) return cls._key_managers[type_url]
def decrypt(self, ciphertext: bytes, associated_data: bytes) -> bytes: data = ciphertext.split(b'|') if (len(data) < 3 or data[1] != associated_data or data[2] != self._name.encode()): raise tink_error.TinkError('failed to decrypt ciphertext ' + ciphertext.decode()) return data[0]
def _encrypt(keyset: tink_pb2.Keyset, master_key_primitive: aead.Aead) -> tink_pb2.EncryptedKeyset: """Encrypts a Keyset and returns an EncryptedKeyset.""" encrypted_keyset = master_key_primitive.encrypt(keyset.SerializeToString(), b'') # Check if we can decrypt, to detect errors try: keyset2 = tink_pb2.Keyset.FromString( master_key_primitive.decrypt(encrypted_keyset, b'')) if keyset != keyset2: raise tink_error.TinkError('cannot encrypt keyset: %s != %s' % (keyset, keyset2)) except message.DecodeError: raise tink_error.TinkError('invalid keyset, corrupted key material') return tink_pb2.EncryptedKeyset(encrypted_keyset=encrypted_keyset, keyset_info=_keyset_info(keyset))
def write_encrypted(self, encrypted_keyset: tink_pb2.EncryptedKeyset) -> None: if not isinstance(encrypted_keyset, tink_pb2.EncryptedKeyset): raise tink_error.TinkError('invalid encrypted keyset.') json_keyset = json_format.MessageToJson(encrypted_keyset) # TODO(b/141106504) Needed for python 2.7 compatibility. StringIO expects # unicode, but MessageToJson outputs UTF-8. if isinstance(json_keyset, bytes): json_keyset = json_keyset.decode('utf-8') self._io_stream.write(json_keyset) self._io_stream.flush()
def write(self, b: bytes) -> int: """Write the given buffer to the stream. May use multiple calls to the underlying file object's write() method. Returns: The number of bytes written, which will always be the length of b in bytes. Raises: BlockingIOError: if the write could not be fully completed, with characters_written set to the number of bytes successfully written. TinkError: if there was a permanent error. Args: b: The buffer to write. """ self._check_not_closed() if not isinstance(b, (bytes, memoryview, bytearray)): raise TypeError('a bytes-like object is required, not {}'.format( type(b).__name__)) # One call to OutputStreamAdapter.write() may call next() multiple times # on the C++ EncryptingStream, but will perform a partial write if there is # a temporary write error. Permanent write errors will bubble up as # exceptions. written = self._output_stream_adapter.write(b) if written < 0: raise tink_error.TinkError('Number of written bytes was negative') self._bytes_written += written if written < len(b): raise io.BlockingIOError( errno.EAGAIN, 'Write could not complete without blocking.', written) elif written > len(b): raise tink_error.TinkError( 'Number of written bytes was greater than length of bytes given' ) return written
def verify(self, signature: bytes, data: bytes): """Verifies that signature is a digital signature for data. Args: signature: The signature bytes to be checked. data: The data bytes to be checked. Raises: tink_error.TinkError if the verification fails. """ if len(signature) <= crypto_format.NON_RAW_PREFIX_SIZE: # This also rejects raw signatures with size of 4 bytes or fewer. # We're not aware of any schemes that output signatures that small. raise tink_error.TinkError('signature too short') key_id = signature[:crypto_format.NON_RAW_PREFIX_SIZE] raw_sig = signature[crypto_format.NON_RAW_PREFIX_SIZE:] for entry in self._primitive_set.primitive_from_identifier(key_id): try: if entry.output_prefix_type == tink_pb2.LEGACY: entry.primitive.verify( raw_sig, data + crypto_format.LEGACY_START_BYTE) else: entry.primitive.verify(raw_sig, data) # Signature is valid, we can return return except tink_error.TinkError as err: logging.info( 'signature prefix matches a key, but cannot verify: %s', err) # No matching key succeeded with verification, try all RAW keys for entry in self._primitive_set.raw_primitives(): try: entry.primitive.verify(signature, data) # Signature is valid, we can return return except tink_error.TinkError: pass raise tink_error.TinkError('invalid signature')
def _validate_keyset(keyset: tink_pb2.Keyset): """Raises tink_error.TinkError if keyset is not valid.""" for key in keyset.key: if key.status != tink_pb2.DESTROYED: _validate_key(key) num_non_destroyed_keys = sum(1 for key in keyset.key if key.status != tink_pb2.DESTROYED) num_non_public_key_material = sum( 1 for key in keyset.key if key.key_data.key_material_type != tink_pb2.KeyData.ASYMMETRIC_PUBLIC) num_primary_keys = sum(1 for key in keyset.key if key.status == tink_pb2.ENABLED and key.key_id == keyset.primary_key_id) if num_non_destroyed_keys == 0: raise tink_error.TinkError('empty keyset') if num_primary_keys > 1: raise tink_error.TinkError('keyset contains multiple primary keys') if num_primary_keys == 0 and num_non_public_key_material > 0: raise tink_error.TinkError( 'keyset does not contain a valid primary key')
def _decrypt(encrypted_keyset: tink_pb2.EncryptedKeyset, master_key_aead: aead.Aead) -> tink_pb2.Keyset: """Decrypts an EncryptedKeyset and returns a Keyset.""" try: keyset = tink_pb2.Keyset.FromString( master_key_aead.decrypt(encrypted_keyset.encrypted_keyset, b'')) # Check emptiness here too, in case the encrypted keys unwrapped to nothing? _assert_enough_key_material(keyset) return keyset except message.DecodeError: raise tink_error.TinkError('invalid keyset, corrupted key material')
def new_key_data(cls, key_template: tink_pb2.KeyTemplate) -> tink_pb2.KeyData: """Generates a new key for the specified key_template.""" key_mgr, new_key_allowed = cls._key_manager_internal( key_template.type_url) if not new_key_allowed: raise tink_error.TinkError( 'KeyManager for type {} does not allow for creation of new keys.' .format(key_template.type_url)) return key_mgr.new_key_data(key_template)
def output_prefix(key: tink_pb2.Keyset.Key) -> bytes: """Generates the prefix for the outputs handled by the specified key.""" if key.output_prefix_type == tink_pb2.TINK: return struct.pack('>cL', TINK_START_BYTE, key.key_id) elif (key.output_prefix_type == tink_pb2.CRUNCHY or key.output_prefix_type == tink_pb2.LEGACY): return struct.pack('>cL', LEGACY_START_BYTE, key.key_id) elif key.output_prefix_type == tink_pb2.RAW: return b'' else: raise tink_error.TinkError( 'The given key has invalid OutputPrefixType {}.'.format( key.output_prefix_type))
def add_primitive(self, primitive: P, key: tink_pb2.Keyset.Key) -> Entry: """Adds a new primitive and key entry to the set, and returns the entry.""" if not isinstance(primitive, self._primitive_class): raise tink_error.TinkError( 'The primitive is not an instance of {}'.format( self._primitive_class)) identifier = crypto_format.output_prefix(key) entry = Entry(primitive, identifier, key.status, key.output_prefix_type) entries = self._primitives.setdefault(identifier, []) entries.append(entry) return entry
def register_primitive_wrapper( cls, wrapper: primitive_wrapper.PrimitiveWrapper) -> None: """Tries to register a PrimitiveWrapper. Args: wrapper: A PrimitiveWrapper object. Raises: Error if a different wrapper has already been registered for the same Primitive. """ if (wrapper.primitive_class() in cls._wrappers and type( cls._wrappers[wrapper.primitive_class()]) != type(wrapper)): # pylint: disable=unidiomatic-typecheck raise tink_error.TinkError( 'A wrapper for primitive {} has already been added.'.format( wrapper.primitive_class().__name__)) wrapped = wrapper.wrap( pset_module.PrimitiveSet(wrapper.primitive_class())) if not isinstance(wrapped, wrapper.primitive_class()): raise tink_error.TinkError( 'Wrapper for primitive {} generates incompatible primitive of type {}' .format(wrapper.primitive_class().__name__, type(wrapped).__name__)) cls._wrappers[wrapper.primitive_class()] = wrapper
def wrap(cls, primitive_set: pset_module.PrimitiveSet) -> Any: # -> Primitive """Tries to register a PrimitiveWrapper. Args: primitive_set: A PrimitiveSet object. Returns: A primitive that wraps the primitives in primitive_set. Raises: Error if no wrapper for this primitive class is registered. """ if primitive_set.primitive_class() not in cls._wrappers: raise tink_error.TinkError( 'No PrimitiveWrapper registered for primitive {}.'.format( primitive_set.primitive_class().__name__)) wrapper = cls._wrappers[primitive_set.primitive_class()] return wrapper.wrap(primitive_set)
def sign(self, data: bytes) -> bytes: """Computes the signature for data using the primary primitive. Args: data: The input data. Returns: The signature. """ primary = self._primitive_set.primary() if not primary: raise tink_error.TinkError('primary primitive not set') sign_data = data if primary.output_prefix_type == tink_pb2.LEGACY: sign_data = sign_data + crypto_format.LEGACY_START_BYTE return primary.identifier + primary.primitive.sign(sign_data)
def decrypt(self, ciphertext: bytes, context_info: bytes) -> bytes: if len(ciphertext) > crypto_format.NON_RAW_PREFIX_SIZE: prefix = ciphertext[:crypto_format.NON_RAW_PREFIX_SIZE] ciphertext_no_prefix = ciphertext[crypto_format. NON_RAW_PREFIX_SIZE:] for entry in self._primitive_set.primitive_from_identifier(prefix): try: return entry.primitive.decrypt(ciphertext_no_prefix, context_info) except tink_error.TinkError as e: logging.info( 'ciphertext prefix matches a key, but cannot decrypt: %s', e) # Let's try all RAW keys. for entry in self._primitive_set.raw_primitives(): try: return entry.primitive.decrypt(ciphertext, context_info) except tink_error.TinkError as e: pass # nothing works. raise tink_error.TinkError('Decryption failed.')
def primitive(cls, key_data: tink_pb2.KeyData, primitive_class: Type[P]) -> P: """Creates a new primitive for the key given in key_data. It looks up a KeyManager identified by key_data.type_url, and calls manager's primitive(key_data) method. Args: key_data: KeyData object primitive_class: The expected primitive class Returns: A primitive for the given key_data Raises: Error if primitive_class does not match the registered primitive class. """ key_mgr = cls.key_manager(key_data.type_url) if key_mgr.primitive_class() != primitive_class: raise tink_error.TinkError( 'Wrong primitive class: type {} uses primitive {}, and not {}.' .format(key_data.type_url, key_mgr.primitive_class().__name__, primitive_class.__name__)) return key_mgr.primitive(key_data)
def write(self, keyset: tink_pb2.Keyset) -> None: if not isinstance(keyset, tink_pb2.Keyset): raise tink_error.TinkError('invalid keyset.') self._io_stream.write(keyset.SerializeToString()) self._io_stream.flush()
def write_encrypted(self, encrypted_keyset: tink_pb2.EncryptedKeyset) -> None: if not isinstance(encrypted_keyset, tink_pb2.EncryptedKeyset): raise tink_error.TinkError('invalid encrypted keyset.') self._io_stream.write(encrypted_keyset.SerializeToString()) self._io_stream.flush()
def read_encrypted(self) -> tink_pb2.EncryptedKeyset: try: return json_format.Parse(self._serialized_keyset, tink_pb2.EncryptedKeyset()) except json_format.ParseError as e: raise tink_error.TinkError(e)
def verify(self, signature: bytes, data: bytes): if signature != data + b'|' + self._name.encode(): raise tink_error.TinkError('invalid signature ' + signature.decode())
def _assert_enough_encrypted_key_material( encrypted_keyset: tink_pb2.EncryptedKeyset): if not encrypted_keyset or not encrypted_keyset.encrypted_keyset: raise tink_error.TinkError('empty keyset')
def __new__(cls): raise tink_error.TinkError( ('KeysetHandle cannot be instantiated directly.'))
def _assert_enough_key_material(keyset: tink_pb2.Keyset): if not keyset or not keyset.key: raise tink_error.TinkError('empty keyset')
def verify_mac(self, mac_value: bytes, data: bytes) -> None: if mac_value != data + b'|' + self._name.encode(): raise tink_error.TinkError('invalid mac ' + mac_value.decode())