From e1437b1b03e32f184b2ad33992f2daaf563ef75a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Philipp=20H=C3=B6rist?= <philipp@hoerist.com>
Date: Tue, 26 Jul 2016 19:47:05 +0200
Subject: [PATCH] Use native cryptography lib if installed

---
 omemo/omemo/aes_gcm.py          | 168 ++++++--------------------------
 omemo/omemo/aes_gcm_fallback.py | 152 +++++++++++++++++++++++++++++
 omemo/omemo/aes_gcm_native.py   |  61 ++++++++++++
 omemo/omemo/state.py            |   6 +-
 4 files changed, 244 insertions(+), 143 deletions(-)
 create mode 100644 omemo/omemo/aes_gcm_fallback.py
 create mode 100644 omemo/omemo/aes_gcm_native.py

diff --git a/omemo/omemo/aes_gcm.py b/omemo/omemo/aes_gcm.py
index 0f47615b..5f067a0b 100644
--- a/omemo/omemo/aes_gcm.py
+++ b/omemo/omemo/aes_gcm.py
@@ -1,158 +1,46 @@
 # -*- coding: utf-8 -*-
 #
-# Copyright 2014 Jonathan Zdziarski <jonathan@zdziarski.com>
+# Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
 #
-# All rights reserved.
+# This file is part of python-omemo library.
 #
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
+# The python-omemo library is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
 #
-# 1. Redistributions of source code must retain the above copyright notice, this
-# list of conditions and the following disclaimer.
+# python-omemo is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 #
-# 2. Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
+# You should have received a copy of the GNU General Public License along with
+# the python-omemo library.  If not, see <http://www.gnu.org/licenses/>.
 #
-# 3. Neither the name of the copyright holder nor the names of its contributors
-# may be used to endorse or promote products derived from this software without
-# specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-import logging
-from struct import pack, unpack
 
-from Crypto.Cipher import AES
-from Crypto.Util import strxor
 
+import sys
+import logging
 log = logging.getLogger('gajim.plugin_system.omemo')
+try:
+    from .aes_gcm_native import aes_decrypt
+    from .aes_gcm_native import aes_encrypt
+    log.debug('Using fast cryptography')
+except ImportError:
+    from .aes_gcm_fallback import aes_decrypt
+    from .aes_gcm_fallback import aes_encrypt
+    log.debug('Using slow cryptography')
 
 
-def gcm_rightshift(vec):
-    for x in range(15, 0, -1):
-        c = vec[x] >> 1
-        c |= (vec[x - 1] << 7) & 0x80
-        vec[x] = c
-    vec[0] >>= 1
-    return vec
-
-
-def gcm_gf_mult(a, b):
-    mask = [0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01]
-    poly = [0x00, 0xe1]
-
-    Z = [0] * 16
-    V = [c for c in a]
-
-    for x in range(128):
-        if b[x >> 3] & mask[x & 7]:
-            Z = [V[y] ^ Z[y] for y in range(16)]
-        bit = V[15] & 1
-        V = gcm_rightshift(V)
-        V[0] ^= poly[bit]
-    return Z
-
-
-def ghash(h, auth_data, data):
-    u = (16 - len(data)) % 16
-    v = (16 - len(auth_data)) % 16
-
-    x = auth_data + chr(0) * v + data + chr(0) * u
-    x += pack('>QQ', len(auth_data) * 8, len(data) * 8)
-
-    y = [0] * 16
-    vec_h = [ord(c) for c in h]
-
-    for i in range(0, len(x), 16):
-        block = [ord(c) for c in x[i:i + 16]]
-        y = [y[j] ^ block[j] for j in range(16)]
-        y = gcm_gf_mult(y, vec_h)
-
-    return ''.join(chr(c) for c in y)
-
-
-def inc32(block):
-    counter, = unpack('>L', block[12:])
-    counter += 1
-    return block[:12] + pack('>L', counter)
-
-
-def gctr(k, icb, plaintext):
-    y = ''
-    if len(plaintext) == 0:
-        return y
+def encrypt(key, iv, plaintext):
+    return aes_encrypt(key, iv, plaintext)
 
-    aes = AES.new(k)
-    cb = icb
 
-    for i in range(0, len(plaintext), aes.block_size):
-        cb = inc32(cb)
-        encrypted = aes.encrypt(cb)
-        plaintext_block = plaintext[i:i + aes.block_size]
-        y += strxor.strxor(plaintext_block, encrypted[:len(plaintext_block)])
-
-    return y
-
-
-def gcm_decrypt(k, iv, encrypted, auth_data, tag):
-    aes = AES.new(k)
-    h = aes.encrypt(chr(0) * aes.block_size)
-
-    if len(iv) == 12:
-        y0 = iv + "\x00\x00\x00\x01"
+def decrypt(key, iv, ciphertext):
+    plaintext = aes_decrypt(key, iv, ciphertext).decode('utf-8')
+    if sys.version_info < (3, 0):
+        return unicode(plaintext)
     else:
-        y0 = ghash(h, '', iv)
-
-    decrypted = gctr(k, y0, encrypted)
-    s = ghash(h, auth_data, encrypted)
-
-    t = aes.encrypt(y0)
-    T = strxor.strxor(s, t)
-    if T != tag:
-        raise ValueError('Decrypted data is invalid')
-    else:
-        return decrypted
-
-
-def gcm_encrypt(k, iv, plaintext, auth_data):
-    aes = AES.new(k)
-    h = aes.encrypt(chr(0) * aes.block_size)
-
-    if len(iv) == 12:
-        y0 = iv + "\x00\x00\x00\x01"
-    else:
-        y0 = ghash(h, '', iv)
-
-    encrypted = gctr(k, y0, plaintext)
-    s = ghash(h, auth_data, encrypted)
-
-    t = aes.encrypt(y0)
-    T = strxor.strxor(s, t)
-    return (encrypted, T)
-
-
-def aes_encrypt(key, nonce, plaintext):
-    """ Use AES128 GCM with the given key and iv to encrypt the payload. """
-    c, t = gcm_encrypt(key, nonce, plaintext, '')
-    result = c + t
-    return result
-
-
-def aes_decrypt(key, nonce, payload):
-    """ Use AES128 GCM with the given key and iv to decrypt the payload. """
-    ciphertext = payload[:-16]
-    mac = payload[-16:]
-    return gcm_decrypt(key, nonce, ciphertext, '', mac)
+        return plaintext
 
 
 class NoValidSessions(Exception):
diff --git a/omemo/omemo/aes_gcm_fallback.py b/omemo/omemo/aes_gcm_fallback.py
new file mode 100644
index 00000000..a34255af
--- /dev/null
+++ b/omemo/omemo/aes_gcm_fallback.py
@@ -0,0 +1,152 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2014 Jonathan Zdziarski <jonathan@zdziarski.com>
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+# list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors
+# may be used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from struct import pack, unpack
+
+from Crypto.Cipher import AES
+from Crypto.Util import strxor
+
+
+def gcm_rightshift(vec):
+    for x in range(15, 0, -1):
+        c = vec[x] >> 1
+        c |= (vec[x - 1] << 7) & 0x80
+        vec[x] = c
+    vec[0] >>= 1
+    return vec
+
+
+def gcm_gf_mult(a, b):
+    mask = [0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01]
+    poly = [0x00, 0xe1]
+
+    Z = [0] * 16
+    V = [c for c in a]
+
+    for x in range(128):
+        if b[x >> 3] & mask[x & 7]:
+            Z = [V[y] ^ Z[y] for y in range(16)]
+        bit = V[15] & 1
+        V = gcm_rightshift(V)
+        V[0] ^= poly[bit]
+    return Z
+
+
+def ghash(h, auth_data, data):
+    u = (16 - len(data)) % 16
+    v = (16 - len(auth_data)) % 16
+
+    x = auth_data + chr(0) * v + data + chr(0) * u
+    x += pack('>QQ', len(auth_data) * 8, len(data) * 8)
+
+    y = [0] * 16
+    vec_h = [ord(c) for c in h]
+
+    for i in range(0, len(x), 16):
+        block = [ord(c) for c in x[i:i + 16]]
+        y = [y[j] ^ block[j] for j in range(16)]
+        y = gcm_gf_mult(y, vec_h)
+
+    return ''.join(chr(c) for c in y)
+
+
+def inc32(block):
+    counter, = unpack('>L', block[12:])
+    counter += 1
+    return block[:12] + pack('>L', counter)
+
+
+def gctr(k, icb, plaintext):
+    y = ''
+    if len(plaintext) == 0:
+        return y
+
+    aes = AES.new(k)
+    cb = icb
+
+    for i in range(0, len(plaintext), aes.block_size):
+        cb = inc32(cb)
+        encrypted = aes.encrypt(cb)
+        plaintext_block = plaintext[i:i + aes.block_size]
+        y += strxor.strxor(plaintext_block, encrypted[:len(plaintext_block)])
+
+    return y
+
+
+def gcm_decrypt(k, iv, encrypted, auth_data, tag):
+    aes = AES.new(k)
+    h = aes.encrypt(chr(0) * aes.block_size)
+
+    if len(iv) == 12:
+        y0 = iv + "\x00\x00\x00\x01"
+    else:
+        y0 = ghash(h, '', iv)
+
+    decrypted = gctr(k, y0, encrypted)
+    s = ghash(h, auth_data, encrypted)
+
+    t = aes.encrypt(y0)
+    T = strxor.strxor(s, t)
+    if T != tag:
+        raise ValueError('Decrypted data is invalid')
+    else:
+        return decrypted
+
+
+def gcm_encrypt(k, iv, plaintext, auth_data):
+    aes = AES.new(k)
+    h = aes.encrypt(chr(0) * aes.block_size)
+
+    if len(iv) == 12:
+        y0 = iv + "\x00\x00\x00\x01"
+    else:
+        y0 = ghash(h, '', iv)
+
+    encrypted = gctr(k, y0, plaintext)
+    s = ghash(h, auth_data, encrypted)
+
+    t = aes.encrypt(y0)
+    T = strxor.strxor(s, t)
+    return (encrypted, T)
+
+
+def aes_encrypt(key, nonce, plaintext):
+    """ Use AES128 GCM with the given key and iv to encrypt the payload. """
+    c, t = gcm_encrypt(key, nonce, plaintext, '')
+    result = c + t
+    return result
+
+
+def aes_decrypt(key, nonce, payload):
+    """ Use AES128 GCM with the given key and iv to decrypt the payload. """
+    ciphertext = payload[:-16]
+    mac = payload[-16:]
+    return gcm_decrypt(key, nonce, ciphertext, '', mac)
diff --git a/omemo/omemo/aes_gcm_native.py b/omemo/omemo/aes_gcm_native.py
new file mode 100644
index 00000000..40bf1271
--- /dev/null
+++ b/omemo/omemo/aes_gcm_native.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright 2015 Bahtiar `kalkin-` Gadimov <bahtiar@gadimov.de>
+#
+# This file is part of python-omemo library.
+#
+# The python-omemo library is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# python-omemo is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# the python-omemo library.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+
+import os
+from cryptography.hazmat.primitives.ciphers import Cipher
+from cryptography.hazmat.primitives.ciphers import algorithms
+from cryptography.hazmat.primitives.ciphers.modes import GCM
+
+# On Windows we have to import a specific backend because the
+# default_backend() mechanism doesnt work in Gajim for Windows.
+# Its because of how Gajim is build with cx_freeze
+
+if os.name == 'nt':
+    from cryptography.hazmat.backends.openssl import backend
+else:
+    from cryptography.hazmat.backends import default_backend
+
+
+def aes_decrypt(key, iv, payload):
+    """ Use AES128 GCM with the given key and iv to decrypt the payload. """
+    data = payload[:-16]
+    tag = payload[-16:]
+    if os.name == 'nt':
+        _backend = backend
+    else:
+        _backend = default_backend()
+    decryptor = Cipher(
+        algorithms.AES(key),
+        GCM(iv, tag=tag),
+        backend=_backend).decryptor()
+    return decryptor.update(data) + decryptor.finalize()
+
+
+def aes_encrypt(key, iv, plaintext):
+    """ Use AES128 GCM with the given key and iv to encrypt the plaintext. """
+    if os.name == 'nt':
+        _backend = backend
+    else:
+        _backend = default_backend()
+    encryptor = Cipher(
+        algorithms.AES(key),
+        GCM(iv),
+        backend=_backend).encryptor()
+    return encryptor.update(plaintext) + encryptor.finalize() + encryptor.tag
diff --git a/omemo/omemo/state.py b/omemo/omemo/state.py
index 92887abe..fb201886 100644
--- a/omemo/omemo/state.py
+++ b/omemo/omemo/state.py
@@ -35,7 +35,7 @@ from axolotl.state.prekeybundle import PreKeyBundle
 from axolotl.util.keyhelper import KeyHelper
 from Crypto.Random import get_random_bytes
 
-from .aes_gcm import NoValidSessions, aes_decrypt, aes_encrypt
+from .aes_gcm import NoValidSessions, decrypt, encrypt
 from .liteaxolotlstore import (LiteAxolotlStore, DEFAULT_PREKEY_AMOUNT,
                                MIN_PREKEY_AMOUNT, SPK_CYCLE_TIME,
                                SPK_ARCHIVE_TIME)
@@ -210,7 +210,7 @@ class OmemoState:
                       ' sid => ' + str(sid))
             return
 
-        result = unicode(aes_decrypt(key, iv, payload))
+        result = unicode(decrypt(key, iv, payload))
 
         if self.own_jid == sender_jid:
             self.add_own_device(sid)
@@ -263,7 +263,7 @@ class OmemoState:
                 log.debug('Skipped own Device because Trust is: ' +
                           str(self.isTrusted(cipher)))
 
-        payload = aes_encrypt(key, iv, plaintext)
+        payload = encrypt(key, iv, plaintext)
 
         result = {'sid': self.own_device_id,
                   'keys': encrypted_keys,
-- 
GitLab