1    	// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2    	// vim: ts=8 sw=2 smarttab
3    	
4    	/** \file
5    	 *
6    	 * This is an OSD class that implements methods for management
7    	 * and use of otp (one time password).
8    	 *
9    	 */
10   	
11   	#include <errno.h>
12   	#include <map>
13   	#include <list>
14   	
15   	#include <boost/range/adaptor/reversed.hpp>
16   	
17   	#include <liboath/oath.h>
18   	
19   	#include "include/types.h"
20   	#include "include/utime.h"
21   	#include "objclass/objclass.h"
22   	
23   	#include "common/errno.h"
24   	#include "common/Clock.h"
25   	
26   	#include "cls/otp/cls_otp_ops.h"
27   	#include "cls/otp/cls_otp_types.h"
28   	
29   	
30   	using namespace rados::cls::otp;
31   	
32   	
33   	CLS_VER(1,0)
34   	CLS_NAME(otp)
35   	
36   	#define ATTEMPTS_PER_WINDOW 5
37   	
38   	static string otp_header_key = "header";
39   	static string otp_key_prefix = "otp/";
40   	
(1) Event deref_parm_field_in_call: Function "operator =" dereferences an offset off "this". [details]
41   	struct otp_header {
42   	  set<string> ids;
43   	
44   	  otp_header() {}
45   	
46   	  void encode(bufferlist &bl) const {
47   	    ENCODE_START(1, 1, bl);
48   	    encode(ids, bl);
49   	    ENCODE_FINISH(bl);
50   	  }
51   	  void decode(bufferlist::const_iterator &bl) {
52   	    DECODE_START(1, bl);
53   	    decode(ids, bl);
54   	    DECODE_FINISH(bl);
55   	  }
56   	};
57   	WRITE_CLASS_ENCODER(otp_header)
58   	
59   	struct otp_instance {
60   	  otp_info_t otp;
61   	
62   	  list<otp_check_t> last_checks; 
63   	  uint64_t last_success{0}; /* otp counter/step of last successful check */
64   	
65   	  otp_instance() {}
66   	
67   	  void encode(bufferlist &bl) const {
68   	    ENCODE_START(1, 1, bl);
69   	    encode(otp, bl);
70   	    encode(last_checks, bl);
71   	    encode(last_success, bl);
72   	    ENCODE_FINISH(bl);
73   	  }
74   	  void decode(bufferlist::const_iterator &bl) {
75   	    DECODE_START(1, bl);
76   	    decode(otp, bl);
77   	    decode(last_checks, bl);
78   	    decode(last_success, bl);
79   	    DECODE_FINISH(bl);
80   	  }
81   	
82   	  void trim_expired(const ceph::real_time& now);
83   	  void check(const string& token, const string& val, bool *update);
84   	  bool verify(const ceph::real_time& timestamp, const string& val);
85   	
86   	  void find(const string& token, otp_check_t *result);
87   	};
88   	WRITE_CLASS_ENCODER(otp_instance)
89   	
90   	
91   	void otp_instance::trim_expired(const ceph::real_time& now)
92   	{
93   	  ceph::real_time window_start = now - std::chrono::seconds(otp.step_size);
94   	
95   	  while (!last_checks.empty() &&
96   	         last_checks.front().timestamp < window_start) {
97   	    last_checks.pop_front();
98   	  }
99   	}
100  	
101  	void otp_instance::check(const string& token, const string& val, bool *update)
102  	{
103  	  ceph::real_time now = ceph::real_clock::now();
104  	  trim_expired(now);
105  	
106  	  if (last_checks.size() >= ATTEMPTS_PER_WINDOW) {
107  	    /* too many attempts */
108  	    *update = false;
109  	    return;
110  	  }
111  	
112  	  otp_check_t check;
113  	  check.token = token;
114  	  check.timestamp = now;
115  	  check.result = (verify(now, val) ? OTP_CHECK_SUCCESS : OTP_CHECK_FAIL);
116  	
117  	  last_checks.push_back(check);
118  	
119  	  *update = true;
120  	}
121  	
122  	bool otp_instance::verify(const ceph::real_time& timestamp, const string& val)
123  	{
124  	  uint64_t index;
125  	  uint32_t secs = (uint32_t)ceph::real_clock::to_time_t(timestamp);
126  	  int result = oath_totp_validate2(otp.seed_bin.c_str(), otp.seed_bin.length(),
127  	                                   secs, otp.step_size, otp.time_ofs, otp.window,
128  	                                   nullptr /* otp pos */,
129  	                                   val.c_str());
130  	  if (result == OATH_INVALID_OTP ||
131  	      result < 0) {
132  	    CLS_LOG(20, "otp check failed, result=%d", result);
133  	    return false;
134  	  }
135  	
136  	  index = result + (secs - otp.time_ofs) / otp.step_size;
137  	
138  	  if (index <= last_success) { /* already used value */
139  	    CLS_LOG(20, "otp, use of old token: index=%lld last_success=%lld", (long long)index, (long long)last_success);
140  	    return false;
141  	  }
142  	
143  	  last_success = index;
144  	
145  	  return true;
146  	}
147  	
148  	void otp_instance::find(const string& token, otp_check_t *result)
149  	{
150  	  ceph::real_time now = real_clock::now();
151  	  trim_expired(now);
152  	
153  	  for (auto& entry : boost::adaptors::reverse(last_checks)) {
154  	    if (entry.token == token) {
155  	      *result = entry;
156  	      return;
157  	    }
158  	  }
159  	  result->token = token;
160  	  result->result = OTP_CHECK_UNKNOWN;
161  	  result->timestamp = now;
162  	}
163  	
164  	static int get_otp_instance(cls_method_context_t hctx, const string& id, otp_instance *instance)
165  	{
166  	  bufferlist bl;
167  	  string key = otp_key_prefix + id;
168  	 
169  	  int r = cls_cxx_map_get_val(hctx, key, &bl);
170  	  if (r < 0) {
171  	    if (r != -ENOENT) {
172  	      CLS_ERR("error reading key %s: %d", key.c_str(), r);
173  	    }
174  	    return r;
175  	  }
176  	
177  	  try {
178  	    auto it = bl.cbegin();
179  	    decode(*instance, it);
180  	  } catch (const buffer::error &err) {
181  	    CLS_ERR("ERROR: failed to decode %s", key.c_str());
182  	    return -EIO;
183  	  }
184  	
185  	  return 0;
186  	}
187  	
188  	static int write_otp_instance(cls_method_context_t hctx, const otp_instance& instance)
189  	{
190  	  string key = otp_key_prefix + instance.otp.id;
191  	
192  	  bufferlist bl;
193  	  encode(instance, bl);
194  	
195  	  int r = cls_cxx_map_set_val(hctx, key, &bl);
196  	  if (r < 0) {
197  	    CLS_ERR("ERROR: %s(): failed to store key (otp id=%s, r=%d)", __func__, instance.otp.id.c_str(), r);
198  	    return r;
199  	  }
200  	
201  	  return 0;
202  	}
203  	
204  	static int remove_otp_instance(cls_method_context_t hctx, const string& id)
205  	{
206  	  string key = otp_key_prefix + id;
207  	
208  	  int r = cls_cxx_map_remove_key(hctx, key);
209  	  if (r < 0) {
210  	    CLS_ERR("ERROR: %s(): failed to remove key (otp id=%s, r=%d)", __func__, id.c_str(), r);
211  	    return r;
212  	  }
213  	
214  	  return 0;
215  	}
216  	
217  	static int read_header(cls_method_context_t hctx, otp_header *h)
218  	{
219  	  bufferlist bl;
(1) Event var_compare_op: Comparing "h" to null implies that "h" might be null.
Also see events: [var_deref_model]
220  	  encode(h, bl);
221  	  int r = cls_cxx_map_get_val(hctx, otp_header_key, &bl);
(2) Event cond_false: Condition "r == -2", taking false branch.
(3) Event cond_false: Condition "r == -61", taking false branch.
222  	  if (r == -ENOENT || r == -ENODATA) {
223  	    *h = otp_header();
224  	    return 0;
(4) Event if_end: End of if statement.
225  	  }
(5) Event cond_false: Condition "r < 0", taking false branch.
226  	  if (r < 0) {
227  	    CLS_ERR("ERROR: %s(): failed to read header (r=%d)", __func__, r);
228  	    return r;
(6) Event if_end: End of if statement.
229  	  }
230  	
(7) Event cond_true: Condition "bl.length() == 0", taking true branch.
231  	  if (bl.length() == 0) {
(8) Event var_deref_model: Passing null pointer "h" to "operator =", which dereferences it. [details]
Also see events: [var_compare_op]
232  	    *h = otp_header();
233  	    return 0;
234  	  }
235  	
236  	  auto iter = bl.cbegin();
237  	  try {
238  	    decode(*h, iter);
239  	  } catch (buffer::error& err) {
240  	    CLS_ERR("failed to decode otp_header");
241  	    return -EIO;
242  	  }
243  	
244  	  return 0;
245  	}
246  	
247  	static int write_header(cls_method_context_t hctx, const otp_header& h)
248  	{
249  	  bufferlist bl;
250  	  encode(h, bl);
251  	
252  	  int r = cls_cxx_map_set_val(hctx, otp_header_key, &bl);
253  	  if (r < 0) {
254  	    CLS_ERR("failed to store header (r=%d)", r);
255  	    return r;
256  	  }
257  	
258  	  return 0;
259  	}
260  	
261  	static int parse_seed(const string& seed, SeedType seed_type, bufferlist *seed_bin)
262  	{
263  	  size_t slen = seed.length();
264  	  char secret[seed.length()];
265  	  char *psecret = secret;
266  	  int result;
267  	  bool need_free = false;
268  	
269  	  seed_bin->clear();
270  	
271  	  switch (seed_type) {
272  	    case OTP_SEED_BASE32:
273  	      need_free = true; /* oath_base32_decode allocates dest buffer */
274  	      result = oath_base32_decode(seed.c_str(), seed.length(),
275  	                                  &psecret, &slen);
276  	      break;
277  	    default: /* just assume hex is the default */
278  	      result = oath_hex2bin(seed.c_str(), psecret, &slen);
279  	  }
280  	  if (result != OATH_OK) {
281  	    CLS_LOG(20, "failed to parse seed");
282  	    return -EINVAL;
283  	  }
284  	
285  	  seed_bin->append(psecret, slen);
286  	
287  	  if (need_free) {
288  	    free(psecret);
289  	  }
290  	
291  	  return 0;
292  	}
293  	
294  	static int otp_set_op(cls_method_context_t hctx,
295  	                       bufferlist *in, bufferlist *out)
296  	{
297  	  CLS_LOG(20, "%s", __func__);
298  	  cls_otp_set_otp_op op;
299  	  try {
300  	    auto iter = in->cbegin();
301  	    decode(op, iter);
302  	  } catch (const buffer::error &err) {
303  	    CLS_ERR("ERROR: %s(): failed to decode request", __func__);
304  	    return -EINVAL;
305  	  }
306  	
307  	  otp_header h;
308  	  int r = read_header(hctx, &h);
309  	  if (r < 0) {
310  	    return r;
311  	  }
312  	
313  	  for (auto entry : op.entries) {
314  	    otp_instance instance;
315  	    r = get_otp_instance(hctx, entry.id, &instance);
316  	    if (r < 0 &&
317  	        r != -ENOENT) {
318  	      return r;
319  	    }
320  	    instance.otp = entry;
321  	
322  	    r = parse_seed(instance.otp.seed, instance.otp.seed_type, &instance.otp.seed_bin);
323  	    if (r < 0) {
324  	      return r;
325  	    }
326  	
327  	    r = write_otp_instance(hctx, instance);
328  	    if (r < 0) {
329  	      return r;
330  	    }
331  	
332  	    h.ids.insert(entry.id);
333  	  }
334  	
335  	  r = write_header(hctx, h);
336  	  if (r < 0) {
337  	    return r;
338  	  }
339  	
340  	  return 0;
341  	}
342  	
343  	static int otp_remove_op(cls_method_context_t hctx,
344  	                          bufferlist *in, bufferlist *out)
345  	{
346  	  CLS_LOG(20, "%s", __func__);
347  	  cls_otp_remove_otp_op op;
348  	  try {
349  	    auto iter = in->cbegin();
350  	    decode(op, iter);
351  	  } catch (const buffer::error &err) {
352  	    CLS_ERR("ERROR: %s(): failed to decode request", __func__);
353  	    return -EINVAL;
354  	  }
355  	
356  	  otp_header h;
357  	  bool removed_existing = false;
358  	  int r = read_header(hctx, &h);
359  	  if (r < 0) {
360  	    return r;
361  	  }
362  	
363  	  for (auto id : op.ids) {
364  	    bool existed = (h.ids.find(id) != h.ids.end());
365  	    removed_existing = (removed_existing || existed);
366  	
367  	    if (!existed) {
368  	      continue;
369  	    }
370  	
371  	    r = remove_otp_instance(hctx, id);
372  	    if (r < 0) {
373  	      return r;
374  	    }
375  	
376  	    h.ids.erase(id);
377  	  }
378  	
379  	  if (removed_existing) {
380  	    r = write_header(hctx, h);
381  	    if (r < 0) {
382  	      return r;
383  	    }
384  	  }
385  	
386  	  return 0;
387  	}
388  	
389  	static int otp_get_op(cls_method_context_t hctx,
390  	                          bufferlist *in, bufferlist *out)
391  	{
392  	  CLS_LOG(20, "%s", __func__);
393  	  cls_otp_get_otp_op op;
394  	  try {
395  	    auto iter = in->cbegin();
396  	    decode(op, iter);
397  	  } catch (const buffer::error &err) {
398  	    CLS_ERR("ERROR: %s(): failed to decode request", __func__);
399  	    return -EINVAL;
400  	  }
401  	
402  	  cls_otp_get_otp_reply result;
403  	
404  	  otp_header h;
405  	  int r;
406  	
407  	  r = read_header(hctx, &h);
408  	  if (r < 0) {
409  	    return r;
410  	  }
411  	
412  	  if (op.get_all) {
413  	    op.ids.clear();
414  	    for (auto id : h.ids) {
415  	      op.ids.push_back(id);
416  	    }
417  	  }
418  	
419  	  for (auto id : op.ids) {
420  	    bool exists = (h.ids.find(id) != h.ids.end());
421  	
422  	    if (!exists) {
423  	      continue;
424  	    }
425  	
426  	    otp_instance instance;
427  	    r = get_otp_instance(hctx, id, &instance);
428  	    if (r < 0) {
429  	      return r;
430  	    }
431  	
432  	    result.found_entries.push_back(instance.otp);
433  	  }
434  	
435  	  encode(result, *out);
436  	
437  	  return 0;
438  	}
439  	
440  	static int otp_check_op(cls_method_context_t hctx,
441  	                          bufferlist *in, bufferlist *out)
442  	{
443  	  CLS_LOG(20, "%s", __func__);
444  	  cls_otp_check_otp_op op;
445  	  try {
446  	    auto iter = in->cbegin();
447  	    decode(op, iter);
448  	  } catch (const buffer::error &err) {
449  	    CLS_ERR("ERROR: %s(): failed to decode request", __func__);
450  	    return -EINVAL;
451  	  }
452  	
453  	  otp_header h;
454  	  int r;
455  	
456  	  otp_instance instance;
457  	
458  	  r = get_otp_instance(hctx, op.id, &instance);
459  	  if (r < 0) {
460  	    return r;
461  	  }
462  	
463  	  bool update{false};
464  	  instance.check(op.token, op.val, &update);
465  	
466  	  if (update) {
467  	    r = write_otp_instance(hctx, instance);
468  	    if (r < 0) {
469  	      return r;
470  	    }
471  	  }
472  	
473  	  return 0;
474  	}
475  	
476  	static int otp_get_result(cls_method_context_t hctx,
477  	                          bufferlist *in, bufferlist *out)
478  	{
479  	  CLS_LOG(20, "%s", __func__);
480  	  cls_otp_check_otp_op op;
481  	  try {
482  	    auto iter = in->cbegin();
483  	    decode(op, iter);
484  	  } catch (const buffer::error &err) {
485  	    CLS_ERR("ERROR: %s(): failed to decode request", __func__);
486  	    return -EINVAL;
487  	  }
488  	
489  	  otp_header h;
490  	  int r;
491  	
492  	  otp_instance instance;
493  	
494  	  r = get_otp_instance(hctx, op.id, &instance);
495  	  if (r < 0) {
496  	    return r;
497  	  }
498  	
499  	  cls_otp_get_result_reply reply;
500  	  instance.find(op.token, &reply.result);
501  	  encode(reply, *out);
502  	
503  	  return 0;
504  	}
505  	
506  	static int otp_get_current_time_op(cls_method_context_t hctx,
507  	                        bufferlist *in, bufferlist *out)
508  	{
509  	  CLS_LOG(20, "%s", __func__);
510  	  cls_otp_get_current_time_op op;
511  	  try {
512  	    auto iter = in->cbegin();
513  	    decode(op, iter);
514  	  } catch (const buffer::error &err) {
515  	    CLS_ERR("ERROR: %s(): failed to decode request", __func__);
516  	    return -EINVAL;
517  	  }
518  	
519  	  cls_otp_get_current_time_reply reply;
520  	  reply.time = real_clock::now();
521  	  encode(reply, *out);
522  	
523  	  return 0;
524  	}
525  	
526  	CLS_INIT(otp)
527  	{
528  	  CLS_LOG(20, "Loaded otp class!");
529  	
530  	  oath_init();
531  	
532  	  cls_handle_t h_class;
533  	  cls_method_handle_t h_set_otp_op;
534  	  cls_method_handle_t h_get_otp_op;
535  	  cls_method_handle_t h_check_otp_op;
536  	  cls_method_handle_t h_get_result_op; /*
537  	                                        * need to check and get check result in two phases. The
538  	                                        * reason is that we need to update failure internally,
539  	                                        * however, there's no way to both return a failure and
540  	                                        * update, because a failure will cancel the operation,
541  	                                        * and write operations will not return a value. So
542  	                                        * we're returning a success, potentially updating the
543  	                                        * status internally, then a subsequent request can try
544  	                                        * to fetch the status. If it fails it means that failed
545  	                                        * to authenticate.
546  	                                        */
547  	  cls_method_handle_t h_remove_otp_op;
548  	  cls_method_handle_t h_get_current_time_op;
549  	
550  	  cls_register("otp", &h_class);
551  	  cls_register_cxx_method(h_class, "otp_set",
552  	                          CLS_METHOD_RD | CLS_METHOD_WR,
553  	                          otp_set_op, &h_set_otp_op);
554  	  cls_register_cxx_method(h_class, "otp_get",
555  	                          CLS_METHOD_RD,
556  	                          otp_get_op, &h_get_otp_op);
557  	  cls_register_cxx_method(h_class, "otp_check",
558  	                          CLS_METHOD_RD | CLS_METHOD_WR,
559  	                          otp_check_op, &h_check_otp_op);
560  	  cls_register_cxx_method(h_class, "otp_get_result",
561  	                          CLS_METHOD_RD,
562  	                          otp_get_result, &h_get_result_op);
563  	  cls_register_cxx_method(h_class, "otp_remove",
564  	                          CLS_METHOD_RD | CLS_METHOD_WR,
565  	                          otp_remove_op, &h_remove_otp_op);
566  	  cls_register_cxx_method(h_class, "get_current_time",
567  	                          CLS_METHOD_RD,
568  	                          otp_get_current_time_op, &h_get_current_time_op);
569  	
570  	  return;
571  	}
572