diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadManager.java index 1da12a5759..87cb00b576 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadManager.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadManager.java @@ -55,6 +55,7 @@ import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnectionRegistry; import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.disco.packet.DiscoverInfo; @@ -69,11 +70,13 @@ /** * A manager for XEP-0363: HTTP File Upload. + * This manager is also capable of XEP-XXXX: OMEMO Media Sharing. * * @author Grigory Fedorov * @author Florian Schmaus * @author Paul Schaub * @see XEP-0363: HTTP File Upload + * @see XEP-XXXX: OMEMO Media Sharing */ public final class HttpFileUploadManager extends Manager { @@ -92,6 +95,7 @@ public final class HttpFileUploadManager extends Manager { public static final String NAMESPACE_0_2 = "urn:xmpp:http:upload"; private static final Logger LOGGER = Logger.getLogger(HttpFileUploadManager.class.getName()); + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); static { XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() { @@ -282,21 +286,21 @@ public URL uploadFile(File file, UploadProgressListener listener) throws Interru * Upload a file encrypted using the scheme described in OMEMO Media Sharing. * The file is being encrypted using a random 256 bit AES key in Galois Counter Mode using a random 16 byte IV and * then uploaded to the server. - * The URL that is returned has a modified scheme (aesgcm:// instead of http(s)://) and has the IV and key attached + * The URL that is returned has a modified scheme (aesgcm:// instead of https://) and has the IV and key attached * as ref part. * * Note: The URL contains the used key and IV in plain text. Keep in mind to only share this URL though a secured * channel (i.e. end-to-end encrypted message), as anybody who can read the URL can also decrypt the file. * * Note: This method uses a IV of length 16 instead of 12. Although not specified in the ProtoXEP, 16 byte IVs are - * currently used by most implementations. {@link #decryptionCipherForEncryptedFile(String)} also supports 12 byte IVs. + * currently used by most implementations. This implementation also supports 12 byte IVs when decrypting. * * @param file file * @return AESGCM URL which contains the key and IV of the encrypted file. * * @see XEP-XXXX: OMEMO Media Sharing */ - public String uploadFileEncrypted(File file) throws InterruptedException, IOException, + public AesgcmUrl uploadFileEncrypted(File file) throws InterruptedException, IOException, XMPPException.XMPPErrorException, SmackException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException { return uploadFileEncrypted(file, null); @@ -305,14 +309,14 @@ public String uploadFileEncrypted(File file) throws InterruptedException, IOExce * Upload a file encrypted using the scheme described in OMEMO Media Sharing. * The file is being encrypted using a random 256 bit AES key in Galois Counter Mode using a random 16 byte IV and * then uploaded to the server. - * The URL that is returned has a modified scheme (aesgcm:// instead of http(s)://) and has the IV and key attached + * The URL that is returned has a modified scheme (aesgcm:// instead of https://) and has the IV and key attached * as ref part. * * Note: The URL contains the used key and IV in plain text. Keep in mind to only share this URL though a secured * channel (i.e. end-to-end encrypted message), as anybody who can read the URL can also decrypt the file. * * Note: This method uses a IV of length 16 instead of 12. Although not specified in the ProtoXEP, 16 byte IVs are - * currently used by most implementations. {@link #decryptionCipherForEncryptedFile(String)} also supports 12 byte IVs. + * currently used by most implementations. This implementation also supports 12 byte IVs when decrypting. * * @param file file * @param listener progress listener or null @@ -320,102 +324,31 @@ public String uploadFileEncrypted(File file) throws InterruptedException, IOExce * * @see XEP-XXXX: OMEMO Media Sharing */ - public String uploadFileEncrypted(File file, UploadProgressListener listener) throws IOException, + public AesgcmUrl uploadFileEncrypted(File file, UploadProgressListener listener) throws IOException, InterruptedException, XMPPException.XMPPErrorException, SmackException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException { if (!file.isFile()) { throw new FileNotFoundException("The path " + file.getAbsolutePath() + " is not a file"); } + // The encrypted file will contain an extra block with the AEAD MAC. long cipherFileLength = file.length() + 16; final Slot slot = requestSlot(file.getName(), cipherFileLength, "application/octet-stream"); URL slotUrl = slot.getGetUrl(); - // fresh AES key - OmemoMediaSharing.Key key = OmemoMediaSharing.generateKey(); + // fresh AES key + iv + byte[] key = OmemoMediaSharingUtils.generateRandomKey(); + byte[] iv = OmemoMediaSharingUtils.generateRandomIV(); + Cipher cipher = OmemoMediaSharingUtils.encryptionCipherFrom(key, iv); FileInputStream fis = new FileInputStream(file); - // encrypt the file on the fly - CipherInputStream cis = new CipherInputStream(fis, key.cipher); + // encrypt the file on the fly - encryption actually happens below in uploadFile() + CipherInputStream cis = new CipherInputStream(fis, cipher); uploadFile(cis, cipherFileLength, slot, listener); - // set URL scheme to aesgcm:// and append IV + KEY - return slotUrl.toString().replaceFirst(slotUrl.getProtocol(), "aesgcm") - + "#" + key.getHexIv() + key.getHexKey(); - } - - /** - * Parse encryption key parameters from an aesgcm-URL and return a {@link Cipher} which can be used to decrypt the - * file to which the URL points to. - * - * @param aesgcmUrl url string that has a protocol of "aesgcm". - * @return {@link Cipher} that can be used to decrypt the file. - * - * @throws InvalidAlgorithmParameterException - * @throws NoSuchAlgorithmException - * @throws InvalidKeyException - * @throws NoSuchPaddingException - */ - public static Cipher decryptionCipherForEncryptedFile(String aesgcmUrl) throws InvalidAlgorithmParameterException, - NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException { - - if (!aesgcmUrl.startsWith("aesgcm://")) { - throw new IllegalArgumentException("The provided URL does not appear to belong to a file which was " + - "uploaded using OMEMO Media Sharing."); - } - - String ref; - try { - ref = aesgcmUrl.substring(aesgcmUrl.lastIndexOf("#") + 1); - } catch (StringIndexOutOfBoundsException e) { - ref = null; - } - - if (ref == null || ref.length() == 0) { - throw new IllegalArgumentException("The provided URL does not have a ref part, which normally includes " + - "the key and initialization vector for file encryption."); - } - - byte[] refBytes = OmemoMediaSharing.hexStringToByteArray(ref); - - byte[] key = new byte[32]; - byte[] iv; - int ivLen; - // determine the length of the initialization vector part - switch (refBytes.length) { - // 32 bytes key + 16 bytes IV - case 48: - ivLen = 16; - break; - - // 32 bytes key + 12 bytes IV - case 44: - ivLen = 12; - break; - default: - throw new IllegalArgumentException("Provided URL has an invalid ref tag."); - } - iv = new byte[ivLen]; - System.arraycopy(refBytes, 0, iv, 0, ivLen); - System.arraycopy(refBytes, ivLen, key, 0, 32); - - return new OmemoMediaSharing.Key(key, iv, OmemoMediaSharing.KEYTYPE, OmemoMediaSharing.CIPHERMODE, false).cipher; - } - - public static URL httpUrlFromAesgcmUrl(String aesgcmUrl) { - final String aesgcmProto = "aesgcm"; - if (!aesgcmUrl.startsWith(aesgcmProto)) { - throw new IllegalArgumentException("Provided URL is not a aesgcm URL."); - } - - String httpUrl = aesgcmUrl.replaceFirst(aesgcmProto, "https"); - try { - return new URL(httpUrl); - } catch (MalformedURLException e) { - throw new AssertionError("Failed to convert aesgcm URL to http URL."); - } + return new AesgcmUrl(slotUrl, key, iv); } /** @@ -647,12 +580,24 @@ private static boolean containsHttpFileUploadNamespace(DiscoverInfo discoverInfo return discoverInfo.containsFeature(NAMESPACE) || discoverInfo.containsFeature(NAMESPACE_0_2); } + /** + * Generate a securely random byte array. + * + * @param len length of the byte array + * @return byte array + */ + private static byte[] secureRandomBytes(int len) { + byte[] bytes = new byte[len]; + SECURE_RANDOM.nextBytes(bytes); + return bytes; + } + /** * Utility code for XEP-XXXX: OMEMO Media Sharing. * * @see XEP-XXXX: OMEMO Media Sharing */ - private static class OmemoMediaSharing { + static class OmemoMediaSharingUtils { private static final String KEYTYPE = "AES"; private static final String CIPHERMODE = "AES/GCM/NoPadding"; @@ -666,91 +611,202 @@ private static class OmemoMediaSharing { // At some point we should switch to 12 bytes though. private static final int LEN_IV = LEN_IV_16; - private static final SecureRandom RANDOM = new SecureRandom(); + static byte[] generateRandomIV() { + return generateRandomIV(LEN_IV); + } + + static byte[] generateRandomIV(int len) { + return secureRandomBytes(len); + } + + /** + * Generate a random 256 bit AES key. + * + * @return encoded AES key + * @throws NoSuchAlgorithmException if the JVM doesn't provide the given key type. + */ + static byte[] generateRandomKey() throws NoSuchAlgorithmException { + KeyGenerator generator = KeyGenerator.getInstance(KEYTYPE); + generator.init(LEN_KEY_BITS); + return generator.generateKey().getEncoded(); + } + + /** + * Create a {@link Cipher} from a given key and iv which is in encryption mode. + * + * @param key aes encryption key + * @param iv initialization vector + * + * @return cipher in encryption mode + * + * @throws NoSuchPaddingException if the JVM doesn't provide the padding specified in the ciphermode. + * @throws NoSuchAlgorithmException if the JVM doesn't provide the encryption method specified in the ciphermode. + * @throws InvalidAlgorithmParameterException if the cipher cannot be initiated. + * @throws InvalidKeyException if the key is invalid. + */ + private static Cipher encryptionCipherFrom(byte[] key, byte[] iv) + throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, + InvalidKeyException { + SecretKey secretKey = new SecretKeySpec(key, KEYTYPE); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + Cipher cipher = Cipher.getInstance(CIPHERMODE); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec); + return cipher; + } + + /** + * Create a {@link Cipher} from a given key and iv which is in decryption mode. + * + * @param key aes encryption key + * @param iv initialization vector + * + * @return cipher in decryption mode + * + * @throws NoSuchPaddingException if the JVM doesn't provide the padding specified in the ciphermode. + * @throws NoSuchAlgorithmException if the JVM doesn't provide the encryption method specified in the ciphermode. + * @throws InvalidAlgorithmParameterException if the cipher cannot be initiated. + * @throws InvalidKeyException if the key is invalid. + */ + private static Cipher decryptionCipherFrom(byte[] key, byte[] iv) + throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, + InvalidKeyException { + SecretKey secretKey = new SecretKeySpec(key, KEYTYPE); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + Cipher cipher = Cipher.getInstance(CIPHERMODE); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec); + return cipher; + } + } + + /** + * This class represents a aesgcm URL as described in XEP-XXXX: OMEMO Media Sharing. + * As the builtin {@link URL} class cannot handle the aesgcm protocol identifier, this class + * is used as a utility class that bundles together a {@link URL}, key and IV. + * + * @see XEP-XXXX: OMEMO Media Sharing + */ + public static class AesgcmUrl { + + public static final String PROTOCOL = "aesgcm"; + + private final URL httpsUrl; + private final byte[] keyBytes; + private final byte[] ivBytes; /** - * Generate a securely random byte array. + * Private constructor that constructs the {@link AesgcmUrl} from a normal https {@link URL}, a key and iv. * - * @param len length of the byte array - * @return byte array + * @param httpsUrl normal https url as given by the {@link Slot}. + * @param key byte array of an encoded 256 bit aes key + * @param iv 16 or 12 byte initialization vector */ - private static byte[] secureRandomBytes(int len) { - byte[] bytes = new byte[len]; - OmemoMediaSharing.RANDOM.nextBytes(bytes); - return bytes; + AesgcmUrl(URL httpsUrl, byte[] key, byte[] iv) { + this.httpsUrl = Objects.requireNonNull(httpsUrl); + this.keyBytes = Objects.requireNonNull(key); + this.ivBytes = Objects.requireNonNull(iv); } /** - * Convert a hexadecimal String to bytes. - * Stolen from https://stackoverflow.com/a/140861/11150851 + * Parse a {@link AesgcmUrl} from a {@link String}. + * The parsed object will provide a normal {@link URL} under which the offered file can be downloaded, + * as well as a {@link Cipher} that can be used to decrypt it. * - * @param s hex string - * @return byte array + * @param aesgcmUrlString aesgcm URL as a {@link String} */ - private static byte[] hexStringToByteArray(String s) { - int len = s.length(); - byte[] data = new byte[len / 2]; - for (int i = 0; i < len; i += 2) { - data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) - + Character.digit(s.charAt(i + 1), 16)); + public AesgcmUrl(String aesgcmUrlString) { + if (!aesgcmUrlString.startsWith(PROTOCOL)) { + throw new IllegalArgumentException("Provided String does not resemble a aesgcm URL."); } - return data; + + // Convert aesgcm Url to https URL + this.httpsUrl = extractHttpsUrl(aesgcmUrlString); + + // Extract IV and Key + byte[][] ivAndKey = extractIVAndKey(aesgcmUrlString); + this.ivBytes = ivAndKey[0]; + this.keyBytes = ivAndKey[1]; } /** - * Generate a {@link Key} which consists of the encryption key encoded as bytes, the initialization vector - * as bytes, as well as a {@link Cipher} which was created using the key and IV with {@link #KEYTYPE} - * and {@link #CIPHERMODE}. + * Return a https {@link URL} under which the file can be downloaded. * - * @return key - * @throws InvalidAlgorithmParameterException in case the encryption parameters are invalid - * @throws NoSuchAlgorithmException if the JVM lacks support for the used cipher - * @throws InvalidKeyException if the key is invalid - * @throws NoSuchPaddingException if there is no such specified padding + * @return https URL */ - static Key generateKey() - throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, InvalidKeyException, - NoSuchPaddingException { - KeyGenerator generator = KeyGenerator.getInstance(KEYTYPE); - generator.init(LEN_KEY_BITS); - byte[] keyBytes = generator.generateKey().getEncoded(); - return new Key(keyBytes, secureRandomBytes(LEN_IV), KEYTYPE, CIPHERMODE, true); + public URL getDownloadUrl() { + return httpsUrl; + } + + /** + * Returns the {@link String} representation of this aesgcm URL. + * + * @return aesgcm URL with key and IV. + */ + public String getAesgcmUrl() { + String aesgcmUrl = httpsUrl.toString().replaceFirst(httpsUrl.getProtocol(), PROTOCOL); + return aesgcmUrl + "#" + StringUtils.encodeHex(ivBytes) + StringUtils.encodeHex(keyBytes); } - private static class Key { - - private final byte[] key; - private final byte[] iv; - private final Cipher cipher; - - Key(byte[] key, byte[] iv, String keyType, String cipherMode, boolean encryptMode) - throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, - InvalidKeyException { - this.key = key; - this.iv = iv; - SecretKey secretKey = new SecretKeySpec(key, keyType); - IvParameterSpec ivSpec = new IvParameterSpec(iv); - this.cipher = Cipher.getInstance(cipherMode); - cipher.init(encryptMode ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, secretKey, ivSpec); + /** + * Returns a {@link Cipher} in decryption mode, which can be used to decrypt the offered file. + * + * @return cipher + * + * @throws NoSuchPaddingException if the JVM cannot provide the specified cipher mode + * @throws NoSuchAlgorithmException if the JVM cannot provide the specified cipher mode + * @throws InvalidAlgorithmParameterException if the JVM cannot provide the specified cipher + * (eg. if no BC provider is added) + * @throws InvalidKeyException if the provided key is invalid + */ + public Cipher getDecryptionCipher() throws NoSuchPaddingException, NoSuchAlgorithmException, + InvalidAlgorithmParameterException, InvalidKeyException { + return OmemoMediaSharingUtils.decryptionCipherFrom(keyBytes, ivBytes); + } + + private static URL extractHttpsUrl(String aesgcmUrlString) { + // aesgcm -> https + String httpsUrlString = aesgcmUrlString.replaceFirst(PROTOCOL, "https"); + // remove #ref + httpsUrlString = httpsUrlString.substring(0, httpsUrlString.indexOf("#")); + + try { + return new URL(httpsUrlString); + } catch (MalformedURLException e) { + throw new AssertionError("Failed to convert aesgcm URL to https URL: '" + aesgcmUrlString + "'", e); } + } - /** - * Return the key encoded as hexadecimal digits. - * - * @return key in hex - */ - String getHexKey() { - return StringUtils.encodeHex(key); + private static byte[][] extractIVAndKey(String aesgcmUrlString) { + int startOfRef = aesgcmUrlString.lastIndexOf("#"); + if (startOfRef == -1) { + throw new IllegalArgumentException("The provided aesgcm Url does not have a ref part which is " + + "supposed to contain the encryption key for file encryption."); } - /** - * Return the IV encoded as hexadecimal digits. - * - * @return IV in hex - */ - String getHexIv() { - return StringUtils.encodeHex(iv); + String ref = aesgcmUrlString.substring(startOfRef + 1); + byte[] refBytes = StringUtils.hexStringToByteArray(ref); + + byte[] key = new byte[32]; + byte[] iv; + int ivLen; + // determine the length of the initialization vector part + switch (refBytes.length) { + // 32 bytes key + 16 bytes IV + case 48: + ivLen = 16; + break; + + // 32 bytes key + 12 bytes IV + case 44: + ivLen = 12; + break; + default: + throw new IllegalArgumentException("Provided URL has an invalid ref tag (" + ref.length() + "): '" + ref + "'"); } + iv = new byte[ivLen]; + System.arraycopy(refBytes, 0, iv, 0, ivLen); + System.arraycopy(refBytes, ivLen, key, 0, 32); + + return new byte[][] {iv,key}; } } } diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/OmemoMediaSharingUtilsTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/OmemoMediaSharingUtilsTest.java new file mode 100644 index 0000000000..335704f3c4 --- /dev/null +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/httpfileupload/OmemoMediaSharingUtilsTest.java @@ -0,0 +1,70 @@ +/** + * + * Copyright © 2019 Paul Schaub + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.httpfileupload; + +import static junit.framework.TestCase.assertEquals; + +import java.net.MalformedURLException; +import java.net.URL; + +import org.jivesoftware.smack.util.StringUtils; + +import org.junit.Test; + +public class OmemoMediaSharingUtilsTest { + + private static final String iv_12 = "8c3d050e9386ec173861778f"; + private static final String iv_16 = "1ad857dcbb119e2642e4f8f7c137819e"; + private static final String key = "4f15af8f1a28100d0101fb1c2e119b0c18c34396c68ad379f5912ee21dca6b0b"; + private static final String key_iv_12 = iv_12 + key; + private static final String key_iv_16 = iv_16 + key; + + private static final String file = "download.montague.tld/4a771ac1-f0b2-4a4a-9700-f2a26fa2bb67/tr%C3%A8s%20cool.jpg"; + private static final String file_https = "https://" + file; + private static final String file_aesgcm_12 = "aesgcm://" + file + "#" + key_iv_12; + private static final String file_aesgcm_16 = "aesgcm://" + file + "#" + key_iv_16; + + @Test + public void test12byteIvVariant() throws MalformedURLException { + HttpFileUploadManager.AesgcmUrl aesgcm = new HttpFileUploadManager.AesgcmUrl(file_aesgcm_12); + + // Make sure, that parsed aesgcm url still equals input string + assertEquals(file_aesgcm_12, aesgcm.getAesgcmUrl()); + assertEquals(file_https, aesgcm.getDownloadUrl().toString()); + + URL url = new URL(file_https); + aesgcm = new HttpFileUploadManager.AesgcmUrl(url, StringUtils.hexStringToByteArray(key), + StringUtils.hexStringToByteArray(iv_12)); + assertEquals(file_aesgcm_12, aesgcm.getAesgcmUrl()); + assertEquals(file_https, aesgcm.getDownloadUrl().toString()); + } + + @Test + public void test16byteIvVariant() throws MalformedURLException { + HttpFileUploadManager.AesgcmUrl aesgcm = new HttpFileUploadManager.AesgcmUrl(file_aesgcm_16); + + // Make sure, that parsed aesgcm url still equals input string + assertEquals(file_aesgcm_16, aesgcm.getAesgcmUrl()); + assertEquals(file_https, aesgcm.getDownloadUrl().toString()); + + URL url = new URL(file_https); + aesgcm = new HttpFileUploadManager.AesgcmUrl(url, StringUtils.hexStringToByteArray(key), + StringUtils.hexStringToByteArray(iv_16)); + assertEquals(file_aesgcm_16, aesgcm.getAesgcmUrl()); + assertEquals(file_https, aesgcm.getDownloadUrl().toString()); + } +} diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadIntegrationTest.java index 7ec883df63..96673d0c13 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadIntegrationTest.java @@ -131,7 +131,7 @@ public void omemoMediaSharingTest() throws IOException, NoSuchPaddingException, fos.close(); } - String aesgcmUrl = hfumOne.uploadFileEncrypted(file, new UploadProgressListener() { + HttpFileUploadManager.AesgcmUrl aesgcmUrl = hfumOne.uploadFileEncrypted(file, new UploadProgressListener() { @Override public void onUploadProgress(long uploadedBytes, long totalBytes) { double progress = uploadedBytes / totalBytes; @@ -139,10 +139,10 @@ public void onUploadProgress(long uploadedBytes, long totalBytes) { } }); - URL httpUrl = HttpFileUploadManager.httpUrlFromAesgcmUrl(aesgcmUrl); - Cipher decryptionCipher = HttpFileUploadManager.decryptionCipherForEncryptedFile(aesgcmUrl); + URL httpsUrl = aesgcmUrl.getDownloadUrl(); + Cipher decryptionCipher = aesgcmUrl.getDecryptionCipher(); - HttpURLConnection urlConnection = getHttpUrlConnectionFor(httpUrl); + HttpURLConnection urlConnection = getHttpUrlConnectionFor(httpsUrl); ByteArrayOutputStream baos = new ByteArrayOutputStream(fileSize); byte[] buffer = new byte[4096];