1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 * Ceph - scalable distributed file system
6 * Copyright (C) 2009-2011 New Dream Network
8 * This is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License version 2.1, as published by the Free Software
11 * Foundation. See file COPYING.
15 #include "CephxProtocol.h"
16 #include "common/Clock.h"
17 #include "common/ceph_context.h"
18 #include "common/config.h"
19 #include "common/debug.h"
20 #include "include/buffer.h"
22 #define dout_subsys ceph_subsys_auth
24 #define dout_prefix *_dout << "cephx: "
30 using ceph::bufferlist
;
34 void cephx_calc_client_server_challenge(CephContext
*cct
, CryptoKey
& secret
, uint64_t server_challenge
,
35 uint64_t client_challenge
, uint64_t *key
, std::string
&error
)
38 b
.server_challenge
= server_challenge
;
39 b
.client_challenge
= client_challenge
;
42 if (encode_encrypt(cct
, b
, secret
, enc
, error
))
46 const ceph_le64
*p
= (const ceph_le64
*)enc
.c_str();
47 for (int pos
= 0; pos
+ sizeof(k
) <= enc
.length(); pos
+=sizeof(k
), p
++)
57 bool cephx_build_service_ticket_blob(CephContext
*cct
, CephXSessionAuthInfo
& info
,
58 CephXTicketBlob
& blob
)
60 CephXServiceTicketInfo ticket_info
;
61 ticket_info
.session_key
= info
.session_key
;
62 ticket_info
.ticket
= info
.ticket
;
63 ticket_info
.ticket
.caps
= info
.ticket
.caps
;
65 ldout(cct
, 10) << "build_service_ticket service "
66 << ceph_entity_type_name(info
.service_id
)
67 << " secret_id " << info
.secret_id
68 << " ticket_info.ticket.name="
69 << ticket_info
.ticket
.name
.to_str()
70 << " ticket.global_id " << info
.ticket
.global_id
<< dendl
;
71 blob
.secret_id
= info
.secret_id
;
73 if (!info
.service_secret
.get_secret().length())
74 error
= "invalid key"; // Bad key?
76 encode_encrypt_enc_bl(cct
, ticket_info
, info
.service_secret
, blob
.blob
, error
);
78 ldout(cct
, -1) << "cephx_build_service_ticket_blob failed with error "
86 * AUTH SERVER: authenticate
88 * Authenticate principal, respond with AuthServiceTicketInfo
90 * {session key, validity}^principal_secret
91 * {principal_ticket, session key}^service_secret ... "enc_ticket"
93 bool cephx_build_service_ticket_reply(CephContext
*cct
,
94 CryptoKey
& principal_secret
,
95 vector
<CephXSessionAuthInfo
> ticket_info_vec
,
96 bool should_encrypt_ticket
,
97 CryptoKey
& ticket_enc_key
,
100 __u8 service_ticket_reply_v
= 1;
102 encode(service_ticket_reply_v
, reply
);
104 uint32_t num
= ticket_info_vec
.size();
106 ldout(cct
, 10) << "build_service_ticket_reply encoding " << num
107 << " tickets with secret " << principal_secret
<< dendl
;
109 for (auto ticket_iter
= ticket_info_vec
.begin();
110 ticket_iter
!= ticket_info_vec
.end();
112 CephXSessionAuthInfo
& info
= *ticket_iter
;
113 encode(info
.service_id
, reply
);
115 __u8 service_ticket_v
= 1;
116 encode(service_ticket_v
, reply
);
118 CephXServiceTicket msg_a
;
119 msg_a
.session_key
= info
.session_key
;
120 msg_a
.validity
= info
.validity
;
122 if (encode_encrypt(cct
, msg_a
, principal_secret
, reply
, error
)) {
123 ldout(cct
, -1) << "error encoding encrypted: " << error
<< dendl
;
127 bufferlist service_ticket_bl
;
128 CephXTicketBlob blob
;
129 if (!cephx_build_service_ticket_blob(cct
, info
, blob
)) {
132 encode(blob
, service_ticket_bl
);
134 ldout(cct
, 30) << "service_ticket_blob is ";
135 service_ticket_bl
.hexdump(*_dout
);
138 encode((__u8
)should_encrypt_ticket
, reply
);
139 if (should_encrypt_ticket
) {
140 if (encode_encrypt(cct
, service_ticket_bl
, ticket_enc_key
, reply
, error
)) {
141 ldout(cct
, -1) << "error encoding encrypted ticket: " << error
<< dendl
;
145 encode(service_ticket_bl
, reply
);
152 * PRINCIPAL: verify our attempt to authenticate succeeded. fill out
153 * this ServiceTicket with the result.
155 bool CephXTicketHandler::verify_service_ticket_reply(
157 bufferlist::const_iterator
& indata
)
161 __u8 service_ticket_v
;
162 decode(service_ticket_v
, indata
);
164 CephXServiceTicket msg_a
;
166 if (decode_decrypt(cct
, msg_a
, secret
, indata
, error
)) {
167 ldout(cct
, 0) << __func__
<< " failed decode_decrypt, error is: " << error
173 decode(ticket_enc
, indata
);
175 bufferlist service_ticket_bl
;
177 ldout(cct
, 10) << __func__
<< " got encrypted ticket" << dendl
;
179 if (decode_decrypt(cct
, service_ticket_bl
, session_key
, indata
, error
)) {
180 ldout(cct
, 10) << __func__
<< " decode_decrypt failed "
181 << "with " << error
<< dendl
;
185 decode(service_ticket_bl
, indata
);
187 auto iter
= service_ticket_bl
.cbegin();
188 decode(ticket
, iter
);
189 ldout(cct
, 10) << __func__
<< " ticket.secret_id=" << ticket
.secret_id
192 ldout(cct
, 10) << __func__
<< " service "
193 << ceph_entity_type_name(service_id
)
194 << " secret_id " << ticket
.secret_id
195 << " session_key " << msg_a
.session_key
196 << " validity=" << msg_a
.validity
<< dendl
;
197 session_key
= msg_a
.session_key
;
198 if (!msg_a
.validity
.is_zero()) {
199 expires
= ceph_clock_now();
200 expires
+= msg_a
.validity
;
201 renew_after
= expires
;
202 renew_after
-= ((double)msg_a
.validity
.sec() / 4);
203 ldout(cct
, 10) << __func__
<< " ticket expires=" << expires
204 << " renew_after=" << renew_after
<< dendl
;
207 have_key_flag
= true;
209 } catch (ceph::buffer::error
& e
) {
210 ldout(cct
, 1) << __func__
<< " decode error: " << e
.what() << dendl
;
215 bool CephXTicketHandler::have_key()
218 have_key_flag
= ceph_clock_now() < expires
;
221 return have_key_flag
;
224 bool CephXTicketHandler::need_key() const
227 return (!expires
.is_zero()) && (ceph_clock_now() >= renew_after
);
233 bool CephXTicketManager::have_key(uint32_t service_id
)
235 auto iter
= tickets_map
.find(service_id
);
236 if (iter
== tickets_map
.end())
238 return iter
->second
.have_key();
241 bool CephXTicketManager::need_key(uint32_t service_id
) const
243 auto iter
= tickets_map
.find(service_id
);
244 if (iter
== tickets_map
.end())
246 return iter
->second
.need_key();
249 void CephXTicketManager::set_have_need_key(uint32_t service_id
, uint32_t& have
, uint32_t& need
)
251 auto iter
= tickets_map
.find(service_id
);
252 if (iter
== tickets_map
.end()) {
255 ldout(cct
, 10) << "set_have_need_key no handler for service "
256 << ceph_entity_type_name(service_id
) << dendl
;
260 //ldout(cct, 10) << "set_have_need_key service " << ceph_entity_type_name(service_id)
261 //<< " (" << service_id << ")"
262 //<< " need=" << iter->second.need_key() << " have=" << iter->second.have_key() << dendl;
263 if (iter
->second
.need_key())
268 if (iter
->second
.have_key())
274 void CephXTicketManager::invalidate_ticket(uint32_t service_id
)
276 auto iter
= tickets_map
.find(service_id
);
277 if (iter
!= tickets_map
.end())
278 iter
->second
.invalidate_ticket();
282 * PRINCIPAL: verify our attempt to authenticate succeeded. fill out
283 * this ServiceTicket with the result.
285 bool CephXTicketManager::verify_service_ticket_reply(CryptoKey
& secret
,
286 bufferlist::const_iterator
& indata
)
288 __u8 service_ticket_reply_v
;
291 decode(service_ticket_reply_v
, indata
);
293 } catch (ceph::buffer::error
& e
) {
294 ldout(cct
, 10) << __func__
<< " failed to decode ticket v or count: "
295 << e
.what() << dendl
;
297 ldout(cct
, 10) << "verify_service_ticket_reply got " << num
<< " keys" << dendl
;
299 for (int i
=0; i
<(int)num
; i
++) {
302 decode(type
, indata
);
303 } catch (ceph::buffer::error
& e
) {
304 ldout(cct
, 10) << __func__
<< " failed to decode ticket type: " << e
.what()
307 ldout(cct
, 10) << "got key for service_id " << ceph_entity_type_name(type
) << dendl
;
308 CephXTicketHandler
& handler
= get_handler(type
);
309 if (!handler
.verify_service_ticket_reply(secret
, indata
)) {
312 handler
.service_id
= type
;
319 * PRINCIPAL: build authorizer to access the service.
321 * ticket, {timestamp}^session_key
323 CephXAuthorizer
*CephXTicketHandler::build_authorizer(uint64_t global_id
) const
325 CephXAuthorizer
*a
= new CephXAuthorizer(cct
);
326 a
->session_key
= session_key
;
327 cct
->random()->get_bytes((char*)&a
->nonce
, sizeof(a
->nonce
));
329 __u8 authorizer_v
= 1; // see AUTH_MODE_* in Auth.h
330 encode(authorizer_v
, a
->bl
);
331 encode(global_id
, a
->bl
);
332 encode(service_id
, a
->bl
);
334 encode(ticket
, a
->bl
);
338 msg
.nonce
= a
->nonce
;
341 if (encode_encrypt(cct
, msg
, session_key
, a
->bl
, error
)) {
342 ldout(cct
, 0) << "failed to encrypt authorizer: " << error
<< dendl
;
350 * PRINCIPAL: build authorizer to access the service.
352 * ticket, {timestamp}^session_key
354 CephXAuthorizer
*CephXTicketManager::build_authorizer(uint32_t service_id
) const
356 auto iter
= tickets_map
.find(service_id
);
357 if (iter
== tickets_map
.end()) {
358 ldout(cct
, 0) << "no TicketHandler for service "
359 << ceph_entity_type_name(service_id
) << dendl
;
363 const CephXTicketHandler
& handler
= iter
->second
;
364 return handler
.build_authorizer(global_id
);
367 void CephXTicketManager::validate_tickets(uint32_t mask
, uint32_t& have
, uint32_t& need
)
371 for (i
= 1; i
<=mask
; i
<<=1) {
373 set_have_need_key(i
, have
, need
);
376 ldout(cct
, 10) << "validate_tickets want " << mask
<< " have " << have
377 << " need " << need
<< dendl
;
380 bool cephx_decode_ticket(CephContext
*cct
, KeyStore
*keys
,
382 const CephXTicketBlob
& ticket_blob
,
383 CephXServiceTicketInfo
& ticket_info
)
385 uint64_t secret_id
= ticket_blob
.secret_id
;
386 CryptoKey service_secret
;
388 if (!ticket_blob
.blob
.length()) {
392 if (secret_id
== (uint64_t)-1) {
393 if (!keys
->get_secret(cct
->_conf
->name
, service_secret
)) {
394 ldout(cct
, 0) << "ceph_decode_ticket could not get general service secret for service_id="
395 << ceph_entity_type_name(service_id
) << " secret_id=" << secret_id
<< dendl
;
399 if (!keys
->get_service_secret(service_id
, secret_id
, service_secret
)) {
400 ldout(cct
, 0) << "ceph_decode_ticket could not get service secret for service_id="
401 << ceph_entity_type_name(service_id
) << " secret_id=" << secret_id
<< dendl
;
407 decode_decrypt_enc_bl(cct
, ticket_info
, service_secret
, ticket_blob
.blob
, error
);
408 if (!error
.empty()) {
409 ldout(cct
, 0) << "ceph_decode_ticket could not decrypt ticket info. error:"
418 * SERVICE: verify authorizer and generate reply authorizer
420 * {timestamp + 1}^session_key
422 bool cephx_verify_authorizer(CephContext
*cct
, const KeyStore
& keys
,
423 bufferlist::const_iterator
& indata
,
424 size_t connection_secret_required_len
,
425 CephXServiceTicketInfo
& ticket_info
,
426 std::unique_ptr
<AuthAuthorizerChallenge
> *challenge
,
427 std::string
*connection_secret
,
428 bufferlist
*reply_bl
)
433 CryptoKey service_secret
;
435 CephXTicketBlob ticket
;
438 decode(authorizer_v
, indata
);
439 decode(global_id
, indata
);
440 decode(service_id
, indata
);
441 decode(ticket
, indata
);
442 } catch (ceph::buffer::end_of_buffer
&e
) {
446 ldout(cct
, 10) << "verify_authorizer decrypted service "
447 << ceph_entity_type_name(service_id
)
448 << " secret_id=" << ticket
.secret_id
<< dendl
;
450 if (ticket
.secret_id
== (uint64_t)-1) {
452 name
.set_type(service_id
);
453 if (!keys
.get_secret(name
, service_secret
)) {
454 ldout(cct
, 0) << "verify_authorizer could not get general service secret for service "
455 << ceph_entity_type_name(service_id
) << " secret_id=" << ticket
.secret_id
<< dendl
;
459 if (!keys
.get_service_secret(service_id
, ticket
.secret_id
, service_secret
)) {
460 ldout(cct
, 0) << "verify_authorizer could not get service secret for service "
461 << ceph_entity_type_name(service_id
) << " secret_id=" << ticket
.secret_id
<< dendl
;
462 if (cct
->_conf
->auth_debug
&& ticket
.secret_id
== 0)
463 ceph_abort_msg("got secret_id=0");
468 if (!service_secret
.get_secret().length())
469 error
= "invalid key"; // Bad key?
471 decode_decrypt_enc_bl(cct
, ticket_info
, service_secret
, ticket
.blob
, error
);
472 if (!error
.empty()) {
473 ldout(cct
, 0) << "verify_authorizer could not decrypt ticket info: error: "
478 if (ticket_info
.ticket
.global_id
!= global_id
) {
479 ldout(cct
, 0) << "verify_authorizer global_id mismatch: declared id=" << global_id
480 << " ticket_id=" << ticket_info
.ticket
.global_id
<< dendl
;
484 ldout(cct
, 10) << "verify_authorizer global_id=" << global_id
<< dendl
;
487 CephXAuthorize auth_msg
;
488 if (decode_decrypt(cct
, auth_msg
, ticket_info
.session_key
, indata
, error
)) {
489 ldout(cct
, 0) << "verify_authorizercould not decrypt authorize request with error: "
495 auto *c
= static_cast<CephXAuthorizeChallenge
*>(challenge
->get());
496 if (!auth_msg
.have_challenge
|| !c
) {
497 c
= new CephXAuthorizeChallenge
;
499 cct
->random()->get_bytes((char*)&c
->server_challenge
, sizeof(c
->server_challenge
));
500 ldout(cct
,10) << __func__
<< " adding server_challenge " << c
->server_challenge
503 encode_encrypt_enc_bl(cct
, *c
, ticket_info
.session_key
, *reply_bl
, error
);
504 if (!error
.empty()) {
505 ldout(cct
, 10) << "verify_authorizer: encode_encrypt error: " << error
<< dendl
;
510 ldout(cct
, 10) << __func__
<< " got server_challenge+1 "
511 << auth_msg
.server_challenge_plus_one
512 << " expecting " << c
->server_challenge
+ 1 << dendl
;
513 if (c
->server_challenge
+ 1 != auth_msg
.server_challenge_plus_one
) {
520 * {timestamp + 1}^session_key
522 CephXAuthorizeReply reply
;
523 // reply.trans_id = auth_msg.trans_id;
524 reply
.nonce_plus_one
= auth_msg
.nonce
+ 1;
525 if (connection_secret
) {
526 // generate a connection secret
527 connection_secret
->resize(connection_secret_required_len
);
528 if (connection_secret_required_len
) {
530 std::random_device rd
;
531 std::generate_n(connection_secret
->data(),
532 connection_secret_required_len
,
533 std::default_random_engine
{rd()});
535 cct
->random()->get_bytes(connection_secret
->data(),
536 connection_secret_required_len
);
539 reply
.connection_secret
= *connection_secret
;
541 if (encode_encrypt(cct
, reply
, ticket_info
.session_key
, *reply_bl
, error
)) {
542 ldout(cct
, 10) << "verify_authorizer: encode_encrypt error: " << error
<< dendl
;
546 ldout(cct
, 10) << "verify_authorizer ok nonce " << hex
<< auth_msg
.nonce
<< dec
547 << " reply_bl.length()=" << reply_bl
->length() << dendl
;
551 bool CephXAuthorizer::verify_reply(bufferlist::const_iterator
& indata
,
552 std::string
*connection_secret
)
554 CephXAuthorizeReply reply
;
557 if (decode_decrypt(cct
, reply
, session_key
, indata
, error
)) {
558 ldout(cct
, 0) << "verify_reply couldn't decrypt with error: " << error
<< dendl
;
562 uint64_t expect
= nonce
+ 1;
563 if (expect
!= reply
.nonce_plus_one
) {
564 ldout(cct
, 0) << "verify_authorizer_reply bad nonce got " << reply
.nonce_plus_one
<< " expected " << expect
565 << " sent " << nonce
<< dendl
;
569 if (connection_secret
&&
570 reply
.connection_secret
.size()) {
571 *connection_secret
= reply
.connection_secret
;
576 bool CephXAuthorizer::add_challenge(CephContext
*cct
,
577 const bufferlist
& challenge
)
584 auto p
= challenge
.begin();
587 CephXAuthorizeChallenge ch
;
588 decode_decrypt_enc_bl(cct
, ch
, session_key
, challenge
, error
);
589 if (!error
.empty()) {
590 ldout(cct
, 0) << "failed to decrypt challenge (" << challenge
.length() << " bytes): "
594 msg
.have_challenge
= true;
595 msg
.server_challenge_plus_one
= ch
.server_challenge
+ 1;
599 if (encode_encrypt(cct
, msg
, session_key
, bl
, error
)) {
600 ldout(cct
, 0) << __func__
<< " failed to encrypt authorizer: " << error
<< dendl
;