A Detailed Description of the Cephx Authentication Protocol
Peter Reiher7/13/12
This document provides deeper detail on the Cephx authorization protocol whose high level flowis described in the memo by Yehuda (12/19/09). Because this memo discusses details ofroutines called and variables used, it represents a snapshot. The code might be changedsubsequent to the creation of this document, and the document is not likely to be updated inlockstep. With luck, code comments will indicate major changes in the way the protocol isimplemented.
Introduction
The basic idea of the protocol is based on Kerberos. A client wishes to obtain something froma server. The server will only offer the requested service to authorized clients. Ratherthan requiring each server to deal with authentication and authorization issues, the systemuses an authorization server. Thus, the client must first communicate with the authorizationserver to authenticate itself and to obtain credentials that will grant it access to theservice it wants.
Authorization is not the same as authentication. Authentication provides evidence that someparty is who it claims to be. Authorization provides evidence that a particular party isallowed to do something. Generally, secure authorization implies secure authentication(since without authentication, you may authorize something for an imposter), but the reverseis not necessarily true. One can authenticate without authorizing. The purposeof this protocol is to authorize.
The basic approach is to use symmetric cryptography throughout. Each client C has its ownsecret key, known only to itself and the authorization server A. Each server S has its ownsecret key, known only to itself and the authorization server A. Authorization informationwill be passed in tickets, encrypted with the secret key of the entity that offers the service.There will be a ticket that A gives to C, which permits C to ask A for other tickets. Thisticket will be encrypted with A’s key, since A is the one who needs to check it. There willlater be tickets that A issues that allow C to communicate with S to ask for service. Thesetickets will be encrypted with S’s key, since S needs to check them. Since we wish to providesecurity of the communications, as well, session keys are set up along with the tickets.Currently, those session keys are only used for authentication purposes during this protocoland the handshake between the client C and the server S, when the client provides its serviceticket. They could be used for authentication or secrecy throughout, with some changes tothe system.
Several parties need to prove something to each other if this protocol is to achieve itsdesired security effects.
The client C must prove to the authenticator A that it really is C. Since everythingis being done via messages, the client must also prove that the message proving authenticityis fresh, and is not being replayed by an attacker.
The authenticator A must prove to client C that it really is the authenticator. Again,proof that replay is not occurring is also required.
A and C must securely share a session key to be used for distribution of laterauthorization material between them. Again, no replay is allowable, and the key must beknown only to A and C.
A must receive evidence from C that allows A to look up C’s authorized operations withserver S.
C must receive a ticket from A that will prove to S that C can perform its authorizedoperations. This ticket must be usable only by C.
C must receive from A a session key to protect the communications between C and S. Thesession key must be fresh and not the result of a replay.
Getting Started With Authorization
When the client first needs to get service, it contacts the monitor. At the moment, it hasno tickets. Therefore, it uses the “unknown” protocol to talk to the monitor. This protocolis specified as CEPH_AUTH_UNKNOWN
. The monitor also takes on the authentication serverrole, A. The remainder of the communications will use the cephx protocol (most of whose codewill be found in files in auth/cephx
). This protocol is responsible for creating andcommunicating the tickets spoken of above.
Currently, this document does not follow the pre-cephx protocol flow. It starts up at thepoint where the client has contacted the server and is ready to start the cephx protocol itself.
Once we are in the cephx protocol, we can get the tickets. First, C needs a ticket thatallows secure communications with A. This ticket can then be used to obtain other tickets.This is phase I of the protocol, and consists of a send from C to A and a response from A to C.Then, C needs a ticket to allow it to talk to S to get services. This is phase II of theprotocol, and consists of a send from C to A and a response from A to C.
Phase I:
The client is set up to know that it needs certain things, using a variable called need
,which is part of the AuthClientHandler
class, which the CephxClientHandler
inheritsfrom. At this point, one thing that’s encoded in the need
variable isCEPH_ENTITY_TYPE_AUTH
, indicating that we need to start the authentication protocolfrom scratch. Since we’re always talking to the same authorization server, if we’ve gonethrough this step of the protocol before (and the resulting ticket/session hasn’t timed out),we can skip this step and just ask for client tickets. But it must be done initially, andwe’ll assume that we are in that state.
The message C sends to A in phase I is build in CephxClientHandler::build_request()
(inauth/cephx/CephxClientHandler.cc
). This routine is used for more than one purpose.In this case, we first call validate_tickets()
(from routineCephXTicektManager::validate_tickets()
which lives in auth/cephx/CephxProtocol.h
).This code runs through the list of possible tickets to determine what we need, setting valuesin the need
flag as necessary. Then we call ticket.get_handler()
. This routine(in CephxProtocol.h
) finds a ticket of the specified type (a ticket to performauthorization) in the ticket map, creates a ticket handler object for it, and puts thehandler into the right place in the map. Then we hit specialized code to deal with individualcases. The case here is when we still need to authenticate to A (theif (need & CEPH_ENTITY_TYPE_AUTH)
branch).
We now create a message of type CEPHX_GET_AUTH_SESSION_KEY
. We need to authenticatethis message with C’s secret key, so we fetch that from the local key repository. We createa random challenge, whose purpose is to prevent replays. We encrypt that challenge usingcephx_calc_client_server_challenge()
. We alreadyhave a server challenge (a similar set of random bytes, but created by the server and sent tothe client) from our pre-cephx stage. We take both challenges and our secret key andproduce a combined encrypted challenge value, which goes into req.key
.
If we have an old ticket, we store it in req.old_ticket
. We’re about to get a new one.
The entire req
structure, including the old ticket and the cryptographic hash of the twochallenges, gets put into the message. Then we return from this function, and themessage is sent.
We now switch over to the authenticator side, A. The server receives the message that wassent, of type CEPH_GET_AUTH_SESSION_KEY
. The message gets handled in prep_auth()
,in mon/AuthMonitor.cc
, which calls handle_request()
is CephxServiceHandler.cc
todo most of the work. This routine, also, handles multiple cases.
The control flow is determined by the request_type
in the cephx_header
associatedwith the message. Our case here is CEPH_GET_AUTH_SESSION_KEY
. We need thesecret key A shares with C, so we call get_secret()
from out local key repository to getit. (It’s called a key_server
in the code, but it’s not really a separate machine orprocessing entity. It’s more like the place where locally used keys are kept.) We shouldhave set up a server challenge already with this client, so we make surewe really do have one. (This variable is specific to a CephxServiceHandler
, so thereis a different one for each such structure we create, presumably one per client A isdealing with.) If there is no challenge, we’ll need to start over, since we need tocheck the client’s crypto hash, which depends on a server challenge, in part.
We now call the same routine the client used to calculate the hash, based on the same values:the client challenge (which is in the incoming message), the server challenge (which we saved),and the client’s key (which we just obtained). We check to see if the client sent the samething we expected. If so, we know we’re talking to the right client. We know the session isfresh, because it used the challenge we sent it to calculate its crypto hash. So we cangive it an authentication ticket.
We fetch C’s eauth
structure. This contains an ID, a key, and a set of caps (capabilities).
The client sent us its old ticket in the message, if it had one. Ifso, we set a flag, should_enc_ticket
, to true and set the globalID to the global ID in that old ticket. If the attempt to decode itsold ticket fails (most probably because it didn’t have one),should_enc_ticket
remains false. Now we set up the new ticket,filling in timestamps, the name of C, and the global ID provided in themethod call (unless there was an old ticket). We need a new sessionkey to help the client communicate securely with us, not using itspermanent key. We set the service ID to CEPH_ENTITY_TYPE_AUTH
,which will tell the client C what to do with the message we send it.We build a cephx response header and callcephx_build_service_ticket_reply()
.
cephx_build_service_ticket_reply()
is in auth/cephx/CephxProtocol.cc
. Thisroutine will build up the response message. Much of it copies data from its parameters toa message structure. Part of that information (the session key and the validity period)gets encrypted with C’s permanent key. If the should_encrypt_ticket
flag is set,encrypt it using the old ticket’s key. Otherwise, there was no old ticket key, so thenew ticket is not encrypted. (It is, of course, already encrypted with A’s permanent key.)Presumably the point of this second encryption is to expose less material encrypted withpermanent keys.
Then we call the key server’s get_service_caps()
routine on the entity name, with aflag CEPH_ENTITY_TYPE_MON
, and capabilities, which will be filled in by this routine.The use of that constant flag means we’re going to get the client’s caps for A, not for someother data server. The ticket here is to access the authorizer A, not the service S. Theresult of this call is that the caps variable (a parameter to the routine we’re in) isfilled in with the monitor capabilities that will allow C to access A’s authorization services.
handle_request()
itself does not send the response message. It builds up theresult_bl
, which basically holds that message’s contents, and the capabilities structure,but it doesn’t send the message. We go back to prep_auth()
, in mon/AuthMonitor.cc
,for that. This routine does some fiddling around with the caps structure that just gotfilled in. There’s a global ID that comes up as a result of this fiddling that is put intothe reply message. The reply message is built here (mostly from the response_bl
buffer)and sent off.
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.
Phase II
This phase starts when C receives the message from A containing a new ticket and session key.The goal of this phase is to provide C with a session key and ticket allowing it tocommunicate with S.
The message A sent to C is dispatched to build_request()
in CephxClientHandler.cc
,the same routine that was used early in Phase I to build the first message in the protocol.This time, when validate_tickets()
is called, the need
variable will not containCEPH_ENTITY_TYPE_AUTH
, so a different branch through the bulk of the routine will beused. This is the branch indicated by if (need)
. We have a ticket for the authorizer,but we still need service tickets.
We must send another message to A to obtain the tickets (and session key) for the serverS. We set the request_type
of the message to CEPHX_GET_PRINCIPAL_SESSION_KEY
andcall ticket_handler.build_authorizer()
to obtain an authorizer. This routine is inCephxProtocol.cc
. We set the key for this authorizer to be the session key we just gotfrom A,and create a new nonce. We put the global ID, the service ID, and the ticket into amessage buffer that is part of the authorizer. Then we create a new CephXAuthorize
structure. The nonce we just created goes there. We encrypt this CephXAuthorize
structure with the current session key and stuff it into the authorizer’s buffer. Wereturn the authorizer.
Back in build_request()
, we take the part of the authorizer that was just built (itsbuffer, not the session key or anything else) and shove it into the buffer we’re creatingfor the message that will go to A. Then we delete the authorizer. We put the requirementsfor what we want in req.keys
, and we put req
into the buffer. Then we return, andthe message gets sent.
The authorizer A receives this message which is of type CEPHX_GET_PRINCIPAL_SESSION_KEY
.The message gets handled in prep_auth()
, in mon/AuthMonitor.cc
, which again callshandle_request()
in CephxServiceHandler.cc
to do most of the work.
In this case, handle_request()
will take the CEPHX_GET_PRINCIPAL_SESSION_KEY
case.It will call cephx_verify_authorizer()
in CephxProtocol.cc
. Here, we will graba bunch of data out of the input buffer, including the global and service IDs and the ticketfor A. The ticket contains a secret_id
, indicating which key is being used for it.If the secret ID pulled out of the ticket was -1, the ticket does not specify which secretkey A should use. In this case, A should use the key for the specific entity that C wantsto contact, rather than a rotating key shared by all server entities of the same type.To get that key, A must consult the key repository to find the right key. Otherwise,there’s already a structure obtained from the key repository to hold the necessary secret.Server secrets rotate on a time expiration basis (key rotation is not covered in thisdocument), so run through that structure to find its current secret. Either way, A nowknows the secret key used to create this ticket. Now decrypt the encrypted part of theticket, using this key. It should be a ticket for A.
The ticket also contains a session key that C should have used to encrypt other parts ofthis message. Use that session key to decrypt the rest of the message.
Create a CephXAuthorizeReply
to hold our reply. Extract the nonce (which was in the stuffwe just decrypted), add 1 to it, and put the result in the reply. Encrypt the reply andput it in the buffer provided in the call to cephx_verify_authorizer()
and returnto handle_request()
. This will be used to prove to C that A (rather than an attacker)created this response.
Having verified that the message is valid and from C, now we need to build it a ticket for S.We need to know what S it wants to communicate with and what services it wants. Pull theticket request that describes those things out of its message. Now run through the ticketrequest to see what it wanted. (He could potentially be asking for multiple differentservices in the same request, but we will assume it’s just one, for this discussion.) Once weknow which service ID it’s after, call build_session_auth_info()
.
build_session_auth_info()
is in CephxKeyServer.cc
. It checks to see if thesecret for the service_ID
of S is available and puts it into the subfield of one ofthe parameters, and calls the similarly named _build_session_auth_info()
, located inthe same file. This routine loads up the new auth_info
structure with theID of S, a ticket, and some timestamps for that ticket. It generates a new session keyand puts it in the structure. It then calls get_caps()
to fill in theinfo.ticket
caps field. get_caps()
is also in CephxKeyServer.cc
. It fills thecaps_info
structure it is provided with caps for S allowed to C.
Once build_session_auth_info()
returns, A has a list of the capabilities allowed toC for S. We put a validity period based on the current TTL for this context into the infostructure, and put it into the info_vec
structure we are preparing in response to themessage.
Now call build_cephx_response_header()
, also in CephxServiceHandler.cc
. Fill inthe request_type
, which is CEPHX_GET_PRINCIPAL_SESSION_KEY
, a status of 0,and the result buffer.
Now call cephx_build_service_ticket_reply()
, which is in CephxProtocol.cc
. Thesame routine was used towards the end of A’s handling of its response in phase I. Here,the session key (now a session key to talk to S, not A) and the validity period for thatkey will be encrypted with the existing session key shared between C and A.The should_encrypt_ticket
parameter is false here, and no key is provided for thatencryption. The ticket in question, destined for S once C sends it there, is alreadyencrypted with S’s secret. So, essentially, this routine will put ID information,the encrypted session key, and the ticket allowing C to talk to S into the buffer tobe sent to C.
After this routine returns, we exit from handle_request()
, going back to prep_auth()
and ultimately to the underlying message send code.
The client receives this message. The nonce is checked as the message passes throughPipe::connect()
, which is in msg/SimpleMessager.cc
. In a lengthy while(1)
loop inthe middle of this routine, it gets an authorizer. If the get was successful, eventuallyit will call verify_reply()
, which checks the nonce. connect()
never explicitlychecks to see if it got an authorizer, which would suggest that failure to provide anauthorizer would allow an attacker to skip checking of the nonce. However, in many places,if there is no authorizer, important connection fields will get set to zero, which willultimately cause the connection to fail to provide data. It would be worth testing, butit looks like failure to provide an authorizer, which contains the nonce, would not be helpfulto an attacker.
The message eventually makes its way through to handle_response()
, inCephxClientHandler.cc
. In this routine, we call get_handler()
to get a tickethandler to hold the ticket we have just received. This routine is embedded in the definitionfor a CephXTicketManager
structure. It takes a type (CEPH_ENTITY_TYPE_AUTH
, inthis case) and looks through the tickets_map
to find that type. There should be one, andit should have the session key of the session between C and A in its entry. This key willbe used to decrypt the information provided by A, particularly the new session key allowingC to talk to S.
We then call verify_service_ticket_reply()
, in CephxProtocol.cc
. This routineneeds to determine if the ticket is OK and also obtain the session key associated with thisticket. It decrypts the encrypted portion of the message buffer, using the session keyshared with A. This ticket was not encrypted (well, not twice - tickets are always encrypted,but sometimes double encrypted, which this one isn’t). So it can be stored in a serviceticket buffer directly. We now grab the ticket out of that buffer.
The stuff we decrypted with the session key shared between C and A included the new sessionkey. That’s our current session key for this ticket, so set it. Check validity andset the expiration times. Now return true, if we got this far.
Back in handle_response()
, we now call validate_tickets()
to adjust what we thinkwe need, since we now have a ticket we didn’t have before. If we’ve taken care ofeverything we need, we’ll return 0.
This ends phase II of the protocol. We have now successfully set up a ticket and session keyfor client C to talk to server S. S will know that C is who it claims to be, since A willverify it. C will know it is S it’s talking to, again because A verified it. The onlycopies of the session key for C and S to communicate were sent encrypted under the permanentkeys of C and S, respectively, so no other party (excepting A, who is trusted by all) knowsthat session key. The ticket will securely indicate to S what C is allowed to do, attestedto by A. The nonces passed back and forth between A and C ensure that they have not beensubject to a replay attack. C has not yet actually talked to S, but it is ready to.
Much of the security here falls apart if one of the permanent keys is compromised. Compromiseof C’s key means that the attacker can pose as C and obtain all of C’s privileges, and caneavesdrop on C’s legitimate conversations. He can also pretend to be A, but only inconversations with C. Since it does not (by hypothesis) have keys for any services, hecannot generate any new tickets for services, though it can replay old tickets and sessionkeys until S’s permanent key is changed or the old tickets time out.
Compromise of S’s key means that the attacker can pose as S to anyone, and can eavesdrop onany user’s conversation with S. Unless some client’s key is also compromised, the attackercannot generate new fake client tickets for S, since doing so requires it to authenticatehimself as A, using the client key it doesn’t know.