Tuesday, July 7, 2009

GSS authentication

As I've described before, the GSS API is a REST-like interface for interacting with a GSS-based service, like Pithos. Using regular HTTP commands a client can upload, download, browse and modify files and folders stored in the GSS server. These interactions have two important properties: they store no client state to the server and they are not encrypted. I have already mentioned the most important benefits from the stateless architecture of the GSS protocol, namely scalability and loose coupling along the client-server boundary. In the same line of thought, SSL/TLS encryption of the transport was avoided for scalability reasons. Consequently, these two properties of the communication protocol, lead to another requirement: message authentication for each API call.


Since no session state is stored in the server, the client must authenticate each request as if it were the first one. Traditional web applications use an initial authentication interaction between client and server, that creates a unique session ID, which is transmitted with each subsequent request in that same session. While using this ID, the client does not need to authenticate again to the server. Stateless protocols, like WebDAV for instance, cannot rely on such an ID and have to transmit authentication data in each call. Ultimately the tradeoff is a minor increase in the message payload, in return for a big boost in scalability. The HTTP specification defines an Authorization header for use in such cases and WebDAV uses the HTTP Digest Access Authentication scheme. The GSS API uses a slight variation in that theme, blended with some ideas from OAuth request signing.

Essentially the standard HTTP Authorization header is populated with a concatenation of the username (for identifying the user making the request) and a HMAC-SHA1 signature of the request. The request signature is obtained by applying a secret authentication token to a concatenated string of the HTTP method name, the date and time of the request and the actual requested path. The GSS API page has all the details. The inclusion of the date and time helps thwart replay attacks using a previously sniffed signature. Furthermore, a separate header with timestamp information, X-GSS-Date, is used to thwart replay attacks with a full copy of the payload. The authentication token is issued securely by the server to the client and is the time-limited secret that is shared between the client and server. Since it is not the user password that is used as a shared secret, were the authentication token to be compromised, any ill effects would have been limited to the time period the token was valid. There are two ways for client applications to obtain the user's authentication token, and they are both described in detail in the API documentation.

In my experience, protocol descriptions are one thing and working code is a totally different one. In that spirit, I'm closing this post with a few tested implementations of the GSS API signature calculation, for client applications in different languages. Here is the method for signing a GSS API request in Java (full code here):


public static String sign(String httpMethod, String timestamp,
String path, String token) {
String input = httpMethod + timestamp + path;
String signed = null;

try {
System.err.println("Token:" + token);
// Get an HMAC-SHA1 key from the authentication token.
System.err.println("Input: " + input);
SecretKeySpec signingKey = new SecretKeySpec(
Base64.decodeBase64(token.getBytes()), "HmacSHA1");

// Get an HMAC-SHA1 Mac instance and initialize with the signing key.
Mac hmac = Mac.getInstance("HmacSHA1");
hmac.init(signingKey);

// Compute the HMAC on the input data bytes.
byte[] rawMac = hmac.doFinal(input.getBytes());

// Do base 64 encoding.
signed = new String(Base64.encodeBase64(rawMac), "US-ASCII");

} catch (InvalidKeyException ikex) {
System.err.println("Fatal key exception: " + ikex.getMessage());
ikex.printStackTrace();
} catch (UnsupportedEncodingException ueex) {
System.err.println("Fatal encoding exception: "
+ ueex.getMessage());
} catch (NoSuchAlgorithmException nsaex) {
System.err.println("Fatal algorithm exception: "
+ nsaex.getMessage());
nsaex.printStackTrace();
}

if (signed == null)
System.exit(-1);
System.err.println("Signed: " + signed);
return signed;
}


Here is a method for signing GSS API requests in JavaScript, that uses the SHA-1 JavaScript implementation by Paul Johnston (full code here):


function sign(method, time, resource, token) {
var q = resource.indexOf('?');
var res = q == -1? resource: resource.substring(0, q);
var data = method + time + res;
// Use strict RFC compliance
b64pad = "=";
return b64_hmac_sha1(atob(token), data);
}

Here is a Tcl implementation by Alexios Zavras (full code here):


proc ::rest::_sign {id} {
variable $id
upvar 0 $id data
set str2sign ""
append str2sign $data(method)
append str2sign $data(date)
set idx [string first ? $data(path)]
if {$idx < 0} {
set str $data(path)
} else {
incr idx -1
set str [string range $data(path) 0 $idx]
}
# append str2sign [::util::url::encode $str]
append str2sign $str
puts "SIGN $str2sign"
set sig [::base64::encode [::sha1::hmac -bin -key $::GG(gsstoken) $str2sign]]
set data(signature) $sig
}

I'd love to show implementations of the signature calculation for other languages as well, but since my time is scarce these days, I could use some help here. If you've written one yourself and you'd like to share, leave a comment and I'll update this post, with proper attribution of course.

Creative Commons License Unless otherwise expressly stated, all original material in this weblog is licensed under a Creative Commons Attribution 3.0 License.