Woo! At this rate we'll have no open issues by the end of the week! :) On Tue, Mar 11, 2008 at 3:44 AM, <[EMAIL PROTECTED]> wrote:
> 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; > + } > +} > > > -- ~Kevin

