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