-
Notifications
You must be signed in to change notification settings - Fork 884
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for XEP-XXXX: OMEMO Media Sharing
- Loading branch information
1 parent
8a74aec
commit ce2b79c
Showing
11 changed files
with
678 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 36 additions & 0 deletions
36
smack-core/src/main/java/org/jivesoftware/smack/util/RandomUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/** | ||
* | ||
* 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.smack.util; | ||
|
||
import java.security.SecureRandom; | ||
|
||
public class RandomUtils { | ||
|
||
private static final SecureRandom SECURE_RANDOM = new SecureRandom(); | ||
|
||
/** | ||
* Generate a securely random byte array. | ||
* | ||
* @param len length of the byte array | ||
* @return byte array | ||
*/ | ||
public static byte[] secureRandomBytes(int len) { | ||
byte[] bytes = new byte[len]; | ||
SECURE_RANDOM.nextBytes(bytes); | ||
return bytes; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
161 changes: 161 additions & 0 deletions
161
smack-experimental/src/main/java/org/jivesoftware/smackx/omemo_media_sharing/AesgcmUrl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
/** | ||
* | ||
* 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.omemo_media_sharing; | ||
|
||
import java.net.MalformedURLException; | ||
import java.net.URL; | ||
import java.security.InvalidAlgorithmParameterException; | ||
import java.security.InvalidKeyException; | ||
import java.security.NoSuchAlgorithmException; | ||
import javax.crypto.Cipher; | ||
import javax.crypto.NoSuchPaddingException; | ||
|
||
import org.jivesoftware.smack.util.Objects; | ||
import org.jivesoftware.smack.util.StringUtils; | ||
import org.jivesoftware.smackx.httpfileupload.element.Slot; | ||
|
||
/** | ||
* 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 <a href="https://xmpp.org/extensions/inbox/omemo-media-sharing.html">XEP-XXXX: OMEMO Media Sharing</a> | ||
*/ | ||
public class AesgcmUrl { | ||
|
||
public static final String PROTOCOL = "aesgcm"; | ||
|
||
private final URL httpsUrl; | ||
private final byte[] keyBytes; | ||
private final byte[] ivBytes; | ||
|
||
/** | ||
* Private constructor that constructs the {@link AesgcmUrl} from a normal https {@link URL}, a key and iv. | ||
* | ||
* @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 | ||
*/ | ||
public AesgcmUrl(URL httpsUrl, byte[] key, byte[] iv) { | ||
this.httpsUrl = Objects.requireNonNull(httpsUrl); | ||
this.keyBytes = Objects.requireNonNull(key); | ||
this.ivBytes = Objects.requireNonNull(iv); | ||
} | ||
|
||
/** | ||
* 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 aesgcmUrlString aesgcm URL as a {@link String} | ||
*/ | ||
public AesgcmUrl(String aesgcmUrlString) { | ||
if (!aesgcmUrlString.startsWith(PROTOCOL)) { | ||
throw new IllegalArgumentException("Provided String does not resemble a aesgcm URL."); | ||
} | ||
|
||
// 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]; | ||
} | ||
|
||
/** | ||
* Return a https {@link URL} under which the file can be downloaded. | ||
* | ||
* @return https URL | ||
*/ | ||
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); | ||
} | ||
|
||
/** | ||
* 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); | ||
} | ||
} | ||
|
||
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."); | ||
} | ||
|
||
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}; | ||
} | ||
} |
Oops, something went wrong.