utility.py 4.35 KB
Newer Older
1
2
3
4
# -*- coding: utf-8 -*-
# libolm python bindings
# Copyright © 2015-2017 OpenMarket Ltd
# Copyright © 2018 Damir Jelić <poljar@termina.org.uk>
5
6
7
8
9
10
11
12
13
14
15
16
#
# 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.
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
"""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
35

36
from future.utils import bytes_to_native_str
37
38
39
40

# pylint: disable=no-name-in-module
from _libolm import ffi, lib  # type: ignore

41
from ._compat import to_bytearray, to_bytes
42
43
44
45
46
47
48
49
50
51
52
53
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."""


54
55
56
57
class OlmHashError(Exception):
    """libolm hash calculation exception."""


58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
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
73
74
    def _check_error(cls, ret, error_class):
        # type: (int, Type) -> None
75
76
77
        if ret != lib.olm_error():
            return

78
        raise error_class("{}".format(
79
80
81
82
83
84
85
86
87
88
            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)
89
        byte_message = to_bytearray(message)
90
        byte_signature = to_bytearray(signature)
91

92
        try:
93
94
95
96
97
98
99
100
101
102
103
104
            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)

105
106
107
108
109
        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
110

111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
    @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))

128
129
130
131
132
133
134
135
136
137
138
139
140

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)
141
142
143
144
145
146
147
148
149
150
151


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)