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