summaryrefslogtreecommitdiff
path: root/src/leap/soledad/client/_secrets/__init__.py
blob: b6c81cda62342dc27588df2f15e28a08e9bd29c1 (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
# -*- coding: utf-8 -*-
# _secrets/__init__.py
# Copyright (C) 2016 LEAP
#
# This program 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.
#
# This program 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 this program. If not, see <http://www.gnu.org/licenses/>.

import os
import scrypt

from leap.soledad.common.log import getLogger

from leap.soledad.client._secrets.storage import SecretsStorage
from leap.soledad.client._secrets.crypto import SecretsCrypto
from leap.soledad.client._secrets.util import emit, UserDataMixin


logger = getLogger(__name__)


class Secrets(UserDataMixin):

    lengths = {
        'remote_secret': 512,  # remote_secret is used to encrypt remote data.
        'local_salt': 64,      # local_salt is used in conjunction with
        'local_secret': 448,   # local_secret to derive a local_key for storage
    }

    def __init__(self, soledad):
        self._soledad = soledad
        self._secrets = {}
        self.crypto = SecretsCrypto(soledad)
        self.storage = SecretsStorage(soledad)
        self._bootstrap()

    #
    # bootstrap
    #

    def _bootstrap(self):

        # attempt to load secrets from local storage
        encrypted = self.storage.load_local()
        if encrypted:
            self._secrets = self.crypto.decrypt(encrypted)
            # maybe update the format of storage of local secret.
            if encrypted['version'] < self.crypto.VERSION:
                self.store_secrets()
            return

        # no secret was found in local storage, so this is a first run of
        # soledad for this user in this device. It is mandatory that we check
        # if there's a secret stored in server.
        encrypted = self.storage.load_remote()
        if encrypted:
            self._secrets = self.crypto.decrypt(encrypted)
            self.store_secrets()
            return

        # we have *not* found a secret neither in local nor in remote storage,
        # so we have to generate a new one, and then store it.
        self._secrets = self._generate()
        self.store_secrets()

    #
    # generation
    #

    @emit('creating')
    def _generate(self):
        logger.info("generating new set of secrets...")
        secrets = {}
        for name, length in self.lengths.iteritems():
            secret = os.urandom(length)
            secrets[name] = secret
        logger.info("new set of secrets successfully generated")
        return secrets

    #
    # crypto
    #

    def store_secrets(self):
        # TODO: we have to improve the logic here, as we want to make sure that
        # whatever is stored locally should only be used after remote storage
        # is successful. Otherwise, this soledad could start encrypting with a
        # secret while another soledad in another device could start encrypting
        # with another secret, which would lead to decryption failures during
        # sync.
        encrypted = self.crypto.encrypt(self._secrets)
        self.storage.save_local(encrypted)
        self.storage.save_remote(encrypted)

    #
    # secrets
    #

    @property
    def remote_secret(self):
        return self._secrets.get('remote_secret')

    @property
    def local_salt(self):
        return self._secrets.get('local_salt')

    @property
    def local_secret(self):
        return self._secrets.get('local_secret')

    @property
    def local_key(self):
        # local storage key is scrypt-derived from `local_secret` and
        # `local_salt` above
        secret = scrypt.hash(
            password=self.local_secret,
            salt=self.local_salt,
            buflen=32,  # we need a key with 256 bits (32 bytes)
        )
        return secret