1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
6 * This is an OSD class that implements methods for management
7 * and use of otp (one time password).
15 #include <boost/range/adaptor/reversed.hpp>
17 #include <liboath/oath.h>
19 #include "include/types.h"
20 #include "include/utime.h"
21 #include "objclass/objclass.h"
23 #include "common/errno.h"
24 #include "common/Clock.h"
26 #include "cls/otp/cls_otp_ops.h"
27 #include "cls/otp/cls_otp_types.h"
30 using namespace rados::cls::otp
;
36 #define ATTEMPTS_PER_WINDOW 5
38 static string otp_header_key
= "header";
39 static string otp_key_prefix
= "otp/";
46 void encode(bufferlist
&bl
) const {
47 ENCODE_START(1, 1, bl
);
51 void decode(bufferlist::const_iterator
&bl
) {
57 WRITE_CLASS_ENCODER(otp_header
)
62 list
<otp_check_t
> last_checks
;
63 uint64_t last_success
{0}; /* otp counter/step of last successful check */
67 void encode(bufferlist
&bl
) const {
68 ENCODE_START(1, 1, bl
);
70 encode(last_checks
, bl
);
71 encode(last_success
, bl
);
74 void decode(bufferlist::const_iterator
&bl
) {
77 decode(last_checks
, bl
);
78 decode(last_success
, bl
);
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
);
86 void find(const string
& token
, otp_check_t
*result
);
88 WRITE_CLASS_ENCODER(otp_instance
)
91 void otp_instance::trim_expired(const ceph::real_time
& now
)
93 ceph::real_time window_start
= now
- std::chrono::seconds(otp
.step_size
);
95 while (!last_checks
.empty() &&
96 last_checks
.front().timestamp
< window_start
) {
97 last_checks
.pop_front();
101 void otp_instance::check(const string
& token
, const string
& val
, bool *update
)
103 ceph::real_time now
= ceph::real_clock::now();
106 if (last_checks
.size() >= ATTEMPTS_PER_WINDOW
) {
107 /* too many attempts */
114 check
.timestamp
= now
;
115 check
.result
= (verify(now
, val
) ? OTP_CHECK_SUCCESS
: OTP_CHECK_FAIL
);
117 last_checks
.push_back(check
);
122 bool otp_instance::verify(const ceph::real_time
& timestamp
, const string
& val
)
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 */,
130 if (result
== OATH_INVALID_OTP
||
132 CLS_LOG(20, "otp check failed, result=%d", result
);
136 index
= result
+ (secs
- otp
.time_ofs
) / otp
.step_size
;
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
);
143 last_success
= index
;
148 void otp_instance::find(const string
& token
, otp_check_t
*result
)
150 ceph::real_time now
= real_clock::now();
153 for (auto& entry
: boost::adaptors::reverse(last_checks
)) {
154 if (entry
.token
== token
) {
159 result
->token
= token
;
160 result
->result
= OTP_CHECK_UNKNOWN
;
161 result
->timestamp
= now
;
164 static int get_otp_instance(cls_method_context_t hctx
, const string
& id
, otp_instance
*instance
)
167 string key
= otp_key_prefix
+ id
;
169 int r
= cls_cxx_map_get_val(hctx
, key
, &bl
);
172 CLS_ERR("error reading key %s: %d", key
.c_str(), r
);
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());
188 static int write_otp_instance(cls_method_context_t hctx
, const otp_instance
& instance
)
190 string key
= otp_key_prefix
+ instance
.otp
.id
;
193 encode(instance
, bl
);
195 int r
= cls_cxx_map_set_val(hctx
, key
, &bl
);
197 CLS_ERR("ERROR: %s(): failed to store key (otp id=%s, r=%d)", __func__
, instance
.otp
.id
.c_str(), r
);
204 static int remove_otp_instance(cls_method_context_t hctx
, const string
& id
)
206 string key
= otp_key_prefix
+ id
;
208 int r
= cls_cxx_map_remove_key(hctx
, key
);
210 CLS_ERR("ERROR: %s(): failed to remove key (otp id=%s, r=%d)", __func__
, id
.c_str(), r
);
217 static int read_header(cls_method_context_t hctx
, otp_header
*h
)
221 int r
= cls_cxx_map_get_val(hctx
, otp_header_key
, &bl
);
222 if (r
== -ENOENT
|| r
== -ENODATA
) {
227 CLS_ERR("ERROR: %s(): failed to read header (r=%d)", __func__
, r
);
231 if (bl
.length() == 0) {
236 auto iter
= bl
.cbegin();
239 } catch (buffer::error
& err
) {
240 CLS_ERR("failed to decode otp_header");
247 static int write_header(cls_method_context_t hctx
, const otp_header
& h
)
252 int r
= cls_cxx_map_set_val(hctx
, otp_header_key
, &bl
);
254 CLS_ERR("failed to store header (r=%d)", r
);
261 static int parse_seed(const string
& seed
, SeedType seed_type
, bufferlist
*seed_bin
)
263 size_t slen
= seed
.length();
264 char secret
[seed
.length()];
265 char *psecret
= secret
;
267 bool need_free
= false;
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(),
277 default: /* just assume hex is the default */
278 result
= oath_hex2bin(seed
.c_str(), psecret
, &slen
);
280 if (result
!= OATH_OK
) {
281 CLS_LOG(20, "failed to parse seed");
285 seed_bin
->append(psecret
, slen
);
294 static int otp_set_op(cls_method_context_t hctx
,
295 bufferlist
*in
, bufferlist
*out
)
297 CLS_LOG(20, "%s", __func__
);
298 cls_otp_set_otp_op op
;
300 auto iter
= in
->cbegin();
302 } catch (const buffer::error
&err
) {
303 CLS_ERR("ERROR: %s(): failed to decode request", __func__
);
308 int r
= read_header(hctx
, &h
);
313 for (auto entry
: op
.entries
) {
314 otp_instance instance
;
315 r
= get_otp_instance(hctx
, entry
.id
, &instance
);
320 instance
.otp
= entry
;
322 r
= parse_seed(instance
.otp
.seed
, instance
.otp
.seed_type
, &instance
.otp
.seed_bin
);
327 r
= write_otp_instance(hctx
, instance
);
332 h
.ids
.insert(entry
.id
);
335 r
= write_header(hctx
, h
);
343 static int otp_remove_op(cls_method_context_t hctx
,
344 bufferlist
*in
, bufferlist
*out
)
346 CLS_LOG(20, "%s", __func__
);
347 cls_otp_remove_otp_op op
;
349 auto iter
= in
->cbegin();
351 } catch (const buffer::error
&err
) {
352 CLS_ERR("ERROR: %s(): failed to decode request", __func__
);
357 bool removed_existing
= false;
358 int r
= read_header(hctx
, &h
);
363 for (auto id
: op
.ids
) {
364 bool existed
= (h
.ids
.find(id
) != h
.ids
.end());
365 removed_existing
= (removed_existing
|| existed
);
371 r
= remove_otp_instance(hctx
, id
);
379 if (removed_existing
) {
380 r
= write_header(hctx
, h
);
389 static int otp_get_op(cls_method_context_t hctx
,
390 bufferlist
*in
, bufferlist
*out
)
392 CLS_LOG(20, "%s", __func__
);
393 cls_otp_get_otp_op op
;
395 auto iter
= in
->cbegin();
397 } catch (const buffer::error
&err
) {
398 CLS_ERR("ERROR: %s(): failed to decode request", __func__
);
402 cls_otp_get_otp_reply result
;
407 r
= read_header(hctx
, &h
);
414 for (auto id
: h
.ids
) {
415 op
.ids
.push_back(id
);
419 for (auto id
: op
.ids
) {
420 bool exists
= (h
.ids
.find(id
) != h
.ids
.end());
426 otp_instance instance
;
427 r
= get_otp_instance(hctx
, id
, &instance
);
432 result
.found_entries
.push_back(instance
.otp
);
435 encode(result
, *out
);
440 static int otp_check_op(cls_method_context_t hctx
,
441 bufferlist
*in
, bufferlist
*out
)
443 CLS_LOG(20, "%s", __func__
);
444 cls_otp_check_otp_op op
;
446 auto iter
= in
->cbegin();
448 } catch (const buffer::error
&err
) {
449 CLS_ERR("ERROR: %s(): failed to decode request", __func__
);
456 otp_instance instance
;
458 r
= get_otp_instance(hctx
, op
.id
, &instance
);
464 instance
.check(op
.token
, op
.val
, &update
);
467 r
= write_otp_instance(hctx
, instance
);
476 static int otp_get_result(cls_method_context_t hctx
,
477 bufferlist
*in
, bufferlist
*out
)
479 CLS_LOG(20, "%s", __func__
);
480 cls_otp_check_otp_op op
;
482 auto iter
= in
->cbegin();
484 } catch (const buffer::error
&err
) {
485 CLS_ERR("ERROR: %s(): failed to decode request", __func__
);
492 otp_instance instance
;
494 r
= get_otp_instance(hctx
, op
.id
, &instance
);
499 cls_otp_get_result_reply reply
;
500 instance
.find(op
.token
, &reply
.result
);
506 static int otp_get_current_time_op(cls_method_context_t hctx
,
507 bufferlist
*in
, bufferlist
*out
)
509 CLS_LOG(20, "%s", __func__
);
510 cls_otp_get_current_time_op op
;
512 auto iter
= in
->cbegin();
514 } catch (const buffer::error
&err
) {
515 CLS_ERR("ERROR: %s(): failed to decode request", __func__
);
519 cls_otp_get_current_time_reply reply
;
520 reply
.time
= real_clock::now();
528 CLS_LOG(20, "Loaded otp class!");
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
547 cls_method_handle_t h_remove_otp_op
;
548 cls_method_handle_t h_get_current_time_op
;
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",
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",
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",
568 otp_get_current_time_op
, &h_get_current_time_op
);