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

=== NOTE ===
1. This patch depends on the new Fedora package: python-yubico. If you
would like to help with the package review, please assign yourself here:
https://bugzilla.redhat.com/show_bug.cgi?id=1111334

2. This patch doesn't actually work and I could use some help. The call
to api.Command.otptoken_add() fails with:
ipa: ERROR: non-public: AttributeError: no context.rpcclient in thread
'MainThread'
Traceback (most recent call last):
  File "/usr/lib/python2.7/site-packages/ipalib/backend.py", line 129,
in execute
    result = self.Command[_name](*args, **options)
  File "/usr/lib/python2.7/site-packages/ipalib/frontend.py", line 439,
in __call__
    ret = self.run(*args, **options)
  File "/usr/lib/python2.7/site-packages/ipalib/frontend.py", line 1118,
in run
    return self.forward(*args, **options)
  File "/usr/lib/python2.7/site-packages/ipalib/plugins/otptoken.py",
line 471, in forward
    **options)
  File "/usr/lib/python2.7/site-packages/ipalib/frontend.py", line 439,
in __call__
    ret = self.run(*args, **options)
  File "/usr/lib/python2.7/site-packages/ipalib/frontend.py", line 755,
in run
    return self.forward(*args, **options)
  File "/usr/lib/python2.7/site-packages/ipalib/frontend.py", line 776,
in forward
    return self.Backend.rpcclient.forward(self.name, *args, **kw)
  File "/usr/lib/python2.7/site-packages/ipalib/rpc.py", line 874, in
forward
    command = getattr(self.conn, name)
  File "/usr/lib/python2.7/site-packages/ipalib/backend.py", line 97, in
__get_conn
    self.id, threading.currentThread().getName())
AttributeError: no context.rpcclient in thread 'MainThread'
ipa: ERROR: an internal error has occurred

From 75f1a74dd2476d61fc47e5ab184acaf84261fe54 Mon Sep 17 00:00:00 2001
From: Nathaniel McCallum <npmccal...@redhat.com>
Date: Thu, 19 Jun 2014 12:28:32 -0400
Subject: [PATCH] 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
---
 freeipa.spec.in            |  1 +
 ipalib/plugins/otptoken.py | 83 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 84 insertions(+)

diff --git a/freeipa.spec.in b/freeipa.spec.in
index b719b4a21fd2263a6fb58b3dffd4784868e630e9..4228c88f939a361a32da6f875c48db8adee3cdc1 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -158,6 +158,7 @@ Requires(pre): certmonger >= 0.65
 Requires(pre): 389-ds-base >= 1.3.2.11
 Requires: fontawesome-fonts
 Requires: open-sans-fonts
+Requires: python-yubico
 
 # With FreeIPA 3.3, package freeipa-server-selinux was obsoleted as the
 # entire SELinux policy is stored in the system policy
diff --git a/ipalib/plugins/otptoken.py b/ipalib/plugins/otptoken.py
index d834d582a16d95ab08c3f1fe1aef29160c77ae23..9746d2c96753111a05864b7151b12761553785a3 100644
--- a/ipalib/plugins/otptoken.py
+++ b/ipalib/plugins/otptoken.py
@@ -21,6 +21,7 @@ from ipalib.plugins.baseldap import DN, LDAPObject, LDAPAddMember, LDAPRemoveMem
 from ipalib.plugins.baseldap import LDAPCreate, LDAPDelete, LDAPUpdate, LDAPSearch, LDAPRetrieve
 from ipalib import api, Int, Str, Bool, Flag, Bytes, IntEnum, StrEnum, _, ngettext
 from ipalib.plugable import Registry
+from ipalib.frontend import Local
 from ipalib.errors import PasswordMismatch, ConversionError, LastMemberError, NotFound
 from ipalib.request import context
 import base64
@@ -28,6 +29,7 @@ import uuid
 import urllib
 import qrcode
 import os
+import yubico
 
 __doc__ = _("""
 OTP Tokens
@@ -383,3 +385,84 @@ class otptoken_remove_managedby(LDAPRemoveMember):
     __doc__ = _('Remove hosts that can manage this host.')
 
     member_attributes = ['managedby']
+
+@register()
+class otptoken_add_yubikey(Local):
+    __doc__ = _('Add a new YubiKey OTP token.')
+
+    takes_args = (
+        Str('ipatokenuniqueid?',
+            cli_name='id',
+            label=_('Unique ID'),
+            primary_key=True,
+        ),
+    )
+
+    takes_options = Local.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',
+        'ipatokenotpkey',
+        'ipatokenotpdigits'
+    )])
+
+    def write_yubikey(self, **kwargs):
+        assert len(kwargs['ipatokenotpkey']) == 20
+
+        # Open the YubiKey
+        yk = yubico.find_yubikey()
+
+        # If no slot is specified, find the first free slot.
+        slot = kwargs.get('slot', None)
+        if slot is None:
+            try:
+                used = yk.status().valid_configs()
+                slot = sorted({1, 2}.difference(used))[0]
+            except IndexError:
+                raise ValueError('No free YubiKey slot!')
+
+        # Write the config.
+        cfg = yk.init_config()
+        cfg.mode_oath_hotp(kwargs['ipatokenotpkey'], kwargs['ipatokenotpdigits'])
+        cfg.extended_flag('SERIAL_API_VISIBLE', True)
+        yk.write_config(cfg, slot=slot)
+
+        return {
+            'vendor': u'YubiCo',
+            'model': unicode(yk.model),
+            'serial': unicode(yk.serial())
+        }
+
+    def forward(self, *args, **kwargs):
+        yk = self.write_yubikey(**kwargs)
+
+        id = args[0]
+        if id is None:
+            id = unicode(uuid.uuid4())
+
+        options = {k: v for k, v in kwargs.items() if k in (
+            'description',
+            'ipatokenowner',
+            'ipatokendisabled',
+            'ipatokennotbefore',
+            'ipatokennotafter',
+            'ipatokenotpkey',
+            'ipatokenotpdigits'
+        )}
+
+        return self.api.Command.otptoken_add(id,
+            type=u'hotp',
+            ipatokenvendor=yk['vendor'],
+            ipatokenmodel=yk['model'],
+            ipatokenserial=yk['serial'],
+            ipatokenotpalgorithm=u'sha1',
+            ipatokenhotpcounter=0,
+            **options)
-- 
2.0.0

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to