olm/python/olm/utility.py
Damir Jelić 2f5590bf38 olm: Allow decryption functions to define how to handle unicode decode errors.
This patch changes the decryption functions not to fail if there was an
unicode decode error while converting the decrypted bytes plaintext into
a native python string.

Characters that cannot be decoded as unicode are now replaced with the
unicode replacement character (U+FFFD).

The old behaviour of raising an UnicodeDecodeError can be achieved by
passing the "strict" error handling scheme to the decrypt function.
2019-06-18 13:50:46 +02:00

151 lines
4.4 KiB
Python

# -*- coding: utf-8 -*-
# libolm python bindings
# Copyright © 2015-2017 OpenMarket Ltd
# Copyright © 2018 Damir Jelić <poljar@termina.org.uk>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""libolm Utility module.
This module contains utilities for olm.
It only contains the ed25519_verify function for signature verification.
Examples:
>>> alice = Account()
>>> message = "Test"
>>> signature = alice.sign(message)
>>> signing_key = alice.identity_keys["ed25519"]
>>> ed25519_verify(signing_key, message, signature)
"""
# pylint: disable=redefined-builtin,unused-import
from typing import AnyStr, Type
from future.utils import bytes_to_native_str
# pylint: disable=no-name-in-module
from _libolm import ffi, lib # type: ignore
from ._compat import to_bytearray, to_bytes
from ._finalize import track_for_finalization
def _clear_utility(utility): # pragma: no cover
# type: (ffi.cdata) -> None
lib.olm_clear_utility(utility)
class OlmVerifyError(Exception):
"""libolm signature verification exception."""
class OlmHashError(Exception):
"""libolm hash calculation exception."""
class _Utility(object):
# pylint: disable=too-few-public-methods
"""libolm Utility class."""
_buf = None
_utility = None
@classmethod
def _allocate(cls):
# type: (Type[_Utility]) -> None
cls._buf = ffi.new("char[]", lib.olm_utility_size())
cls._utility = lib.olm_utility(cls._buf)
track_for_finalization(cls, cls._utility, _clear_utility)
@classmethod
def _check_error(cls, ret, error_class):
# type: (int, Type) -> None
if ret != lib.olm_error():
return
raise error_class("{}".format(
ffi.string(lib.olm_utility_last_error(
cls._utility)).decode("utf-8")))
@classmethod
def _ed25519_verify(cls, key, message, signature):
# type: (Type[_Utility], AnyStr, AnyStr, AnyStr) -> None
if not cls._utility:
cls._allocate()
byte_key = to_bytes(key)
byte_message = to_bytearray(message)
byte_signature = to_bytearray(signature)
try:
ret = lib.olm_ed25519_verify(
cls._utility,
byte_key,
len(byte_key),
ffi.from_buffer(byte_message),
len(byte_message),
ffi.from_buffer(byte_signature),
len(byte_signature)
)
cls._check_error(ret, OlmVerifyError)
finally:
# clear out copies of the message, which may be a plaintext
if byte_message is not message:
for i in range(0, len(byte_message)):
byte_message[i] = 0
@classmethod
def _sha256(cls, input):
# type: (Type[_Utility], AnyStr) -> str
if not cls._utility:
cls._allocate()
byte_input = to_bytes(input)
hash_length = lib.olm_sha256_length(cls._utility)
hash = ffi.new("char[]", hash_length)
ret = lib.olm_sha256(cls._utility, byte_input, len(byte_input),
hash, hash_length)
cls._check_error(ret, OlmHashError)
return bytes_to_native_str(ffi.unpack(hash, hash_length))
def ed25519_verify(key, message, signature):
# type: (AnyStr, AnyStr, AnyStr) -> None
"""Verify an ed25519 signature.
Raises an OlmVerifyError if verification fails.
Args:
key(str): The ed25519 public key used for signing.
message(str): The signed message.
signature(bytes): The message signature.
"""
return _Utility._ed25519_verify(key, message, signature)
def sha256(input_string):
# type: (AnyStr) -> str
"""Calculate the SHA-256 hash of the input and encodes it as base64.
Args:
input_string(str): The input for which the hash will be calculated.
"""
return _Utility._sha256(input_string)