Clarens authentication/login protocol ------------------------------------- 1. Introduction The Clarens web service layer performs user authentication using X509 certificates issued by a certificate authority. It does so within the confines if the http Basic authentication protocol. This means that authentication information is passed along in the http header information using the AUTHORIZATION field. E.g.: AUTHORIZATION: Basic a80a844c376705cd8ecb8debdae06bcf46037dee The string following the Basic keyword is a Base64 encoding of some information that the user wishes to pass to the server, usually the string "user:password". In Clarens usernames and password are not used, but since the Basic authentication scheme is known by most http client programs and libraries, it eases the implementation of new clients to Clarens services. Note that the following assumes some knowledge of encryption using public/private keys. Also note that the authentication can be done without using an encrypted link ( i.e. using http instead of https), but https is recommended if security of transferred data is required. For the primary HEP application where Clarens is used http is normally used since the data is not ordinarily confidential, and the encryption/decryption slows down data transfer significantly. 2. Two steps In order to do proper authentication of the client by the server, and vice versa, a challenge-response (or two-step) authentication must be used. The Clarens scheme is based on GSI, with the exception that does it was designed to fit into the http protocol without changes to servers or libraries. The information interchange is described below, with some implementation examples interspersed written in Python. There is also a C++ implementation available which uses OpenSSL directly, see the Clarens layer for the Root package, which is part of the Clarens package. a. Create a session 'nonce' value to identify the session. This should be as random as possible, but is combined with an IP address at the server to identify the session. In Python: random.seed() ustring_raw="%s_%f_%f"%(os.getpid(),time.time(),random.random()) sha1=MessageDigest('sha1') sha1.update(ustring_raw) ustring=encodestring(sha1.digest()) That is, ustring is the SHA1 digest of ustring_raw. b. Load the user's X509 certificate from a text file (in PEM-encoded format): text_file=open(certfile) text_ucert=text_file.read() text_file.close() c. Construct the first XML-RPC call to the server, calling the "system.auth" method. In the process the http header must be set as: AUTHORIZATION: Basic where is the Base64 encoding of "ustring:text_ucert". In Python: h.putheader("AUTHORIZATION", "Basic %s" % string.replace( encodestring("%s:%s" % (ustring,text_ucert)), "\012", "")) Note that the above also removes any linefeed character from the resultant string to make sure the http server does not get confused, since a CRLF sequence is used to indicate the end of a header line in the http protocol. A typical system.auth call would look like this in string format: "POST /xmlrpc/clarens_server.py HTTP/1.0\r\n" "Host: localhost\r\n" "User-Agent: xmlrpclib.py/0.9.9 (by www.pythonware.com)\r\n" "Content-Type: text/xml\r\n" "Content-Length: 105\r\n" "AUTHORIZATION: Basic MkhVTm9VazYxbXArVEZLS0dCY2tIRlA3bjVzPQo6RnJvbSBi\r\n" "\n\nsystem.auth\n \n \n\n" Note that '\r\n' is a carriage-return/line-feed sequence in C escape sequence form, and that the single quotes should obviously be omitted. Also, the string above have been shortened. Most http libraries in one form or another allows you to set the username and password of the http request, and you would not need to do the header construction manually. In Python: mtrans=BasicAuthTransport(ustring,text_ucert) xmlrpclib.Server.__init__(self,url,transport=mtrans) = self.system.auth() That is, a transport with the username and password of (ustring,text_ucert) is constructed and the xmlrpc client object is initialized using that transport. d. The server will send back three strings in response to the system.auth call above: STRING1 STRING2 STRING3 These three strings are as follows: STRING1: The PEM-encoded server certificate. (PEM-encoding is basically Base64) STRING2: A server 'nonce' value that was encrypted using the client's public key, and then Base64 encoded. STRING3: The client's 'nonce' value encrypted using the server's private key, and then Base64 encoded. In Python this is obtained by calling text_scert,crypt_server_nonce_64,crypt_user_nonce_64 = self.system.auth() e. Read the server certificate contained in STRING1 and extract the public key contained therein. E.g.: server_cert=X509.load_cert_bio(BIO.MemoryBuffer(text_scert)) server_pub_key=server_cert.get_pubkey() server_pub_rsa=RSA.RSA_pub(m2.rsa_from_pkey(server_pub_key)) Verify that the key is authentic and still valid: server_cert.verify() f. Reverse the Base64-encoding of STRING2 and decrypt the resultant data to see if it matches the user nonce that we have constructed. This verifies that the server can encrypt data using its private key, and that we in turn can decrypt that data using its public key. In Python: server_ustring=server_pub_rsa.public_decrypt(decodestring(crypt_user_nonce_64), mpadding) Then check if server_ustring is equal to ustring g. Reverse the Base64-encoding of STRING3 and decrypt the resultant data using the client's private key. The resultant value is a secure session ID provided to us by the server. In Python: server_nonce=user_priv_rsa.private_decrypt(decodestring(crypt_server_nonce_64), mpadding) h. Calculate the SHA1 hash of the resultant data, and set the password in subsequent http request headers to the Base64 encoding of this hash value: sha1=MessageDigest('sha1') sha1.update(server_nonce) mcrypt_server_nonce=encodestring(sha1.digest()) mtrans.set_password(mcrypt_server_nonce) Note that when the http header is constructed the user and password (or in our case the ustring and 'mcrypt_server_nonce' values are again combined into a string (separated by a colon) and Base64 encoded when sent to the server. i. Finally, to end the session, call the 'system.logout' method of the server, which produces an XML request that looks like this: "POST /xmlrpc/clarens_server.py HTTP/1.0\r\n" "Host: localhost\r\n" "User-Agent: xmlrpclib.py/0.9.9 (by www.pythonware.com)\r\n" "Content-Type: text/xml\r\n" "Content-Length: 107\r\n" "AUTHORIZATION: Basic MkhVTm9VazYxbXArVEZLS0dCY2tIRlA3bjVzPQo6UFRHdHcrRVlX\r\n" "\n\nsystem.logout\n \n\n\n" The server responds with and integer of value 0 when the call succeeds: "HTTP/1.1 200 OK\r\n" "Server: Sourcelight Technologies py-xmlrpc-0.8.8.2" "Content-Type: text/xml" "Content-length: 220" " 0 \n" 3. Discussion The above protocol is designed to prove the user and server identities to both parties without prior knowledge of each other, except through the ability to check certificate signatures against known CA signatures. Since the http protocol is designed to handle multiple requests from the same client using one or more socket connections, the server and user session ids remain valif between requests, and the server should store those session ids. This makes it possible to have a single sign-on for a client to use different applications, or different instances of the same application to connect to the server and make requests, as long as every application knows the two session ids. Obviously the server should keep track of who makes these reqeusts, and this is done by ensuring that a pair of session ids are unique to a unique IP address. It is also possible to connect to multiple servers from a single client application, the only requirement is that a session id pair must be negiotiated with every server. Note that most private encryption keys are themselves encrypted, so that a password is usually required to enable the client to perform that negotiation process. Note that Globus works around this inconvenience by creating a temporary unencrypted public/private keypair through the 'globus-proxy-init' command, which in turn presents its own set ow problems. It is also once again recommended that SSL encryption (https) be used if data confidentiality is important.