/* Copyright (c) 2008 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package sample.authsub.src; import com.google.gdata.util.common.util.Base64; import java.security.GeneralSecurityException; import java.security.Key; import java.security.MessageDigest; import java.util.Date; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; /** * Generates and verifies tokens to protect URLs. * * The URL command attack is a cross site scripting vulnerability. It * is applicable when cookies are used for authentication and user commands * are done through HTTP requests (GET or POST). * This attack is illustrated through the following * example. Assume goodsite.com and evilsite.com. Assume goodsite.com offers a * URL of the following form * http://goodsite.com/addtophonebook?email=hello@gmail.com that adds * hello@gmail.com to the current logged in user's phonebook. * An attack may look like this: * <p> * 1. User logs into goodsite.com and gets an login cookie * 2. User goes to evilsite.com * 3. On evilsite.com there is an HTML element with a URL source to some * command for goodsite.com (Perhaps through an invisible image). * 4. The browser will fetch the URL and the login cookie will go along with * it * 5. The user's account is now modified by an evil site * <p> * This can happen when URL's aren't protected. * eg. http://goodsite.com/add=temp@gmail.com * <p> * Thus to protect the GData URL proxy, we require requests to the GData proxy * servlet to contain a secure token that is a hash of the user's login cookie * issued by the web service and the URL to be accessed. Since evilsite.com * does not have access to the login cookie, it will not be able to generate a * valid secure token for this user. * <p> * The token used to secure the URL has the following format: * token = HMAC<sub>SHA1(login-cookie)</sub>({data}) * <p> * where <code>{data}</code> is a string formed in the following manner: * data = http-url SP http-method SP timestamp * <code>SP</code> is a single ASCII space character. * <code>http-url</code> is the GData feed URl being requested. * <code>timestamp</code> is an integer representing the time * the request was sent, in seconds since Jan 1, 1970 UTC, * formatted as an ASCII string (in decimal). * <p> * The token used in the URL will be Base64 encoded. * * */ public class SecureUrl { private static final int TOKEN_LIFE_SECONDS = 30 * 60; /** * Generates a secure token to be used to protect a URL. * * @param cookie the authentication cookie of the user * @param url the URL to protect * @param method the HTTP method used against the URL * @param currentTimeSecs the current time in seconds * @return the secure token */ public static String generateToken(String cookie, String url, String method, long currentTimeSecs) { // Form the data to be HMAC'ed String data = url + " " + method + " " + currentTimeSecs; // Compute SHA1-HMAC byte[] hmac; try { hmac = computeSHA1HMac(data, cookie); } catch (GeneralSecurityException e) { throw new RuntimeException("Security exception - " + e.getMessage()); } return Base64.encodeWebSafe(hmac, true); } /** * Checks the validity of the secure token to ensure that it was not created * by a malicious third party. * * @param token the token to verify * @param cookie the cookie of the user * @param url the URL requested * @param method the HTTP method used to request the URL * @param timestamp the timestamp of the token */ public static boolean isTokenValid(String token, String cookie, String url, String method, String timestamp) { long createTime = Long.parseLong(timestamp); long currentTime = (new Date()).getTime() / 1000; if ((currentTime - createTime) > TOKEN_LIFE_SECONDS) { return false; } String data = url + " " + method + " " + createTime; byte[] hmac; try{ hmac = computeSHA1HMac(data, cookie); } catch (GeneralSecurityException e) { throw new RuntimeException("Security exception - " + e.getMessage()); } String hmacEnc = Base64.encodeWebSafe(hmac, true); return hmacEnc.equals(token); } /** * Computes a SHA1-HMAC. A SHA1 hash of the cookie is used as the key to * MAC the data. * * @param data the data to MAC * @param cookie the authentication cookie of the user * @return the SHA1-HMAC of the data given the cookie */ public static byte[] computeSHA1HMac(String data, String cookie) throws GeneralSecurityException { // Compute SHA-1 hash of the cookie byte[] hash; MessageDigest digest = MessageDigest.getInstance("SHA-1"); hash = digest.digest(cookie.getBytes()); // Compute the HMAC of the data using hash(cookie) as the key byte[] hmacResult; Mac hm; hm = Mac.getInstance("HMacSHA1"); Key k1 = new SecretKeySpec(hash, 0, hash.length, "HMacSHA1"); hm.init(k1); return hm.doFinal(data.getBytes()); } }