All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
rlm_yubikey.c
Go to the documentation of this file.
1 /*
2  * This program is is free software; you can redistribute it and/or modify
3  * it under the terms of the GNU General Public License as published by
4  * the Free Software Foundation; either version 2 of the License, or (at
5  * your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software
14  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16 
17 /**
18  * $Id: bf91acbd24b86fb09604e41809fedfe6a11daccd $
19  * @file rlm_yubikey.c
20  * @brief Authentication for yubikey OTP tokens.
21  *
22  * @author Arran Cudbard-Bell <a.cudbardb@networkradius.com>
23  * @copyright 2013 The FreeRADIUS server project
24  * @copyright 2013 Network RADIUS <info@networkradius.com>
25  */
26 RCSID("$Id: bf91acbd24b86fb09604e41809fedfe6a11daccd $")
27 
28 #include "rlm_yubikey.h"
29 
30 /*
31  * A mapping of configuration file names to internal variables.
32  *
33  * Note that the string is dynamically allocated, so it MUST
34  * be freed. When the configuration file parse re-reads the string,
35  * it free's the old one, and strdup's the new one, placing the pointer
36  * to the strdup'd string into 'config.string'. This gets around
37  * buffer over-flows.
38  */
39 
40 #ifdef HAVE_YKCLIENT
41 static const CONF_PARSER validation_config[] = {
42  { FR_CONF_OFFSET("client_id", PW_TYPE_INTEGER, rlm_yubikey_t, client_id), .dflt = 0 },
43  { FR_CONF_OFFSET("api_key", PW_TYPE_STRING | PW_TYPE_SECRET, rlm_yubikey_t, api_key) },
45 };
46 #endif
47 
48 static const CONF_PARSER module_config[] = {
49  { FR_CONF_OFFSET("id_length", PW_TYPE_INTEGER, rlm_yubikey_t, id_len), .dflt = "12" },
50  { FR_CONF_OFFSET("split", PW_TYPE_BOOLEAN, rlm_yubikey_t, split), .dflt = "yes" },
51  { FR_CONF_OFFSET("decrypt", PW_TYPE_BOOLEAN, rlm_yubikey_t, decrypt), .dflt = "no" },
52  { FR_CONF_OFFSET("validate", PW_TYPE_BOOLEAN, rlm_yubikey_t, validate), .dflt = "no" },
53 #ifdef HAVE_YKCLIENT
54  { FR_CONF_POINTER("validation", PW_TYPE_SUBSECTION, NULL), .dflt = (void const *) validation_config },
55 #endif
57 };
58 
59 static char const modhextab[] = "cbdefghijklnrtuv";
60 static char const hextab[] = "0123456789abcdef";
61 
62 #define is_modhex(x) (memchr(modhextab, tolower(x), 16))
63 
64 /** Convert yubikey modhex to normal hex
65  *
66  * The same buffer may be passed as modhex and hex to convert the modhex in place.
67  *
68  * Modhex and hex must be the same size.
69  *
70  * @param[in] modhex data.
71  * @param[in] len of input and output buffers.
72  * @param[out] hex where to write the standard hexits.
73  * @return
74  * - The number of bytes written to the output buffer.
75  * - -1 on failure.
76  */
77 static ssize_t modhex2hex(char const *modhex, uint8_t *hex, size_t len)
78 {
79  size_t i;
80  char *c1, *c2;
81 
82  for (i = 0; i < len; i++) {
83  if (modhex[i << 1] == '\0') {
84  break;
85  }
86 
87  /*
88  * We only deal with whole bytes
89  */
90  if (modhex[(i << 1) + 1] == '\0')
91  return -1;
92 
93  if (!(c1 = memchr(modhextab, tolower((int) modhex[i << 1]), 16)) ||
94  !(c2 = memchr(modhextab, tolower((int) modhex[(i << 1) + 1]), 16)))
95  return -1;
96 
97  hex[i] = hextab[c1 - modhextab];
98  hex[i + 1] = hextab[c2 - modhextab];
99  }
100 
101  return i;
102 }
103 
104 /**
105  * @brief Convert Yubikey modhex to standard hex
106  *
107  * Example: "%{modhextohex:vvrbuctetdhc}" == "ffc1e0d3d260"
108  */
109 static ssize_t modhex_to_hex_xlat(char **out, size_t outlen,
110  UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
111  REQUEST *request, char const *fmt)
112 {
113  ssize_t len;
114 
115  if (outlen < strlen(fmt)) return 0;
116 
117  /*
118  * mod2hex allows conversions in place
119  */
120  len = modhex2hex(fmt, (uint8_t *) *out, strlen(fmt));
121  if (len <= 0) {
122  REDEBUG("Modhex string invalid");
123  return -1;
124  }
125 
126  return len;
127 }
128 
129 
130 static int mod_bootstrap(CONF_SECTION *conf, void *instance)
131 {
132  rlm_yubikey_t *inst = instance;
133 
134  inst->name = cf_section_name2(conf);
135  if (!inst->name) inst->name = cf_section_name1(conf);
136 
137 #ifndef HAVE_YUBIKEY
138  if (inst->decrypt) {
139  cf_log_err_cs(conf, "Requires libyubikey for OTP decryption");
140  return -1;
141  }
142 #endif
143 
144  if (!cf_section_name2(conf)) return 0;
145 
146  xlat_register(inst, "modhextohex", modhex_to_hex_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN);
147 
148  return 0;
149 }
150 
151 /*
152  * Do any per-module initialization that is separate to each
153  * configured instance of the module. e.g. set up connections
154  * to external databases, read configuration files, set up
155  * dictionary entries, etc.
156  *
157  * If configuration information is given in the config section
158  * that must be referenced in later calls, store a handle to it
159  * in *instance otherwise put a null pointer there.
160  */
161 static int mod_instantiate(CONF_SECTION *conf, void *instance)
162 {
163  rlm_yubikey_t *inst = instance;
164 
165  if (inst->validate) {
166 #ifdef HAVE_YKCLIENT
167  CONF_SECTION *cs;
168 
169  cs = cf_section_sub_find(conf, "validation");
170  if (!cs) {
171  cf_log_err_cs(conf, "Missing validation section");
172  return -1;
173  }
174 
175  if (rlm_yubikey_ykclient_init(cs, inst) < 0) {
176  return -1;
177  }
178 #else
179  cf_log_err_cs(conf, "Requires libykclient for OTP validation against Yubicloud servers");
180  return -1;
181 #endif
182  }
183 
184  return 0;
185 }
186 
187 /*
188  * Only free memory we allocated. The strings allocated via
189  * cf_section_parse() do not need to be freed.
190  */
191 #ifdef HAVE_YKCLIENT
192 static int mod_detach(void *instance)
193 {
195  return 0;
196 }
197 #endif
198 
199 static int CC_HINT(nonnull) otp_string_valid(rlm_yubikey_t *inst, char const *otp, size_t len)
200 {
201  size_t i;
202 
203  for (i = inst->id_len; i < len; i++) {
204  if (!is_modhex(otp[i])) return -i;
205  }
206 
207  return 1;
208 }
209 
210 
211 /*
212  * Find the named user in this modules database. Create the set
213  * of attribute-value pairs to check and reply with for this user
214  * from the database. The authentication code only needs to check
215  * the password, the rest is done here.
216  */
217 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
218 {
219  rlm_yubikey_t *inst = instance;
220 
221  fr_dict_enum_t *dval;
222  char const *passcode;
223  size_t len;
224  VALUE_PAIR *vp;
225  char const *otp;
226  size_t password_len;
227  int ret;
228 
229  /*
230  * Can't do yubikey auth if there's no password.
231  */
232  if (!request->password || (request->password->da->attr != PW_USER_PASSWORD)) {
233  /*
234  * Don't print out debugging messages if we know
235  * they're useless.
236  */
237  if (request->packet->code != PW_CODE_ACCESS_CHALLENGE) {
238  RDEBUG2("No cleartext password in the request. Can't do Yubikey authentication");
239  }
240 
241 
242  return RLM_MODULE_NOOP;
243  }
244 
245  passcode = request->password->vp_strvalue;
246  len = request->password->vp_length;
247 
248  /*
249  * Now see if the passcode is the correct length (in its raw
250  * modhex encoded form).
251  *
252  * <public_id (6-16 bytes)> + <aes-block (32 bytes)>
253  *
254  */
255  if (len < (inst->id_len + YUBIKEY_TOKEN_LEN)) {
256  RDEBUG2("User-Password value is not the correct length, expected at least %u bytes, got %zu bytes",
257  inst->id_len + YUBIKEY_TOKEN_LEN, len);
258  return RLM_MODULE_NOOP;
259  }
260 
261  password_len = (len - (inst->id_len + YUBIKEY_TOKEN_LEN));
262  otp = passcode + password_len;
263  ret = otp_string_valid(inst, otp, (inst->id_len + YUBIKEY_TOKEN_LEN));
264  if (ret <= 0) {
265  if (RDEBUG_ENABLED3) {
266  RDMARKER(otp, -ret, "User-Password (aes-block) value contains non modhex chars");
267  } else {
268  RDEBUG("User-Password (aes-block) value contains non modhex chars");
269  }
270  return RLM_MODULE_NOOP;
271  }
272 
273  /* May be a concatenation, check the last 32 bytes are modhex */
274  if (inst->split) {
275  char *password;
276 
277  /*
278  * Insert a new request attribute just containing the OTP
279  * portion.
280  */
281  vp = pair_make_request("Yubikey-OTP", otp, T_OP_SET);
282  if (!vp) {
283  REDEBUG("Failed creating 'Yubikey-OTP' attribute");
284  return RLM_MODULE_FAIL;
285  }
286 
287  /*
288  * Replace the existing string buffer for the password
289  * attribute with one just containing the password portion.
290  */
291  MEM(password = talloc_array(request->password, char, password_len + 1));
292  strlcpy(password, passcode, password_len + 1);
293  fr_pair_value_strsteal(request->password, password);
294 
295  RINDENT();
296  if (RDEBUG_ENABLED3) {
297  RDEBUG3("&request:Yubikey-OTP := '%s'", vp->vp_strvalue);
298  RDEBUG3("&request:User-Password := '%s'", request->password->vp_strvalue);
299  } else {
300  RDEBUG2("&request:Yubikey-OTP := <<< secret >>>");
301  RDEBUG2("&request:User-Password := <<< secret >>>");
302  }
303  REXDENT();
304 
305  /*
306  * So the ID split code works on the non password portion.
307  */
308  passcode = vp->vp_strvalue;
309  }
310 
311  /*
312  * Split out the Public ID in case another module in authorize
313  * needs to verify it's associated with the user.
314  *
315  * It's left up to the user if they want to decode it or not.
316  */
317  if (inst->id_len) {
318  vp = fr_pair_make(request, &request->packet->vps, "Yubikey-Public-ID", NULL, T_OP_SET);
319  if (!vp) {
320  REDEBUG("Failed creating Yubikey-Public-ID");
321 
322  return RLM_MODULE_FAIL;
323  }
324 
325  fr_pair_value_bstrncpy(vp, passcode, inst->id_len);
326  }
327 
328  dval = fr_dict_enum_by_name(NULL, fr_dict_attr_by_num(NULL, 0, PW_AUTH_TYPE), inst->name);
329  if (dval) {
330  vp = radius_pair_create(request, &request->config, PW_AUTH_TYPE, 0);
331  vp->vp_integer = dval->value;
332  }
333 
334  return RLM_MODULE_OK;
335 }
336 
337 
338 /*
339  * Authenticate the user with the given password.
340  */
341 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
342 {
344  rlm_yubikey_t *inst = instance;
345  char const *passcode = NULL;
346  fr_dict_attr_t const *da;
347  VALUE_PAIR const *vp;
348  size_t len;
349  int ret;
350 
351  da = fr_dict_attr_by_name(NULL, "Yubikey-OTP");
352  if (!da) {
353  RDEBUG2("No Yubikey-OTP attribute defined, falling back to User-Password");
354  goto user_password;
355  }
356 
357  vp = fr_pair_find_by_da(request->packet->vps, da, TAG_ANY);
358  if (!vp) {
359  RDEBUG2("No Yubikey-OTP attribute found, falling back to User-Password");
360  user_password:
361  /*
362  * Can't do yubikey auth if there's no password.
363  */
364  if (!request->password || (request->password->da->attr != PW_USER_PASSWORD)) {
365  REDEBUG("No User-Password in the request. Can't do Yubikey authentication");
366  return RLM_MODULE_INVALID;
367  }
368 
369  vp = request->password;
370  }
371 
372  passcode = vp->vp_strvalue;
373  len = vp->vp_length;
374 
375  /*
376  * Verify the passcode is the correct length (in its raw
377  * modhex encoded form).
378  *
379  * <public_id (6-16 bytes)> + <aes-block (32 bytes)>
380  */
381  if (len != (inst->id_len + YUBIKEY_TOKEN_LEN)) {
382  REDEBUG("%s value is not the correct length, expected bytes %u, got bytes %zu",
383  vp->da->name, inst->id_len + YUBIKEY_TOKEN_LEN, len);
384  return RLM_MODULE_INVALID;
385  }
386 
387  ret = otp_string_valid(inst, passcode, (inst->id_len + YUBIKEY_TOKEN_LEN));
388  if (ret <= 0) {
389  if (RDEBUG_ENABLED3) {
390  REMARKER(passcode, -ret, "Passcode (aes-block) value contains non modhex chars");
391  } else {
392  RERROR("Passcode (aes-block) value contains non modhex chars");
393  }
394  return RLM_MODULE_INVALID;
395  }
396 
397 #ifdef HAVE_YUBIKEY
398  if (inst->decrypt) {
399  rcode = rlm_yubikey_decrypt(inst, request, passcode);
400  if (rcode != RLM_MODULE_OK) {
401  return rcode;
402  }
403  /* Fall-Through to doing ykclient auth in addition to local auth */
404  }
405 #endif
406 
407 #ifdef HAVE_YKCLIENT
408  if (inst->validate) {
409  return rlm_yubikey_validate(inst, request, passcode);
410  }
411 #endif
412  return rcode;
413 }
414 
415 /*
416  * The module name should be the only globally exported symbol.
417  * That is, everything else should be 'static'.
418  *
419  * If the module needs to temporarily modify it's instantiation
420  * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
421  * The server will then take care of ensuring that the module
422  * is single-threaded.
423  */
424 extern module_t rlm_yubikey;
425 module_t rlm_yubikey = {
427  .name = "yubikey",
428  .type = RLM_TYPE_THREAD_SAFE,
429  .inst_size = sizeof(rlm_yubikey_t),
430  .config = module_config,
431  .bootstrap = mod_bootstrap,
432  .instantiate = mod_instantiate,
433 #ifdef HAVE_YKCLIENT
434  .detach = mod_detach,
435 #endif
436  .methods = {
439  },
440 };
module_t rlm_yubikey
Definition: rlm_yubikey.c:425
#define RDMARKER(_m, _i, _e)
Output string with error marker, showing where format error occurred.
Definition: log.h:323
#define RINDENT()
Indent R* messages by one level.
Definition: log.h:265
#define RERROR(fmt,...)
Definition: log.h:207
int xlat_register(void *mod_inst, char const *name, xlat_func_t func, xlat_escape_t escape, xlat_instantiate_t instantiate, size_t inst_size, size_t buf_len)
Register an xlat function.
Definition: xlat.c:717
#define RDEBUG_ENABLED3
True if request debug level 1-3 messages are enabled.
Definition: log.h:239
RFC2865 - Access-Challenge.
Definition: radius.h:102
static int mod_instantiate(CONF_SECTION *conf, void *instance)
Definition: rlm_yubikey.c:161
The module is OK, continue.
Definition: radiusd.h:91
char const * name
Instance name.
Definition: rlm_yubikey.h:25
Metadata exported by the module.
Definition: modules.h:134
Dictionary attribute.
Definition: dict.h:77
rlm_rcode_t rlm_yubikey_validate(rlm_yubikey_t *inst, REQUEST *request, char const *passcode)
#define MEM(x)
Definition: radiusd.h:396
int rlm_yubikey_ykclient_detach(rlm_yubikey_t *inst)
VALUE_PAIR * radius_pair_create(TALLOC_CTX *ctx, VALUE_PAIR **vps, unsigned int attribute, unsigned int vendor)
Create a VALUE_PAIR and add it to a list of VALUE_PAIR s.
Definition: pair.c:704
static rlm_rcode_t mod_authorize(void *instance, REQUEST *request)
Handle authorization requests using Couchbase document data.
#define RLM_TYPE_THREAD_SAFE
Module is threadsafe.
Definition: modules.h:75
#define REMARKER(_m, _i, _e)
Output string with error marker, showing where format error occurred.
Definition: log.h:306
#define UNUSED
Definition: libradius.h:134
#define RLM_MODULE_INIT
Definition: modules.h:86
bool validate
Validate the OTP string using the ykclient library.
Definition: rlm_yubikey.h:30
#define CONF_PARSER_TERMINATOR
Definition: conffile.h:289
static const CONF_PARSER module_config[]
Definition: rlm_yubikey.c:48
#define pair_make_request(_a, _b, _c)
Definition: radiusd.h:545
#define is_modhex(x)
Definition: rlm_yubikey.c:62
#define PW_TYPE_SECRET
Only print value if debug level >= 3.
Definition: conffile.h:202
static char const hextab[]
Definition: rlm_yubikey.c:60
bool split
Split password string into components.
Definition: rlm_yubikey.h:28
#define inst
The module considers the request invalid.
Definition: radiusd.h:93
#define XLAT_DEFAULT_BUF_LEN
Definition: xlat.h:89
static rlm_rcode_t mod_authenticate(void *instance, REQUEST *request) CC_HINT(nonnull)
#define PW_TYPE_SUBSECTION
Definition: conffile.h:188
Defines a CONF_PAIR to C data type mapping.
Definition: conffile.h:267
fr_dict_enum_t * fr_dict_enum_by_name(fr_dict_t *dict, fr_dict_attr_t const *da, char const *val)
Definition: dict.c:3703
void fr_pair_value_strsteal(VALUE_PAIR *vp, char const *src)
Reparent an allocated char buffer to a VALUE_PAIR.
Definition: pair.c:1955
static int CC_HINT(nonnull)
Definition: rlm_yubikey.c:199
static int mod_bootstrap(CONF_SECTION *conf, void *instance)
Definition: rlm_yubikey.c:130
int rlm_yubikey_ykclient_init(CONF_SECTION *conf, rlm_yubikey_t *inst)
Stores an attribute, a value and various bits of other data.
Definition: pair.h:112
void void cf_log_err_cs(CONF_SECTION const *cs, char const *fmt,...) CC_HINT(format(printf
0 methods index for authenticate section.
Definition: modules.h:41
#define REXDENT()
Exdent (unindent) R* messages by one level.
Definition: log.h:272
A truth value.
Definition: radius.h:56
Definition: token.h:45
32 Bit unsigned integer.
Definition: radius.h:34
enum rlm_rcodes rlm_rcode_t
Return codes indicating the result of the module call.
static rs_t * conf
Definition: radsniff.c:46
rlm_rcode_t rlm_yubikey_decrypt(rlm_yubikey_t *inst, REQUEST *request, char const *passcode)
CONF_SECTION * cf_section_sub_find(CONF_SECTION const *, char const *name)
Find a sub-section in a section.
Definition: conffile.c:3708
struct rlm_yubikey_t rlm_yubikey_t
#define YUBIKEY_TOKEN_LEN
Definition: rlm_yubikey.h:15
char const * cf_section_name1(CONF_SECTION const *cs)
Definition: conffile.c:3592
VALUE_PAIR * fr_pair_find_by_da(VALUE_PAIR *head, fr_dict_attr_t const *da, int8_t tag)
Find the pair with the matching DAs.
Definition: pair.c:624
Module succeeded without doing anything.
Definition: radiusd.h:96
#define RDEBUG2(fmt,...)
Definition: log.h:244
char name[1]
Attribute name.
Definition: dict.h:89
uint64_t magic
Used to validate module struct.
Definition: modules.h:135
Module failed, don't reply.
Definition: radiusd.h:90
#define TAG_ANY
Definition: pair.h:191
#define FR_CONF_OFFSET(_n, _t, _s, _f)
Definition: conffile.h:168
int value
Enum value.
Definition: dict.h:96
#define REDEBUG(fmt,...)
Definition: log.h:254
unsigned int id_len
The length of the Public ID portion of the OTP string.
Definition: rlm_yubikey.h:27
static ssize_t modhex_to_hex_xlat(char **out, size_t outlen, UNUSED void const *mod_inst, UNUSED void const *xlat_inst, REQUEST *request, char const *fmt)
Convert Yubikey modhex to standard hex.
Definition: rlm_yubikey.c:109
size_t strlcpy(char *dst, char const *src, size_t siz)
Definition: strlcpy.c:38
fr_dict_attr_t const * da
Dictionary attribute defines the attribute.
Definition: pair.h:113
void fr_pair_value_bstrncpy(VALUE_PAIR *vp, void const *src, size_t len)
Copy data into an "string" data type.
Definition: pair.c:2043
static int mod_detach(void *instance)
Free any memory allocated under the instance.
Definition: rlm_cache.c:849
fr_dict_attr_t const * fr_dict_attr_by_num(fr_dict_t *dict, unsigned int vendor, unsigned int attr)
Lookup a fr_dict_attr_t by its vendor and attribute numbers.
Definition: dict.c:3519
static char const hex[]
Definition: smbencrypt.c:34
String of printable characters.
Definition: radius.h:33
#define FR_CONF_POINTER(_n, _t, _p)
Definition: conffile.h:172
1 methods index for authorize section.
Definition: modules.h:42
#define RCSID(id)
Definition: build.h:135
bool decrypt
Decrypt the OTP string using the yubikey library.
Definition: rlm_yubikey.h:29
static char const modhextab[]
Definition: rlm_yubikey.c:59
VALUE_PAIR * fr_pair_make(TALLOC_CTX *ctx, VALUE_PAIR **vps, char const *attribute, char const *value, FR_TOKEN op)
Create a VALUE_PAIR from ASCII strings.
Definition: pair.c:338
#define RDEBUG(fmt,...)
Definition: log.h:243
static ssize_t modhex2hex(char const *modhex, uint8_t *hex, size_t len)
Convert yubikey modhex to normal hex.
Definition: rlm_yubikey.c:77
Value of an enumerated attribute.
Definition: dict.h:94
char const * cf_section_name2(CONF_SECTION const *cs)
Definition: conffile.c:3601
fr_dict_attr_t const * fr_dict_attr_by_name(fr_dict_t *dict, char const *attr)
Locate a fr_dict_attr_t by its name.
Definition: dict.c:3493
#define RDEBUG3(fmt,...)
Definition: log.h:245