1 /* zxpw.c  -  Password and other authentication methods for IdP
2  * Copyright (c) 2012-2015 Synergetics NV (sampo@synergetics.be), All Rights Reserved.
3  * Copyright (c) 2009-2010 Sampo Kellomaki (sampo@iki.fi), All Rights Reserved.
4  * Copyright (c) 2007-2009 Symlabs (symlabs@symlabs.com), All Rights Reserved.
5  * Author: Sampo Kellomaki (sampo@iki.fi)
6  * This is confidential unpublished proprietary source code of the author.
7  * NO WARRANTY, not even implied warranties. Contains trade secrets.
8  * Distribution prohibited unless authorized in writing.
9  * Licensed under Apache License 2.0, see file COPYING.
10  * $Id: zxiduser.c,v 1.18 2009-11-29 12:23:06 sampo Exp $
11  *
12  * 12.10.2007, created --Sampo
13  * 7.10.2008,  added documentation --Sampo
14  * 14.11.2009, added yubikey (yubico.com) support --Sampo
15  * 23.9.2010,  added delegation support --Sampo
16  * 1.9.2012,   distilled the authentication backend from zxiduser.c to its own module --Sampo
17  * 29.5.2015,  added two factor authentication, i.e. pin + yubikey --Sampo
18  *
19  * Ranking of authentication methods
20  *  0 = No authentication or failed authentication
21  *  1 = Username + PIN
22  *  2 = Username + Simple password
23  *  3 = Yubikey
24  *  4 = PIN + Yubikey
25  */
26 
27 #include "platform.h"  /* for dirent.h */
28 
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <fcntl.h>
32 #include <string.h>
33 #include <stdio.h>
34 #include <errno.h>
35 
36 #ifdef USE_OPENSSL
37 #include <openssl/des.h>
38 #endif
39 
40 #include "errmac.h"
41 #include "zxid.h"
42 #include "zxidutil.h"
43 #include "zxidconf.h"
44 #include "yubikey.h"   /* from libyubikey-1.5 */
45 
46 /*() Low level password check using various different types of hash
47  * The passw contains the supplied password and the pw_buf the password
48  * from the database (the caller must make this query). The pw_buf from
49  * database may indicate various hashing and other methods which are
50  * handled by this function. The fd_hint is only used for debug prints.
51  * return:: 0 on failure, 1 on success  */
52 
53 /* Called by:  zx_password_authn */
zx_pw_chk(const char * uid,const char * pw_buf,const char * passw,int fd_hint)54 static int zx_pw_chk(const char* uid, const char* pw_buf, const char* passw, int fd_hint)
55 {
56   unsigned char pw_hash[120];
57 
58   /* *** Add here support for other authentication backends */
59 
60   DD("io(%x) pw_buf (%s) len=%d", fd_hint, pw_buf, strlen(pw_buf));
61 
62   if (!memcmp(pw_buf, "$1$", sizeof("$1$")-1)) {              /* MD5 hashed password */
63     zx_md5_crypt(passw, (char*)pw_buf, (char*)pw_hash);
64     D("io(%x) pw_hash(%s)", fd_hint, pw_hash);
65     if (strcmp((char*)pw_buf, (char*)pw_hash)) {
66       ERR("Bad password. uid(%s)", uid);
67       D("md5 pw(%s) .pw(%s) pw_hash(%s)", passw, pw_buf, pw_hash);
68       return 0;
69     }
70 #ifdef USE_OPENSSL
71   } else if (!memcmp(pw_buf, "$c$", sizeof("$c$")-1)) {       /* DES fcrypt hashed password */
72     DES_fcrypt(passw, (char*)pw_buf+3, (char*)pw_hash);
73     D("io(%x) pw_hash(%s)", fd_hint, pw_hash);
74     if (strcmp((char*)pw_buf+3, (char*)pw_hash)) {
75       ERR("Bad password for uid(%s)", uid);
76       D("crypt pw(%s) .pw(%s) pw_hash(%s)", passw, pw_buf, pw_hash);
77       return 0;
78     }
79 #endif
80   } else if (ONE_OF_2(pw_buf[0], '$', '_')) {                 /* Unsupported hash */
81     ERR("Unsupported password hash. uid(%s)", uid);
82     D("io(%x) pw(%s) .pw(%s)", fd_hint, passw, pw_buf);
83     return 0;
84   } else {                                                    /* Plaintext password (no hash) */
85     if (strcmp((char*)pw_buf, passw)) {
86       ERR("Bad password. uid(%s)", uid);
87       D("io(%x) pw(%s) .pw(%s)", fd_hint, passw, pw_buf);
88       return 0;
89     }
90   }
91   INFO("Login(%x) OK acnt(%s)", fd_hint, uid);
92   return 2;
93 }
94 
95 /*() Low level Yubikey one time password token (usbkey) authentication.
96  * The yubikey system requires that spent OTPs are remembered to prevent
97  * replay attack. We do this by keeping per user /CPATH/uid/UID/.ykspent/
98  * directory: if the key is already in this directory, then fail.
99  * Yubikey OTP looks like this
100  *  tructedjlkijterkbcfjevdkflenbtbtentfeilkjidt
101  *  tructedjlkijftlbuviijebbjvernhghlieukckvuuhk
102  *  tructedjlkijcbudcjnhbrntktctirtdgrkjbdkgjjfj
103  *              12345678901234567890123456789012
104  *  UID part    OTP part 1         2         3
105  *
106  * The last 32 characters of the input are the actual OTP. The first
107  * variable length part is the username, which can be either the
108  * usename programmed into the key, or concatenation of manually
109  * entered password and username from the key.
110  *
111  * See: yubico.com
112  *
113  * cpath:: The configuration path from which uid directory path is formed, typically cf->cpath
114  * uid:: Both the UID and OTP concatenated
115  * passw:: not used in Yubikey authentication
116  * return:: 0 on failure, 1 (1 factor yubikey) or 2 (pin+yubikey) on success  */
117 
118 /* Called by:  zx_password_authn */
zx_yubikey_authn(const char * cpath,char * uid,const char * passw,const char * pin)119 int zx_yubikey_authn(const char* cpath, char* uid, const char* passw, const char* pin)
120 {
121   unsigned char uidpath[256];
122   unsigned char pw_buf[256];
123   unsigned char pw_hash[120];
124   yubikey_token_st yktok;
125   int len = strlen(uid);
126 
127   strcpy((char*)pw_hash, uid + len - 32);
128   uid[len - 32] = 0;
129   D("yubikey user(%s) ticket(%s) pin(%s)", uid, pw_hash, STRNULLCHK(pin));
130 
131   snprintf((char*)uidpath, sizeof(uidpath)-1, "%s" ZXID_UID_DIR "%s", cpath, uid);
132   uidpath[sizeof(uidpath)-1] = 0;
133   len = read_all(sizeof(pw_buf), (char*)pw_buf, "ykspent", 0, "%s/.ykspent/%s", uidpath, pw_hash);
134   if (len) {
135     ERR("The One Time Password has already been spent. ticket(%s%s) pw_buf(%.*s)", uid, pw_hash, len, pw_buf);
136     return 0;
137   }
138   if (!write_all_path("ykspent", "%s/.ykspent/%s", (char*)uidpath, (char*)pw_hash, 1, "1"))
139     return 0;
140 
141   len = read_all(sizeof(pw_buf), (char*)pw_buf, "ykaes", 1, "%s/.yk", uidpath);
142   D("buf    (%s) got=%d", pw_buf, len);
143   if (len < 32) {
144     ERR("User's %s/.yk file must contain aes128 key as 32 hexadecimal characters. Too few characters %d ticket(%s)", uid, len, pw_hash);
145     return 0;
146   }
147   if (len > 32) {
148     INFO("User's %s/.yk file must contain aes128 key as 32 hexadecimal characters. Too many characters %d ticket(%s). Truncating.", uid, len, pw_hash);
149     len = 32;
150     pw_buf[len] = 0;
151   }
152   zx_hexdec((char*)pw_buf, (char*)pw_buf, len, hex_trans);
153   ZERO(&yktok, sizeof(yktok));
154   zx_hexdec((void*)&yktok, (char*)pw_hash, 32, ykmodhex_trans);
155   yubikey_aes_decrypt((void*)&yktok, pw_buf);
156   D("internal uid %02x %02x %02x %02x %02x %02x counter=%d 0x%x timestamp=%d (hi=%x lo=%x) use=%d 0x%x rnd=0x%x crc=0x%x", yktok.uid[0], yktok.uid[1], yktok.uid[2], yktok.uid[3], yktok.uid[4], yktok.uid[5], yktok.ctr, yktok.ctr, (yktok.tstph << 16) | yktok.tstpl, yktok.tstph, yktok.tstpl, yktok.use, yktok.use, yktok.rnd, yktok.crc);
157 
158   if (!yubikey_crc_ok_p((unsigned char*)&yktok)) {
159     ERR("yubikey ticket validation failure %d", 0);
160     return 0;
161   }
162 
163   if (pin && *pin) { /* Pin supplied, may be we can perform two factor authn? */
164     len = read_all(sizeof(pw_buf), (char*)pw_buf, "pin", 1, "%s/.pin", uidpath);
165     if (zx_pw_chk(uid, (char*)pw_buf, pin, 0)) {
166       D("Two factor pin+yubikey successful. %d", 1);
167       return 4;
168     }
169     ERR("pin validation failure (after successful yubikey) %d", 0);
170     return 0;
171   }
172 
173   return 3;
174 }
175 
176 /*() Authenticate user using password like mechanism
177  * Expects to get username and password as in cgi->au and cgi->ap
178  * respectively. User authetication is done against local database or
179  * by default using /var/zxid/uid/UID/.pw file. When filesystem
180  * backend is used, for safety reasons the uid (user) component can
181  * not have certain characters, such as slash (/) or sequences like "..".
182  * See also: zxpasswd.c (user provisioning tool)
183  *
184  * return:: 0 on failure, 1 or larger on success depending on authentication quality  */
185 
186 /* Called by:  zxbus_pw_authn_ent, zxid_pw_authn */
zx_password_authn(const char * cpath,char * uid,const char * passw,const char * pin,int fd_hint)187 int zx_password_authn(const char* cpath, char* uid, const char* passw, const char* pin, int fd_hint)
188 {
189   char pw_buf[256];
190   int len;
191 
192   if (!uid || !uid[0]) {
193     ERR("No uid (user's login name) supplied. %p", uid);
194     D("io(%x) no user name pw(%s)", fd_hint, STRNULLCHK(passw));
195     return 0;
196   }
197 
198   /* Check for filesystem unsafe characters. (*** Is this list complete?) */
199   if (strstr(uid, "..") || strchr(uid, '/')
200       || strchr(uid, '\\') || strchr(uid, '~')) {
201     ERR("uid(%s) is not filesystem safe", uid);
202     D("io(%x) pw(%s)", fd_hint, STRNULLCHK(passw));
203     return 0;
204   }
205 
206   len = strlen(uid);
207   if (len > 32)
208     return zx_yubikey_authn(cpath, uid, passw, pin);
209 
210   if (!passw || !passw[0]) {
211     ERR("No password supplied. uid(%s)", uid);
212     return 0;
213   }
214 
215   len = read_all(sizeof(pw_buf), pw_buf, "pw_authn", 1, "%s" ZXID_UID_DIR "%s/.pw", cpath, uid);
216   if (len < 1) {
217     ERR("No account found for uid(%s) or account does not have .pw file.", uid);
218     D("io(%x) pw(%s)", fd_hint, passw);
219     return 0;
220   }
221 
222   if (len) {
223     if (pw_buf[len-1] == '\012') --len;
224     if (pw_buf[len-1] == '\015') --len;
225   }
226   pw_buf[len] = 0;
227   return zx_pw_chk(uid, pw_buf, passw, fd_hint);
228 }
229 
230 /* EOF  --  zxpw.c */
231