class automountlocation_tofiles(MethodOverride): @classmethod def __NO_CLI_getter(cls): return (api.Command.get_plugin('automountlocation_show') is _fake_automountlocation_show) NO_CLI = classproperty(__NO_CLI_getter) def output_for_cli(self, textui, result, *keys, **options): maps = result['result']['maps'] keys = result['result']['keys'] orphanmaps = result['result']['orphanmaps'] orphankeys = result['result']['orphankeys'] textui.print_plain('/etc/auto.master:') for m in maps: if m['automountinformation'][0].startswith('-'): textui.print_plain( '%s\t%s' % ( m['automountkey'][0], m['automountinformation'][0] ) ) else: textui.print_plain( '%s\t/etc/%s' % ( m['automountkey'][0], m['automountinformation'][0] ) ) for m in maps: if m['automountinformation'][0].startswith('-'): continue info = m['automountinformation'][0] textui.print_plain('---------------------------') textui.print_plain('/etc/%s:' % info) for k in keys[info]: textui.print_plain( '%s\t%s' % ( k['automountkey'][0], k['automountinformation'][0] ) ) textui.print_plain('') textui.print_plain(_('maps not connected to /etc/auto.master:')) for m in orphanmaps: textui.print_plain('---------------------------') textui.print_plain('/etc/%s:' % m['automountmapname']) for k in orphankeys: if len(k) == 0: continue dn = DN(k[0]['dn']) if dn['automountmapname'] == m['automountmapname'][0]: textui.print_plain( '%s\t%s' % ( k[0]['automountkey'][0], k[0]['automountinformation'][0] ) )
class Plugin(ReadOnly): """ Base class for all plugins. """ version = '1' def __init__(self, api): assert api is not None self.__api = api self.__finalize_called = False self.__finalized = False self.__finalize_lock = threading.RLock() log_mgr.get_logger(self, True) @classmethod def __name_getter(cls): return cls.__name__ # you know nothing, pylint name = classproperty(__name_getter) @classmethod def __full_name_getter(cls): return '{}/{}'.format(cls.name, cls.version) full_name = classproperty(__full_name_getter) @classmethod def __bases_getter(cls): return cls.__bases__ bases = classproperty(__bases_getter) @classmethod def __doc_getter(cls): return cls.__doc__ doc = classproperty(__doc_getter) @classmethod def __summary_getter(cls): doc = cls.doc if not _(doc).msg: return u'<%s.%s>' % (cls.__module__, cls.__name__) else: return unicode(doc).split('\n\n', 1)[0].strip() summary = classproperty(__summary_getter) @property def api(self): """ Return `API` instance passed to `__init__()`. """ return self.__api # FIXME: for backward compatibility only @property def env(self): return self.__api.env # FIXME: for backward compatibility only @property def Backend(self): return self.__api.Backend # FIXME: for backward compatibility only @property def Command(self): return self.__api.Command def finalize(self): """ Finalize plugin initialization. This method calls `_on_finalize()` and locks the plugin object. Subclasses should not override this method. Custom finalization is done in `_on_finalize()`. """ with self.__finalize_lock: assert self.__finalized is False if self.__finalize_called: # No recursive calls! return self.__finalize_called = True self._on_finalize() self.__finalized = True if not self.__api.is_production_mode(): lock(self) def _on_finalize(self): """ Do custom finalization. This method is called from `finalize()`. Subclasses can override this method in order to add custom finalization. """ pass def ensure_finalized(self): """ Finalize plugin initialization if it has not yet been finalized. """ with self.__finalize_lock: if not self.__finalized: self.finalize() class finalize_attr(object): """ Create a stub object for plugin attribute that isn't set until the finalization of the plugin initialization. When the stub object is accessed, it calls `ensure_finalized()` to make sure the plugin initialization is finalized. The stub object is expected to be replaced with the actual attribute value during the finalization (preferably in `_on_finalize()`), otherwise an `AttributeError` is raised. This is used to implement on-demand finalization of plugin initialization. """ __slots__ = ('name', 'value') def __init__(self, name, value=None): self.name = name self.value = value def __get__(self, obj, cls): if obj is None or obj.api is None: return self.value obj.ensure_finalized() try: return getattr(obj, self.name) except RuntimeError: # If the actual attribute value is not set in _on_finalize(), # getattr() calls __get__() again, which leads to infinite # recursion. This can happen only if the plugin is written # badly, so advise the developer about that instead of giving # them a generic "maximum recursion depth exceeded" error. raise AttributeError( "attribute '%s' of plugin '%s' was not set in finalize()" % (self.name, obj.name)) def __repr__(self): """ Return 'module_name.class_name()' representation. This representation could be used to instantiate this Plugin instance given the appropriate environment. """ return '%s.%s()' % (self.__class__.__module__, self.__class__.__name__)
class vault_retrieve(ModVaultData): __doc__ = _('Retrieve a data from a vault.') takes_options = ( Str( 'out?', doc=_('File to store retrieved data'), ), Str( 'password?', cli_name='password', doc=_('Vault password'), ), Str( # TODO: use File parameter 'password_file?', cli_name='password_file', doc=_('File containing the vault password'), ), Bytes( 'private_key?', cli_name='private_key', doc=_('Vault private key'), ), Str( # TODO: use File parameter 'private_key_file?', cli_name='private_key_file', doc=_('File containing the vault private key'), ), ) has_output_params = ( Bytes( 'data', label=_('Data'), ), ) @classmethod def __NO_CLI_getter(cls): return (api.Command.get_plugin('vault_retrieve_internal') is _fake_vault_retrieve_internal) NO_CLI = classproperty(__NO_CLI_getter) @property def api_version(self): return self.api.Command.vault_retrieve_internal.api_version def get_args(self): for arg in self.api.Command.vault_retrieve_internal.args(): yield arg for arg in super(vault_retrieve, self).get_args(): yield arg def get_options(self): for option in self.api.Command.vault_retrieve_internal.options(): if option.name not in ('session_key', 'version'): yield option for option in super(vault_retrieve, self).get_options(): yield option def get_output_params(self): for param in self.api.Command.vault_retrieve_internal.output_params(): yield param for param in super(vault_retrieve, self).get_output_params(): yield param def _iter_output(self): return self.api.Command.vault_retrieve_internal.output() def _unwrap_response(self, algo, nonce, vault_data): cipher = Cipher(algo, modes.CBC(nonce), backend=default_backend()) # decrypt decryptor = cipher.decryptor() padded_data = decryptor.update(vault_data) padded_data += decryptor.finalize() # remove padding unpadder = PKCS7(algo.block_size).unpadder() json_vault_data = unpadder.update(padded_data) json_vault_data += unpadder.finalize() # load JSON return json.loads(json_vault_data.decode('utf-8')) def forward(self, *args, **options): output_file = options.get('out') password = options.get('password') password_file = options.get('password_file') private_key = options.get('private_key') private_key_file = options.get('private_key_file') # don't send these parameters to server if 'out' in options: del options['out'] if 'password' in options: del options['password'] if 'password_file' in options: del options['password_file'] if 'private_key' in options: del options['private_key'] if 'private_key_file' in options: del options['private_key_file'] if self.api.env.in_server: backend = self.api.Backend.ldap2 else: backend = self.api.Backend.rpcclient if not backend.isconnected(): backend.connect() # retrieve vault info vault = self.api.Command.vault_show(*args, **options)['result'] vault_type = vault['ipavaulttype'][0] # generate session key algo = self._generate_session_key() # send retrieval request to server response = self.internal(algo, *args, **options) # unwrap data with session key vault_data = self._unwrap_response( algo, response['result']['nonce'], response['result']['vault_data'] ) del algo data = base64.b64decode(vault_data[u'data'].encode('utf-8')) encrypted_key = None if 'encrypted_key' in vault_data: encrypted_key = base64.b64decode(vault_data[u'encrypted_key'] .encode('utf-8')) if vault_type == u'standard': pass elif vault_type == u'symmetric': salt = vault['ipavaultsalt'][0] # get encryption key from vault password if password and password_file: raise errors.MutuallyExclusiveError( reason=_('Password specified multiple times')) elif password: pass elif password_file: password = validated_read('password-file', password_file, encoding='utf-8') password = password.rstrip('\n') else: password = self.api.Backend.textui.prompt_password( 'Password', confirm=False) # generate encryption key from password encryption_key = generate_symmetric_key(password, salt) # decrypt data with encryption key data = decrypt(data, symmetric_key=encryption_key) elif vault_type == u'asymmetric': # get encryption key with vault private key if private_key and private_key_file: raise errors.MutuallyExclusiveError( reason=_('Private key specified multiple times')) elif private_key: pass elif private_key_file: private_key = validated_read('private-key-file', private_key_file, mode='rb') else: raise errors.ValidationError( name='private_key', error=_('Missing vault private key')) # decrypt encryption key with private key encryption_key = decrypt(encrypted_key, private_key=private_key) # decrypt data with encryption key data = decrypt(data, symmetric_key=encryption_key) else: raise errors.ValidationError( name='vault_type', error=_('Invalid vault type')) if output_file: with open(output_file, 'wb') as f: f.write(data) else: response['result'] = {'data': data} return response
class vault_archive(ModVaultData): __doc__ = _('Archive data into a vault.') takes_options = ( Bytes( 'data?', doc=_('Binary data to archive'), ), Str( # TODO: use File parameter 'in?', doc=_('File containing data to archive'), ), Str( 'password?', cli_name='password', doc=_('Vault password'), ), Str( # TODO: use File parameter 'password_file?', cli_name='password_file', doc=_('File containing the vault password'), ), Flag( 'override_password?', doc=_('Override existing password'), ), ) @classmethod def __NO_CLI_getter(cls): return (api.Command.get_plugin('vault_archive_internal') is _fake_vault_archive_internal) NO_CLI = classproperty(__NO_CLI_getter) @property def api_version(self): return self.api.Command.vault_archive_internal.api_version def get_args(self): for arg in self.api.Command.vault_archive_internal.args(): yield arg for arg in super(vault_archive, self).get_args(): yield arg def get_options(self): for option in self.api.Command.vault_archive_internal.options(): if option.name not in ('nonce', 'session_key', 'vault_data', 'version'): yield option for option in super(vault_archive, self).get_options(): yield option def get_output_params(self): for param in self.api.Command.vault_archive_internal.output_params(): yield param for param in super(vault_archive, self).get_output_params(): yield param def _iter_output(self): return self.api.Command.vault_archive_internal.output() def _wrap_data(self, algo, json_vault_data): """Encrypt data with wrapped session key and transport cert :param bytes algo: wrapping algorithm instance :param bytes json_vault_data: dumped vault data :return: """ nonce = os.urandom(algo.block_size // 8) # wrap vault_data with session key padder = PKCS7(algo.block_size).padder() padded_data = padder.update(json_vault_data) padded_data += padder.finalize() cipher = Cipher(algo, modes.CBC(nonce), backend=default_backend()) encryptor = cipher.encryptor() wrapped_vault_data = encryptor.update(padded_data) + encryptor.finalize() return nonce, wrapped_vault_data def forward(self, *args, **options): data = options.get('data') input_file = options.get('in') password = options.get('password') password_file = options.get('password_file') override_password = options.pop('override_password', False) # don't send these parameters to server if 'data' in options: del options['data'] if 'in' in options: del options['in'] if 'password' in options: del options['password'] if 'password_file' in options: del options['password_file'] # get data if data and input_file: raise errors.MutuallyExclusiveError( reason=_('Input data specified multiple times')) elif data: if len(data) > MAX_VAULT_DATA_SIZE: raise errors.ValidationError(name="data", error=_( "Size of data exceeds the limit. Current vault data size " "limit is %(limit)d B") % {'limit': MAX_VAULT_DATA_SIZE}) elif input_file: try: stat = os.stat(input_file) except OSError as exc: raise errors.ValidationError(name="in", error=_( "Cannot read file '%(filename)s': %(exc)s") % {'filename': input_file, 'exc': exc.args[1]}) if stat.st_size > MAX_VAULT_DATA_SIZE: raise errors.ValidationError(name="in", error=_( "Size of data exceeds the limit. Current vault data size " "limit is %(limit)d B") % {'limit': MAX_VAULT_DATA_SIZE}) data = validated_read('in', input_file, mode='rb') else: data = b'' if self.api.env.in_server: backend = self.api.Backend.ldap2 else: backend = self.api.Backend.rpcclient if not backend.isconnected(): backend.connect() # retrieve vault info vault = self.api.Command.vault_show(*args, **options)['result'] vault_type = vault['ipavaulttype'][0] if vault_type == u'standard': encrypted_key = None elif vault_type == u'symmetric': # get password if password and password_file: raise errors.MutuallyExclusiveError( reason=_('Password specified multiple times')) elif password: pass elif password_file: password = validated_read('password-file', password_file, encoding='utf-8') password = password.rstrip('\n') else: if override_password: password = self.api.Backend.textui.prompt_password( 'New password') else: password = self.api.Backend.textui.prompt_password( 'Password', confirm=False) if not override_password: # verify password by retrieving existing data opts = options.copy() opts['password'] = password try: self.api.Command.vault_retrieve(*args, **opts) except errors.NotFound: pass salt = vault['ipavaultsalt'][0] # generate encryption key from vault password encryption_key = generate_symmetric_key(password, salt) # encrypt data with encryption key data = encrypt(data, symmetric_key=encryption_key) encrypted_key = None elif vault_type == u'asymmetric': public_key = vault['ipavaultpublickey'][0] # generate encryption key encryption_key = base64.b64encode(os.urandom(32)) # encrypt data with encryption key data = encrypt(data, symmetric_key=encryption_key) # encrypt encryption key with public key encrypted_key = encrypt(encryption_key, public_key=public_key) else: raise errors.ValidationError( name='vault_type', error=_('Invalid vault type')) vault_data = { 'data': base64.b64encode(data).decode('utf-8') } if encrypted_key: vault_data[u'encrypted_key'] = base64.b64encode(encrypted_key)\ .decode('utf-8') json_vault_data = json.dumps(vault_data).encode('utf-8') # generate session key algo = self._generate_session_key() # wrap vault data nonce, wrapped_vault_data = self._wrap_data(algo, json_vault_data) options.update( nonce=nonce, vault_data=wrapped_vault_data ) return self.internal(algo, *args, **options)
class vault_mod(Local): __doc__ = _('Modify a vault.') takes_options = ( Flag( 'change_password?', doc=_('Change password'), ), Str( 'old_password?', cli_name='old_password', doc=_('Old vault password'), ), Str( # TODO: use File parameter 'old_password_file?', cli_name='old_password_file', doc=_('File containing the old vault password'), ), Str( 'new_password?', cli_name='new_password', doc=_('New vault password'), ), Str( # TODO: use File parameter 'new_password_file?', cli_name='new_password_file', doc=_('File containing the new vault password'), ), Bytes( 'private_key?', cli_name='private_key', doc=_('Old vault private key'), ), Str( # TODO: use File parameter 'private_key_file?', cli_name='private_key_file', doc=_('File containing the old vault private key'), ), Str( # TODO: use File parameter 'public_key_file?', cli_name='public_key_file', doc=_('File containing the new vault public key'), ), ) @classmethod def __NO_CLI_getter(cls): return (api.Command.get_plugin('vault_mod_internal') is _fake_vault_mod_internal) NO_CLI = classproperty(__NO_CLI_getter) @property def api_version(self): return self.api.Command.vault_mod_internal.api_version def get_args(self): for arg in self.api.Command.vault_mod_internal.args(): yield arg for arg in super(vault_mod, self).get_args(): yield arg def get_options(self): for option in self.api.Command.vault_mod_internal.options(): if option.name != 'version': yield option for option in super(vault_mod, self).get_options(): yield option def get_output_params(self): for param in self.api.Command.vault_mod_internal.output_params(): yield param for param in super(vault_mod, self).get_output_params(): yield param def _iter_output(self): return self.api.Command.vault_mod_internal.output() def forward(self, *args, **options): vault_type = options.pop('ipavaulttype', False) salt = options.pop('ipavaultsalt', False) change_password = options.pop('change_password', False) old_password = options.pop('old_password', None) old_password_file = options.pop('old_password_file', None) new_password = options.pop('new_password', None) new_password_file = options.pop('new_password_file', None) old_private_key = options.pop('private_key', None) old_private_key_file = options.pop('private_key_file', None) new_public_key = options.pop('ipavaultpublickey', None) new_public_key_file = options.pop('public_key_file', None) if self.api.env.in_server: backend = self.api.Backend.ldap2 else: backend = self.api.Backend.rpcclient if not backend.isconnected(): backend.connect() # determine the vault type based on parameters specified if vault_type: pass elif change_password or new_password or new_password_file or salt: vault_type = u'symmetric' elif new_public_key or new_public_key_file: vault_type = u'asymmetric' # if vault type is specified, retrieve existing secret if vault_type: opts = options.copy() opts.pop('description', None) opts['password'] = old_password opts['password_file'] = old_password_file opts['private_key'] = old_private_key opts['private_key_file'] = old_private_key_file response = self.api.Command.vault_retrieve(*args, **opts) data = response['result']['data'] opts = options.copy() # if vault type is specified, update crypto attributes if vault_type: opts['ipavaulttype'] = vault_type if vault_type == u'standard': opts['ipavaultsalt'] = None opts['ipavaultpublickey'] = None elif vault_type == u'symmetric': if salt: opts['ipavaultsalt'] = salt else: opts['ipavaultsalt'] = os.urandom(16) opts['ipavaultpublickey'] = None elif vault_type == u'asymmetric': # get new vault public key if new_public_key and new_public_key_file: raise errors.MutuallyExclusiveError( reason=_('New public key specified multiple times')) elif new_public_key: pass elif new_public_key_file: new_public_key = validated_read('public_key_file', new_public_key_file, mode='rb') else: raise errors.ValidationError( name='ipavaultpublickey', error=_('Missing new vault public key')) opts['ipavaultsalt'] = None opts['ipavaultpublickey'] = new_public_key response = self.api.Command.vault_mod_internal(*args, **opts) # if vault type is specified, rearchive existing secret if vault_type: opts = options.copy() opts.pop('description', None) opts['data'] = data opts['password'] = new_password opts['password_file'] = new_password_file opts['override_password'] = True self.api.Command.vault_archive(*args, **opts) return response
class vault_add(Local): __doc__ = _('Create a new vault.') takes_options = ( Str( 'password?', cli_name='password', doc=_('Vault password'), ), Str( # TODO: use File parameter 'password_file?', cli_name='password_file', doc=_('File containing the vault password'), ), Str( # TODO: use File parameter 'public_key_file?', cli_name='public_key_file', doc=_('File containing the vault public key'), ), ) @classmethod def __NO_CLI_getter(cls): return (api.Command.get_plugin('vault_add_internal') is _fake_vault_add_internal) NO_CLI = classproperty(__NO_CLI_getter) @property def api_version(self): return self.api.Command.vault_add_internal.api_version def get_args(self): for arg in self.api.Command.vault_add_internal.args(): yield arg for arg in super(vault_add, self).get_args(): yield arg def get_options(self): for option in self.api.Command.vault_add_internal.options(): if option.name not in ('ipavaultsalt', 'version'): yield option for option in super(vault_add, self).get_options(): yield option def get_output_params(self): for param in self.api.Command.vault_add_internal.output_params(): yield param for param in super(vault_add, self).get_output_params(): yield param def _iter_output(self): return self.api.Command.vault_add_internal.output() def forward(self, *args, **options): vault_type = options.get('ipavaulttype') if vault_type is None: internal_cmd = self.api.Command.vault_add_internal vault_type = internal_cmd.params.ipavaulttype.default password = options.get('password') password_file = options.get('password_file') public_key = options.get('ipavaultpublickey') public_key_file = options.get('public_key_file') # don't send these parameters to server if 'password' in options: del options['password'] if 'password_file' in options: del options['password_file'] if 'public_key_file' in options: del options['public_key_file'] if vault_type != u'symmetric' and (password or password_file): raise errors.MutuallyExclusiveError( reason=_('Password can be specified only for ' 'symmetric vault') ) if vault_type != u'asymmetric' and (public_key or public_key_file): raise errors.MutuallyExclusiveError( reason=_('Public key can be specified only for ' 'asymmetric vault') ) if self.api.env.in_server: backend = self.api.Backend.ldap2 else: backend = self.api.Backend.rpcclient if not backend.isconnected(): backend.connect() if vault_type == u'standard': pass elif vault_type == u'symmetric': # get password if password and password_file: raise errors.MutuallyExclusiveError( reason=_('Password specified multiple times')) elif password: pass elif password_file: password = validated_read('password-file', password_file, encoding='utf-8') password = password.rstrip('\n') else: password = self.api.Backend.textui.prompt_password( 'New password') # generate vault salt options['ipavaultsalt'] = os.urandom(16) elif vault_type == u'asymmetric': # get new vault public key if public_key and public_key_file: raise errors.MutuallyExclusiveError( reason=_('Public key specified multiple times')) elif public_key: pass elif public_key_file: public_key = validated_read('public-key-file', public_key_file, mode='rb') # store vault public key options['ipavaultpublickey'] = public_key else: raise errors.ValidationError( name='ipavaultpublickey', error=_('Missing vault public key')) # validate public key and prevent users from accidentally # sending a private key to the server. try: load_pem_public_key( data=public_key, backend=default_backend() ) except ValueError as e: raise errors.ValidationError( name='ipavaultpublickey', error=_('Invalid or unsupported vault public key: %s') % e, ) # create vault response = self.api.Command.vault_add_internal(*args, **options) # prepare parameters for archival opts = options.copy() if 'description' in opts: del opts['description'] if 'ipavaulttype' in opts: del opts['ipavaulttype'] if vault_type == u'symmetric': opts['password'] = password del opts['ipavaultsalt'] elif vault_type == u'asymmetric': del opts['ipavaultpublickey'] # archive blank data self.api.Command.vault_archive(*args, **opts) return response
class CommandOverride(Command): def __init__(self, api): super(CommandOverride, self).__init__(api) next_class = self.__get_next() self.next = next_class(api) @classmethod def __get_next(cls): return api.get_plugin_next(cls) @classmethod def __doc_getter(cls): return cls.__get_next().doc doc = classproperty(__doc_getter) @classmethod def __summary_getter(cls): return cls.__get_next().summary summary = classproperty(__summary_getter) @classmethod def __NO_CLI_getter(cls): return cls.__get_next().NO_CLI NO_CLI = classproperty(__NO_CLI_getter) @classmethod def __topic_getter(cls): return cls.__get_next().topic topic = classproperty(__topic_getter) @property def forwarded_name(self): return self.next.forwarded_name @property def api_version(self): return self.next.api_version def _on_finalize(self): self.next.finalize() super(CommandOverride, self)._on_finalize() def get_args(self): for arg in self.next.args(): yield arg for arg in super(CommandOverride, self).get_args(): yield arg def get_options(self): for option in self.next.options(): yield option for option in super(CommandOverride, self).get_options(): if option.name not in ('all', 'raw', 'version'): yield option def get_output_params(self): for output_param in self.next.output_params(): yield output_param for output_param in super(CommandOverride, self).get_output_params(): yield output_param def _iter_output(self): return self.next.output()
class otptoken_add_yubikey(Command): __doc__ = _('Add a new YubiKey OTP token.') takes_options = (IntEnum( 'slot?', cli_name='slot', label=_('YubiKey slot'), values=(1, 2), ), ) has_output_params = takes_options @classmethod def __NO_CLI_getter(cls): return api.Command.get_plugin('otptoken_add') is _fake_otptoken_add NO_CLI = classproperty(__NO_CLI_getter) @property def api_version(self): return self.api.Command.otptoken_add.api_version def get_args(self): for arg in self.api.Command.otptoken_add.args(): yield arg for arg in super(otptoken_add_yubikey, self).get_args(): yield arg def get_options(self): for option in self.api.Command.otptoken_add.options(): if option.name not in ('type', 'ipatokenvendor', 'ipatokenmodel', 'ipatokenserial', 'ipatokenotpalgorithm', 'ipatokenhotpcounter', 'ipatokenotpkey', 'ipatokentotpclockoffset', 'ipatokentotptimestep', 'no_qrcode', 'qrcode', 'version'): yield option for option in super(otptoken_add_yubikey, self).get_options(): yield option def get_output_params(self): for param in self.api.Command.otptoken_add.output_params(): yield param for param in super(otptoken_add_yubikey, self).get_output_params(): yield param def _iter_output(self): return self.api.Command.otptoken_add.output() def forward(self, *args, **kwargs): # Open the YubiKey try: yk = yubico.find_yubikey() except usb.core.USBError as e: raise NotFound(reason="No YubiKey found: %s" % e.strerror) except yubico.yubikey.YubiKeyError as e: raise NotFound(reason=e.reason) assert yk.version_num() >= (2, 1) # If no slot is specified, find the first free slot. if kwargs.get('slot', None) is None: try: used = yk.status().valid_configs() kwargs['slot'] = sorted({1, 2}.difference(used))[0] except IndexError: raise NotFound(reason=_('No free YubiKey slot!')) # Create the key (NOTE: the length is fixed). key = os.urandom(20) # Write the config. cfg = yk.init_config() cfg.mode_oath_hotp(key, kwargs['ipatokenotpdigits']) cfg.extended_flag('SERIAL_API_VISIBLE', True) yk.write_config(cfg, slot=kwargs['slot']) # Filter the options we want to pass. options = { k: v for k, v in kwargs.items() if k in ( 'version', 'description', 'ipatokenowner', 'ipatokendisabled', 'ipatokennotbefore', 'ipatokennotafter', 'ipatokenotpdigits', ) } # Run the command. answer = self.Backend.rpcclient.forward( 'otptoken_add', *args, type=u'hotp', ipatokenvendor=u'YubiCo', ipatokenmodel=unicode(yk.model), ipatokenserial=unicode(yk.serial()), ipatokenotpalgorithm=u'sha1', ipatokenhotpcounter=0, ipatokenotpkey=key, no_qrcode=True, **options) # Suppress values we don't want to return. for k in (u'uri', u'ipatokenotpkey'): if k in answer.get('result', {}): del answer['result'][k] # Return which slot was used for writing. answer.get('result', {})['slot'] = kwargs['slot'] return answer
class vault_retrieve(Local): __doc__ = _('Retrieve a data from a vault.') takes_options = ( Str( 'out?', doc=_('File to store retrieved data'), ), Str( 'password?', cli_name='password', doc=_('Vault password'), ), Str( # TODO: use File parameter 'password_file?', cli_name='password_file', doc=_('File containing the vault password'), ), Bytes( 'private_key?', cli_name='private_key', doc=_('Vault private key'), ), Str( # TODO: use File parameter 'private_key_file?', cli_name='private_key_file', doc=_('File containing the vault private key'), ), ) has_output_params = ( Bytes( 'data', label=_('Data'), ), ) @classmethod def __NO_CLI_getter(cls): return (api.Command.get_plugin('vault_retrieve_internal') is _fake_vault_retrieve_internal) NO_CLI = classproperty(__NO_CLI_getter) @property def api_version(self): return self.api.Command.vault_retrieve_internal.api_version def get_args(self): for arg in self.api.Command.vault_retrieve_internal.args(): yield arg for arg in super(vault_retrieve, self).get_args(): yield arg def get_options(self): for option in self.api.Command.vault_retrieve_internal.options(): if option.name not in ('session_key', 'version'): yield option for option in super(vault_retrieve, self).get_options(): yield option def get_output_params(self): for param in self.api.Command.vault_retrieve_internal.output_params(): yield param for param in super(vault_retrieve, self).get_output_params(): yield param def _iter_output(self): return self.api.Command.vault_retrieve_internal.output() def forward(self, *args, **options): output_file = options.get('out') password = options.get('password') password_file = options.get('password_file') private_key = options.get('private_key') private_key_file = options.get('private_key_file') # don't send these parameters to server if 'out' in options: del options['out'] if 'password' in options: del options['password'] if 'password_file' in options: del options['password_file'] if 'private_key' in options: del options['private_key'] if 'private_key_file' in options: del options['private_key_file'] if self.api.env.in_server: backend = self.api.Backend.ldap2 else: backend = self.api.Backend.rpcclient if not backend.isconnected(): backend.connect() # retrieve vault info vault = self.api.Command.vault_show(*args, **options)['result'] vault_type = vault['ipavaulttype'][0] # initialize NSS database nss.nss_init(api.env.nss_dir) # retrieve transport certificate config = self.api.Command.vaultconfig_show()['result'] transport_cert_der = config['transport_cert'] nss_transport_cert = nss.Certificate(transport_cert_der) # generate session key mechanism = nss.CKM_DES3_CBC_PAD slot = nss.get_best_slot(mechanism) key_length = slot.get_best_key_length(mechanism) session_key = slot.key_gen(mechanism, None, key_length) # wrap session key with transport certificate # pylint: disable=no-member public_key = nss_transport_cert.subject_public_key_info.public_key # pylint: enable=no-member wrapped_session_key = nss.pub_wrap_sym_key(mechanism, public_key, session_key) # send retrieval request to server options['session_key'] = wrapped_session_key.data response = self.api.Command.vault_retrieve_internal(*args, **options) result = response['result'] nonce = result['nonce'] # unwrap data with session key wrapped_vault_data = result['vault_data'] iv_si = nss.SecItem(nonce) iv_param = nss.param_from_iv(mechanism, iv_si) decoding_ctx = nss.create_context_by_sym_key(mechanism, nss.CKA_DECRYPT, session_key, iv_param) json_vault_data = decoding_ctx.cipher_op(wrapped_vault_data)\ + decoding_ctx.digest_final() vault_data = json.loads(json_vault_data.decode('utf-8')) data = base64.b64decode(vault_data[u'data'].encode('utf-8')) encrypted_key = None if 'encrypted_key' in vault_data: encrypted_key = base64.b64decode(vault_data[u'encrypted_key'] .encode('utf-8')) if vault_type == u'standard': pass elif vault_type == u'symmetric': salt = vault['ipavaultsalt'][0] # get encryption key from vault password if password and password_file: raise errors.MutuallyExclusiveError( reason=_('Password specified multiple times')) elif password: pass elif password_file: password = validated_read('password-file', password_file, encoding='utf-8') password = password.rstrip('\n') else: password = get_existing_password() # generate encryption key from password encryption_key = generate_symmetric_key(password, salt) # decrypt data with encryption key data = decrypt(data, symmetric_key=encryption_key) elif vault_type == u'asymmetric': # get encryption key with vault private key if private_key and private_key_file: raise errors.MutuallyExclusiveError( reason=_('Private key specified multiple times')) elif private_key: pass elif private_key_file: private_key = validated_read('private-key-file', private_key_file, mode='rb') else: raise errors.ValidationError( name='private_key', error=_('Missing vault private key')) # decrypt encryption key with private key encryption_key = decrypt(encrypted_key, private_key=private_key) # decrypt data with encryption key data = decrypt(data, symmetric_key=encryption_key) else: raise errors.ValidationError( name='vault_type', error=_('Invalid vault type')) if output_file: with open(output_file, 'w') as f: f.write(data) else: response['result'] = {'data': data} return response
class vault_archive(Local): __doc__ = _('Archive data into a vault.') takes_options = ( Bytes( 'data?', doc=_('Binary data to archive'), ), Str( # TODO: use File parameter 'in?', doc=_('File containing data to archive'), ), Str( 'password?', cli_name='password', doc=_('Vault password'), ), Str( # TODO: use File parameter 'password_file?', cli_name='password_file', doc=_('File containing the vault password'), ), Flag( 'override_password?', doc=_('Override existing password'), ), ) @classmethod def __NO_CLI_getter(cls): return (api.Command.get_plugin('vault_archive_internal') is _fake_vault_archive_internal) NO_CLI = classproperty(__NO_CLI_getter) @property def api_version(self): return self.api.Command.vault_archive_internal.api_version def get_args(self): for arg in self.api.Command.vault_archive_internal.args(): yield arg for arg in super(vault_archive, self).get_args(): yield arg def get_options(self): for option in self.api.Command.vault_archive_internal.options(): if option.name not in ('nonce', 'session_key', 'vault_data', 'version'): yield option for option in super(vault_archive, self).get_options(): yield option def get_output_params(self): for param in self.api.Command.vault_archive_internal.output_params(): yield param for param in super(vault_archive, self).get_output_params(): yield param def _iter_output(self): return self.api.Command.vault_archive_internal.output() def forward(self, *args, **options): data = options.get('data') input_file = options.get('in') password = options.get('password') password_file = options.get('password_file') override_password = options.pop('override_password', False) # don't send these parameters to server if 'data' in options: del options['data'] if 'in' in options: del options['in'] if 'password' in options: del options['password'] if 'password_file' in options: del options['password_file'] # get data if data and input_file: raise errors.MutuallyExclusiveError( reason=_('Input data specified multiple times')) elif data: if len(data) > MAX_VAULT_DATA_SIZE: raise errors.ValidationError(name="data", error=_( "Size of data exceeds the limit. Current vault data size " "limit is %(limit)d B") % {'limit': MAX_VAULT_DATA_SIZE}) elif input_file: try: stat = os.stat(input_file) except OSError as exc: raise errors.ValidationError(name="in", error=_( "Cannot read file '%(filename)s': %(exc)s") % {'filename': input_file, 'exc': exc.args[1]}) if stat.st_size > MAX_VAULT_DATA_SIZE: raise errors.ValidationError(name="in", error=_( "Size of data exceeds the limit. Current vault data size " "limit is %(limit)d B") % {'limit': MAX_VAULT_DATA_SIZE}) data = validated_read('in', input_file, mode='rb') else: data = '' if self.api.env.in_server: backend = self.api.Backend.ldap2 else: backend = self.api.Backend.rpcclient if not backend.isconnected(): backend.connect() # retrieve vault info vault = self.api.Command.vault_show(*args, **options)['result'] vault_type = vault['ipavaulttype'][0] if vault_type == u'standard': encrypted_key = None elif vault_type == u'symmetric': # get password if password and password_file: raise errors.MutuallyExclusiveError( reason=_('Password specified multiple times')) elif password: pass elif password_file: password = validated_read('password-file', password_file, encoding='utf-8') password = password.rstrip('\n') else: if override_password: password = get_new_password() else: password = get_existing_password() if not override_password: # verify password by retrieving existing data opts = options.copy() opts['password'] = password try: self.api.Command.vault_retrieve(*args, **opts) except errors.NotFound: pass salt = vault['ipavaultsalt'][0] # generate encryption key from vault password encryption_key = generate_symmetric_key(password, salt) # encrypt data with encryption key data = encrypt(data, symmetric_key=encryption_key) encrypted_key = None elif vault_type == u'asymmetric': public_key = vault['ipavaultpublickey'][0].encode('utf-8') # generate encryption key encryption_key = base64.b64encode(os.urandom(32)) # encrypt data with encryption key data = encrypt(data, symmetric_key=encryption_key) # encrypt encryption key with public key encrypted_key = encrypt(encryption_key, public_key=public_key) else: raise errors.ValidationError( name='vault_type', error=_('Invalid vault type')) # initialize NSS database nss.nss_init(api.env.nss_dir) # retrieve transport certificate config = self.api.Command.vaultconfig_show()['result'] transport_cert_der = config['transport_cert'] nss_transport_cert = nss.Certificate(transport_cert_der) # generate session key mechanism = nss.CKM_DES3_CBC_PAD slot = nss.get_best_slot(mechanism) key_length = slot.get_best_key_length(mechanism) session_key = slot.key_gen(mechanism, None, key_length) # wrap session key with transport certificate # pylint: disable=no-member public_key = nss_transport_cert.subject_public_key_info.public_key # pylint: enable=no-member wrapped_session_key = nss.pub_wrap_sym_key(mechanism, public_key, session_key) options['session_key'] = wrapped_session_key.data nonce_length = nss.get_iv_length(mechanism) nonce = nss.generate_random(nonce_length) options['nonce'] = nonce vault_data = {} vault_data[u'data'] = base64.b64encode(data).decode('utf-8') if encrypted_key: vault_data[u'encrypted_key'] = base64.b64encode(encrypted_key)\ .decode('utf-8') json_vault_data = json.dumps(vault_data) # wrap vault_data with session key iv_si = nss.SecItem(nonce) iv_param = nss.param_from_iv(mechanism, iv_si) encoding_ctx = nss.create_context_by_sym_key(mechanism, nss.CKA_ENCRYPT, session_key, iv_param) wrapped_vault_data = encoding_ctx.cipher_op(json_vault_data)\ + encoding_ctx.digest_final() options['vault_data'] = wrapped_vault_data return self.api.Command.vault_archive_internal(*args, **options)