aboutsummaryrefslogtreecommitdiff
path: root/python/olm/utility.py
blob: 10d5ab49ee38eb4922b0c5eff13552219c58e57c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# -*- 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)