Fix some bugs when testing opensds ansible
[stor4nfv.git] / src / ceph / doc / dev / cephx_protocol.rst
1 ============================================================
2 A Detailed Description of the Cephx Authentication Protocol
3 ============================================================
4 Peter Reiher
5 7/13/12
6
7 This document provides deeper detail on the Cephx authorization protocol whose high level flow 
8 is described in the memo by Yehuda (12/19/09).  Because this memo discusses details of 
9 routines called and variables used, it represents a snapshot.  The code might be changed 
10 subsequent to the creation of this document, and the document is not likely to be updated in
11 lockstep.  With luck, code comments will indicate major changes in the way the protocol is
12 implemented.
13
14 Introduction
15 -------------
16
17 The basic idea of the protocol is based on Kerberos.  A client wishes to obtain something from 
18 a server.  The server will only offer the requested service to authorized clients.  Rather 
19 than requiring each server to deal with authentication and authorization issues, the system 
20 uses an authorization server.  Thus, the client must first communicate with the authorization 
21 server to authenticate itself and to obtain credentials that will grant it access to the
22 service it wants.
23
24 Authorization is not the same as authentication.  Authentication provides evidence that some 
25 party is who it claims to be.  Authorization provides evidence that a particular party is
26 allowed to do something.  Generally, secure authorization implies secure authentication 
27 (since without authentication, you may authorize something for an imposter), but the reverse 
28 is not necessarily true.  One can authenticate without authorizing.  The purpose 
29 of this protocol is to authorize.
30
31 The basic approach is to use symmetric cryptography throughout.  Each client C has its own
32 secret key, known only to itself and the authorization server A.  Each server S has its own
33 secret key, known only to itself and the authorization server A.  Authorization information 
34 will be passed in tickets, encrypted with the secret key of the entity that offers the service.
35 There will be a ticket that A gives to C, which permits C to ask A for other tickets.  This 
36 ticket will be encrypted with A's key, since A is the one who needs to check it.  There will 
37 later be tickets that A issues that allow C to communicate with S to ask for service.  These 
38 tickets will be encrypted with S's key, since S needs to check them.   Since we wish to provide 
39 security of the communications, as well, session keys are set up along with the tickets.  
40 Currently, those session keys are only used for authentication purposes during this protocol 
41 and the handshake between the client C and the server S, when the client provides its service 
42 ticket.  They could be used for authentication or secrecy throughout, with some changes to 
43 the system.
44
45 Several parties need to prove something to each other if this protocol is to achieve its 
46 desired security effects.
47
48 1.  The client C must prove to the authenticator A that it really is C.  Since everything
49 is being done via messages, the client must also prove that the message proving authenticity
50 is fresh, and is not being replayed by an attacker.
51
52 2.  The authenticator A must prove to client C that it really is the authenticator.  Again,
53 proof that replay is not occurring is also required.
54
55 3.  A and C must securely share a session key to be used for distribution of later
56 authorization material between them.  Again, no replay is allowable, and the key must be
57 known only to A and C.
58
59 4.  A must receive evidence from C that allows A to look up C's authorized operations with
60 server S.  
61
62 5.  C must receive a ticket from A that will prove to S that C can perform its authorized
63 operations.   This ticket must be usable only by C.
64
65 6.  C must receive from A a session key to protect the communications between C and S.  The
66 session key must be fresh and not the result of a replay.
67
68 Getting Started With Authorization
69 -----------------------------------
70
71 When the client first needs to get service, it contacts the monitor.  At the moment, it has 
72 no tickets.  Therefore, it uses the "unknown" protocol to talk to the monitor.  This protocol 
73 is specified as ``CEPH_AUTH_UNKNOWN``.  The monitor also takes on the authentication server 
74 role, A.  The remainder of the communications will use the cephx protocol (most of whose code 
75 will be found in files in ``auth/cephx``).  This protocol is responsible for creating and 
76 communicating the tickets spoken of above.  
77
78 Currently, this document does not follow the pre-cephx protocol flow.  It starts up at the 
79 point where the client has contacted the server and is ready to start the cephx protocol itself.
80
81 Once we are in the cephx protocol, we can get the tickets.  First, C needs a ticket that 
82 allows secure communications with A.  This ticket can then be used to obtain other tickets. 
83 This is phase I of the protocol, and consists of a send from C to A and a response from A to C.
84 Then, C needs a ticket to allow it to talk to S to get services.  This is phase II of the 
85 protocol, and consists of a send from C to A and a response from A to C.
86
87 Phase I:
88 --------
89
90 The client is set up to know that it needs certain things, using a variable called ``need``, 
91 which is part of the ``AuthClientHandler`` class, which the ``CephxClientHandler`` inherits 
92 from.  At this point, one thing that's encoded in the ``need`` variable is 
93 ``CEPH_ENTITY_TYPE_AUTH``, indicating that we need to start the authentication protocol 
94 from scratch.  Since we're always talking to the same authorization server, if we've gone 
95 through this step of the protocol before (and the resulting ticket/session hasn't timed out), 
96 we can skip this step and just ask for client tickets.  But it must be done initially, and 
97 we'll assume that we are in that state.
98
99 The message C sends to A in phase I is build in ``CephxClientHandler::build_request()`` (in 
100 ``auth/cephx/CephxClientHandler.cc``).  This routine is used for more than one purpose.  
101 In this case, we first call ``validate_tickets()`` (from routine 
102 ``CephXTicektManager::validate_tickets()`` which lives in ``auth/cephx/CephxProtocol.h``).  
103 This code runs through the list of possible tickets to determine what we need, setting values 
104 in the ``need`` flag as necessary.  Then we call ``ticket.get_handler()``.  This routine 
105 (in ``CephxProtocol.h``) finds a ticket of the specified type (a ticket to perform 
106 authorization) in the ticket map, creates a ticket handler object for it,  and puts the 
107 handler into the right place in the map.  Then we hit specialized code to deal with individual 
108 cases.  The case here is when we still need to authenticate to A (the 
109 ``if (need & CEPH_ENTITY_TYPE_AUTH)`` branch).
110
111 We now create a message of type ``CEPH_AUTH_UNKNOWN``.  We need to authenticate 
112 this message with C's secret key, so we fetch that from the local key repository.  (It's 
113 called a key server in the code, but it's not really a separate machine or processing entity.
114 It's more like the place where locally used keys are kept.)  We create a 
115 random challenge, whose purpose is to prevent replays.  We encrypt that challenge.  We already 
116 have a server challenge (a similar set of random bytes, but created by the server and sent to
117 the client) from our pre-cephx stage.  We take both challenges and our secret key and 
118 produce a combined encrypted challenge value, which goes into ``req.key``.
119
120 If we have an old ticket, we store it in ``req.old_ticket``.  We're about to get a new one.
121
122 The entire ``req`` structure, including the old ticket and the cryptographic hash of the two 
123 challenges, gets put into the message.  Then we return from this function, and the 
124 message is sent.
125
126 We now switch over to the authenticator side, A.  The server receives the message that was 
127 sent, of type ``CEPH_AUTH_UNKNOWN``.  The message gets handled in ``prep_auth()``, 
128 in ``mon/AuthMonitor.cc``, which calls ``handle_request()`` is ``CephxServiceHandler.cc`` to 
129 do most of the work.  This routine, also, handles multiple cases.  
130
131 The control flow is determined by the ``request_type`` in the ``cephx_header`` associated 
132 with the message.  Our case here is ``CEPH_AUTH_UNKNOWN``.  We need the 
133 secret key A shares with C, so we call ``get_secret()`` from out local key repository to get 
134 it.  We should have set up a server challenge already with this client, so we make sure 
135 we really do have one.  (This variable is specific to a ``CephxServiceHandler``, so there 
136 is a different one for each such structure we create, presumably one per client A is 
137 dealing with.)  If there is no challenge, we'll need to start over, since we need to 
138 check the client's crypto hash, which depends on a server challenge, in part.
139
140 We now call the same routine the client used to calculate the hash, based on the same values: 
141 the client challenge (which is in the incoming message), the server challenge (which we saved), 
142 and the client's key (which we just obtained).  We check to see if the client sent the same 
143 thing we expected.  If so, we know we're talking to the right client.  We know the session is 
144 fresh, because it used the challenge we sent it to calculate its crypto hash.  So we can
145 give it an authentication ticket.
146
147 We fetch C's ``eauth`` structure.  This contains an ID, a key, and a set of caps (capabilities).
148
149 The client sent us its old ticket in the message, if it had one.  If so, we set a flag,
150 ``should_enc_ticket``, to true and set the global ID to the global ID in that old ticket.  
151 If the attempt to decode its old ticket fails (most probably because it didn't have one),
152 ``should_enc_ticket`` remains false.  Now we set up the new ticket, filling in timestamps, 
153 the name of C, the global ID provided in the method call (unless there was an old ticket), and 
154 his ``auid``, obtained from the ``eauth`` structure obtained above.  We need a new session key 
155 to help the client communicate securely with us, not using its permanent key.    We set the
156 service ID to ``CEPH_ENTITY_TYPE_AUTH``, which will tell the client C what to do with the 
157 message we send it.  We build a cephx response header and call 
158 ``cephx_build_service_ticket_reply()``.
159
160 ``cephx_build_service_ticket_reply()`` is in ``auth/cephx/CephxProtocol.cc``.  This 
161 routine will build up the response message.   Much of it copies data from its parameters to 
162 a message structure.  Part of that information (the session key and the validity period) 
163 gets encrypted with C's permanent key.  If the ``should_encrypt_ticket`` flag is set, 
164 encrypt it using the old ticket's key.  Otherwise, there was no old ticket key, so the 
165 new ticket is not encrypted.  (It is, of course, already encrypted with A's permanent key.)  
166 Presumably the point of this second encryption is to expose less material encrypted with 
167 permanent keys.
168
169 Then we call the key server's ``get_service_caps()`` routine on the entity name, with a 
170 flag ``CEPH_ENTITY_TYPE_MON``, and capabilities, which will be filled in by this routine.  
171 The use of that constant flag means we're going to get the client's caps for A, not for some 
172 other data server.  The ticket here is to access the authorizer A, not the service S.  The 
173 result of this call is that the caps variable  (a parameter to the routine we're in) is 
174 filled in with the monitor capabilities that will allow C to  access A's authorization services.
175
176 ``handle_request()`` itself does not send the response message.  It builds up the 
177 ``result_bl``, which basically holds that message's contents, and the capabilities structure, 
178 but it doesn't send the message.  We go back to ``prep_auth()``, in ``mon/AuthMonitor.cc``, 
179 for that.    This routine does some fiddling around with the caps structure that just got 
180 filled in.  There's a global ID that comes up as a result of this fiddling that is put into 
181 the reply message.  The reply message is built here (mostly from the ``response_bl`` buffer) 
182 and sent off.
183
184 This completes Phase I of the protocol.  At this point, C has authenticated itself to A, and A has generated a new session key and ticket allowing C to obtain server tickets from A.
185
186 Phase II
187 --------
188
189 This phase starts when C receives the message from A containing a new ticket and session key.
190 The goal of this phase is to provide C with a session key and ticket allowing it to
191 communicate with S.
192
193 The message A sent to C is dispatched to ``build_request()`` in ``CephxClientHandler.cc``, 
194 the same routine that was used early in Phase I to build the first message in the protocol.  
195 This time, when ``validate_tickets()`` is called, the ``need`` variable will not contain 
196 ``CEPH_ENTITY_TYPE_AUTH``, so a different branch through the bulk of the routine will be 
197 used.  This is the branch indicated by ``if (need)``.  We have a ticket for the authorizer, 
198 but we still need service tickets.
199
200 We must send another message to A to obtain the tickets (and session key) for the server 
201 S.  We set the ``request_type`` of the message to ``CEPHX_GET_PRINCIPAL_SESSION_KEY`` and 
202 call ``ticket_handler.build_authorizer()`` to obtain an authorizer.  This routine is in 
203 ``CephxProtocol.cc``.  We set the key for this authorizer to be the session key we just got 
204 from A,and create a new nonce.  We put the global ID, the service ID, and the ticket into a 
205 message buffer that is part of the authorizer.  Then we create a new ``CephXAuthorize`` 
206 structure.  The nonce we just created goes there.  We encrypt this ``CephXAuthorize`` 
207 structure with the current session key and stuff it into the authorizer's buffer.  We 
208 return the authorizer.
209
210 Back in ``build_request()``, we take the part of the authorizer that was just built (its 
211 buffer, not the session key or anything else) and shove it into the buffer we're creating 
212 for the message that will go to A.  Then we delete the authorizer.  We put the requirements 
213 for what we want in ``req.keys``, and we put ``req`` into the buffer.  Then we return, and 
214 the message gets sent.
215
216 The authorizer A receives this message which is of type ``CEPHX_GET_PRINCIPAL_SESSION_KEY``.
217 The message gets handled in ``prep_auth()``, in ``mon/AuthMonitor.cc``, which again calls 
218 ``handle_request()`` in ``CephxServiceHandler.cc`` to do most of the work.  
219
220 In this case, ``handle_request()`` will take the ``CEPHX_GET_PRINCIPAL_SESSION_KEY`` case. 
221 It will call ``cephx_verify_authorizer()`` in ``CephxProtocol.cc``.  Here, we will grab 
222 a bunch of data out of the input buffer, including the global and service IDs and the ticket 
223 for A.   The ticket contains a ``secret_id``, indicating which key is being used for it.     
224 If the secret ID pulled out of the ticket was -1, the ticket does not specify which secret 
225 key A should use.  In this case, A should use the key for the specific entity that C wants
226 to contact, rather than a rotating key shared by all server entities of the same type.
227 To get that key, A must consult the key repository to find the right key.   Otherwise, 
228 there's already a structure obtained from the key repository to hold the necessary secret.  
229 Server secrets rotate on a time expiration basis (key rotation is not covered in this
230 document), so run through that structure to find its current secret.  Either way, A now 
231 knows the secret key used to create this ticket.  Now decrypt the encrypted part of the 
232 ticket, using this key.  It should be a ticket for A.  
233
234 The ticket also contains a session key that C should have used to encrypt other parts of 
235 this message.  Use that session key to decrypt the rest of the message.  
236
237 Create a ``CephXAuthorizeReply`` to hold our reply.  Extract the nonce (which was in the stuff 
238 we just decrypted), add 1 to it, and put the result in the reply.  Encrypt the reply and 
239 put it in the buffer provided in the call to ``cephx_verify_authorizer()`` and return 
240 to ``handle_request()``.  This will be used to prove to C that A (rather than an attacker) 
241 created this response.
242
243 Having verified that the message is valid and from C, now we need to build it a ticket for S.
244 We need to know what S it wants to communicate with and what services it wants.  Pull the
245 ticket request that describes those things out of its message.  Now run through the ticket
246 request to see what it wanted.  (He could potentially be asking for multiple different
247 services in the same request, but we will assume it's just one, for this discussion.)  Once we 
248 know which service ID it's after, call ``build_session_auth_info()``.
249
250 ``build_session_auth_info()`` is in ``CephxKeyServer.cc``.  It checks to see if the 
251 secret for the ``service_ID`` of S is available and puts it into the subfield of one of 
252 the parameters, and calls the similarly named ``_build_session_auth_info()``, located in 
253 the same file.      This routine loads up the new ``auth_info`` structure with the 
254 ID of S, a ticket, and some timestamps for that ticket.  It generates a new session key 
255 and puts it in the structure.   It then calls ``get_caps()`` to fill in the 
256 ``info.ticket`` caps field.  ``get_caps()`` is also in ``CephxKeyServer.cc``.  It fills the 
257 ``caps_info`` structure it is provided with caps for S allowed to C.
258
259 Once ``build_session_auth_info()`` returns, A has a list of the capabilities allowed to 
260 C for S.  We put a validity period based on the current TTL for this context into the info 
261 structure, and put it into the ``info_vec`` structure we are preparing in response to the 
262 message.  
263
264 Now call ``build_cephx_response_header()``, also in ``CephxServiceHandler.cc``.   Fill in 
265 the ``request_type``, which is ``CEPHX_GET_PRINCIPAL_SESSION_KEY``, a status of 0, 
266 and the result buffer.  
267
268 Now call ``cephx_build_service_ticket_reply()``, which is in ``CephxProtocol.cc``.  The 
269 same routine was used towards the end of A's handling of its response in phase I.  Here, 
270 the session key (now a session key to talk to S, not A) and the validity period for that 
271 key will be encrypted with the existing session key shared between C and A.  
272 The ``should_encrypt_ticket`` parameter is false here, and no key is provided for that 
273 encryption.  The ticket in question, destined for S once C sends it there, is already 
274 encrypted with S's secret.  So, essentially, this routine will put ID information, 
275 the encrypted session key, and the ticket allowing C to talk to S into the buffer to 
276 be sent to C.
277
278 After this routine returns, we exit from ``handle_request()``, going back to ``prep_auth()`` 
279 and ultimately to the underlying message send code.  
280
281 The client receives this message. The nonce is checked as the message passes through
282 ``Pipe::connect()``, which is in ``msg/SimpleMessager.cc``.  In a lengthy ``while(1)`` loop in
283 the middle of this routine, it gets an authorizer.  If the get was successful, eventually
284 it will call ``verify_reply()``, which checks the nonce.  ``connect()`` never explicitly
285 checks to see if it got an authorizer, which would suggest that failure to provide an
286 authorizer would allow an attacker to skip checking of the nonce.  However, in many places,
287 if there is no authorizer, important connection fields will get set to zero, which will
288 ultimately cause the connection to fail to provide data.  It would be worth testing, but
289 it looks like failure to provide an authorizer, which contains the nonce, would not be helpful
290 to an attacker.
291
292 The message eventually makes its way through to ``handle_response()``, in 
293 ``CephxClientHandler.cc``.    In this routine, we call ``get_handler()`` to get a ticket 
294 handler to hold the ticket we have just received.  This routine is embedded in the definition 
295 for a ``CephXTicketManager`` structure.  It takes a type (``CEPH_ENTITY_TYPE_AUTH``, in 
296 this case) and looks through the ``tickets_map`` to find that type.  There should be one, and 
297 it should have the session key of the session between C and A in its entry.  This key will 
298 be used to decrypt the information provided by A, particularly the new session key allowing 
299 C to talk to S.
300
301 We then call ``verify_service_ticket_reply()``, in ``CephxProtocol.cc``.  This routine 
302 needs to determine if the ticket is OK and also obtain the session key associated with this 
303 ticket.  It decrypts the encrypted portion of the message buffer, using the session key 
304 shared with A.  This ticket was not encrypted (well, not twice - tickets are always encrypted, 
305 but sometimes double encrypted, which this one isn't).  So it can be stored in a service 
306 ticket buffer directly.  We now grab the ticket out of that buffer.  
307
308 The stuff we decrypted with the session key shared between C and A included the new session 
309 key.  That's our current session key for this ticket, so set it.  Check validity and 
310 set the expiration times.  Now return true, if we got this far.  
311
312 Back in ``handle_response()``, we now call ``validate_tickets()`` to adjust what we think 
313 we need, since we now have a ticket we didn't have before.  If we've taken care of 
314 everything we need, we'll return 0.
315
316 This ends phase II of the protocol.  We have now successfully set up a ticket and session key 
317 for client C to talk to server S.  S will know that C is who it claims to be, since A will
318 verify it.  C will know it is S it's talking to, again because A verified it.  The only
319 copies of the session key for C and S to communicate were sent encrypted under the permanent
320 keys of C and S, respectively, so no other party (excepting A, who is trusted by all) knows
321 that session key.  The ticket will securely indicate to S what C is allowed to do, attested 
322 to by A.  The nonces passed back and forth between A and C ensure that they have not been 
323 subject to a replay attack.  C has not yet actually talked to S, but it is ready to.
324
325 Much of the security here falls apart if one of the permanent keys is compromised.  Compromise
326 of C's key means that the attacker can pose as C and obtain all of C's privileges, and can
327 eavesdrop on C's legitimate conversations.  He can also pretend to be A, but only in 
328 conversations with C.  Since it does not (by hypothesis) have keys for any services, he
329 cannot generate any new tickets for services, though it can replay old tickets and session
330 keys until S's permanent key is changed or the old tickets time out. 
331
332 Compromise of S's key means that the attacker can pose as S to anyone, and can eavesdrop on 
333 any user's conversation with S.  Unless some client's key is also compromised, the attacker
334 cannot generate new fake client tickets for S, since doing so requires it to authenticate
335 himself as A, using the client key it doesn't know.