diff options
| -rw-r--r-- | __init__.py | 151 | 
1 files changed, 115 insertions, 36 deletions
| diff --git a/__init__.py b/__init__.py index 6b12a73d..54ec783f 100644 --- a/__init__.py +++ b/__init__.py @@ -23,8 +23,16 @@ from leap.soledad.backends.leap_backend import (  ) -class KeyMissing(Exception): -    pass +class KeyDoesNotExist(Exception): +    """ +    Soledad attempted to find a key that does not exist locally. +    """ + + +class KeyAlreadyExists(Exception): +    """ +    Soledad attempted to create a key that already exists locally. +    """  #----------------------------------------------------------------------------- @@ -45,10 +53,17 @@ class Soledad(object):      # other configs      SECRET_LENGTH = 50 +    DEFAULT_CONF = { +        'gnupg_home': '%s/gnupg', +        'secret_path': '%s/secret.gpg', +        'local_db_path': '%s/soledad.u1db', +        'config_file': '%s/soledad.ini', +        'soledad_server_url': '', +    }      def __init__(self, user_email, prefix=None, gnupg_home=None,                   secret_path=None, local_db_path=None, -                 config_file=None, server_url=None, auth_token=None, +                 config_file=None, soledad_server_url=None, auth_token=None,                   initialize=True):          """          Bootstrap Soledad, initialize cryptographic material and open @@ -56,43 +71,72 @@ class Soledad(object):          """          self._user_email = user_email          self._auth_token = auth_token -        self._init_config(prefix, gnupg_home, secret_path, local_db_path, -                          config_file) -        # TODO: how to obtain server's URL? -        if server_url: -            self._init_client(server_url, token=auth_token) +        self._init_config( +            {'prefix': prefix, +             'gnupg_home': gnupg_home, +             'secret_path': secret_path, +             'local_db_path': local_db_path, +             'config_file': config_file, +             'soledad_server_url': soledad_server_url, +             } +        ) +        if self.soledad_server_url: +            self._client = SoledadClient(server_url, token=auth_token)          if initialize: -            self._init_dirs() -            self._init_crypto() -            self._init_db() - -    def _init_client(self, url, token=None): -        self._client = SoledadClient(server_url, token) - -    def _init_config(self, prefix, gnupg_home, secret_path, local_db_path, -                     config_file): -        # set default config -        self.prefix = prefix or os.environ['HOME'] + '/.config/leap/soledad' -        default_conf = { -            'gnupg_home': gnupg_home or '%s/gnupg', -            'secret_path': secret_path or '%s/secret.gpg', -            'local_db_path': local_db_path or '%s/soledad.u1db', -            'config_file': config_file or '%s/soledad.ini', -            'soledad_server_url': '', -        } +            self.bootstrap() + +    def _bootstrap(self): +        """ +        Bootstrap local Soledad instance. + +        There are 3 stages for Soledad Client bootstrap: + +            1. No key material has been generated, so we need to generate and +               upload to the server. + +            2. Key material has already been generated and uploaded to the +               server, but has not been downloaded to this device/installation +               yet. + +            3. Key material has already been generated and uploaded, and is +               also stored locally, so we just need to load it from disk. + +        This method decides which bootstrap stage has to be performed and +        performs it. +        """ +        self._init_dirs() +        self._gpg = GPGWrapper(gnupghome=self.gnupg_home) +        if not self._has_keys(): +            try: +                # stage 2 bootstrap +                self._retrieve_keys() +            except Exception: +            # stage 1 bootstrap +                self._init_keys() +                self._send_keys() +        # stage 3 bootstrap +        self._load_keys() +        self._init_db() + +    def _init_config(self, param_conf): +        """ +        Initialize configuration, with precedence order give by: instance +        parameters > config file > default values. +        """ +        self.prefix = param_conf['prefix'] or \ +            os.environ['HOME'] + '/.config/leap/soledad'          m = re.compile('.*%s.*') -        for key, default_value in default_conf.iteritems(): -            if m.match(default_value): -                val = default_value % self.prefix -            else: -                val = default_value +        for key, default_value in self.DEFAULT_CONF.iteritems(): +            val = param_conf[key] or default_value +            if m.match(val): +                val = val % self.prefix              setattr(self, key, val)          # get config from file          config = configparser.ConfigParser()          config.read(self.config_file)          if 'soledad-client' in config:              for key in default_conf: -                if key in config['soledad-client']: +                if key in config['soledad-client'] and not param_conf[key]:                      setattr(self, key, config['soledad-client'][key])      def _init_dirs(self): @@ -102,11 +146,11 @@ class Soledad(object):          if not os.path.isdir(self.prefix):              os.makedirs(self.prefix) -    def _init_crypto(self): +    def _init_keys(self):          """ -        Load/generate OpenPGP keypair and secret for symmetric encryption. +        Generate (if needed) and load OpenPGP keypair and secret for symmetric +        encryption.          """ -        self._gpg = GPGWrapper(gnupghome=self.gnupg_home)          # load/generate OpenPGP keypair          if not self._has_openpgp_keypair():              self._gen_openpgp_keypair() @@ -157,13 +201,18 @@ class Soledad(object):          # can we decrypt it?          fp = self._gpg.encrypted_to(content)['fingerprint']          if fp != self._fingerprint: -            raise KeyMissing("Key %s missing." % fp) +            raise KeyDoesNotExist("Secret for symmetric encryption is " +                                  "encrypted to key with fingerprint '%s' " +                                  "which we don't have." % fp)          return True      def _load_secret(self):          """          Load secret for symmetric encryption from local encrypted file.          """ +        if not self._has_secret(): +            raise KeyDoesNotExist("Tried to load key for symmetric " +                                  "encryption but it does not exist on disk.")          try:              with open(self.secret_path) as f:                  self._secret = str(self._gpg.decrypt(f.read())) @@ -175,6 +224,10 @@ class Soledad(object):          Generate a secret for symmetric encryption and store in a local          encrypted file.          """ +        if self._has_secret(): +            raise KeyAlreadyExists("Tried to generate secret for symmetric " +                                   "encryption but it already exists on " +                                   "disk.")          self._secret = ''.join(              random.choice(                  string.ascii_letters + @@ -204,6 +257,9 @@ class Soledad(object):          """          Generate an OpenPGP keypair for this user.          """ +        if self._has_openpgp_keypair(): +            raise KeyAlreadyExists("Tried to generate OpenPGP keypair but it " +                                   "already exists on disk.")          params = self._gpg.gen_key_input(              key_type='RSA',              key_length=4096, @@ -216,6 +272,9 @@ class Soledad(object):          """          Find fingerprint for this user's OpenPGP keypair.          """ +        if not self._has_openpgp_keypair(): +            raise KeyDoesNotExist("Tried to load OpenPGP keypair but it does " +                                  "not exist on disk.")          self._fingerprint = self._gpg.find_key_by_email(              self._user_email)['fingerprint'] @@ -227,6 +286,26 @@ class Soledad(object):          pass      #------------------------------------------------------------------------- +    # General crypto utility methods. +    #------------------------------------------------------------------------- + +    def _has_keys(self): +        return self._has_openpgp_keypair() and self._has_secret() + +    def _load_keys(self): +        self._load_openpgp_keypair() +        self._load_secret() + +    def _gen_keys(self): +        self._gen_openpgp_keypair() +        self._gen_secret() + +    def _retrieve_keys(self): +        h = hmac.new(self._user_email, 'user-keys').hexdigest() +        self._client._request_json('GET', ['user-keys', h]) +        # TODO: create corresponding error on server side + +    #-------------------------------------------------------------------------      # Data encryption and decryption      #------------------------------------------------------------------------- | 
