Author: lindner
Date: Tue Mar 11 03:44:17 2008
New Revision: 635874

URL: http://svn.apache.org/viewvc?rev=635874&view=rev
Log:
Commit crypto functions provided by Brian Eaton for SHINDIG-92

Added:
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/BlobCrypter.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/BlobCrypterException.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/BlobExpiredException.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/Crypto.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/TimeSource.java
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/util/BlobCrypterTest.java
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/util/CryptoTest.java
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/util/FakeTimeSource.java
Modified:
    incubator/shindig/trunk/java/gadgets/pom.xml

Modified: incubator/shindig/trunk/java/gadgets/pom.xml
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/pom.xml?rev=635874&r1=635873&r2=635874&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/pom.xml (original)
+++ incubator/shindig/trunk/java/gadgets/pom.xml Tue Mar 11 03:44:17 2008
@@ -236,5 +236,10 @@
       <version>2.3</version>
       <scope>compile</scope>
     </dependency>
+    <dependency>
+      <groupId>commons-codec</groupId>
+      <artifactId>commons-codec</artifactId>
+      <version>1.3</version>
+    </dependency>
   </dependencies>
 </project>

Added: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/BlobCrypter.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/BlobCrypter.java?rev=635874&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/BlobCrypter.java
 (added)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/BlobCrypter.java
 Tue Mar 11 03:44:17 2008
@@ -0,0 +1,200 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.shindig.util;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.digest.DigestUtils;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.security.GeneralSecurityException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Utility class for managing signed, encrypted, and time stamped blobs.
+ * Blobs are made up of name/value pairs.  Time stamps are automatically
+ * included under BlobCrypter.TIMESTAMP_KEY.
+ */
+public class BlobCrypter {
+
+  // Labels for key derivation
+  private static final byte CIPHER_KEY_LABEL = 0;
+  private static final byte HMAC_KEY_LABEL = 1;
+  
+  /** Key used for time stamp (in seconds) of data */
+  public static final String TIMESTAMP_KEY = "t";
+  
+  /** minimum length of master key */
+  public static final int MASTER_KEY_MIN_LEN = 16;
+  
+  /** allow three minutes for clock skew */
+  private static final long CLOCK_SKEW_ALLOWANCE = 180;
+  
+  private static final String UTF8 = "UTF-8";
+  
+  public TimeSource timeSource = new TimeSource();  
+  private byte[] cipherKey;
+  private byte[] hmacKey;
+  
+  /**
+   * Builds a BlobCrypter from the specified master key
+   * 
+   * @param masterKey
+   */
+  public BlobCrypter(byte[] masterKey) {
+    if (masterKey.length < MASTER_KEY_MIN_LEN) {
+      throw new IllegalArgumentException("Master key needs at least " +
+          MASTER_KEY_MIN_LEN + " bytes");
+    }
+    cipherKey = deriveKey(CIPHER_KEY_LABEL, masterKey, Crypto.CIPHER_KEY_LEN);
+    hmacKey = deriveKey(HMAC_KEY_LABEL, masterKey, 0);
+  }
+
+  /**
+   * Generates unique keys from a master key.
+   * 
+   * @param label type of key to derive
+   * @param masterKey master key
+   * @param len length of key needed, less than 20 bytes.  20 bytes are 
returned
+   * if len is 0.  
+   * 
+   * @return a derived key of the specified length
+   */
+  private byte[] deriveKey(byte label, byte[] masterKey, int len) {
+    byte[] base = Crypto.concat(new byte[] { label }, masterKey);
+    byte[] hash = DigestUtils.sha(base);
+    if (len == 0) {
+      return hash;
+    }
+    byte[] out = new byte[len];
+    System.arraycopy(hash, 0, out, 0, out.length);
+    return out;
+  }
+  
+  /**
+   * Time stamps, encrypts, and signs a blob.
+   * 
+   * @param in name/value pairs to encrypt
+   * @return a base64 encoded blob
+   * 
+   * @throws BlobCrypterException
+   */
+  public String wrap(Map<String, String> in)
+  throws BlobCrypterException {
+    if (in.containsKey(TIMESTAMP_KEY)) {
+      throw new IllegalArgumentException("No 't' keys allowed for 
BlobCrypter");
+    }
+    try {
+      byte[] encoded = serializeAndTimestamp(in);
+      byte[] cipherText = Crypto.aes128cbcEncrypt(cipherKey, encoded);
+      byte[] hmac = Crypto.hmacSha1(hmacKey, cipherText);
+      byte[] b64 = Base64.encodeBase64(Crypto.concat(cipherText, hmac));
+      return new String(b64, UTF8);
+    } catch (UnsupportedEncodingException e) {
+      throw new BlobCrypterException(e);
+    } catch (GeneralSecurityException e) {
+      throw new BlobCrypterException(e);
+    }
+  }
+
+  /**
+   * Encode the input for transfer.  We use something a lot like HTML form
+   * encodings.  The time stamp is in seconds since the epoch.
+   */
+  private byte[] serializeAndTimestamp(Map<String, String> in)
+  throws UnsupportedEncodingException {
+    StringBuffer sb = new StringBuffer();
+    Iterator<Map.Entry<String,String>> vals = in.entrySet().iterator();
+
+    while (vals.hasNext()) {
+      Map.Entry<String, String> val = vals.next();
+      sb.append(URLEncoder.encode(val.getKey(), UTF8));
+      sb.append("=");
+      sb.append(URLEncoder.encode(val.getValue(), UTF8));
+      sb.append("&");
+    }
+    sb.append(TIMESTAMP_KEY);
+    sb.append("=");
+    sb.append(timeSource.currentTimeMillis()/1000);
+    return sb.toString().getBytes(UTF8);
+  }
+
+  /**
+   * Unwraps a blob.
+   * 
+   * @param in blob
+   * @param maxAgeSec maximum age for the blob
+   * @return the name/value pairs, including the origin timestamp.
+   * 
+   * @throws BlobExpiredException if the blob is too old to be accepted.
+   * @throws BlobCrypterException if the blob can't be decoded.
+   */
+  public Map<String, String> unwrap(String in, int maxAgeSec)
+  throws BlobCrypterException {
+    try {
+      byte[] bin = Base64.decodeBase64(in.getBytes());
+      byte[] hmac = new byte[Crypto.HMAC_SHA1_LEN];
+      byte[] cipherText = new byte[bin.length-Crypto.HMAC_SHA1_LEN];
+      System.arraycopy(bin, 0, cipherText, 0, cipherText.length);
+      System.arraycopy(bin, cipherText.length, hmac, 0, hmac.length);
+      Crypto.hmacSha1Verify(hmacKey, cipherText, hmac);
+      byte[] plain = Crypto.aes128cbcDecrypt(cipherKey, cipherText);
+      Map<String, String> out = deserialize(plain);
+      checkTimestamp(out, maxAgeSec);
+      return out;
+    } catch (GeneralSecurityException e) {
+      throw new BlobCrypterException("Invalid token signature", e);
+    } catch (ArrayIndexOutOfBoundsException e) {
+      throw new BlobCrypterException("Invalid token format", e);
+    } catch (UnsupportedEncodingException e) {
+      throw new BlobCrypterException(e);
+    }
+
+  }
+
+  private Map<String, String> deserialize(byte[] plain) throws 
UnsupportedEncodingException {
+    String base = new String(plain, UTF8);
+    String[] items = base.split("[&=]");
+    Map<String, String> map = new HashMap<String, String>();
+    for (int i=0; i < items.length; ) {
+      String key = items[i++];
+      String val = items[i++];
+      map.put(key, val);
+    }
+    return map;
+  }
+  
+  /**
+   * We allow a few minutes on either side of the validity window to account
+   * for clock skew.
+   */
+  private void checkTimestamp(Map<String, String> out, int maxAge)
+  throws BlobExpiredException {
+    long origin = Long.parseLong(out.get(TIMESTAMP_KEY));
+    long minTime = origin - CLOCK_SKEW_ALLOWANCE;
+    long maxTime = origin + maxAge + CLOCK_SKEW_ALLOWANCE;
+    long now = timeSource.currentTimeMillis()/1000;
+    if (!(minTime < now && now < maxTime)) {
+      throw new BlobExpiredException(minTime, now, maxTime);
+    }    
+  }
+
+}

Added: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/BlobCrypterException.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/BlobCrypterException.java?rev=635874&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/BlobCrypterException.java
 (added)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/BlobCrypterException.java
 Tue Mar 11 03:44:17 2008
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.shindig.util;
+
+/**
+ * For all exceptions thrown by BlobCrypter
+ */
+public class BlobCrypterException extends Exception {
+  public BlobCrypterException(Throwable cause) {
+    super(cause);
+  }
+  
+  public BlobCrypterException(String msg, Throwable cause) {
+    super(msg, cause);
+  }
+  
+  protected BlobCrypterException(String msg) {
+    super(msg);
+  }
+}

Added: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/BlobExpiredException.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/BlobExpiredException.java?rev=635874&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/BlobExpiredException.java
 (added)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/BlobExpiredException.java
 Tue Mar 11 03:44:17 2008
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.shindig.util;
+
+import java.util.Date;
+
+/**
+ * Thrown when a blob has expired.
+ */
+public class BlobExpiredException extends BlobCrypterException {
+
+  public final Date minDate;
+  public final Date used;
+  public final Date maxDate;
+
+  public BlobExpiredException(long minTime, long now, long maxTime) {
+    this(new Date(minTime*1000), new Date(now*1000), new Date(maxTime*1000));
+  }
+
+  public BlobExpiredException(Date minTime, Date now, Date maxTime) {
+    super("Blob expired, was valid from " + minTime + " to " + maxTime
+        + ", attempted use at " + now);
+    this.minDate = minTime;
+    this.used = now;
+    this.maxDate = maxTime;
+  }
+
+}

Added: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/Crypto.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/Crypto.java?rev=635874&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/Crypto.java
 (added)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/Crypto.java
 Tue Mar 11 03:44:17 2008
@@ -0,0 +1,215 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.shindig.util;
+
+import org.apache.commons.codec.binary.Hex;
+
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.SecureRandom;
+
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Cryptographic utility functions.
+ */
+public class Crypto {
+  
+  /** 
+   * Use this random number generator instead of creating your own.  This is
+   * thread-safe.
+   */
+  public static SecureRandom rand = new SecureRandom();
+  
+  /**
+   * HMAC algorithm to use
+   */
+  private final static String HMAC_TYPE = "HMACSHA1";
+  
+  /** 
+   * minimum safe length for hmac keys (this is good practice, but not 
+   * actually a requirement of the algorithm
+   */
+  private final static int MIN_HMAC_KEY_LEN = 8;
+  
+  /**
+   * Encryption algorithm to use
+   */
+  private final static String CIPHER_TYPE = "AES/CBC/PKCS5Padding";
+  
+  private final static String CIPHER_KEY_TYPE = "AES";
+  
+  /**
+   * Use keys of this length for encryption operations
+   */
+  public final static int CIPHER_KEY_LEN = 16;
+  
+  private static int CIPHER_BLOCK_SIZE = 16;
+  
+  /**
+   * Length of HMAC SHA1 output
+   */
+  public final static int HMAC_SHA1_LEN = 20;
+  
+  // everything is static, no instantiating this class
+  private Crypto() { 
+  }
+
+  /**
+   * Gets a hex encoded random string.
+   * 
+   * @param numBytes number of bytes of randomness.
+   */
+  public static String getRandomString(int numBytes) {
+    return new String(Hex.encodeHex(getRandomBytes(numBytes)));
+  }
+  
+  /**
+   * Returns strong random bytes.
+   * 
+   * @param numBytes number of bytes of randomness
+   */
+  public static byte[] getRandomBytes(int numBytes) {
+    byte[] out = new byte[numBytes];
+    rand.nextBytes(out);
+    return out;
+  }
+  
+  /**
+   * HMAC sha1
+   * 
+   * @param key the key must be at least 8 bytes in length.
+   * @param in byte array to HMAC.
+   * @return the hash
+   * 
+   * @throws GeneralSecurityException
+   */
+  public static byte[] hmacSha1(byte[] key, byte[] in) throws 
GeneralSecurityException {
+    if (key.length < MIN_HMAC_KEY_LEN) {
+      throw new GeneralSecurityException("HMAC key should be at least "
+          + MIN_HMAC_KEY_LEN + " bytes.");
+    }
+    Mac hmac = Mac.getInstance(HMAC_TYPE);
+    Key hmacKey = new SecretKeySpec(key, HMAC_TYPE);
+    hmac.init(hmacKey);
+    hmac.update(in);
+    return hmac.doFinal();
+  }
+  
+  /**
+   * Verifies an HMAC SHA1 hash.  Throws if the verification fails.
+   * 
+   * @param key
+   * @param in
+   * @param expected
+   * @throws GeneralSecurityException
+   */
+  public static void hmacSha1Verify(byte[] key, byte[] in, byte[] expected)
+  throws GeneralSecurityException {
+    Mac hmac = Mac.getInstance(HMAC_TYPE);
+    Key hmacKey = new SecretKeySpec(key, HMAC_TYPE);
+    hmac.init(hmacKey);
+    hmac.update(in);
+    byte actual[] = hmac.doFinal();
+    if (actual.length != expected.length) {
+      throw new GeneralSecurityException("HMAC verification failure");
+    }
+    for (int i=0; i < actual.length; i++) {
+      if (actual[i] != expected[i]) {
+        throw new GeneralSecurityException("HMAC verification failure");       
 
+      }
+    }
+  }
+  
+  /**
+   * AES-128-CBC encryption.  The IV is returned as the first 16 bytes
+   * of the cipher text.
+   * 
+   * @param key
+   * @param plain
+   * 
+   * @return the IV and cipher text
+   * 
+   * @throws GeneralSecurityException
+   */
+  public static byte[] aes128cbcEncrypt(byte[] key, byte[] plain)
+  throws GeneralSecurityException {
+    Cipher cipher = Cipher.getInstance(CIPHER_TYPE);
+    Key cipherKey = new SecretKeySpec(key, CIPHER_KEY_TYPE);
+    byte iv[] = getRandomBytes(cipher.getBlockSize());
+    IvParameterSpec ivSpec = new IvParameterSpec(iv);
+    cipher.init(Cipher.ENCRYPT_MODE, cipherKey, ivSpec);
+    byte[] cipherText = cipher.doFinal(plain);
+    return concat(iv, cipherText);
+  }
+
+  /**
+   * AES-128-CBC decryption.  The IV is assumed to be the first 16 bytes
+   * of the cipher text.
+   * 
+   * @param key
+   * @param cipherText
+   * 
+   * @return the plain text
+   * 
+   * @throws GeneralSecurityException
+   */
+  public static byte[] aes128cbcDecrypt(byte[] key, byte[] cipherText)
+  throws GeneralSecurityException {
+    byte iv[] = new byte[CIPHER_BLOCK_SIZE];
+    System.arraycopy(cipherText, 0, iv, 0, iv.length);
+    return aes128cbcDecryptWithIv(key, iv, cipherText, iv.length);
+  }
+  
+  /**
+   * AES-128-CBC decryption with a particular IV.
+   * 
+   * @param key decryption key
+   * @param iv initial vector for decryption
+   * @param cipherText cipher text to decrypt
+   * @param offset offset into cipher text to begin decryption
+   * 
+   * @return the plain text
+   * 
+   * @throws GeneralSecurityException
+   */
+  public static byte[] aes128cbcDecryptWithIv(byte[] key, byte[] iv,
+      byte[] cipherText, int offset) throws GeneralSecurityException {
+    Cipher cipher = Cipher.getInstance(CIPHER_TYPE);
+    Key cipherKey = new SecretKeySpec(key, CIPHER_KEY_TYPE);
+    IvParameterSpec ivSpec = new IvParameterSpec(iv);
+    cipher.init(Cipher.DECRYPT_MODE, cipherKey, ivSpec);
+    return cipher.doFinal(cipherText, offset, cipherText.length-offset);
+  }
+
+  /**
+   * Concatenate two byte arrays.
+   */
+  public static byte[] concat(byte[] a, byte[] b) {
+    byte[] out = new byte[a.length + b.length];
+    int cursor = 0;
+    System.arraycopy(a, 0, out, cursor, a.length);
+    cursor += a.length;
+    System.arraycopy(b, 0, out, cursor, b.length);
+    return out;
+  }
+}

Added: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/TimeSource.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/TimeSource.java?rev=635874&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/TimeSource.java
 (added)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/TimeSource.java
 Tue Mar 11 03:44:17 2008
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.shindig.util;
+
+/**
+ * Simple source of current time to use for dependency injection.
+ */
+public class TimeSource {
+  
+  public long currentTimeMillis() {
+    return System.currentTimeMillis();
+  }
+}

Added: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/util/BlobCrypterTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/util/BlobCrypterTest.java?rev=635874&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/util/BlobCrypterTest.java
 (added)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/util/BlobCrypterTest.java
 Tue Mar 11 03:44:17 2008
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.shindig.util;
+
+import junit.framework.JUnit4TestAdapter;
+
+import static org.junit.Assert.*;
+
+import org.apache.commons.codec.binary.Base64;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class BlobCrypterTest {
+  public static junit.framework.Test suite() {
+    return new JUnit4TestAdapter(BlobCrypterTest.class);
+  }
+
+  private BlobCrypter crypter;
+  private FakeTimeSource timeSource;
+  
+  public BlobCrypterTest() {
+    crypter = new BlobCrypter("0123456789abcdef".getBytes());
+    timeSource = new FakeTimeSource();
+    crypter.timeSource = timeSource;
+  }
+  
+  @Test
+  public void testEncryptAndDecrypt() throws Exception {
+    checkString("");
+    checkString("a");
+    checkString("ab");
+    checkString("dfkljdasklsdfklasdjfklajsdfkljasdklfjasdkljfaskldjf");
+    checkString(Crypto.getRandomString(500));
+  }
+
+  private void checkString(String string) throws Exception {
+    Map<String, String> in = new HashMap<String, String>();
+    if (string != null) {
+      in.put("a", string);
+    }
+    String blob = crypter.wrap(in);
+    Map<String, String> out = crypter.unwrap(blob, 0);
+    assertEquals(string, out.get("a"));
+  }
+  
+  @Test
+  public void testManyEntries() throws Exception {
+    Map<String, String> in = new HashMap<String, String>();
+    for (int i=0; i < 1000; i++) {
+      in.put(Integer.toString(i), Integer.toString(i));
+    }
+    String blob = crypter.wrap(in);
+    Map<String, String> out = crypter.unwrap(blob, 0);
+    for (int i=0; i < 1000; i++) {
+      assertEquals(out.get(Integer.toString(i)), Integer.toString(i));
+    }
+  }
+  
+  @Test
+  public void testTimeStamping() throws Exception {
+    long start = 1201917724000L;
+    long skew = 180000;
+    int maxAge = 300; // 5 minutes
+    int realAge = 600; // 10 minutes
+    try {
+      
+      timeSource.setCurrentTimeMillis(start);
+      Map<String, String> in = new HashMap<String, String>();
+      in.put("a", "b");
+      String blob = crypter.wrap(in);
+      timeSource.incrementSeconds(realAge);
+      crypter.unwrap(blob, maxAge);
+      fail("Blob should have expired");
+    } catch (BlobExpiredException e) {
+      assertEquals(start-skew, e.minDate.getTime());
+      assertEquals(start+realAge*1000, e.used.getTime());
+      assertEquals(start+skew+maxAge*1000, e.maxDate.getTime());
+    }
+  }
+  
+  @Test
+  public void testTamperIV() throws Exception {
+    try {
+      Map<String, String> in = new HashMap<String, String>();
+      in.put("a", "b");
+      String blob = crypter.wrap(in);
+      byte[] blobBytes = Base64.decodeBase64(blob.getBytes());
+      blobBytes[0] ^= 0x01;
+      String tampered = new String(Base64.encodeBase64(blobBytes));
+      crypter.unwrap(tampered, 30);
+      fail("Signature verification should have failed.");
+    } catch (BlobCrypterException e) {
+      // Good
+    }
+  }
+  
+  @Test
+  public void testTamperData() throws Exception {
+    try {
+      Map<String, String> in = new HashMap<String, String>();
+      in.put("a", "b");
+      String blob = crypter.wrap(in);
+      byte[] blobBytes = Base64.decodeBase64(blob.getBytes());
+      blobBytes[30] ^= 0x01;
+      String tampered = new String(Base64.encodeBase64(blobBytes));
+      crypter.unwrap(tampered, 30);
+      fail("Signature verification should have failed.");
+    } catch (BlobCrypterException e) {
+      // Good
+    }
+  }
+  
+  @Test
+  public void testTamperMac() throws Exception {
+    try {
+      Map<String, String> in = new HashMap<String, String>();
+      in.put("a", "b");
+      String blob = crypter.wrap(in);
+      byte[] blobBytes = Base64.decodeBase64(blob.getBytes());
+      blobBytes[blobBytes.length-1] ^= 0x01;
+      String tampered = new String(Base64.encodeBase64(blobBytes));
+      crypter.unwrap(tampered, 30);
+      fail("Signature verification should have failed.");
+    } catch (BlobCrypterException e) {
+      // Good
+    }
+  }
+  
+  @Test
+  public void testFixedKey() throws Exception {
+    BlobCrypter alt = new BlobCrypter("0123456789abcdef".getBytes());
+    Map<String, String> in = new HashMap<String, String>();
+    in.put("a", "b");
+    String blob = crypter.wrap(in);
+    Map<String, String> out = alt.unwrap(blob, 30);
+    assertEquals("b", out.get("a"));
+  }
+  
+  @Test
+  public void testBadKey() throws Exception {
+    BlobCrypter alt = new BlobCrypter("1123456789abcdef".getBytes());
+    Map<String, String> in = new HashMap<String, String>();
+    in.put("a", "b");
+    String blob = crypter.wrap(in);
+    try {
+      Map<String, String> out = alt.unwrap(blob, 30);
+      fail("Decryption should have failed");
+    } catch (BlobCrypterException e) {
+      // Good.
+    }
+  }
+  
+  @Test
+  public void testShortKeyFails() throws Exception {
+    try {
+      new BlobCrypter("0123456789abcde".getBytes());
+      fail("Short key should fail");
+    } catch (IllegalArgumentException e) {
+      // good.
+    }
+  }
+}

Added: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/util/CryptoTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/util/CryptoTest.java?rev=635874&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/util/CryptoTest.java
 (added)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/util/CryptoTest.java
 Tue Mar 11 03:44:17 2008
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.shindig.util;
+
+import static org.junit.Assert.*;
+
+import junit.framework.JUnit4TestAdapter;
+
+import org.junit.Test;
+
+import java.security.GeneralSecurityException;
+
+public class CryptoTest {
+  public static junit.framework.Test suite() {
+    return new JUnit4TestAdapter(CryptoTest.class);
+  }
+
+  private BlobCrypter crypter;
+  
+  public CryptoTest() {
+    crypter = new BlobCrypter("0123456789abcdef".getBytes());
+    crypter.timeSource = new FakeTimeSource();
+  }
+  
+  @Test
+  public void testHmacSha1() throws Exception { 
+    String key = "abcd1234";
+    String val = "your mother is a hedgehog";
+    byte[] expected = new byte[] {
+        -21, 2, 47, -101, 9, -40, 18, 43, 76, 117,
+        -51, 115, -122, -91, 39, 26, -18, 122, 30, 90,     
+    };
+    byte[] hmac = Crypto.hmacSha1(key.getBytes(), val.getBytes());
+    assertArrayEquals(expected, hmac);
+  }
+  
+  @Test
+  public void testHmacSha1Verify() throws Exception { 
+    String key = "abcd1234";
+    String val = "your mother is a hedgehog";
+    byte[] expected = new byte[] {
+        -21, 2, 47, -101, 9, -40, 18, 43, 76, 117,
+        -51, 115, -122, -91, 39, 26, -18, 122, 30, 90,     
+    };
+    Crypto.hmacSha1Verify(key.getBytes(), val.getBytes(), expected);
+  }
+  
+  
+  @Test
+  public void testHmacSha1VerifyTampered() throws Exception { 
+    String key = "abcd1234";
+    String val = "your mother is a hedgehog";
+    byte[] expected = new byte[] {
+        -21, 2, 47, -101, 9, -40, 18, 43, 76, 117,
+        -51, 115, -122, -91, 39, 0, -18, 122, 30, 90,     
+    };
+    try {
+      Crypto.hmacSha1Verify(key.getBytes(), val.getBytes(), expected);
+      fail();
+    } catch (GeneralSecurityException e) {
+      // OK
+    }
+  }
+  
+  @Test
+  public void testAes128Cbc() throws Exception { 
+    byte[] key = Crypto.getRandomBytes(Crypto.CIPHER_KEY_LEN);
+    for (byte i=0; i < 50; i++) {
+      byte[] orig = new byte[i];
+      for (byte j=0; j < i; j++) {
+        orig[j] = j;
+      }
+      byte[] cipherText = Crypto.aes128cbcEncrypt(key, orig);
+      byte[] plainText = Crypto.aes128cbcDecrypt(key, cipherText);
+      assertArrayEquals("Array of length " + i, orig, plainText);
+    }
+  }
+}

Added: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/util/FakeTimeSource.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/util/FakeTimeSource.java?rev=635874&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/util/FakeTimeSource.java
 (added)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/util/FakeTimeSource.java
 Tue Mar 11 03:44:17 2008
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.shindig.util;
+
+/**
+ * Fake time source for dependency injection.
+ */
+public class FakeTimeSource extends TimeSource {
+
+  public long now;
+
+  public FakeTimeSource() {
+    this(System.currentTimeMillis());
+  }
+  
+  public FakeTimeSource(long now) {
+    this.now = now;
+  }
+  
+  @Override
+  public long currentTimeMillis() {
+    return now;
+  }
+  
+  public void setCurrentTimeMillis(long now) {
+    this.now = now;
+  }
+  
+  public void incrementSeconds(int seconds) {
+    now += seconds*1000;
+  }
+}


Reply via email to