python3-olm 3.2.18__cp314-cp314-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
olm/session.py ADDED
@@ -0,0 +1,510 @@
1
+ # -*- coding: utf-8 -*-
2
+ # libolm python bindings
3
+ # Copyright © 2015-2017 OpenMarket Ltd
4
+ # Copyright © 2018 Damir Jelić <poljar@termina.org.uk>
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ """libolm Session module.
18
+
19
+ This module contains the Olm Session part of the Olm library.
20
+
21
+ It is used to establish a peer-to-peer encrypted communication channel between
22
+ two Olm accounts.
23
+
24
+ Examples:
25
+ >>> alice = Account()
26
+ >>> bob = Account()
27
+ >>> bob.generate_one_time_keys(1)
28
+ >>> id_key = bob.identity_keys['curve25519']
29
+ >>> one_time = list(bob.one_time_keys["curve25519"].values())[0]
30
+ >>> session = OutboundSession(alice, id_key, one_time)
31
+
32
+ """
33
+
34
+ # pylint: disable=redefined-builtin,unused-import
35
+ from builtins import bytes, super
36
+ from typing import AnyStr, Optional, Type
37
+
38
+ # pylint: disable=no-name-in-module
39
+ from _libolm import ffi, lib # type: ignore
40
+
41
+ from ._compat import URANDOM, to_bytearray, to_bytes, to_unicode_str
42
+ from ._finalize import track_for_finalization
43
+
44
+ # This is imported only for type checking purposes
45
+ if False:
46
+ from .account import Account # pragma: no cover
47
+
48
+
49
+ class OlmSessionError(Exception):
50
+ """libolm Session exception."""
51
+
52
+
53
+ class _OlmMessage(object):
54
+ def __init__(self, ciphertext, message_type):
55
+ # type: (AnyStr, ffi.cdata) -> None
56
+ if not ciphertext:
57
+ raise ValueError("Ciphertext can't be empty")
58
+
59
+ # I don't know why mypy wants a type annotation here nor why AnyStr
60
+ # doesn't work
61
+ self.ciphertext = ciphertext # type: ignore
62
+ self.message_type = message_type
63
+
64
+ def __str__(self):
65
+ # type: () -> str
66
+ type_to_prefix = {
67
+ lib.OLM_MESSAGE_TYPE_PRE_KEY: "PRE_KEY",
68
+ lib.OLM_MESSAGE_TYPE_MESSAGE: "MESSAGE"
69
+ }
70
+
71
+ prefix = type_to_prefix[self.message_type]
72
+ return "{} {}".format(prefix, self.ciphertext)
73
+
74
+
75
+ class OlmPreKeyMessage(_OlmMessage):
76
+ """Olm prekey message class
77
+
78
+ Prekey messages are used to establish an Olm session. After the first
79
+ message exchange the session switches to normal messages
80
+ """
81
+
82
+ def __init__(self, ciphertext):
83
+ # type: (AnyStr) -> None
84
+ """Create a new Olm prekey message with the supplied ciphertext
85
+
86
+ Args:
87
+ ciphertext(str): The ciphertext of the prekey message.
88
+ """
89
+ _OlmMessage.__init__(self, ciphertext, lib.OLM_MESSAGE_TYPE_PRE_KEY)
90
+
91
+ def __repr__(self):
92
+ # type: () -> str
93
+ return "OlmPreKeyMessage({})".format(self.ciphertext)
94
+
95
+
96
+ class OlmMessage(_OlmMessage):
97
+ """Olm message class"""
98
+
99
+ def __init__(self, ciphertext):
100
+ # type: (AnyStr) -> None
101
+ """Create a new Olm message with the supplied ciphertext
102
+
103
+ Args:
104
+ ciphertext(str): The ciphertext of the message.
105
+ """
106
+ _OlmMessage.__init__(self, ciphertext, lib.OLM_MESSAGE_TYPE_MESSAGE)
107
+
108
+ def __repr__(self):
109
+ # type: () -> str
110
+ return "OlmMessage({})".format(self.ciphertext)
111
+
112
+
113
+ def _clear_session(session):
114
+ # type: (ffi.cdata) -> None
115
+ lib.olm_clear_session(session)
116
+
117
+
118
+ class Session(object):
119
+ """libolm Session class.
120
+ This is an abstract class that can't be instantiated except when unpickling
121
+ a previously pickled InboundSession or OutboundSession object with
122
+ from_pickle.
123
+ """
124
+
125
+ def __new__(cls):
126
+ # type: (Type[Session]) -> Session
127
+
128
+ obj = super().__new__(cls)
129
+ obj._buf = ffi.new("char[]", lib.olm_session_size())
130
+ obj._session = lib.olm_session(obj._buf)
131
+ track_for_finalization(obj, obj._session, _clear_session)
132
+ return obj
133
+
134
+ def __init__(self):
135
+ # type: () -> None
136
+ if type(self) is Session:
137
+ raise TypeError("Session class may not be instantiated.")
138
+
139
+ if False:
140
+ self._session = self._session # type: ffi.cdata
141
+
142
+ def _check_error(self, ret):
143
+ # type: (int) -> None
144
+ if ret != lib.olm_error():
145
+ return
146
+
147
+ last_error = ffi.string(lib.olm_session_last_error(self._session)).decode()
148
+
149
+ raise OlmSessionError(last_error)
150
+
151
+ def pickle(self, passphrase=""):
152
+ # type: (Optional[str]) -> bytes
153
+ """Store an Olm session.
154
+
155
+ Stores a session as a base64 string. Encrypts the session using the
156
+ supplied passphrase. Returns a byte object containing the base64
157
+ encoded string of the pickled session. Raises OlmSessionError on
158
+ failure.
159
+
160
+ Args:
161
+ passphrase(str, optional): The passphrase to be used to encrypt
162
+ the session.
163
+ """
164
+ byte_key = bytearray(passphrase, "utf-8") if passphrase else b""
165
+
166
+ pickle_length = lib.olm_pickle_session_length(self._session)
167
+ pickle_buffer = ffi.new("char[]", pickle_length)
168
+
169
+ try:
170
+ self._check_error(
171
+ lib.olm_pickle_session(self._session,
172
+ ffi.from_buffer(byte_key),
173
+ len(byte_key),
174
+ pickle_buffer, pickle_length))
175
+ finally:
176
+ # clear out copies of the passphrase
177
+ for i in range(0, len(byte_key)):
178
+ byte_key[i] = 0
179
+
180
+ return ffi.unpack(pickle_buffer, pickle_length)
181
+
182
+ @classmethod
183
+ def from_pickle(cls, pickle, passphrase=""):
184
+ # type: (bytes, Optional[str]) -> Session
185
+ """Load a previously stored Olm session.
186
+
187
+ Loads a session from a pickled base64 string and returns a Session
188
+ object. Decrypts the session using the supplied passphrase. Raises
189
+ OlmSessionError on failure. If the passphrase doesn't match the one
190
+ used to encrypt the session then the error message for the
191
+ exception will be "BAD_ACCOUNT_KEY". If the base64 couldn't be decoded
192
+ then the error message will be "INVALID_BASE64".
193
+
194
+ Args:
195
+ pickle(bytes): Base64 encoded byte string containing the pickled
196
+ session
197
+ passphrase(str, optional): The passphrase used to encrypt the
198
+ session.
199
+ """
200
+ if not pickle:
201
+ raise ValueError("Pickle can't be empty")
202
+
203
+ byte_key = bytearray(passphrase, "utf-8") if passphrase else b""
204
+ # copy because unpickle will destroy the buffer
205
+ pickle_buffer = ffi.new("char[]", pickle)
206
+
207
+ session = cls.__new__(cls)
208
+
209
+ try:
210
+ ret = lib.olm_unpickle_session(session._session,
211
+ ffi.from_buffer(byte_key),
212
+ len(byte_key),
213
+ pickle_buffer,
214
+ len(pickle))
215
+ session._check_error(ret)
216
+ finally:
217
+ # clear out copies of the passphrase
218
+ for i in range(0, len(byte_key)):
219
+ byte_key[i] = 0
220
+
221
+ return session
222
+
223
+ def encrypt(self, plaintext):
224
+ # type: (AnyStr) -> _OlmMessage
225
+ """Encrypts a message using the session. Returns the ciphertext as a
226
+ base64 encoded string on success. Raises OlmSessionError on failure.
227
+
228
+ Args:
229
+ plaintext(str): The plaintext message that will be encrypted.
230
+ """
231
+ byte_plaintext = to_bytearray(plaintext)
232
+
233
+ r_length = lib.olm_encrypt_random_length(self._session)
234
+ random = URANDOM(r_length)
235
+
236
+ try:
237
+ message_type = lib.olm_encrypt_message_type(self._session)
238
+
239
+ self._check_error(message_type)
240
+
241
+ ciphertext_length = lib.olm_encrypt_message_length(
242
+ self._session, len(byte_plaintext)
243
+ )
244
+ ciphertext_buffer = ffi.new("char[]", ciphertext_length)
245
+
246
+ self._check_error(lib.olm_encrypt(
247
+ self._session,
248
+ ffi.from_buffer(byte_plaintext), len(byte_plaintext),
249
+ ffi.from_buffer(random), r_length,
250
+ ciphertext_buffer, ciphertext_length,
251
+ ))
252
+ finally:
253
+ # clear out copies of plaintext
254
+ if byte_plaintext is not plaintext:
255
+ for i in range(0, len(byte_plaintext)):
256
+ byte_plaintext[i] = 0
257
+
258
+ if message_type == lib.OLM_MESSAGE_TYPE_PRE_KEY:
259
+ return OlmPreKeyMessage(
260
+ ffi.unpack(
261
+ ciphertext_buffer,
262
+ ciphertext_length
263
+ ).decode())
264
+ elif message_type == lib.OLM_MESSAGE_TYPE_MESSAGE:
265
+ return OlmMessage(
266
+ ffi.unpack(
267
+ ciphertext_buffer,
268
+ ciphertext_length
269
+ ).decode())
270
+ else: # pragma: no cover
271
+ raise ValueError("Unknown message type")
272
+
273
+ def decrypt(self, message, unicode_errors="replace"):
274
+ # type: (_OlmMessage, str) -> str
275
+ """Decrypts a message using the session. Returns the plaintext string
276
+ on success. Raises OlmSessionError on failure. If the base64 couldn't
277
+ be decoded then the error message will be "INVALID_BASE64". If the
278
+ message is for an unsupported version of the protocol the error message
279
+ will be "BAD_MESSAGE_VERSION". If the message couldn't be decoded then
280
+ the error message will be "BAD_MESSAGE_FORMAT". If the MAC on the
281
+ message was invalid then the error message will be "BAD_MESSAGE_MAC".
282
+
283
+ Args:
284
+ message(OlmMessage): The Olm message that will be decrypted. It can
285
+ be either an OlmPreKeyMessage or an OlmMessage.
286
+ unicode_errors(str, optional): The error handling scheme to use for
287
+ unicode decoding errors. The default is "replace" meaning that
288
+ the character that was unable to decode will be replaced with
289
+ the unicode replacement character (U+FFFD). Other possible
290
+ values are "strict", "ignore" and "xmlcharrefreplace" as well
291
+ as any other name registered with codecs.register_error that
292
+ can handle UnicodeEncodeErrors.
293
+ """
294
+ if not message.ciphertext:
295
+ raise ValueError("Ciphertext can't be empty")
296
+
297
+ byte_ciphertext = to_bytes(message.ciphertext)
298
+ # make a copy the ciphertext buffer, because
299
+ # olm_decrypt_max_plaintext_length wants to destroy something
300
+ ciphertext_buffer = ffi.new("char[]", byte_ciphertext)
301
+
302
+ max_plaintext_length = lib.olm_decrypt_max_plaintext_length(
303
+ self._session, message.message_type, ciphertext_buffer,
304
+ len(byte_ciphertext)
305
+ )
306
+ self._check_error(max_plaintext_length)
307
+ plaintext_buffer = ffi.new("char[]", max_plaintext_length)
308
+
309
+ # make a copy the ciphertext buffer, because
310
+ # olm_decrypt_max_plaintext_length wants to destroy something
311
+ ciphertext_buffer = ffi.new("char[]", byte_ciphertext)
312
+ plaintext_length = lib.olm_decrypt(
313
+ self._session, message.message_type,
314
+ ciphertext_buffer, len(byte_ciphertext),
315
+ plaintext_buffer, max_plaintext_length
316
+ )
317
+ self._check_error(plaintext_length)
318
+ plaintext = to_unicode_str(
319
+ ffi.unpack(plaintext_buffer, plaintext_length),
320
+ errors=unicode_errors
321
+ )
322
+
323
+ # clear out copies of the plaintext
324
+ lib.memset(plaintext_buffer, 0, max_plaintext_length)
325
+
326
+ return plaintext
327
+
328
+ @property
329
+ def id(self):
330
+ # type: () -> str
331
+ """str: An identifier for this session. Will be the same for both
332
+ ends of the conversation.
333
+ """
334
+ id_length = lib.olm_session_id_length(self._session)
335
+ id_buffer = ffi.new("char[]", id_length)
336
+
337
+ self._check_error(
338
+ lib.olm_session_id(self._session, id_buffer, id_length)
339
+ )
340
+ return ffi.unpack(id_buffer, id_length).decode()
341
+
342
+ def matches(self, message, identity_key=None):
343
+ # type: (OlmPreKeyMessage, Optional[AnyStr]) -> bool
344
+ """Checks if the PRE_KEY message is for this in-bound session.
345
+ This can happen if multiple messages are sent to this session before
346
+ this session sends a message in reply. Returns True if the session
347
+ matches. Returns False if the session does not match. Raises
348
+ OlmSessionError on failure. If the base64 couldn't be decoded then the
349
+ error message will be "INVALID_BASE64". If the message was for an
350
+ unsupported protocol version then the error message will be
351
+ "BAD_MESSAGE_VERSION". If the message couldn't be decoded then then the
352
+ error message will be * "BAD_MESSAGE_FORMAT".
353
+
354
+ Args:
355
+ message(OlmPreKeyMessage): The Olm prekey message that will checked
356
+ if it is intended for this session.
357
+ identity_key(str, optional): The identity key of the sender. To
358
+ check if the message was also sent using this identity key.
359
+ """
360
+ if not isinstance(message, OlmPreKeyMessage):
361
+ raise TypeError("Matches can only be called with prekey messages.")
362
+
363
+ if not message.ciphertext:
364
+ raise ValueError("Ciphertext can't be empty")
365
+
366
+ ret = None
367
+
368
+ byte_ciphertext = to_bytes(message.ciphertext)
369
+ # make a copy, because olm_matches_inbound_session(_from) will distroy
370
+ # it
371
+ message_buffer = ffi.new("char[]", byte_ciphertext)
372
+
373
+ if identity_key:
374
+ byte_id_key = to_bytes(identity_key)
375
+
376
+ ret = lib.olm_matches_inbound_session_from(
377
+ self._session,
378
+ ffi.from_buffer(byte_id_key), len(byte_id_key),
379
+ message_buffer, len(byte_ciphertext)
380
+ )
381
+
382
+ else:
383
+ ret = lib.olm_matches_inbound_session(
384
+ self._session,
385
+ message_buffer, len(byte_ciphertext))
386
+
387
+ self._check_error(ret)
388
+
389
+ return bool(ret)
390
+
391
+ def describe(self, buffer_length=600):
392
+ # type: (int) -> str
393
+ """
394
+ Generate a string describing the internal state of an olm session
395
+ for debugging and logging purposes.
396
+
397
+ Args:
398
+ buffer_length(int): The size of buffer for the string.
399
+ If the buffer is not large enough to hold the entire string, it
400
+ will be truncated and will end with "...". A buffer length of
401
+ 600 will be enough to hold any output.
402
+ """
403
+ describe_buffer = ffi.new("char[]", buffer_length)
404
+ lib.olm_session_describe(
405
+ self._session, describe_buffer, buffer_length
406
+ )
407
+ return ffi.string(describe_buffer).decode()
408
+
409
+
410
+ class InboundSession(Session):
411
+ """Inbound Olm session for p2p encrypted communication.
412
+ """
413
+
414
+ def __new__(cls, account, message, identity_key=None):
415
+ # type: (Account, OlmPreKeyMessage, Optional[AnyStr]) -> Session
416
+ return super().__new__(cls)
417
+
418
+ def __init__(self, account, message, identity_key=None):
419
+ # type: (Account, OlmPreKeyMessage, Optional[AnyStr]) -> None
420
+ """Create a new inbound Olm session.
421
+
422
+ Create a new in-bound session for sending/receiving messages from an
423
+ incoming prekey message. Raises OlmSessionError on failure. If the
424
+ base64 couldn't be decoded then error message will be "INVALID_BASE64".
425
+ If the message was for an unsupported protocol version then
426
+ the errror message will be "BAD_MESSAGE_VERSION". If the message
427
+ couldn't be decoded then then the error message will be
428
+ "BAD_MESSAGE_FORMAT". If the message refers to an unknown one-time
429
+ key then the error message will be "BAD_MESSAGE_KEY_ID".
430
+
431
+ Args:
432
+ account(Account): The Olm Account that will be used to create this
433
+ session.
434
+ message(OlmPreKeyMessage): The Olm prekey message that will checked
435
+ that will be used to create this session.
436
+ identity_key(str, optional): The identity key of the sender. To
437
+ check if the message was also sent using this identity key.
438
+ """
439
+ if not message.ciphertext:
440
+ raise ValueError("Ciphertext can't be empty")
441
+
442
+ super().__init__()
443
+ byte_ciphertext = to_bytes(message.ciphertext)
444
+ message_buffer = ffi.new("char[]", byte_ciphertext)
445
+
446
+ if identity_key:
447
+ byte_id_key = to_bytes(identity_key)
448
+ identity_key_buffer = ffi.new("char[]", byte_id_key)
449
+ self._check_error(lib.olm_create_inbound_session_from(
450
+ self._session,
451
+ account._account,
452
+ identity_key_buffer, len(byte_id_key),
453
+ message_buffer, len(byte_ciphertext)
454
+ ))
455
+ else:
456
+ self._check_error(lib.olm_create_inbound_session(
457
+ self._session,
458
+ account._account,
459
+ message_buffer, len(byte_ciphertext)
460
+ ))
461
+
462
+
463
+ class OutboundSession(Session):
464
+ """Outbound Olm session for p2p encrypted communication."""
465
+
466
+ def __new__(cls, account, identity_key, one_time_key):
467
+ # type: (Account, AnyStr, AnyStr) -> Session
468
+ return super().__new__(cls)
469
+
470
+ def __init__(self, account, identity_key, one_time_key):
471
+ # type: (Account, AnyStr, AnyStr) -> None
472
+ """Create a new outbound Olm session.
473
+
474
+ Creates a new outbound session for sending messages to a given
475
+ identity key and one-time key.
476
+
477
+ Raises OlmSessionError on failure. If the keys couldn't be decoded as
478
+ base64 then the error message will be "INVALID_BASE64".
479
+
480
+ Args:
481
+ account(Account): The Olm Account that will be used to create this
482
+ session.
483
+ identity_key(str): The identity key of the person with whom we want
484
+ to start the session.
485
+ one_time_key(str): A one-time key from the person with whom we want
486
+ to start the session.
487
+ """
488
+ if not identity_key:
489
+ raise ValueError("Identity key can't be empty")
490
+
491
+ if not one_time_key:
492
+ raise ValueError("One-time key can't be empty")
493
+
494
+ super().__init__()
495
+
496
+ byte_id_key = to_bytes(identity_key)
497
+ byte_one_time = to_bytes(one_time_key)
498
+
499
+ session_random_length = lib.olm_create_outbound_session_random_length(
500
+ self._session)
501
+
502
+ random = URANDOM(session_random_length)
503
+
504
+ self._check_error(lib.olm_create_outbound_session(
505
+ self._session,
506
+ account._account,
507
+ ffi.from_buffer(byte_id_key), len(byte_id_key),
508
+ ffi.from_buffer(byte_one_time), len(byte_one_time),
509
+ ffi.from_buffer(random), session_random_length
510
+ ))
olm/utility.py ADDED
@@ -0,0 +1,149 @@
1
+ # -*- coding: utf-8 -*-
2
+ # libolm python bindings
3
+ # Copyright © 2015-2017 OpenMarket Ltd
4
+ # Copyright © 2018 Damir Jelić <poljar@termina.org.uk>
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ """libolm Utility module.
18
+
19
+ This module contains utilities for olm.
20
+ It only contains the ed25519_verify function for signature verification.
21
+
22
+ Examples:
23
+ >>> alice = Account()
24
+
25
+ >>> message = "Test"
26
+ >>> signature = alice.sign(message)
27
+ >>> signing_key = alice.identity_keys["ed25519"]
28
+
29
+ >>> ed25519_verify(signing_key, message, signature)
30
+
31
+ """
32
+
33
+ # pylint: disable=redefined-builtin,unused-import
34
+ from typing import AnyStr, Type
35
+
36
+ # pylint: disable=no-name-in-module
37
+ from _libolm import ffi, lib # type: ignore
38
+
39
+ from ._compat import to_bytearray, to_bytes
40
+ from ._finalize import track_for_finalization
41
+
42
+
43
+ def _clear_utility(utility): # pragma: no cover
44
+ # type: (ffi.cdata) -> None
45
+ lib.olm_clear_utility(utility)
46
+
47
+
48
+ class OlmVerifyError(Exception):
49
+ """libolm signature verification exception."""
50
+
51
+
52
+ class OlmHashError(Exception):
53
+ """libolm hash calculation exception."""
54
+
55
+
56
+ class _Utility(object):
57
+ # pylint: disable=too-few-public-methods
58
+ """libolm Utility class."""
59
+
60
+ _buf = None
61
+ _utility = None
62
+
63
+ @classmethod
64
+ def _allocate(cls):
65
+ # type: (Type[_Utility]) -> None
66
+ cls._buf = ffi.new("char[]", lib.olm_utility_size())
67
+ cls._utility = lib.olm_utility(cls._buf)
68
+ track_for_finalization(cls, cls._utility, _clear_utility)
69
+
70
+ @classmethod
71
+ def _check_error(cls, ret, error_class):
72
+ # type: (int, Type) -> None
73
+ if ret != lib.olm_error():
74
+ return
75
+
76
+ raise error_class("{}".format(
77
+ ffi.string(lib.olm_utility_last_error(
78
+ cls._utility)).decode("utf-8")))
79
+
80
+ @classmethod
81
+ def _ed25519_verify(cls, key, message, signature):
82
+ # type: (Type[_Utility], AnyStr, AnyStr, AnyStr) -> None
83
+ if not cls._utility:
84
+ cls._allocate()
85
+
86
+ byte_key = to_bytes(key)
87
+ byte_message = to_bytearray(message)
88
+ byte_signature = to_bytearray(signature)
89
+
90
+ try:
91
+ ret = lib.olm_ed25519_verify(
92
+ cls._utility,
93
+ byte_key,
94
+ len(byte_key),
95
+ ffi.from_buffer(byte_message),
96
+ len(byte_message),
97
+ ffi.from_buffer(byte_signature),
98
+ len(byte_signature)
99
+ )
100
+
101
+ cls._check_error(ret, OlmVerifyError)
102
+
103
+ finally:
104
+ # clear out copies of the message, which may be a plaintext
105
+ if byte_message is not message:
106
+ for i in range(0, len(byte_message)):
107
+ byte_message[i] = 0
108
+
109
+ @classmethod
110
+ def _sha256(cls, input):
111
+ # type: (Type[_Utility], AnyStr) -> str
112
+ if not cls._utility:
113
+ cls._allocate()
114
+
115
+ byte_input = to_bytes(input)
116
+ hash_length = lib.olm_sha256_length(cls._utility)
117
+ hash = ffi.new("char[]", hash_length)
118
+
119
+ ret = lib.olm_sha256(cls._utility, byte_input, len(byte_input),
120
+ hash, hash_length)
121
+
122
+ cls._check_error(ret, OlmHashError)
123
+
124
+ return ffi.unpack(hash, hash_length).decode()
125
+
126
+
127
+ def ed25519_verify(key, message, signature):
128
+ # type: (AnyStr, AnyStr, AnyStr) -> None
129
+ """Verify an ed25519 signature.
130
+
131
+ Raises an OlmVerifyError if verification fails.
132
+
133
+ Args:
134
+ key(str): The ed25519 public key used for signing.
135
+ message(str): The signed message.
136
+ signature(bytes): The message signature.
137
+ """
138
+ return _Utility._ed25519_verify(key, message, signature)
139
+
140
+
141
+ def sha256(input_string):
142
+ # type: (AnyStr) -> str
143
+ """Calculate the SHA-256 hash of the input and encodes it as base64.
144
+
145
+ Args:
146
+ input_string(str): The input for which the hash will be calculated.
147
+
148
+ """
149
+ return _Utility._sha256(input_string)