summaryrefslogtreecommitdiff
path: root/src/leap/soledad
diff options
context:
space:
mode:
Diffstat (limited to 'src/leap/soledad')
-rw-r--r--src/leap/soledad/__init__.py12
-rw-r--r--src/leap/soledad/auth.py4
-rw-r--r--src/leap/soledad/backends/leap_backend.py199
-rw-r--r--src/leap/soledad/crypto.py64
-rw-r--r--src/leap/soledad/server.py1
-rw-r--r--src/leap/soledad/tests/__init__.py18
-rw-r--r--src/leap/soledad/tests/test_crypto.py75
-rw-r--r--src/leap/soledad/tests/test_leap_backend.py64
-rw-r--r--src/leap/soledad/tests/test_soledad.py3
-rw-r--r--src/leap/soledad/tests/test_sqlcipher.py10
-rw-r--r--src/leap/soledad/tests/u1db_tests/test_https.py3
11 files changed, 312 insertions, 141 deletions
diff --git a/src/leap/soledad/__init__.py b/src/leap/soledad/__init__.py
index c70fee91..b051a80c 100644
--- a/src/leap/soledad/__init__.py
+++ b/src/leap/soledad/__init__.py
@@ -374,7 +374,6 @@ class Soledad(object):
This method will also replace the secret in the crypto object.
"""
self._secret_id = secret_id
- self._crypto.secret = self._get_storage_secret()
def _load_secrets(self):
"""
@@ -407,7 +406,7 @@ class Soledad(object):
content = json.loads(f.read())
self._secrets = content[self.STORAGE_SECRETS_KEY]
# choose first secret if no secret_id was given
- if self._secret_id == None:
+ if self._secret_id is None:
self._set_secret_id(self._secrets.items()[0][0])
# check secret is isncrypted
if not self._crypto.is_encrypted_sym(
@@ -952,7 +951,7 @@ class Soledad(object):
# set uuid
self._uuid = data[self.UUID_KEY]
# choose first secret to use
- self._set_secret_id(self._secrets.items()[0][0])
+ self._set_secret_id(data[self.STORAGE_SECRETS_KEY].items()[0][0])
#
# Setters/getters
@@ -984,6 +983,10 @@ class Soledad(object):
_get_server_url,
doc='The URL of the Soledad server.')
+ storage_secret = property(
+ _get_storage_secret,
+ doc='The secret used for symmetric encryption.')
+
#-----------------------------------------------------------------------------
# Monkey patching u1db to be able to provide a custom SSL cert
@@ -1004,8 +1007,7 @@ class VerifiedHTTPSConnection(httplib.HTTPSConnection):
self.sock = ssl.wrap_socket(sock,
ca_certs=SOLEDAD_CERT,
cert_reqs=ssl.CERT_REQUIRED)
- # TODO: enable this when the certificate is fixed
- #match_hostname(self.sock.getpeercert(), self.host)
+ match_hostname(self.sock.getpeercert(), self.host)
old__VerifiedHTTPSConnection = http_client._VerifiedHTTPSConnection
diff --git a/src/leap/soledad/auth.py b/src/leap/soledad/auth.py
index 1d8f1a42..562a8263 100644
--- a/src/leap/soledad/auth.py
+++ b/src/leap/soledad/auth.py
@@ -44,7 +44,6 @@ class TokenBasedAuth(object):
"""
self._creds = {'token': (uuid, token)}
-
def _sign_request(self, method, url_query, params):
"""
Return an authorization header to be included in the HTTP request, in
@@ -67,4 +66,5 @@ class TokenBasedAuth(object):
auth = '%s:%s' % (uuid, token)
return [('Authorization', 'Token %s' % auth.encode('base64')[:-1])]
else:
- return HTTPClientBase._sign_request(self, method, url_query, params)
+ return HTTPClientBase._sign_request(
+ self, method, url_query, params)
diff --git a/src/leap/soledad/backends/leap_backend.py b/src/leap/soledad/backends/leap_backend.py
index 2585379a..8fa662e9 100644
--- a/src/leap/soledad/backends/leap_backend.py
+++ b/src/leap/soledad/backends/leap_backend.py
@@ -25,6 +25,9 @@ try:
import simplejson as json
except ImportError:
import json # noqa
+import hashlib
+import hmac
+import binascii
from u1db import Document
@@ -33,10 +36,16 @@ from u1db.errors import BrokenSyncStream
from u1db.remote.http_target import HTTPSyncTarget
+from leap.common.crypto import (
+ EncryptionMethods,
+ encrypt_sym,
+ decrypt_sym,
+)
from leap.common.keymanager import KeyManager
from leap.common.check import leap_assert
from leap.soledad.auth import TokenBasedAuth
+
#
# Exceptions
#
@@ -48,10 +57,25 @@ class DocumentNotEncrypted(Exception):
pass
-class UnknownEncryptionSchemes(Exception):
+class UnknownEncryptionScheme(Exception):
"""
Raised when trying to decrypt from unknown encryption schemes.
"""
+ pass
+
+
+class UnknownMacMethod(Exception):
+ """
+ Raised when trying to authenticate document's content with unknown MAC
+ mehtod.
+ """
+ pass
+
+
+class WrongMac(Exception):
+ """
+ Raised when failing to authenticate document's contents based on MAC.
+ """
#
@@ -68,96 +92,164 @@ class EncryptionSchemes(object):
PUBKEY = 'pubkey'
+class MacMethods(object):
+ """
+ Representation of MAC methods used to authenticate document's contents.
+ """
+
+ HMAC = 'hmac'
+
+
#
# Crypto utilities for a LeapDocument.
#
ENC_JSON_KEY = '_enc_json'
ENC_SCHEME_KEY = '_enc_scheme'
+ENC_METHOD_KEY = '_enc_method'
+ENC_IV_KEY = '_enc_iv'
MAC_KEY = '_mac'
+MAC_METHOD_KEY = '_mac_method'
-def encrypt_doc_json(crypto, doc_id, doc_json):
+def mac_doc(crypto, doc_id, doc_rev, ciphertext, mac_method):
"""
- Return a valid JSON string containing the C{doc} content encrypted to
- a symmetric key and the encryption scheme.
+ Calculate a MAC for C{doc} using C{ciphertext}.
- The returned JSON string is the serialization of the following dictionary:
+ Current MAC method used is HMAC, with the following parameters:
+
+ * key: sha256(storage_secret, doc_id)
+ * msg: doc_id + doc_rev + ciphertext
+ * digestmod: sha256
+
+ @param crypto: A SoledadCryto instance used to perform the encryption.
+ @type crypto: leap.soledad.crypto.SoledadCrypto
+ @param doc_id: The id of the document.
+ @type doc_id: str
+ @param doc_rev: The revision of the document.
+ @type doc_rev: str
+ @param ciphertext: The content of the document.
+ @type ciphertext: str
+ @param mac_method: The MAC method to use.
+ @type mac_method: str
+
+ @return: The calculated MAC.
+ @rtype: str
+ """
+ if mac_method == MacMethods.HMAC:
+ return hmac.new(
+ crypto.doc_mac_key(doc_id),
+ str(doc_id) + str(doc_rev) + ciphertext,
+ hashlib.sha256).digest()
+ # raise if we do not know how to handle this MAC method
+ raise UnknownMacMethod('Unknown MAC method: %s.' % mac_method)
+
+
+def encrypt_doc(crypto, doc):
+ """
+ Encrypt C{doc}'s content.
+
+ Encrypt doc's contents using AES-256 CTR mode and return a valid JSON
+ string representing the following:
{
- '_enc_json': encrypt_sym(doc_content),
- '_enc_scheme': 'symkey',
- '_mac': <mac> [Not implemented yet]
+ ENC_JSON_KEY: '<encrypted doc JSON string>',
+ ENC_SCHEME_KEY: 'symkey',
+ ENC_METHOD_KEY: EncryptionMethods.AES_256_CTR,
+ ENC_IV_KEY: '<the initial value used to encrypt>',
+ MAC_KEY: '<mac>'
+ MAC_METHOD_KEY: 'hmac'
}
- @param crypto: A SoledadCryto instance to perform the encryption.
+ @param crypto: A SoledadCryto instance used to perform the encryption.
@type crypto: leap.soledad.crypto.SoledadCrypto
- @param doc_id: The unique id of the document.
- @type doc_id: str
- @param doc_json: The JSON serialization of the document's contents.
- @type doc_json: str
+ @param doc: The document with contents to be encrypted.
+ @type doc: LeapDocument
- @return: The JSON serialization representing the encrypted content.
+ @return: The JSON serialization of the dict representing the encrypted
+ content.
@rtype: str
"""
- ciphertext = crypto.encrypt_sym(
- doc_json,
- crypto.passphrase_hash(doc_id))
- if not crypto.is_encrypted_sym(ciphertext):
- raise DocumentNotEncrypted('Failed encrypting document.')
+ leap_assert(doc.is_tombstone() is False)
+ # encrypt content using AES-256 CTR mode
+ iv, ciphertext = encrypt_sym(
+ doc.get_json(),
+ crypto.doc_passphrase(doc.doc_id),
+ method=EncryptionMethods.AES_256_CTR)
+ # Return a representation for the encrypted content. In the following, we
+ # convert binary data to hexadecimal representation so the JSON
+ # serialization does not complain about what it tries to serialize.
+ hex_ciphertext = binascii.b2a_hex(ciphertext)
return json.dumps({
- ENC_JSON_KEY: ciphertext,
+ ENC_JSON_KEY: hex_ciphertext,
ENC_SCHEME_KEY: EncryptionSchemes.SYMKEY,
+ ENC_METHOD_KEY: EncryptionMethods.AES_256_CTR,
+ ENC_IV_KEY: iv,
+ MAC_KEY: binascii.b2a_hex(mac_doc( # store the mac as hex.
+ crypto, doc.doc_id, doc.rev,
+ ciphertext,
+ MacMethods.HMAC)),
+ MAC_METHOD_KEY: MacMethods.HMAC,
})
-def decrypt_doc_json(crypto, doc_id, doc_json):
+def decrypt_doc(crypto, doc):
"""
- Return a JSON serialization of the decrypted content contained in
- C{encrypted_json}.
+ Decrypt C{doc}'s content.
+
+ Return the JSON string representation of the document's decrypted content.
- The C{encrypted_json} parameter is the JSON serialization of the
- following dictionary:
+ The content of the document should have the following structure:
{
- ENC_JSON_KEY: enc_blob,
- ENC_SCHEME_KEY: enc_scheme,
+ ENC_JSON_KEY: '<enc_blob>',
+ ENC_SCHEME_KEY: '<enc_scheme>',
+ ENC_METHOD_KEY: '<enc_method>',
+ ENC_IV_KEY: '<initial value used to encrypt>', # (optional)
+ MAC_KEY: '<mac>'
+ MAC_METHOD_KEY: 'hmac'
}
C{enc_blob} is the encryption of the JSON serialization of the document's
content. For now Soledad just deals with documents whose C{enc_scheme} is
- EncryptionSchemes.SYMKEY.
+ EncryptionSchemes.SYMKEY and C{enc_method} is
+ EncryptionMethods.AES_256_CTR.
@param crypto: A SoledadCryto instance to perform the encryption.
@type crypto: leap.soledad.crypto.SoledadCrypto
- @param doc_id: The unique id of the document.
- @type doc_id: str
- @param doc_json: The JSON serialization representation of the encrypted
- document's contents.
- @type doc_json: str
+ @param doc: The document to be decrypted.
+ @type doc: LeapDocument
@return: The JSON serialization of the decrypted content.
@rtype: str
"""
- leap_assert(isinstance(doc_id, str), 'Document id is not a string.')
- leap_assert(doc_id != '', 'Received empty document id.')
- leap_assert(isinstance(doc_json, str), 'Document JSON is not a string.')
- leap_assert(doc_json != '', 'Received empty document JSON.')
- content = json.loads(doc_json)
- ciphertext = content[ENC_JSON_KEY]
- enc_scheme = content[ENC_SCHEME_KEY]
+ leap_assert(doc.is_tombstone() is False)
+ leap_assert(ENC_JSON_KEY in doc.content)
+ leap_assert(ENC_SCHEME_KEY in doc.content)
+ leap_assert(ENC_METHOD_KEY in doc.content)
+ leap_assert(MAC_KEY in doc.content)
+ leap_assert(MAC_METHOD_KEY in doc.content)
+ # verify MAC
+ ciphertext = binascii.a2b_hex( # content is stored as hex.
+ doc.content[ENC_JSON_KEY])
+ mac = mac_doc(
+ crypto, doc.doc_id, doc.rev,
+ ciphertext,
+ doc.content[MAC_METHOD_KEY])
+ if binascii.a2b_hex(doc.content[MAC_KEY]) != mac: # mac is stored as hex.
+ raise WrongMac('Could not authenticate document\'s contents.')
+ # decrypt doc's content
+ enc_scheme = doc.content[ENC_SCHEME_KEY]
plainjson = None
if enc_scheme == EncryptionSchemes.SYMKEY:
- if not crypto.is_encrypted_sym(ciphertext):
- raise DocumentNotEncrypted(
- 'Unable to identify document encryption for incoming '
- 'document, although it is marked as being encrypted with a '
- 'symmetric key.')
- plainjson = crypto.decrypt_sym(
+ leap_assert(ENC_IV_KEY in doc.content)
+ plainjson = decrypt_sym(
ciphertext,
- crypto.passphrase_hash(doc_id))
+ crypto.doc_passphrase(doc.doc_id),
+ method=doc.content[ENC_METHOD_KEY],
+ iv=doc.content[ENC_IV_KEY])
else:
- raise UnknownEncryptionSchemes(enc_scheme)
+ raise UnknownEncryptionScheme(enc_scheme)
return plainjson
@@ -354,9 +446,7 @@ class LeapSyncTarget(HTTPSyncTarget, TokenBasedAuth):
if doc.content and ENC_SCHEME_KEY in doc.content:
if doc.content[ENC_SCHEME_KEY] == \
EncryptionSchemes.SYMKEY:
- doc.set_json(
- decrypt_doc_json(
- self._crypto, doc.doc_id, entry['content']))
+ doc.set_json(decrypt_doc(self._crypto, doc))
#-------------------------------------------------------------
# end of symmetric decryption
#-------------------------------------------------------------
@@ -433,15 +523,14 @@ class LeapSyncTarget(HTTPSyncTarget, TokenBasedAuth):
#-------------------------------------------------------------
# symmetric encryption of document's contents
#-------------------------------------------------------------
- enc_json = doc.get_json()
+ doc_json = doc.get_json()
if not doc.is_tombstone():
- enc_json = encrypt_doc_json(
- self._crypto, doc.doc_id, doc.get_json())
+ doc_json = encrypt_doc(self._crypto, doc)
#-------------------------------------------------------------
# end of symmetric encryption
#-------------------------------------------------------------
size += prepare(id=doc.doc_id, rev=doc.rev,
- content=enc_json,
+ content=doc_json,
gen=gen, trans_id=trans_id)
entries.append('\r\n]')
size += len(entries[-1])
diff --git a/src/leap/soledad/crypto.py b/src/leap/soledad/crypto.py
index 605380ec..d0e2c720 100644
--- a/src/leap/soledad/crypto.py
+++ b/src/leap/soledad/crypto.py
@@ -21,7 +21,8 @@ Cryptographic utilities for Soledad.
"""
-from hashlib import sha256
+import hmac
+import hashlib
from leap.common.keymanager import openpgp
@@ -38,6 +39,8 @@ class SoledadCrypto(object):
General cryptographic functionality.
"""
+ MAC_KEY_LENGTH = 64
+
def __init__(self, soledad):
"""
Initialize the crypto object.
@@ -47,7 +50,6 @@ class SoledadCrypto(object):
"""
self._soledad = soledad
self._pgp = openpgp.OpenPGPScheme(self._soledad)
- self._secret = None
def encrypt_sym(self, data, passphrase):
"""
@@ -98,33 +100,61 @@ class SoledadCrypto(object):
"""
return openpgp.is_encrypted_sym(data)
- def passphrase_hash(self, suffix):
+ def doc_passphrase(self, doc_id):
"""
- Generate a passphrase for symmetric encryption.
+ Generate a passphrase for symmetric encryption of document's contents.
- The password is derived from the secret for symmetric encryption and
- a C{suffix} that is appended to the secret prior to hashing.
+ The password is derived using HMAC having sha256 as underlying hash
+ function. The key used for HMAC is Soledad's storage secret stripped
+ from the first MAC_KEY_LENGTH characters. The HMAC message is
+ C{doc_id}.
- @param suffix: Will be appended to the symmetric key before hashing.
- @type suffix: str
+ @param doc_id: The id of the document that will be encrypted using
+ this passphrase.
+ @type doc_id: str
- @return: the passphrase
+ @return: The passphrase.
@rtype: str
+
@raise NoSymmetricSecret: if no symmetric secret was supplied.
"""
- if self._secret is None:
+ if self.secret is None:
raise NoSymmetricSecret()
- return sha256('%s%s' % (self._secret, suffix)).hexdigest()
+ return hmac.new(
+ self.secret[self.MAC_KEY_LENGTH:],
+ doc_id,
+ hashlib.sha256).digest()
+
+ def doc_mac_key(self, doc_id):
+ """
+ Generate a key for calculating a MAC for a document whose id is
+ C{doc_id}.
+
+ The key is derived using HMAC having sha256 as underlying hash
+ function. The key used for HMAC is the first MAC_KEY_LENGTH characters
+ of Soledad's storage secret. The HMAC message is C{doc_id}.
+
+ @param doc_id: The id of the document.
+ @type doc_id: str
+
+ @return: The key.
+ @rtype: str
+
+ @raise NoSymmetricSecret: if no symmetric secret was supplied.
+ """
+ if self.secret is None:
+ raise NoSymmetricSecret()
+ return hmac.new(
+ self.secret[:self.MAC_KEY_LENGTH],
+ doc_id,
+ hashlib.sha256).digest()
#
# secret setters/getters
#
def _get_secret(self):
- return self._secret
-
- def _set_secret(self, secret):
- self._secret = secret
+ return self._soledad.storage_secret
- secret = property(_get_secret, _set_secret,
- doc='The key used for symmetric encryption')
+ secret = property(
+ _get_secret, doc='The secret used for symmetric encryption')
diff --git a/src/leap/soledad/server.py b/src/leap/soledad/server.py
index e7b55a3e..7aa253a3 100644
--- a/src/leap/soledad/server.py
+++ b/src/leap/soledad/server.py
@@ -187,7 +187,6 @@ class SoledadAuthMiddleware(object):
return not environ.get(self.PATH_INFO_KEY).startswith('/shared/')
-
#-----------------------------------------------------------------------------
# Soledad WSGI application
#-----------------------------------------------------------------------------
diff --git a/src/leap/soledad/tests/__init__.py b/src/leap/soledad/tests/__init__.py
index 6787aa9d..79ee69c4 100644
--- a/src/leap/soledad/tests/__init__.py
+++ b/src/leap/soledad/tests/__init__.py
@@ -10,7 +10,7 @@ from leap.soledad import Soledad
from leap.soledad.crypto import SoledadCrypto
from leap.soledad.backends.leap_backend import (
LeapDocument,
- decrypt_doc_json,
+ decrypt_doc,
ENC_SCHEME_KEY,
)
from leap.common.testing.basetest import BaseLeapTest
@@ -23,7 +23,7 @@ from leap.common.testing.basetest import BaseLeapTest
class BaseSoledadTest(BaseLeapTest):
"""
- Instantiates GPG and Soledad for usage in tests.
+ Instantiates Soledad for usage in tests.
"""
def setUp(self):
@@ -44,7 +44,8 @@ class BaseSoledadTest(BaseLeapTest):
self._db2.close()
self._soledad.close()
- def _soledad_instance(self, user='leap@leap.se', passphrase='123', prefix='',
+ def _soledad_instance(self, user='leap@leap.se', passphrase='123',
+ prefix='',
secrets_path=Soledad.STORAGE_SECRETS_FILE_NAME,
local_db_path='/soledad.u1db', server_url='',
cert_file=None):
@@ -69,15 +70,16 @@ class BaseSoledadTest(BaseLeapTest):
server_url=server_url, # Soledad will fail if not given an url.
cert_file=cert_file)
- def assertGetEncryptedDoc(self, db, doc_id, doc_rev, content, has_conflicts):
- """Assert that the document in the database looks correct."""
+ def assertGetEncryptedDoc(
+ self, db, doc_id, doc_rev, content, has_conflicts):
+ """
+ Assert that the document in the database looks correct.
+ """
exp_doc = self.make_document(doc_id, doc_rev, content,
has_conflicts=has_conflicts)
doc = db.get_doc(doc_id)
if ENC_SCHEME_KEY in doc.content:
- doc.set_json(
- decrypt_doc_json(
- self._soledad._crypto, doc.doc_id, doc.get_json()))
+ doc.set_json(decrypt_doc(self._soledad._crypto, doc))
self.assertEqual(exp_doc.doc_id, doc.doc_id)
self.assertEqual(exp_doc.rev, doc.rev)
self.assertEqual(exp_doc.has_conflicts, doc.has_conflicts)
diff --git a/src/leap/soledad/tests/test_crypto.py b/src/leap/soledad/tests/test_crypto.py
index 61c5f5b0..9a219bd0 100644
--- a/src/leap/soledad/tests/test_crypto.py
+++ b/src/leap/soledad/tests/test_crypto.py
@@ -31,13 +31,16 @@ except ImportError:
from leap.soledad.backends.leap_backend import (
LeapDocument,
- encrypt_doc_json,
- decrypt_doc_json,
+ encrypt_doc,
+ decrypt_doc,
EncryptionSchemes,
LeapSyncTarget,
ENC_JSON_KEY,
ENC_SCHEME_KEY,
+ MAC_METHOD_KEY,
MAC_KEY,
+ UnknownMacMethod,
+ WrongMac,
)
from leap.soledad.backends.couch import CouchDatabase
from leap.soledad import KeyAlreadyExists, Soledad
@@ -66,16 +69,21 @@ class EncryptedSyncTestCase(BaseSoledadTest):
"""
Test encrypting and decrypting documents.
"""
+ simpledoc = {'key': 'val'}
doc1 = LeapDocument(doc_id='id')
- doc1.content = {'key': 'val'}
- enc_json = encrypt_doc_json(
- self._soledad._crypto, doc1.doc_id, doc1.get_json())
- plain_json = decrypt_doc_json(
- self._soledad._crypto, doc1.doc_id, enc_json)
- doc2 = LeapDocument(doc_id=doc1.doc_id, json=plain_json)
- res1 = doc1.get_json()
- res2 = doc2.get_json()
- self.assertEqual(res1, res2, 'incorrect document encryption')
+ doc1.content = simpledoc
+ # encrypt doc
+ doc1.set_json(encrypt_doc(self._soledad._crypto, doc1))
+ # assert content is different and includes keys
+ self.assertNotEqual(
+ simpledoc, doc1.content,
+ 'incorrect document encryption')
+ self.assertTrue(ENC_JSON_KEY in doc1.content)
+ self.assertTrue(ENC_SCHEME_KEY in doc1.content)
+ # decrypt doc
+ doc1.set_json(decrypt_doc(self._soledad._crypto, doc1))
+ self.assertEqual(
+ simpledoc, doc1.content, 'incorrect document encryption')
def test_encrypt_sym(self):
"""
@@ -84,9 +92,7 @@ class EncryptedSyncTestCase(BaseSoledadTest):
doc1 = LeapDocument()
doc1.content = {'key': 'val'}
enc_json = json.loads(
- encrypt_doc_json(
- self._soledad._crypto,
- doc1.doc_id, doc1.get_json()))[ENC_JSON_KEY]
+ encrypt_doc(self._soledad._crypto, doc1))[ENC_JSON_KEY]
self.assertEqual(
True,
self._soledad._crypto.is_encrypted_sym(enc_json),
@@ -161,7 +167,7 @@ class EncryptedSyncTestCase(BaseSoledadTest):
# # create and encrypt a doc to insert directly in couchdb
# doc = LeapDocument('doc-id')
# doc.set_json(
-# encrypt_doc_json(
+# encrypt_doc(
# self._soledad._crypto, 'doc-id', json.dumps(simple_doc)))
# db.put_doc(doc)
# # setup credentials for access to soledad server
@@ -241,3 +247,42 @@ class CryptoMethodsTestCase(BaseSoledadTest):
sol = self._soledad_instance(user='user@leap.se', prefix='/3')
self.assertTrue(sol._has_secret(), "Should have a secret at "
"this point")
+
+
+class MacAuthTestCase(BaseSoledadTest):
+
+ def test_decrypt_with_wrong_mac_raises(self):
+ """
+ Trying to decrypt a document with wrong MAC should raise.
+ """
+ simpledoc = {'key': 'val'}
+ doc = LeapDocument(doc_id='id')
+ doc.content = simpledoc
+ # encrypt doc
+ doc.set_json(encrypt_doc(self._soledad._crypto, doc))
+ self.assertTrue(MAC_KEY in doc.content)
+ self.assertTrue(MAC_METHOD_KEY in doc.content)
+ # mess with MAC
+ doc.content[MAC_KEY] = 'wrongmac'
+ # try to decrypt doc
+ self.assertRaises(
+ WrongMac,
+ decrypt_doc, self._soledad._crypto, doc)
+
+ def test_decrypt_with_unknown_mac_method_raises(self):
+ """
+ Trying to decrypt a document with unknown MAC method should raise.
+ """
+ simpledoc = {'key': 'val'}
+ doc = LeapDocument(doc_id='id')
+ doc.content = simpledoc
+ # encrypt doc
+ doc.set_json(encrypt_doc(self._soledad._crypto, doc))
+ self.assertTrue(MAC_KEY in doc.content)
+ self.assertTrue(MAC_METHOD_KEY in doc.content)
+ # mess with MAC method
+ doc.content[MAC_METHOD_KEY] = 'mymac'
+ # try to decrypt doc
+ self.assertRaises(
+ UnknownMacMethod,
+ decrypt_doc, self._soledad._crypto, doc)
diff --git a/src/leap/soledad/tests/test_leap_backend.py b/src/leap/soledad/tests/test_leap_backend.py
index dbebadb5..8afae6f6 100644
--- a/src/leap/soledad/tests/test_leap_backend.py
+++ b/src/leap/soledad/tests/test_leap_backend.py
@@ -106,7 +106,8 @@ def make_token_http_database_for_test(test, replica_uid):
auth.TokenBasedAuth.set_token_credentials(self, uuid, token)
def _sign_request(self, method, url_query, params):
- return auth.TokenBasedAuth._sign_request(self, method, url_query, params)
+ return auth.TokenBasedAuth._sign_request(
+ self, method, url_query, params)
http_db = _HTTPDatabaseWithToken(test.getURL('test'))
http_db.set_token_credentials('user-uuid', 'auth-token')
@@ -162,7 +163,8 @@ class TestLeapClientBase(test_http_client.TestHTTPClientBase):
auth.TokenBasedAuth.set_token_credentials(self, uuid, token)
def _sign_request(self, method, url_query, params):
- return auth.TokenBasedAuth._sign_request(self, method, url_query, params)
+ return auth.TokenBasedAuth._sign_request(
+ self, method, url_query, params)
return _HTTPClientWithToken(self.getURL('dbase'), **kwds)
@@ -185,7 +187,8 @@ class TestLeapClientBase(test_http_client.TestHTTPClientBase):
pass
def app(self, environ, start_response):
- res = test_http_client.TestHTTPClientBase.app(self, environ, start_response)
+ res = test_http_client.TestHTTPClientBase.app(
+ self, environ, start_response)
if res is not None:
return res
# mime solead application here.
@@ -195,13 +198,13 @@ class TestLeapClientBase(test_http_client.TestHTTPClientBase):
start_response("401 Unauthorized",
[('Content-Type', 'application/json')])
return [json.dumps({"error": "unauthorized",
- "message": e.message})]
+ "message": e.message})]
scheme, encoded = auth.split(None, 1)
if scheme.lower() != 'token':
start_response("401 Unauthorized",
[('Content-Type', 'application/json')])
return [json.dumps({"error": "unauthorized",
- "message": e.message})]
+ "message": e.message})]
uuid, token = encoded.decode('base64').split(':', 1)
if uuid != 'user-uuid' and token != 'auth-token':
return unauth_err("Incorrect address or token.")
@@ -228,7 +231,6 @@ class TestLeapClientBase(test_http_client.TestHTTPClientBase):
['/dbase/doc/token', 'user-uuid', 'auth-token'], json.loads(res))
-
#-----------------------------------------------------------------------------
# The following tests come from `u1db.tests.test_document`.
#-----------------------------------------------------------------------------
@@ -284,10 +286,9 @@ class TestLeapParsingSyncStream(
"""
Test adapted to use encrypted content.
"""
- doc = leap_backend.LeapDocument('i')
+ doc = leap_backend.LeapDocument('i', rev='r')
doc.content = {}
- enc_json = leap_backend.encrypt_doc_json(
- self._soledad._crypto, doc.doc_id, doc.get_json())
+ enc_json = leap_backend.encrypt_doc(self._soledad._crypto, doc)
tgt = leap_backend.LeapSyncTarget(
"http://foo/foo", crypto=self._soledad._crypto)
@@ -367,6 +368,7 @@ def oauth_leap_sync_target(test, path):
tests.token1.key, tests.token1.secret)
return st
+
def token_leap_sync_target(test, path):
st = leap_sync_target(test, path)
st.set_token_credentials('user-uuid', 'auth-token')
@@ -374,7 +376,7 @@ def token_leap_sync_target(test, path):
class TestLeapSyncTarget(
- test_remote_sync_target.TestRemoteSyncTargets, BaseSoledadTest):
+ test_remote_sync_target.TestRemoteSyncTargets, BaseSoledadTest):
scenarios = [
('http', {'make_app_with_state': make_soledad_app,
@@ -383,9 +385,10 @@ class TestLeapSyncTarget(
('oauth_http', {'make_app_with_state': make_oauth_http_app,
'make_document_for_test': make_leap_document_for_test,
'sync_target': oauth_leap_sync_target}),
- ('token_soledad', {'make_app_with_state': make_token_soledad_app,
- 'make_document_for_test': make_leap_document_for_test,
- 'sync_target': token_leap_sync_target}),
+ ('token_soledad',
+ {'make_app_with_state': make_token_soledad_app,
+ 'make_document_for_test': make_leap_document_for_test,
+ 'sync_target': token_leap_sync_target}),
]
def test_sync_exchange_send(self):
@@ -523,10 +526,11 @@ class TestLeapSyncTargetHttpsSupport(test_https.TestHttpSyncTargetHttpsSupport,
BaseSoledadTest):
scenarios = [
- ('token_soledad_https', {'server_def': test_https.https_server_def,
- 'make_app_with_state': make_token_soledad_app,
- 'make_document_for_test': make_leap_document_for_test,
- 'sync_target': token_leap_https_sync_target}),
+ ('token_soledad_https',
+ {'server_def': test_https.https_server_def,
+ 'make_app_with_state': make_token_soledad_app,
+ 'make_document_for_test': make_leap_document_for_test,
+ 'sync_target': token_leap_https_sync_target}),
]
def setUp(self):
@@ -568,6 +572,7 @@ class TestLeapSyncTargetHttpsSupport(test_https.TestHttpSyncTargetHttpsSupport,
http_client.CertificateError, remote_target.record_sync_info,
'other-id', 2, 'T-id')
+
#-----------------------------------------------------------------------------
# The following tests come from `u1db.tests.test_http_database`.
#-----------------------------------------------------------------------------
@@ -585,7 +590,8 @@ class _HTTPDatabase(http_database.HTTPDatabase, auth.TokenBasedAuth):
self, method, url_query, params)
-class TestHTTPDatabaseWithCreds(test_http_database.TestHTTPDatabaseCtrWithCreds):
+class TestHTTPDatabaseWithCreds(
+ test_http_database.TestHTTPDatabaseCtrWithCreds):
def test_get_sync_target_inherits_token_credentials(self):
# this test was from TestDatabaseSimpleOperations but we put it here
@@ -595,7 +601,6 @@ class TestHTTPDatabaseWithCreds(test_http_database.TestHTTPDatabaseCtrWithCreds)
st = self.db.get_sync_target()
self.assertEqual(self.db._creds, st._creds)
-
def test_ctr_with_creds(self):
db1 = _HTTPDatabase('http://dbs/db', creds={'token': {
'uuid': 'user-uuid',
@@ -658,7 +663,6 @@ class LeapDatabaseSyncTargetTests(
(self.other_changes, new_gen, last_trans_id))
self.assertEqual(10, self.st.get_sync_info('replica')[3])
-
def test_sync_exchange_push_many(self):
"""
Test sync exchange.
@@ -666,9 +670,10 @@ class LeapDatabaseSyncTargetTests(
This test was adapted to decrypt remote content before assert.
"""
docs_by_gen = [
- (self.make_document('doc-id', 'replica:1', tests.simple_doc), 10, 'T-1'),
- (self.make_document('doc-id2', 'replica:1', tests.nested_doc), 11,
- 'T-2')]
+ (self.make_document(
+ 'doc-id', 'replica:1', tests.simple_doc), 10, 'T-1'),
+ (self.make_document(
+ 'doc-id2', 'replica:1', tests.nested_doc), 11, 'T-2')]
new_gen, trans_id = self.st.sync_exchange(
docs_by_gen, 'replica', last_known_generation=0,
last_known_trans_id=None, return_doc_cb=self.receive_doc)
@@ -682,7 +687,6 @@ class LeapDatabaseSyncTargetTests(
(self.other_changes, new_gen, trans_id))
self.assertEqual(11, self.st.get_sync_info('replica')[3])
-
def test_sync_exchange_returns_many_new_docs(self):
"""
Test sync exchange.
@@ -766,10 +770,10 @@ class TestLeapDbSync(test_sync.TestDbSync, BaseSoledadTest):
self.assertEqual(1, len(changes))
self.assertEqual(doc2.doc_id, changes[0][0])
self.assertEqual(1, gen - local_gen_before_sync)
- self.assertGetEncryptedDoc(self.db2, doc1.doc_id, doc1.rev, tests.simple_doc,
- False)
- self.assertGetEncryptedDoc(self.db, doc2.doc_id, doc2.rev, tests.nested_doc,
- False)
+ self.assertGetEncryptedDoc(
+ self.db2, doc1.doc_id, doc1.rev, tests.simple_doc, False)
+ self.assertGetEncryptedDoc(
+ self.db, doc2.doc_id, doc2.rev, tests.nested_doc, False)
def test_db_sync_autocreate(self):
"""
@@ -785,8 +789,8 @@ class TestLeapDbSync(test_sync.TestDbSync, BaseSoledadTest):
gen, _, changes = db3.whats_changed()
self.assertEqual(1, len(changes))
self.assertEqual(doc1.doc_id, changes[0][0])
- self.assertGetEncryptedDoc(db3, doc1.doc_id, doc1.rev, tests.simple_doc,
- False)
+ self.assertGetEncryptedDoc(
+ db3, doc1.doc_id, doc1.rev, tests.simple_doc, False)
t_gen, _ = self.db._get_replica_gen_and_trans_id('test3.db')
s_gen, _ = db3._get_replica_gen_and_trans_id('test1')
self.assertEqual(1, t_gen)
diff --git a/src/leap/soledad/tests/test_soledad.py b/src/leap/soledad/tests/test_soledad.py
index 49358ab6..6a4261c0 100644
--- a/src/leap/soledad/tests/test_soledad.py
+++ b/src/leap/soledad/tests/test_soledad.py
@@ -69,7 +69,8 @@ class AuxMethodsTestCase(BaseSoledadTest):
secrets_path=None, local_db_path=None,
server_url='', cert_file=None) # otherwise Soledad will fail.
self.assertEquals(
- os.path.join(sol.DEFAULT_PREFIX, Soledad.STORAGE_SECRETS_FILE_NAME),
+ os.path.join(
+ sol.DEFAULT_PREFIX, Soledad.STORAGE_SECRETS_FILE_NAME),
sol.secrets_path)
self.assertEquals(
os.path.join(sol.DEFAULT_PREFIX, 'soledad.u1db'),
diff --git a/src/leap/soledad/tests/test_sqlcipher.py b/src/leap/soledad/tests/test_sqlcipher.py
index c4282c0f..60261111 100644
--- a/src/leap/soledad/tests/test_sqlcipher.py
+++ b/src/leap/soledad/tests/test_sqlcipher.py
@@ -52,10 +52,9 @@ from leap.soledad.backends.sqlcipher import open as u1db_open
from leap.soledad.backends.leap_backend import (
LeapDocument,
EncryptionSchemes,
- decrypt_doc_json,
+ decrypt_doc,
ENC_JSON_KEY,
ENC_SCHEME_KEY,
- MAC_KEY,
)
@@ -634,9 +633,7 @@ class SQLCipherDatabaseSyncTests(
self.sync(self.db2, db3)
doc3 = db3.get_doc('the-doc')
if ENC_SCHEME_KEY in doc3.content:
- doc3.set_json(
- decrypt_doc_json(
- self._soledad._crypto, doc3.doc_id, doc3.get_json()))
+ doc3.set_json(decrypt_doc(self._soledad._crypto, doc3))
self.assertEqual(doc4.get_json(), doc3.get_json())
self.assertFalse(doc3.has_conflicts)
@@ -715,7 +712,8 @@ class SQLCipherSyncTargetTests(
sever-side.
"""
docs_by_gen = [
- (self.make_document('doc-id', 'replica:1', tests.simple_doc), 10, 'T-1'),
+ (self.make_document(
+ 'doc-id', 'replica:1', tests.simple_doc), 10, 'T-1'),
(self.make_document('doc-id2', 'replica:1', tests.nested_doc), 11,
'T-2')]
new_gen, trans_id = self.st.sync_exchange(
diff --git a/src/leap/soledad/tests/u1db_tests/test_https.py b/src/leap/soledad/tests/u1db_tests/test_https.py
index b4b14722..62180f8c 100644
--- a/src/leap/soledad/tests/u1db_tests/test_https.py
+++ b/src/leap/soledad/tests/u1db_tests/test_https.py
@@ -74,7 +74,8 @@ class TestHttpSyncTargetHttpsSupport(tests.TestCaseWithServer):
# class with one that will do HTTPS independent of the platform. In
# order to maintain the compatibility with u1db default tests, we undo
# that replacement here.
- http_client._VerifiedHTTPSConnection = soledad.old__VerifiedHTTPSConnection
+ http_client._VerifiedHTTPSConnection = \
+ soledad.old__VerifiedHTTPSConnection
super(TestHttpSyncTargetHttpsSupport, self).setUp()
def getSyncTarget(self, host, path=None):