diff options
author | Nathaniel McCallum <npmccallum@redhat.com> | 2014-06-19 12:28:32 -0400 |
---|---|---|
committer | Martin Kosek <mkosek@redhat.com> | 2014-06-26 16:10:16 +0200 |
commit | 2767fb584a4240bf3742144fd6d362053b76dadd (patch) | |
tree | 04b0edbef3ab49e735df06524951ce940a258604 /ipalib/plugins/otptoken_yubikey.py | |
parent | 14b38b7704778b4000a7b1b31d78fbb6b45e647b (diff) | |
download | freeipa-2767fb584a4240bf3742144fd6d362053b76dadd.tar.gz freeipa-2767fb584a4240bf3742144fd6d362053b76dadd.tar.xz freeipa-2767fb584a4240bf3742144fd6d362053b76dadd.zip |
Add the otptoken-add-yubikey command
This command behaves almost exactly like otptoken-add except:
1. The new token data is written directly to a YubiKey
2. The vendor/model/serial fields are populated from the YubiKey
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
Diffstat (limited to 'ipalib/plugins/otptoken_yubikey.py')
-rw-r--r-- | ipalib/plugins/otptoken_yubikey.py | 139 |
1 files changed, 139 insertions, 0 deletions
diff --git a/ipalib/plugins/otptoken_yubikey.py b/ipalib/plugins/otptoken_yubikey.py new file mode 100644 index 000000000..e70ddb6e4 --- /dev/null +++ b/ipalib/plugins/otptoken_yubikey.py @@ -0,0 +1,139 @@ +# Authors: +# Nathaniel McCallum <npmccallum@redhat.com> +# +# Copyright (C) 2014 Red Hat +# see file 'COPYING' for use and warranty information +# +# 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/>. + +from ipalib import _, Str, IntEnum +from ipalib.errors import NotFound +from ipalib.plugable import Registry +from ipalib.frontend import Command +from ipalib.plugins.otptoken import otptoken + +import os + +import yubico + +__doc__ = _(""" +YubiKey Tokens +""") + _(""" +Manage YubiKey tokens. +""") + _(""" +This code is an extension to the otptoken plugin and provides support for +reading/writing YubiKey tokens directly. +""") + _(""" +EXAMPLES: +""") + _(""" + Add a new token: + ipa otptoken-add-yubikey --owner=jdoe --desc="My YubiKey" +""") + +register = Registry() + +@register() +class otptoken_add_yubikey(Command): + __doc__ = _('Add a new YubiKey OTP token.') + + takes_args = ( + Str('ipatokenuniqueid?', + cli_name='id', + label=_('Unique ID'), + primary_key=True, + ), + ) + + takes_options = Command.takes_options + ( + IntEnum('slot?', + cli_name='slot', + label=_('YubiKey slot'), + values=(1, 2), + ), + ) + tuple(x for x in otptoken.takes_params if x.name in ( + 'description', + 'ipatokenowner', + 'ipatokendisabled', + 'ipatokennotbefore', + 'ipatokennotafter', + 'ipatokenotpdigits' + )) + + has_output_params = Command.has_output_params + \ + tuple(x for x in otptoken.takes_params if x.name in ( + 'ipatokenvendor', + 'ipatokenmodel', + 'ipatokenserial', + )) + + def forward(self, *args, **kwargs): + # Open the YubiKey + try: + yk = yubico.find_yubikey() + except yubico.yubikey.YubiKeyError, e: + raise NotFound(reason=_('No YubiKey found')) + + 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, + **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'] + + del answer['value'] # Why does this cause an error if omitted? + del answer['summary'] # Why does this cause an error if omitted? + return answer |