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;
+ }
+}