Skip to content

Commit be5f811

Browse files
committed
Fix issues pointed out by code review
1 parent eef6d59 commit be5f811

File tree

11 files changed

+510
-306
lines changed

11 files changed

+510
-306
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
*
3+
* Copyright © 2019 Paul Schaub
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.jivesoftware.smack.util;
18+
19+
import java.security.SecureRandom;
20+
21+
public class RandomUtils {
22+
23+
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
24+
25+
/**
26+
* Generate a securely random byte array.
27+
*
28+
* @param len length of the byte array
29+
* @return byte array
30+
*/
31+
public static byte[] secureRandomBytes(int len) {
32+
byte[] bytes = new byte[len];
33+
SECURE_RANDOM.nextBytes(bytes);
34+
return bytes;
35+
}
36+
}

smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,8 @@ public static String encodeHex(byte[] bytes) {
246246

247247
/**
248248
* Convert a hexadecimal String to bytes.
249-
* Stolen from https://stackoverflow.com/a/140861/11150851
249+
*
250+
* Source: https://stackoverflow.com/a/140861/11150851
250251
*
251252
* @param s hex string
252253
* @return byte array

smack-experimental/src/main/java/org/jivesoftware/smackx/httpfileupload/HttpFileUploadManager.java

Lines changed: 2 additions & 239 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,10 @@
2424
import java.io.InputStream;
2525
import java.io.OutputStream;
2626
import java.net.HttpURLConnection;
27-
import java.net.MalformedURLException;
2827
import java.net.URL;
2928
import java.security.InvalidAlgorithmParameterException;
3029
import java.security.InvalidKeyException;
3130
import java.security.NoSuchAlgorithmException;
32-
import java.security.SecureRandom;
3331
import java.util.List;
3432
import java.util.Map;
3533
import java.util.Map.Entry;
@@ -38,11 +36,7 @@
3836
import java.util.logging.Logger;
3937
import javax.crypto.Cipher;
4038
import javax.crypto.CipherInputStream;
41-
import javax.crypto.KeyGenerator;
4239
import javax.crypto.NoSuchPaddingException;
43-
import javax.crypto.SecretKey;
44-
import javax.crypto.spec.IvParameterSpec;
45-
import javax.crypto.spec.SecretKeySpec;
4640
import javax.net.ssl.HttpsURLConnection;
4741
import javax.net.ssl.SSLContext;
4842
import javax.net.ssl.SSLSocketFactory;
@@ -55,14 +49,14 @@
5549
import org.jivesoftware.smack.XMPPConnection;
5650
import org.jivesoftware.smack.XMPPConnectionRegistry;
5751
import org.jivesoftware.smack.XMPPException;
58-
import org.jivesoftware.smack.util.Objects;
59-
import org.jivesoftware.smack.util.StringUtils;
6052
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
6153
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
6254
import org.jivesoftware.smackx.httpfileupload.UploadService.Version;
6355
import org.jivesoftware.smackx.httpfileupload.element.Slot;
6456
import org.jivesoftware.smackx.httpfileupload.element.SlotRequest;
6557
import org.jivesoftware.smackx.httpfileupload.element.SlotRequest_V0_2;
58+
import org.jivesoftware.smackx.omemo_media_sharing.AesgcmUrl;
59+
import org.jivesoftware.smackx.omemo_media_sharing.OmemoMediaSharingUtils;
6660
import org.jivesoftware.smackx.xdata.FormField;
6761
import org.jivesoftware.smackx.xdata.packet.DataForm;
6862

@@ -95,7 +89,6 @@ public final class HttpFileUploadManager extends Manager {
9589
public static final String NAMESPACE_0_2 = "urn:xmpp:http:upload";
9690

9791
private static final Logger LOGGER = Logger.getLogger(HttpFileUploadManager.class.getName());
98-
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
9992

10093
static {
10194
XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
@@ -579,234 +572,4 @@ public static UploadService.Version namespaceToVersion(String namespace) {
579572
private static boolean containsHttpFileUploadNamespace(DiscoverInfo discoverInfo) {
580573
return discoverInfo.containsFeature(NAMESPACE) || discoverInfo.containsFeature(NAMESPACE_0_2);
581574
}
582-
583-
/**
584-
* Generate a securely random byte array.
585-
*
586-
* @param len length of the byte array
587-
* @return byte array
588-
*/
589-
private static byte[] secureRandomBytes(int len) {
590-
byte[] bytes = new byte[len];
591-
SECURE_RANDOM.nextBytes(bytes);
592-
return bytes;
593-
}
594-
595-
/**
596-
* Utility code for XEP-XXXX: OMEMO Media Sharing.
597-
*
598-
* @see <a href="https://xmpp.org/extensions/inbox/omemo-media-sharing.html">XEP-XXXX: OMEMO Media Sharing</a>
599-
*/
600-
static class OmemoMediaSharingUtils {
601-
602-
private static final String KEYTYPE = "AES";
603-
private static final String CIPHERMODE = "AES/GCM/NoPadding";
604-
// 256 bit = 32 byte
605-
private static final int LEN_KEY = 32;
606-
private static final int LEN_KEY_BITS = LEN_KEY * 8;
607-
608-
private static final int LEN_IV_12 = 12;
609-
private static final int LEN_IV_16 = 16;
610-
// Note: Contrary to what the ProtoXEP states, 16 byte IV length is used in the wild instead of 12.
611-
// At some point we should switch to 12 bytes though.
612-
private static final int LEN_IV = LEN_IV_16;
613-
614-
static byte[] generateRandomIV() {
615-
return generateRandomIV(LEN_IV);
616-
}
617-
618-
static byte[] generateRandomIV(int len) {
619-
return secureRandomBytes(len);
620-
}
621-
622-
/**
623-
* Generate a random 256 bit AES key.
624-
*
625-
* @return encoded AES key
626-
* @throws NoSuchAlgorithmException if the JVM doesn't provide the given key type.
627-
*/
628-
static byte[] generateRandomKey() throws NoSuchAlgorithmException {
629-
KeyGenerator generator = KeyGenerator.getInstance(KEYTYPE);
630-
generator.init(LEN_KEY_BITS);
631-
return generator.generateKey().getEncoded();
632-
}
633-
634-
/**
635-
* Create a {@link Cipher} from a given key and iv which is in encryption mode.
636-
*
637-
* @param key aes encryption key
638-
* @param iv initialization vector
639-
*
640-
* @return cipher in encryption mode
641-
*
642-
* @throws NoSuchPaddingException if the JVM doesn't provide the padding specified in the ciphermode.
643-
* @throws NoSuchAlgorithmException if the JVM doesn't provide the encryption method specified in the ciphermode.
644-
* @throws InvalidAlgorithmParameterException if the cipher cannot be initiated.
645-
* @throws InvalidKeyException if the key is invalid.
646-
*/
647-
private static Cipher encryptionCipherFrom(byte[] key, byte[] iv)
648-
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException,
649-
InvalidKeyException {
650-
SecretKey secretKey = new SecretKeySpec(key, KEYTYPE);
651-
IvParameterSpec ivSpec = new IvParameterSpec(iv);
652-
Cipher cipher = Cipher.getInstance(CIPHERMODE);
653-
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
654-
return cipher;
655-
}
656-
657-
/**
658-
* Create a {@link Cipher} from a given key and iv which is in decryption mode.
659-
*
660-
* @param key aes encryption key
661-
* @param iv initialization vector
662-
*
663-
* @return cipher in decryption mode
664-
*
665-
* @throws NoSuchPaddingException if the JVM doesn't provide the padding specified in the ciphermode.
666-
* @throws NoSuchAlgorithmException if the JVM doesn't provide the encryption method specified in the ciphermode.
667-
* @throws InvalidAlgorithmParameterException if the cipher cannot be initiated.
668-
* @throws InvalidKeyException if the key is invalid.
669-
*/
670-
private static Cipher decryptionCipherFrom(byte[] key, byte[] iv)
671-
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException,
672-
InvalidKeyException {
673-
SecretKey secretKey = new SecretKeySpec(key, KEYTYPE);
674-
IvParameterSpec ivSpec = new IvParameterSpec(iv);
675-
Cipher cipher = Cipher.getInstance(CIPHERMODE);
676-
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
677-
return cipher;
678-
}
679-
}
680-
681-
/**
682-
* This class represents a aesgcm URL as described in XEP-XXXX: OMEMO Media Sharing.
683-
* As the builtin {@link URL} class cannot handle the aesgcm protocol identifier, this class
684-
* is used as a utility class that bundles together a {@link URL}, key and IV.
685-
*
686-
* @see <a href="https://xmpp.org/extensions/inbox/omemo-media-sharing.html">XEP-XXXX: OMEMO Media Sharing</a>
687-
*/
688-
public static class AesgcmUrl {
689-
690-
public static final String PROTOCOL = "aesgcm";
691-
692-
private final URL httpsUrl;
693-
private final byte[] keyBytes;
694-
private final byte[] ivBytes;
695-
696-
/**
697-
* Private constructor that constructs the {@link AesgcmUrl} from a normal https {@link URL}, a key and iv.
698-
*
699-
* @param httpsUrl normal https url as given by the {@link Slot}.
700-
* @param key byte array of an encoded 256 bit aes key
701-
* @param iv 16 or 12 byte initialization vector
702-
*/
703-
AesgcmUrl(URL httpsUrl, byte[] key, byte[] iv) {
704-
this.httpsUrl = Objects.requireNonNull(httpsUrl);
705-
this.keyBytes = Objects.requireNonNull(key);
706-
this.ivBytes = Objects.requireNonNull(iv);
707-
}
708-
709-
/**
710-
* Parse a {@link AesgcmUrl} from a {@link String}.
711-
* The parsed object will provide a normal {@link URL} under which the offered file can be downloaded,
712-
* as well as a {@link Cipher} that can be used to decrypt it.
713-
*
714-
* @param aesgcmUrlString aesgcm URL as a {@link String}
715-
*/
716-
public AesgcmUrl(String aesgcmUrlString) {
717-
if (!aesgcmUrlString.startsWith(PROTOCOL)) {
718-
throw new IllegalArgumentException("Provided String does not resemble a aesgcm URL.");
719-
}
720-
721-
// Convert aesgcm Url to https URL
722-
this.httpsUrl = extractHttpsUrl(aesgcmUrlString);
723-
724-
// Extract IV and Key
725-
byte[][] ivAndKey = extractIVAndKey(aesgcmUrlString);
726-
this.ivBytes = ivAndKey[0];
727-
this.keyBytes = ivAndKey[1];
728-
}
729-
730-
/**
731-
* Return a https {@link URL} under which the file can be downloaded.
732-
*
733-
* @return https URL
734-
*/
735-
public URL getDownloadUrl() {
736-
return httpsUrl;
737-
}
738-
739-
/**
740-
* Returns the {@link String} representation of this aesgcm URL.
741-
*
742-
* @return aesgcm URL with key and IV.
743-
*/
744-
public String getAesgcmUrl() {
745-
String aesgcmUrl = httpsUrl.toString().replaceFirst(httpsUrl.getProtocol(), PROTOCOL);
746-
return aesgcmUrl + "#" + StringUtils.encodeHex(ivBytes) + StringUtils.encodeHex(keyBytes);
747-
}
748-
749-
/**
750-
* Returns a {@link Cipher} in decryption mode, which can be used to decrypt the offered file.
751-
*
752-
* @return cipher
753-
*
754-
* @throws NoSuchPaddingException if the JVM cannot provide the specified cipher mode
755-
* @throws NoSuchAlgorithmException if the JVM cannot provide the specified cipher mode
756-
* @throws InvalidAlgorithmParameterException if the JVM cannot provide the specified cipher
757-
* (eg. if no BC provider is added)
758-
* @throws InvalidKeyException if the provided key is invalid
759-
*/
760-
public Cipher getDecryptionCipher() throws NoSuchPaddingException, NoSuchAlgorithmException,
761-
InvalidAlgorithmParameterException, InvalidKeyException {
762-
return OmemoMediaSharingUtils.decryptionCipherFrom(keyBytes, ivBytes);
763-
}
764-
765-
private static URL extractHttpsUrl(String aesgcmUrlString) {
766-
// aesgcm -> https
767-
String httpsUrlString = aesgcmUrlString.replaceFirst(PROTOCOL, "https");
768-
// remove #ref
769-
httpsUrlString = httpsUrlString.substring(0, httpsUrlString.indexOf("#"));
770-
771-
try {
772-
return new URL(httpsUrlString);
773-
} catch (MalformedURLException e) {
774-
throw new AssertionError("Failed to convert aesgcm URL to https URL: '" + aesgcmUrlString + "'", e);
775-
}
776-
}
777-
778-
private static byte[][] extractIVAndKey(String aesgcmUrlString) {
779-
int startOfRef = aesgcmUrlString.lastIndexOf("#");
780-
if (startOfRef == -1) {
781-
throw new IllegalArgumentException("The provided aesgcm Url does not have a ref part which is " +
782-
"supposed to contain the encryption key for file encryption.");
783-
}
784-
785-
String ref = aesgcmUrlString.substring(startOfRef + 1);
786-
byte[] refBytes = StringUtils.hexStringToByteArray(ref);
787-
788-
byte[] key = new byte[32];
789-
byte[] iv;
790-
int ivLen;
791-
// determine the length of the initialization vector part
792-
switch (refBytes.length) {
793-
// 32 bytes key + 16 bytes IV
794-
case 48:
795-
ivLen = 16;
796-
break;
797-
798-
// 32 bytes key + 12 bytes IV
799-
case 44:
800-
ivLen = 12;
801-
break;
802-
default:
803-
throw new IllegalArgumentException("Provided URL has an invalid ref tag (" + ref.length() + "): '" + ref + "'");
804-
}
805-
iv = new byte[ivLen];
806-
System.arraycopy(refBytes, 0, iv, 0, ivLen);
807-
System.arraycopy(refBytes, ivLen, key, 0, 32);
808-
809-
return new byte[][] {iv,key};
810-
}
811-
}
812575
}

0 commit comments

Comments
 (0)