]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- |
2 | // vim: ts=8 sw=2 smarttab | |
3 | /* | |
4 | * Ceph - scalable distributed file system | |
5 | * | |
6 | * Copyright (C) 2009-2011 New Dream Network | |
7 | * | |
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. | |
12 | * | |
13 | */ | |
14 | ||
15 | #include "CephxProtocol.h" | |
16 | #include "common/Clock.h" | |
17 | #include "common/config.h" | |
18 | #include "common/debug.h" | |
19 | #include "include/buffer.h" | |
20 | ||
21 | #define dout_subsys ceph_subsys_auth | |
22 | #undef dout_prefix | |
23 | #define dout_prefix *_dout << "cephx: " | |
24 | ||
25 | ||
26 | ||
27 | void cephx_calc_client_server_challenge(CephContext *cct, CryptoKey& secret, uint64_t server_challenge, | |
28 | uint64_t client_challenge, uint64_t *key, std::string &error) | |
29 | { | |
30 | CephXChallengeBlob b; | |
31 | b.server_challenge = server_challenge; | |
32 | b.client_challenge = client_challenge; | |
33 | ||
34 | bufferlist enc; | |
35 | if (encode_encrypt(cct, b, secret, enc, error)) | |
36 | return; | |
37 | ||
38 | uint64_t k = 0; | |
39 | const uint64_t *p = (const uint64_t *)enc.c_str(); | |
40 | for (int pos = 0; pos + sizeof(k) <= enc.length(); pos+=sizeof(k), p++) | |
41 | k ^= mswab(*p); | |
42 | *key = k; | |
43 | } | |
44 | ||
45 | ||
46 | /* | |
47 | * Authentication | |
48 | */ | |
49 | ||
50 | bool cephx_build_service_ticket_blob(CephContext *cct, CephXSessionAuthInfo& info, | |
51 | CephXTicketBlob& blob) | |
52 | { | |
53 | CephXServiceTicketInfo ticket_info; | |
54 | ticket_info.session_key = info.session_key; | |
55 | ticket_info.ticket = info.ticket; | |
56 | ticket_info.ticket.caps = info.ticket.caps; | |
57 | ||
58 | ldout(cct, 10) << "build_service_ticket service " << ceph_entity_type_name(info.service_id) | |
59 | << " secret_id " << info.secret_id | |
60 | << " ticket_info.ticket.name=" << ticket_info.ticket.name.to_str() << dendl; | |
61 | blob.secret_id = info.secret_id; | |
62 | std::string error; | |
63 | if (!info.service_secret.get_secret().length()) | |
64 | error = "invalid key"; // Bad key? | |
65 | else | |
66 | encode_encrypt_enc_bl(cct, ticket_info, info.service_secret, blob.blob, error); | |
67 | if (!error.empty()) { | |
68 | ldout(cct, -1) << "cephx_build_service_ticket_blob failed with error " | |
69 | << error << dendl; | |
70 | return false; | |
71 | } | |
72 | return true; | |
73 | } | |
74 | ||
75 | /* | |
76 | * AUTH SERVER: authenticate | |
77 | * | |
78 | * Authenticate principal, respond with AuthServiceTicketInfo | |
79 | * | |
80 | * {session key, validity}^principal_secret | |
81 | * {principal_ticket, session key}^service_secret ... "enc_ticket" | |
82 | */ | |
83 | bool cephx_build_service_ticket_reply(CephContext *cct, | |
84 | CryptoKey& principal_secret, | |
85 | vector<CephXSessionAuthInfo> ticket_info_vec, | |
86 | bool should_encrypt_ticket, | |
87 | CryptoKey& ticket_enc_key, | |
88 | bufferlist& reply) | |
89 | { | |
90 | __u8 service_ticket_reply_v = 1; | |
91 | ::encode(service_ticket_reply_v, reply); | |
92 | ||
93 | uint32_t num = ticket_info_vec.size(); | |
94 | ::encode(num, reply); | |
95 | ldout(cct, 10) << "build_service_ticket_reply encoding " << num | |
96 | << " tickets with secret " << principal_secret << dendl; | |
97 | ||
98 | for (vector<CephXSessionAuthInfo>::iterator ticket_iter = ticket_info_vec.begin(); | |
99 | ticket_iter != ticket_info_vec.end(); | |
100 | ++ticket_iter) { | |
101 | CephXSessionAuthInfo& info = *ticket_iter; | |
102 | ::encode(info.service_id, reply); | |
103 | ||
104 | __u8 service_ticket_v = 1; | |
105 | ::encode(service_ticket_v, reply); | |
106 | ||
107 | CephXServiceTicket msg_a; | |
108 | msg_a.session_key = info.session_key; | |
109 | msg_a.validity = info.validity; | |
110 | std::string error; | |
111 | if (encode_encrypt(cct, msg_a, principal_secret, reply, error)) { | |
112 | ldout(cct, -1) << "error encoding encrypted: " << error << dendl; | |
113 | return false; | |
114 | } | |
115 | ||
116 | bufferlist service_ticket_bl; | |
117 | CephXTicketBlob blob; | |
118 | if (!cephx_build_service_ticket_blob(cct, info, blob)) { | |
119 | return false; | |
120 | } | |
121 | ::encode(blob, service_ticket_bl); | |
122 | ||
123 | ldout(cct, 30) << "service_ticket_blob is "; | |
124 | service_ticket_bl.hexdump(*_dout); | |
125 | *_dout << dendl; | |
126 | ||
127 | ::encode((__u8)should_encrypt_ticket, reply); | |
128 | if (should_encrypt_ticket) { | |
129 | if (encode_encrypt(cct, service_ticket_bl, ticket_enc_key, reply, error)) { | |
130 | ldout(cct, -1) << "error encoding encrypted ticket: " << error << dendl; | |
131 | return false; | |
132 | } | |
133 | } else { | |
134 | ::encode(service_ticket_bl, reply); | |
135 | } | |
136 | } | |
137 | return true; | |
138 | } | |
139 | ||
140 | /* | |
141 | * PRINCIPAL: verify our attempt to authenticate succeeded. fill out | |
142 | * this ServiceTicket with the result. | |
143 | */ | |
144 | bool CephXTicketHandler::verify_service_ticket_reply(CryptoKey& secret, | |
145 | bufferlist::iterator& indata) | |
146 | { | |
147 | __u8 service_ticket_v; | |
148 | ::decode(service_ticket_v, indata); | |
149 | ||
150 | CephXServiceTicket msg_a; | |
151 | std::string error; | |
152 | if (decode_decrypt(cct, msg_a, secret, indata, error)) { | |
153 | ldout(cct, 0) << "verify_service_ticket_reply: failed decode_decrypt, error is: " << error << dendl; | |
154 | return false; | |
155 | } | |
156 | ||
157 | __u8 ticket_enc; | |
158 | ::decode(ticket_enc, indata); | |
159 | ||
160 | bufferlist service_ticket_bl; | |
161 | if (ticket_enc) { | |
162 | ldout(cct, 10) << " got encrypted ticket" << dendl; | |
163 | std::string error; | |
164 | if (decode_decrypt(cct, service_ticket_bl, session_key, indata, error)) { | |
165 | ldout(cct, 10) << "verify_service_ticket_reply: decode_decrypt failed " | |
166 | << "with " << error << dendl; | |
167 | return false; | |
168 | } | |
169 | } else { | |
170 | ::decode(service_ticket_bl, indata); | |
171 | } | |
172 | bufferlist::iterator iter = service_ticket_bl.begin(); | |
173 | ::decode(ticket, iter); | |
174 | ldout(cct, 10) << " ticket.secret_id=" << ticket.secret_id << dendl; | |
175 | ||
176 | ldout(cct, 10) << "verify_service_ticket_reply service " << ceph_entity_type_name(service_id) | |
177 | << " secret_id " << ticket.secret_id | |
178 | << " session_key " << msg_a.session_key | |
179 | << " validity=" << msg_a.validity << dendl; | |
180 | session_key = msg_a.session_key; | |
181 | if (!msg_a.validity.is_zero()) { | |
182 | expires = ceph_clock_now(); | |
183 | expires += msg_a.validity; | |
184 | renew_after = expires; | |
185 | renew_after -= ((double)msg_a.validity.sec() / 4); | |
186 | ldout(cct, 10) << "ticket expires=" << expires << " renew_after=" << renew_after << dendl; | |
187 | } | |
188 | ||
189 | have_key_flag = true; | |
190 | return true; | |
191 | } | |
192 | ||
193 | bool CephXTicketHandler::have_key() | |
194 | { | |
195 | if (have_key_flag) { | |
196 | have_key_flag = ceph_clock_now() < expires; | |
197 | } | |
198 | ||
199 | return have_key_flag; | |
200 | } | |
201 | ||
202 | bool CephXTicketHandler::need_key() const | |
203 | { | |
204 | if (have_key_flag) { | |
205 | return (!expires.is_zero()) && (ceph_clock_now() >= renew_after); | |
206 | } | |
207 | ||
208 | return true; | |
209 | } | |
210 | ||
211 | bool CephXTicketManager::have_key(uint32_t service_id) | |
212 | { | |
213 | map<uint32_t, CephXTicketHandler>::iterator iter = tickets_map.find(service_id); | |
214 | if (iter == tickets_map.end()) | |
215 | return false; | |
216 | return iter->second.have_key(); | |
217 | } | |
218 | ||
219 | bool CephXTicketManager::need_key(uint32_t service_id) const | |
220 | { | |
221 | map<uint32_t, CephXTicketHandler>::const_iterator iter = tickets_map.find(service_id); | |
222 | if (iter == tickets_map.end()) | |
223 | return true; | |
224 | return iter->second.need_key(); | |
225 | } | |
226 | ||
227 | void CephXTicketManager::set_have_need_key(uint32_t service_id, uint32_t& have, uint32_t& need) | |
228 | { | |
229 | map<uint32_t, CephXTicketHandler>::iterator iter = tickets_map.find(service_id); | |
230 | if (iter == tickets_map.end()) { | |
231 | have &= ~service_id; | |
232 | need |= service_id; | |
233 | ldout(cct, 10) << "set_have_need_key no handler for service " | |
234 | << ceph_entity_type_name(service_id) << dendl; | |
235 | return; | |
236 | } | |
237 | ||
238 | //ldout(cct, 10) << "set_have_need_key service " << ceph_entity_type_name(service_id) | |
239 | //<< " (" << service_id << ")" | |
240 | //<< " need=" << iter->second.need_key() << " have=" << iter->second.have_key() << dendl; | |
241 | if (iter->second.need_key()) | |
242 | need |= service_id; | |
243 | else | |
244 | need &= ~service_id; | |
245 | ||
246 | if (iter->second.have_key()) | |
247 | have |= service_id; | |
248 | else | |
249 | have &= ~service_id; | |
250 | } | |
251 | ||
252 | void CephXTicketManager::invalidate_ticket(uint32_t service_id) | |
253 | { | |
254 | map<uint32_t, CephXTicketHandler>::iterator iter = tickets_map.find(service_id); | |
255 | if (iter != tickets_map.end()) | |
256 | iter->second.invalidate_ticket(); | |
257 | } | |
258 | ||
259 | /* | |
260 | * PRINCIPAL: verify our attempt to authenticate succeeded. fill out | |
261 | * this ServiceTicket with the result. | |
262 | */ | |
263 | bool CephXTicketManager::verify_service_ticket_reply(CryptoKey& secret, | |
264 | bufferlist::iterator& indata) | |
265 | { | |
266 | __u8 service_ticket_reply_v; | |
267 | ::decode(service_ticket_reply_v, indata); | |
268 | ||
269 | uint32_t num; | |
270 | ::decode(num, indata); | |
271 | ldout(cct, 10) << "verify_service_ticket_reply got " << num << " keys" << dendl; | |
272 | ||
273 | for (int i=0; i<(int)num; i++) { | |
274 | uint32_t type; | |
275 | ::decode(type, indata); | |
276 | ldout(cct, 10) << "got key for service_id " << ceph_entity_type_name(type) << dendl; | |
277 | CephXTicketHandler& handler = get_handler(type); | |
278 | if (!handler.verify_service_ticket_reply(secret, indata)) { | |
279 | return false; | |
280 | } | |
281 | handler.service_id = type; | |
282 | } | |
283 | ||
284 | if (!indata.end()) | |
285 | return false; | |
286 | ||
287 | return true; | |
288 | } | |
289 | ||
290 | /* | |
291 | * PRINCIPAL: build authorizer to access the service. | |
292 | * | |
293 | * ticket, {timestamp}^session_key | |
294 | */ | |
295 | CephXAuthorizer *CephXTicketHandler::build_authorizer(uint64_t global_id) const | |
296 | { | |
297 | CephXAuthorizer *a = new CephXAuthorizer(cct); | |
298 | a->session_key = session_key; | |
299 | a->nonce = ((uint64_t)rand() << 32) + rand(); | |
300 | ||
301 | __u8 authorizer_v = 1; | |
302 | ::encode(authorizer_v, a->bl); | |
303 | ::encode(global_id, a->bl); | |
304 | ::encode(service_id, a->bl); | |
305 | ||
306 | ::encode(ticket, a->bl); | |
307 | ||
308 | CephXAuthorize msg; | |
309 | msg.nonce = a->nonce; | |
310 | ||
311 | std::string error; | |
312 | if (encode_encrypt(cct, msg, session_key, a->bl, error)) { | |
313 | ldout(cct, 0) << "failed to encrypt authorizer: " << error << dendl; | |
314 | delete a; | |
315 | return 0; | |
316 | } | |
317 | return a; | |
318 | } | |
319 | ||
320 | /* | |
321 | * PRINCIPAL: build authorizer to access the service. | |
322 | * | |
323 | * ticket, {timestamp}^session_key | |
324 | */ | |
325 | CephXAuthorizer *CephXTicketManager::build_authorizer(uint32_t service_id) const | |
326 | { | |
327 | map<uint32_t, CephXTicketHandler>::const_iterator iter = tickets_map.find(service_id); | |
328 | if (iter == tickets_map.end()) { | |
329 | ldout(cct, 0) << "no TicketHandler for service " | |
330 | << ceph_entity_type_name(service_id) << dendl; | |
331 | return NULL; | |
332 | } | |
333 | ||
334 | const CephXTicketHandler& handler = iter->second; | |
335 | return handler.build_authorizer(global_id); | |
336 | } | |
337 | ||
338 | void CephXTicketManager::validate_tickets(uint32_t mask, uint32_t& have, uint32_t& need) | |
339 | { | |
340 | uint32_t i; | |
341 | need = 0; | |
342 | for (i = 1; i<=mask; i<<=1) { | |
343 | if (mask & i) { | |
344 | set_have_need_key(i, have, need); | |
345 | } | |
346 | } | |
347 | ldout(cct, 10) << "validate_tickets want " << mask << " have " << have | |
348 | << " need " << need << dendl; | |
349 | } | |
350 | ||
351 | bool cephx_decode_ticket(CephContext *cct, KeyStore *keys, uint32_t service_id, | |
352 | CephXTicketBlob& ticket_blob, CephXServiceTicketInfo& ticket_info) | |
353 | { | |
354 | uint64_t secret_id = ticket_blob.secret_id; | |
355 | CryptoKey service_secret; | |
356 | ||
357 | if (!ticket_blob.blob.length()) { | |
358 | return false; | |
359 | } | |
360 | ||
361 | if (secret_id == (uint64_t)-1) { | |
362 | if (!keys->get_secret(cct->_conf->name, service_secret)) { | |
363 | ldout(cct, 0) << "ceph_decode_ticket could not get general service secret for service_id=" | |
364 | << ceph_entity_type_name(service_id) << " secret_id=" << secret_id << dendl; | |
365 | return false; | |
366 | } | |
367 | } else { | |
368 | if (!keys->get_service_secret(service_id, secret_id, service_secret)) { | |
369 | ldout(cct, 0) << "ceph_decode_ticket could not get service secret for service_id=" | |
370 | << ceph_entity_type_name(service_id) << " secret_id=" << secret_id << dendl; | |
371 | return false; | |
372 | } | |
373 | } | |
374 | ||
375 | std::string error; | |
376 | decode_decrypt_enc_bl(cct, ticket_info, service_secret, ticket_blob.blob, error); | |
377 | if (!error.empty()) { | |
378 | ldout(cct, 0) << "ceph_decode_ticket could not decrypt ticket info. error:" | |
379 | << error << dendl; | |
380 | return false; | |
381 | } | |
382 | ||
383 | return true; | |
384 | } | |
385 | ||
386 | /* | |
387 | * SERVICE: verify authorizer and generate reply authorizer | |
388 | * | |
389 | * {timestamp + 1}^session_key | |
390 | */ | |
391 | bool cephx_verify_authorizer(CephContext *cct, KeyStore *keys, | |
392 | bufferlist::iterator& indata, | |
393 | CephXServiceTicketInfo& ticket_info, bufferlist& reply_bl) | |
394 | { | |
395 | __u8 authorizer_v; | |
396 | uint32_t service_id; | |
397 | uint64_t global_id; | |
398 | CryptoKey service_secret; | |
399 | // ticket blob | |
400 | CephXTicketBlob ticket; | |
401 | ||
402 | ||
403 | try { | |
404 | ::decode(authorizer_v, indata); | |
405 | ::decode(global_id, indata); | |
406 | ::decode(service_id, indata); | |
407 | ::decode(ticket, indata); | |
408 | } catch (buffer::end_of_buffer &e) { | |
409 | // Unable to decode! | |
410 | return false; | |
411 | } | |
412 | ldout(cct, 10) << "verify_authorizer decrypted service " | |
413 | << ceph_entity_type_name(service_id) | |
414 | << " secret_id=" << ticket.secret_id << dendl; | |
415 | ||
416 | if (ticket.secret_id == (uint64_t)-1) { | |
417 | EntityName name; | |
418 | name.set_type(service_id); | |
419 | if (!keys->get_secret(name, service_secret)) { | |
420 | ldout(cct, 0) << "verify_authorizer could not get general service secret for service " | |
421 | << ceph_entity_type_name(service_id) << " secret_id=" << ticket.secret_id << dendl; | |
422 | return false; | |
423 | } | |
424 | } else { | |
425 | if (!keys->get_service_secret(service_id, ticket.secret_id, service_secret)) { | |
426 | ldout(cct, 0) << "verify_authorizer could not get service secret for service " | |
427 | << ceph_entity_type_name(service_id) << " secret_id=" << ticket.secret_id << dendl; | |
428 | if (cct->_conf->auth_debug && ticket.secret_id == 0) | |
429 | assert(0 == "got secret_id=0"); | |
430 | return false; | |
431 | } | |
432 | } | |
433 | std::string error; | |
434 | if (!service_secret.get_secret().length()) | |
435 | error = "invalid key"; // Bad key? | |
436 | else | |
437 | decode_decrypt_enc_bl(cct, ticket_info, service_secret, ticket.blob, error); | |
438 | if (!error.empty()) { | |
439 | ldout(cct, 0) << "verify_authorizer could not decrypt ticket info: error: " | |
440 | << error << dendl; | |
441 | return false; | |
442 | } | |
443 | ||
444 | if (ticket_info.ticket.global_id != global_id) { | |
445 | ldout(cct, 0) << "verify_authorizer global_id mismatch: declared id=" << global_id | |
446 | << " ticket_id=" << ticket_info.ticket.global_id << dendl; | |
447 | return false; | |
448 | } | |
449 | ||
450 | ldout(cct, 10) << "verify_authorizer global_id=" << global_id << dendl; | |
451 | ||
452 | // CephXAuthorize | |
453 | CephXAuthorize auth_msg; | |
454 | if (decode_decrypt(cct, auth_msg, ticket_info.session_key, indata, error)) { | |
455 | ldout(cct, 0) << "verify_authorizercould not decrypt authorize request with error: " | |
456 | << error << dendl; | |
457 | return false; | |
458 | } | |
459 | ||
460 | /* | |
461 | * Reply authorizer: | |
462 | * {timestamp + 1}^session_key | |
463 | */ | |
464 | CephXAuthorizeReply reply; | |
465 | // reply.trans_id = auth_msg.trans_id; | |
466 | reply.nonce_plus_one = auth_msg.nonce + 1; | |
467 | if (encode_encrypt(cct, reply, ticket_info.session_key, reply_bl, error)) { | |
468 | ldout(cct, 10) << "verify_authorizer: encode_encrypt error: " << error << dendl; | |
469 | return false; | |
470 | } | |
471 | ||
472 | ldout(cct, 10) << "verify_authorizer ok nonce " << hex << auth_msg.nonce << dec | |
473 | << " reply_bl.length()=" << reply_bl.length() << dendl; | |
474 | return true; | |
475 | } | |
476 | ||
477 | bool CephXAuthorizer::verify_reply(bufferlist::iterator& indata) | |
478 | { | |
479 | CephXAuthorizeReply reply; | |
480 | ||
481 | std::string error; | |
482 | if (decode_decrypt(cct, reply, session_key, indata, error)) { | |
483 | ldout(cct, 0) << "verify_reply couldn't decrypt with error: " << error << dendl; | |
484 | return false; | |
485 | } | |
486 | ||
487 | uint64_t expect = nonce + 1; | |
488 | if (expect != reply.nonce_plus_one) { | |
489 | ldout(cct, 0) << "verify_authorizer_reply bad nonce got " << reply.nonce_plus_one << " expected " << expect | |
490 | << " sent " << nonce << dendl; | |
491 | return false; | |
492 | } | |
493 | return true; | |
494 | } | |
495 |