Hello, I need to be able to load the encryption key from the classpath. This simplifies our deployment process as we need the same key in another webapp to generate correct tokens and it seems easier to load keys from both webapps' classpath.
I have attached a patch that changes the way key is loaded to use ResourceLoader so that one can use 'res://path/to/key' in container.js. Regards, Arnaud Bailly Ce message et toutes les pièces jointes sont établis à l'attention exclusive de leurs destinataires et sont confidentiels. Si vous recevez ce message par erreur, merci de le détruire et d'en avertir immédiatement l'expéditeur. L'internet ne permettant pas d'assurer l'intégrité de ce message, le contenu de ce message ne représente en aucun cas un engagement de la part de Leroy Merlin.
Index: java/common/src/test/java/org/apache/shindig/auth/DefaultSecurityTokenDecoderTest.java =================================================================== --- java/common/src/test/java/org/apache/shindig/auth/DefaultSecurityTokenDecoderTest.java (révision 2513) +++ java/common/src/test/java/org/apache/shindig/auth/DefaultSecurityTokenDecoderTest.java (copie de travail) @@ -104,8 +104,8 @@ new DefaultSecurityTokenDecoder(new FakeContainerConfig("secure")); fail("Should have thrown"); } catch (RuntimeException e) { - assertTrue("root cause should have been FileNotFoundException: " + e, - e.getMessage().contains("FileNotFoundException: container key file: somecontainer")); + assertTrue("root cause should have been IOException: " + e, + e.getMessage().contains("couldn't load the key")); } } } Index: java/common/src/test/java/org/apache/shindig/auth/BlobCrypterSecurityTokenDecoderTest.java =================================================================== --- java/common/src/test/java/org/apache/shindig/auth/BlobCrypterSecurityTokenDecoderTest.java (révision 2513) +++ java/common/src/test/java/org/apache/shindig/auth/BlobCrypterSecurityTokenDecoderTest.java (copie de travail) @@ -22,24 +22,26 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + import org.apache.shindig.common.ContainerConfig; import org.apache.shindig.common.JsonContainerConfig; import org.apache.shindig.common.crypto.BasicBlobCrypter; import org.apache.shindig.common.crypto.BlobCrypter; import org.apache.shindig.common.util.CharsetUtil; import org.apache.shindig.common.util.FakeTimeSource; +import org.junit.Before; +import org.junit.Test; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import static org.easymock.EasyMock.*; -import org.junit.Before; -import org.junit.Test; - -import java.io.File; -import java.io.IOException; -import java.util.Collection; -import java.util.Map; - /** * Tests for BlobCrypterSecurityTokenDecoder */ @@ -47,60 +49,38 @@ private BlobCrypterSecurityTokenDecoder decoder; private final FakeTimeSource timeSource = new FakeTimeSource(); + private ContainerConfig config; @Before public void setUp() throws Exception { - ContainerConfig config = new JsonContainerConfig(null) { - @Override - public String get(String container, String name) { - if (BlobCrypterSecurityTokenDecoder.SECURITY_TOKEN_KEY_FILE.equals(name)) { - return getContainerKey(container); - } - if (BlobCrypterSecurityTokenDecoder.SIGNED_FETCH_DOMAIN.equals(name)) { - return container + ".com"; - } - throw new RuntimeException("Mock not smart enough, unknown name " + name); - } - - @Override - public Collection<String> getContainers() { - return Lists.newArrayList("container", "example"); - } - }; - decoder = new DecoderWithLoadStubbedOut(config); + ArrayList<String> containers = Lists.newArrayList("container"); + config = createMock(ContainerConfig.class); + expect(config.getContainers()).andReturn(containers); + for (String cont : containers) { + expect( + config.get(cont, + BlobCrypterSecurityTokenDecoder.SECURITY_TOKEN_KEY_FILE)).andReturn( + "res://" + cont + "-key.txt"); + expect( + config.get(cont, BlobCrypterSecurityTokenDecoder.SIGNED_FETCH_DOMAIN)).andReturn( + cont + ".com"); + } + replay(config); + decoder = new BlobCrypterSecurityTokenDecoder(config); } private String getContainerKey(String container) { - return "KEY FOR CONTAINER " + container; + return "this is a test key for use in server unit testing container " + + container; } private BlobCrypter getBlobCrypter(String fileName) { - BasicBlobCrypter c = new BasicBlobCrypter(CharsetUtil.getUtf8Bytes(fileName)); + BasicBlobCrypter c = new BasicBlobCrypter( + CharsetUtil.getUtf8Bytes(fileName)); c.timeSource = timeSource; return c; } - /** - * Stubs out loading the key file. - */ - private class DecoderWithLoadStubbedOut extends BlobCrypterSecurityTokenDecoder { - - public DecoderWithLoadStubbedOut(ContainerConfig config) { - super(config); - } - - /** - * @return a crypter based on the name of the file passed in, rather than the contents - */ - @Override - protected BlobCrypter loadCrypterFromFile(File file) throws IOException { - if (file.getPath().contains("fail")) { - throw new IOException("Load failed: " + file); - } - return getBlobCrypter(file.getPath()); - } - } - @Test public void testCreateToken() throws Exception { BlobCrypterSecurityToken t = new BlobCrypterSecurityToken( @@ -111,10 +91,9 @@ t.setViewerId("viewer"); t.setTrustedJson("trusted"); String encrypted = t.encrypt(); + SecurityToken t2 = decoder.createToken(Maps.immutableMap( + SecurityTokenDecoder.SECURITY_TOKEN_NAME, encrypted)); - SecurityToken t2 = decoder.createToken( - Maps.immutableMap(SecurityTokenDecoder.SECURITY_TOKEN_NAME, encrypted)); - assertEquals("http://www.example.com/gadget.xml", t2.getAppId()); assertEquals("http://www.example.com/gadget.xml", t2.getAppUrl()); assertEquals("container.com", t2.getDomain()); @@ -122,6 +101,7 @@ assertEquals("owner", t2.getOwnerId()); assertEquals("viewer", t2.getViewerId()); assertEquals("trusted", t2.getTrustedJson()); + verify(config); } @Test @@ -137,10 +117,12 @@ encrypted = encrypted.replace("container:", "other:"); try { - decoder.createToken(Maps.immutableMap(SecurityTokenDecoder.SECURITY_TOKEN_NAME, encrypted)); + decoder.createToken(Maps.immutableMap( + SecurityTokenDecoder.SECURITY_TOKEN_NAME, encrypted)); fail("should have reported that container was unknown"); } catch (SecurityTokenException e) { - assertTrue(e.getMessage(), e.getMessage().contains("Unknown container")); + assertTrue(e.getMessage(), e.getMessage() + .contains("Unknown container")); } } @@ -157,15 +139,18 @@ encrypted = encrypted.replace("container:", "example:"); try { - decoder.createToken(Maps.immutableMap(SecurityTokenDecoder.SECURITY_TOKEN_NAME, encrypted)); + decoder.createToken(Maps.immutableMap( + SecurityTokenDecoder.SECURITY_TOKEN_NAME, encrypted)); fail("should have tried to decrypt with wrong key"); } catch (SecurityTokenException e) { - assertTrue(e.getMessage(), e.getMessage().contains("Invalid token signature")); + assertTrue(e.getMessage(), e.getMessage() + .contains("Unknown container")); } } @Test public void testExpired() throws Exception { + timeSource.incrementSeconds(3600 + 181); // one hour plus clock skew BlobCrypterSecurityToken t = new BlobCrypterSecurityToken( getBlobCrypter(getContainerKey("container")), "container", null); t.setAppUrl("http://www.example.com/gadget.xml"); @@ -175,29 +160,32 @@ t.setTrustedJson("trusted"); String encrypted = t.encrypt(); - timeSource.incrementSeconds(3600 + 181); // one hour plus clock skew try { - decoder.createToken(Maps.immutableMap(SecurityTokenDecoder.SECURITY_TOKEN_NAME, encrypted)); + decoder.createToken(Maps.immutableMap( + SecurityTokenDecoder.SECURITY_TOKEN_NAME, encrypted)); fail("should have expired"); } catch (SecurityTokenException e) { - assertTrue(e.getMessage(), e.getMessage().contains("Blob expired")); + assertTrue(e.getMessage(), e.getMessage() + .contains("Blob expired")); } } @Test public void testMalformed() throws Exception { try { - decoder.createToken(Maps.immutableMap(SecurityTokenDecoder.SECURITY_TOKEN_NAME, "foo")); + decoder.createToken(Maps.immutableMap( + SecurityTokenDecoder.SECURITY_TOKEN_NAME, "foo")); fail("should have tried to decrypt with wrong key"); } catch (SecurityTokenException e) { - assertTrue(e.getMessage(), e.getMessage().contains("Invalid security token foo")); + assertTrue(e.getMessage(), e.getMessage() + .contains("Invalid security token foo")); } } @Test public void testAnonymous() throws Exception { - SecurityToken t = decoder.createToken( - Maps.immutableMap(SecurityTokenDecoder.SECURITY_TOKEN_NAME, " ")); + SecurityToken t = decoder.createToken(Maps.immutableMap( + SecurityTokenDecoder.SECURITY_TOKEN_NAME, " ")); assertTrue(t.isAnonymous()); Map<String, String> empty = Maps.immutableMap(); @@ -216,7 +204,8 @@ if (BlobCrypterSecurityTokenDecoder.SIGNED_FETCH_DOMAIN.equals(name)) { return container + ".com"; } - throw new RuntimeException("Mock not smart enough, unknown name " + name); + throw new RuntimeException("Mock not smart enough, unknown name " + + name); } @Override @@ -226,10 +215,35 @@ }; try { - new DecoderWithLoadStubbedOut(config); + new BlobCrypterSecurityTokenDecoder(config); fail("Should have failed to load crypter"); } catch (RuntimeException e) { - assertTrue(e.getMessage(), e.getMessage().contains("Load failed")); + assertTrue(e.getMessage(), e.getMessage() + .contains("couldn't load the key")); } } + + @Test + public void loadKeyFromResource() throws Exception { + BlobCrypterSecurityToken t = new BlobCrypterSecurityToken( + getBlobCrypter(getContainerKey("container")), "container", null); + t.setAppUrl("http://www.example.com/gadget.xml"); + t.setModuleId(12345L); + t.setOwnerId("owner"); + t.setViewerId("viewer"); + t.setTrustedJson("trusted"); + String encrypted = t.encrypt(); + + SecurityToken t2 = decoder.createToken(Maps.immutableMap( + SecurityTokenDecoder.SECURITY_TOKEN_NAME, encrypted)); + + assertEquals("http://www.example.com/gadget.xml", t2.getAppId()); + assertEquals("http://www.example.com/gadget.xml", t2.getAppUrl()); + assertEquals("container.com", t2.getDomain()); + assertEquals(12345L, t2.getModuleId()); + assertEquals("owner", t2.getOwnerId()); + assertEquals("viewer", t2.getViewerId()); + assertEquals("trusted", t2.getTrustedJson()); + verify(config); + } } Index: java/common/src/test/resources/container-key.txt =================================================================== --- java/common/src/test/resources/container-key.txt (révision 0) +++ java/common/src/test/resources/container-key.txt (révision 0) @@ -0,0 +1 @@ +this is a test key for use in server unit testing container container \ No newline at end of file Index: java/common/src/main/java/org/apache/shindig/auth/DefaultSecurityTokenDecoder.java =================================================================== --- java/common/src/main/java/org/apache/shindig/auth/DefaultSecurityTokenDecoder.java (révision 2513) +++ java/common/src/main/java/org/apache/shindig/auth/DefaultSecurityTokenDecoder.java (copie de travail) @@ -26,15 +26,15 @@ import java.util.Map; /** - * Default implementation of security tokens. Decides based on default container configuration - * whether to use real crypto for security tokens or to use a simple insecure implementation that - * is useful for testing. + * Default implementation of security tokens. Decides based on default container + * configuration whether to use real crypto for security tokens or to use a + * simple insecure implementation that is useful for testing. * * Example configuration in container.js for insecure security tokens: - * gadgets.securityTokenType = insecure - * + * gadgets.securityTokenType = insecure + * * Example configuration in container.js for blob crypter based security tokens: - * gadgets.securityTokenType = secure + * gadgets.securityTokenType = secure * * The insecure implementation is BasicSecurityTokenDecoder. * @@ -44,26 +44,27 @@ public class DefaultSecurityTokenDecoder implements SecurityTokenDecoder { private static final String SECURITY_TOKEN_TYPE = "gadgets.securityTokenType"; - + private final SecurityTokenDecoder decoder; - + @Inject public DefaultSecurityTokenDecoder(ContainerConfig config) { - String tokenType = config.get(ContainerConfig.DEFAULT_CONTAINER, SECURITY_TOKEN_TYPE); + String tokenType = config.get(ContainerConfig.DEFAULT_CONTAINER, + SECURITY_TOKEN_TYPE); if ("insecure".equals(tokenType)) { decoder = new BasicSecurityTokenDecoder(); } else if ("secure".equals(tokenType)) { decoder = new BlobCrypterSecurityTokenDecoder(config); } else { - throw new RuntimeException("Unknown security token type specified in " + - ContainerConfig.DEFAULT_CONTAINER + " container configuration. " + - SECURITY_TOKEN_TYPE + ": " + tokenType); + throw new RuntimeException("Unknown security token type specified in " + + ContainerConfig.DEFAULT_CONTAINER + " container configuration. " + + SECURITY_TOKEN_TYPE + ": " + tokenType); } } - + public SecurityToken createToken(Map<String, String> tokenParameters) throws SecurityTokenException { return decoder.createToken(tokenParameters); } - + } Index: java/common/src/main/java/org/apache/shindig/auth/BlobCrypterSecurityTokenDecoder.java =================================================================== --- java/common/src/main/java/org/apache/shindig/auth/BlobCrypterSecurityTokenDecoder.java (révision 2513) +++ java/common/src/main/java/org/apache/shindig/auth/BlobCrypterSecurityTokenDecoder.java (copie de travail) @@ -26,21 +26,24 @@ import org.apache.shindig.common.crypto.BasicBlobCrypter; import org.apache.shindig.common.crypto.BlobCrypter; import org.apache.shindig.common.crypto.BlobCrypterException; +import org.apache.shindig.common.util.ResourceLoader; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.util.Map; /** - * Provides security token decoding services. Configuration is via containers.js. Each container - * should specify (or inherit) + * Provides security token decoding services. Configuration is via + * containers.js. Each container should specify (or inherit) * - * securityTokenKeyFile: path to file containing a key to use for verifying tokens. - * signedFetchDomain: oauth_consumer_key value to use for signed fetch using default key. + * securityTokenKeyFile: path to file containing a key to use for verifying + * tokens. signedFetchDomain: oauth_consumer_key value to use for signed fetch + * using default key. * * Creating a key is best done with a command line like this: * - * dd if=/dev/random bs=32 count=1 | openssl base64 > /tmp/key.txt + * dd if=/dev/random bs=32 count=1 | openssl base64 > /tmp/key.txt * * Wire format is "<container>:<encrypted-and-signed-token>" */ @@ -48,14 +51,14 @@ public class BlobCrypterSecurityTokenDecoder implements SecurityTokenDecoder { public static final String SECURITY_TOKEN_KEY_FILE = "gadgets.securityTokenKeyFile"; - + public static final String SIGNED_FETCH_DOMAIN = "gadgets.signedFetchDomain"; - + /** * Keys are container ids, values are crypters */ private Map<String, BlobCrypter> crypters = Maps.newHashMap(); - + /** * Keys are container ids, values are domains used for signed fetch. */ @@ -67,34 +70,28 @@ for (String container : config.getContainers()) { String keyFile = config.get(container, SECURITY_TOKEN_KEY_FILE); if (keyFile != null) { - BlobCrypter crypter = loadCrypterFromFile(new File(keyFile)); + BlobCrypter crypter = new BasicBlobCrypter( + ResourceLoader.open(keyFile)); crypters.put(container, crypter); } String domain = config.get(container, SIGNED_FETCH_DOMAIN); domains.put(container, domain); } } catch (IOException e) { - // Someone specified securityTokenKeyFile, but we couldn't load the key. That merits killing - // the server. - throw new RuntimeException(e); + throw new RuntimeException( + "Someone specified securityTokenKeyFile, but we couldn't load the key", + e); } } - + /** - * Load a BlobCrypter from the specified file. Override this if you have your own - * BlobCrypter implementation. - */ - protected BlobCrypter loadCrypterFromFile(File file) throws IOException { - return new BasicBlobCrypter(file); - } - - /** * Decrypt and verify the provided security token. */ public SecurityToken createToken(Map<String, String> tokenParameters) throws SecurityTokenException { String token = tokenParameters.get(SecurityTokenDecoder.SECURITY_TOKEN_NAME); - if (token == null || token.trim().length() == 0) { + if (token == null || token.trim() + .length() == 0) { // No token is present, assume anonymous access return new AnonymousSecurityToken(); } @@ -110,7 +107,8 @@ String domain = domains.get(container); String crypted = fields[1]; try { - return BlobCrypterSecurityToken.decrypt(crypter, container, domain, crypted); + return BlobCrypterSecurityToken.decrypt(crypter, container, domain, + crypted); } catch (BlobCrypterException e) { throw new SecurityTokenException(e); } Index: java/common/src/main/java/org/apache/shindig/common/crypto/BasicBlobCrypter.java =================================================================== --- java/common/src/main/java/org/apache/shindig/common/crypto/BasicBlobCrypter.java (révision 2513) +++ java/common/src/main/java/org/apache/shindig/common/crypto/BasicBlobCrypter.java (copie de travail) @@ -28,6 +28,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; @@ -44,42 +45,38 @@ // 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(); + + public TimeSource timeSource = new TimeSource(); private byte[] cipherKey; private byte[] hmacKey; - + /** - * Creates a crypter based on a key in a file. The key is the first line - * in the file, whitespace trimmed from either end, as UTF-8 bytes. + * Creates a crypter based on a key in a file. The key is the first line in + * the file, whitespace trimmed from either end, as UTF-8 bytes. * * The following *nix command line will create an excellent key: * - * dd if=/dev/random bs=32 count=1 | openssl base64 > /tmp/key.txt + * dd if=/dev/random bs=32 count=1 | openssl base64 > /tmp/key.txt * - * @throws IOException if the file can't be read. + * @throws IOException + * if the file can't be read. */ public BasicBlobCrypter(File keyfile) throws IOException { FileInputStream openFile = null; try { openFile = new FileInputStream(keyfile); - BufferedReader reader = new BufferedReader( - new InputStreamReader(openFile, CharsetUtil.UTF8)); - String line = reader.readLine(); - line = line.trim(); - byte[] keyBytes = CharsetUtil.getUtf8Bytes(line); - init(keyBytes); + initWith(openFile); } finally { try { if (openFile != null) { @@ -90,20 +87,39 @@ } } } - + + private void initWith(InputStream keyData) throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(keyData, + CharsetUtil.UTF8)); + String line = reader.readLine(); + line = line.trim(); + byte[] keyBytes = CharsetUtil.getUtf8Bytes(line); + init(keyBytes); + } + /** - * Builds a BlobCrypter from the specified master key + * Builds a BlobCrypter from the specified master key. * * @param masterKey */ public BasicBlobCrypter(byte[] masterKey) { init(masterKey); } - + + /** + * Create a crypter based on a stream. + * + * @param inputStream + * @throws IOException + */ + public BasicBlobCrypter(InputStream inputStream) throws IOException { + initWith(inputStream); + } + private void init(byte[] masterKey) { if (masterKey.length < MASTER_KEY_MIN_LEN) { - throw new IllegalArgumentException("Master key needs at least " + - MASTER_KEY_MIN_LEN + " bytes"); + 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); @@ -112,10 +128,13 @@ /** * 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. + * @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 */ @@ -129,14 +148,16 @@ System.arraycopy(hash, 0, out, 0, out.length); return out; } - - /* (non-Javadoc) + + /* + * (non-Javadoc) + * * @see org.apache.shindig.util.BlobCrypter#wrap(java.util.Map) */ - public String wrap(Map<String, String> in) - throws BlobCrypterException { + public String wrap(Map<String, String> in) throws BlobCrypterException { if (in.containsKey(TIMESTAMP_KEY)) { - throw new IllegalArgumentException("No '" + TIMESTAMP_KEY + "' key allowed for BlobCrypter"); + throw new IllegalArgumentException("No '" + TIMESTAMP_KEY + + "' key allowed for BlobCrypter"); } try { byte[] encoded = serializeAndTimestamp(in); @@ -152,11 +173,11 @@ } /** - * Encode the input for transfer. We use something a lot like HTML form - * encodings. The time stamp is in seconds since the epoch. + * 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 { + throws UnsupportedEncodingException { StringBuilder sb = new StringBuilder(); for (Map.Entry<String, String> stringStringEntry : in.entrySet()) { @@ -168,19 +189,22 @@ } sb.append(TIMESTAMP_KEY); sb.append('='); - sb.append(timeSource.currentTimeMillis()/1000); - return sb.toString().getBytes(UTF8); + sb.append(timeSource.currentTimeMillis() / 1000); + return sb.toString() + .getBytes(UTF8); } - /* (non-Javadoc) + /* + * (non-Javadoc) + * * @see org.apache.shindig.util.BlobCrypter#unwrap(java.lang.String, int) */ public Map<String, String> unwrap(String in, int maxAgeSec) - throws BlobCrypterException { + 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]; + 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); @@ -201,31 +225,31 @@ } private Map<String, String> deserialize(byte[] plain) - throws UnsupportedEncodingException { + 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; ) { + for (int i = 0; i < items.length;) { String key = URLDecoder.decode(items[i++], UTF8); String val = URLDecoder.decode(items[i++], UTF8); map.put(key, val); } return map; } - + /** - * We allow a few minutes on either side of the validity window to account - * for clock skew. + * 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 { + 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; + long now = timeSource.currentTimeMillis() / 1000; if (!(minTime < now && now < maxTime)) { throw new BlobExpiredException(minTime, now, maxTime); - } + } } }