2009-01-23

Yubikey and sv_dvorak

I recently received a yubikey which I immediately started to play with.



To get it to work with a pam authentication module I needed its id. As instructed I tried logging in at the administration site but it kept saying my key was invalid. After some head-scratching I realized why: the yubikey acts as a keyboard and "hits" the keys in the password string. The problem is, I don't use qwerty, so the key was not outputting the characters it thought it did.

So I ended up writing a patch for YubiPAM. It is a bit hackish and I should really have used some sort of dictionary, but at least it works ;)

yubipam-1.0.4-sv_dvorak.patch

--- src/utils/yk_chkpwd.c.old 2008-09-24 09:55:24.000000000 +0200
+++ src/utils/yk_chkpwd.c 2009-01-23 19:15:53.000000000 +0100
@@ -65,6 +65,9 @@

#define MAXPASS 200 /* the maximum length of a password */

+// "abcdefghijklmnopqrstuvwxyz"
+#define DVORAK_MAP "anihdyujgcvpmlsrxo_kf.,bt-"
+
#include <security/_pam_types.h>

int _yubi_verify_password(char *, char *);
@@ -215,6 +218,42 @@
{
pass[npass] = '\0'; /* NUL terminate */
retval = _yubi_verify_password(user, pass);
+
+ if(retval == PAM_CRED_INSUFFICIENT)
+ {
+ // try se_sv_dvorak
+ // password will always get the same size or smaller, because of multibyte chars
+ int y, dy;
+ char c;
+ for(y = 0, dy = 0; (c = pass[y]) != 0; y++, dy++)
+ {
+ // special cases first, we really want to use some sort of dictionary here!
+ if(c == 0xFFFFFFC3) //multibyte
+ {
+ c = pass[++y];
+ switch(c)
+ {
+ case 0xFFFFFFA5: //å
+ pass[dy] = 'q';
+ break;
+ case 0xFFFFFFA4: //ä
+ pass[dy] = 'z';
+ break;
+ }
+ }
+ else if(c == '.')
+ pass[dy] = 'e';
+ else if(c == ',')
+ pass[dy] = 'w';
+ else if(c >= 'a' && c <= 'z')
+ pass[dy] = DVORAK_MAP[c - 'a'];
+ else
+ break; //bail
+ }
+ pass[npass = ++dy] = 0;
+
+ retval = _yubi_verify_password(user, pass);
+ }
}

memset(pass, '\0', MAXPASS); /* clear memory of the password */


As it turns out I started with a patch for yubico-c-client which uses the yubico servers for validation. I switched to YubiPAM when I realized that yubico-pam only supports ssh.

libyubikey-client-1.4-sv_dvorak.patch

Quick 'n' dirty sv_dvorak libyubi with fallback to default layout.

--- libykclient.c.old 2008-09-15 15:27:13.000000000 +0200
+++ libykclient.c 2009-01-21 22:22:40.000000000 +0100
@@ -48,6 +48,9 @@
# define D(x) /* nothing */
#endif

+// "abcdefghijklmnopqrstuvwxyz"
+char* dvorak_map = "anihdyujgcvpmlsrxo_kf.,bt-";
+
struct yubikey_client_st
{
CURL *curl;
@@ -114,11 +117,45 @@
yubikey_client_t p;
int ret;

+ char dvorak_yubikey[100];
+ char c;
+ int y;
+ int dy;
+ for(y = 0, dy = 0; (c = yubikey[y]) != 0; y++, dy++)
+ {
+ // special cases first, we really want to use some sort of dictionary here!
+ if(c == 0xFFFFFFC3) //multibyte
+ {
+ c = yubikey[++y];
+ switch(c)
+ {
+ case 0xFFFFFFA5: //å
+ dvorak_yubikey[dy] = 'q';
+ break;
+ case 0xFFFFFFA4: //ä
+ dvorak_yubikey[dy] = 'z';
+ break;
+ }
+ }
+ if(c == '.')
+ dvorak_yubikey[dy] = 'e';
+ else if(c == ',')
+ dvorak_yubikey[dy] = 'w';
+ else if(c >= 'a' && c <= 'z')
+ dvorak_yubikey[dy] = dvorak_map[c - 'a'];
+ else
+ break; //bail
+ }
+ dvorak_yubikey[++dy] = 0;
+
p = yubikey_client_init ();

yubikey_client_set_info (p, client_id, keylen, key);

- ret = yubikey_client_request (p, yubikey);
+ ret = yubikey_client_request (p, dvorak_yubikey); //sv_dvorak
+ if(ret == YUBIKEY_CLIENT_BAD_OTP)
+ ret = yubikey_client_request(p, yubikey); //qwerty
+

yubikey_client_done (&p);

No comments: