162 lines
6.0 KiB
Java
162 lines
6.0 KiB
Java
/**
|
|
*
|
|
* 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};
|
|
}
|
|
}
|