2121import java .io .FileInputStream ;
2222import java .io .FileNotFoundException ;
2323import java .io .IOException ;
24+ import java .io .InputStream ;
2425import java .io .OutputStream ;
2526import java .net .HttpURLConnection ;
2627import java .net .URL ;
28+ import java .security .InvalidAlgorithmParameterException ;
29+ import java .security .InvalidKeyException ;
30+ import java .security .NoSuchAlgorithmException ;
2731import java .util .List ;
2832import java .util .Map ;
2933import java .util .Map .Entry ;
3034import java .util .WeakHashMap ;
3135import java .util .logging .Level ;
3236import java .util .logging .Logger ;
33-
37+ import javax .crypto .Cipher ;
38+ import javax .crypto .CipherInputStream ;
39+ import javax .crypto .NoSuchPaddingException ;
3440import javax .net .ssl .HttpsURLConnection ;
3541import javax .net .ssl .SSLContext ;
3642import javax .net .ssl .SSLSocketFactory ;
4349import org .jivesoftware .smack .XMPPConnection ;
4450import org .jivesoftware .smack .XMPPConnectionRegistry ;
4551import org .jivesoftware .smack .XMPPException ;
46-
4752import org .jivesoftware .smackx .disco .ServiceDiscoveryManager ;
4853import org .jivesoftware .smackx .disco .packet .DiscoverInfo ;
4954import org .jivesoftware .smackx .httpfileupload .UploadService .Version ;
5055import org .jivesoftware .smackx .httpfileupload .element .Slot ;
5156import org .jivesoftware .smackx .httpfileupload .element .SlotRequest ;
5257import 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 ;
5360import org .jivesoftware .smackx .xdata .FormField ;
5461import org .jivesoftware .smackx .xdata .packet .DataForm ;
5562
5663import org .jxmpp .jid .DomainBareJid ;
5764
5865/**
5966 * A manager for XEP-0363: HTTP File Upload.
67+ * This manager is also capable of XEP-XXXX: OMEMO Media Sharing.
6068 *
6169 * @author Grigory Fedorov
6270 * @author Florian Schmaus
71+ * @author Paul Schaub
6372 * @see <a href="http://xmpp.org/extensions/xep-0363.html">XEP-0363: HTTP File Upload</a>
73+ * @see <a href="https://xmpp.org/extensions/inbox/omemo-media-sharing.html">XEP-XXXX: OMEMO Media Sharing</a>
6474 */
6575public final class HttpFileUploadManager extends Manager {
6676
@@ -245,7 +255,7 @@ public URL uploadFile(File file) throws InterruptedException, XMPPException.XMPP
245255 * Note that this is a synchronous call -- Smack must wait for the server response.
246256 *
247257 * @param file file to be uploaded
248- * @param listener upload progress listener of null
258+ * @param listener upload progress listener or null
249259 * @return public URL for sharing uploaded file
250260 *
251261 * @throws InterruptedException
@@ -265,6 +275,74 @@ public URL uploadFile(File file, UploadProgressListener listener) throws Interru
265275 return slot .getGetUrl ();
266276 }
267277
278+ /**
279+ * Upload a file encrypted using the scheme described in OMEMO Media Sharing.
280+ * The file is being encrypted using a random 256 bit AES key in Galois Counter Mode using a random 16 byte IV and
281+ * then uploaded to the server.
282+ * The URL that is returned has a modified scheme (aesgcm:// instead of https://) and has the IV and key attached
283+ * as ref part.
284+ *
285+ * Note: The URL contains the used key and IV in plain text. Keep in mind to only share this URL though a secured
286+ * channel (i.e. end-to-end encrypted message), as anybody who can read the URL can also decrypt the file.
287+ *
288+ * Note: This method uses a IV of length 16 instead of 12. Although not specified in the ProtoXEP, 16 byte IVs are
289+ * currently used by most implementations. This implementation also supports 12 byte IVs when decrypting.
290+ *
291+ * @param file file
292+ * @return AESGCM URL which contains the key and IV of the encrypted file.
293+ *
294+ * @see <a href="https://xmpp.org/extensions/inbox/omemo-media-sharing.html">XEP-XXXX: OMEMO Media Sharing</a>
295+ */
296+ public AesgcmUrl uploadFileEncrypted (File file ) throws InterruptedException , IOException ,
297+ XMPPException .XMPPErrorException , SmackException , InvalidAlgorithmParameterException ,
298+ NoSuchAlgorithmException , InvalidKeyException , NoSuchPaddingException {
299+ return uploadFileEncrypted (file , null );
300+ }
301+ /**
302+ * Upload a file encrypted using the scheme described in OMEMO Media Sharing.
303+ * The file is being encrypted using a random 256 bit AES key in Galois Counter Mode using a random 16 byte IV and
304+ * then uploaded to the server.
305+ * The URL that is returned has a modified scheme (aesgcm:// instead of https://) and has the IV and key attached
306+ * as ref part.
307+ *
308+ * Note: The URL contains the used key and IV in plain text. Keep in mind to only share this URL though a secured
309+ * channel (i.e. end-to-end encrypted message), as anybody who can read the URL can also decrypt the file.
310+ *
311+ * Note: This method uses a IV of length 16 instead of 12. Although not specified in the ProtoXEP, 16 byte IVs are
312+ * currently used by most implementations. This implementation also supports 12 byte IVs when decrypting.
313+ *
314+ * @param file file
315+ * @param listener progress listener or null
316+ * @return AESGCM URL which contains the key and IV of the encrypted file.
317+ *
318+ * @see <a href="https://xmpp.org/extensions/inbox/omemo-media-sharing.html">XEP-XXXX: OMEMO Media Sharing</a>
319+ */
320+ public AesgcmUrl uploadFileEncrypted (File file , UploadProgressListener listener ) throws IOException ,
321+ InterruptedException , XMPPException .XMPPErrorException , SmackException , NoSuchPaddingException ,
322+ NoSuchAlgorithmException , InvalidAlgorithmParameterException , InvalidKeyException {
323+ if (!file .isFile ()) {
324+ throw new FileNotFoundException ("The path " + file .getAbsolutePath () + " is not a file" );
325+ }
326+
327+ // The encrypted file will contain an extra block with the AEAD MAC.
328+ long cipherFileLength = file .length () + 16 ;
329+
330+ final Slot slot = requestSlot (file .getName (), cipherFileLength , "application/octet-stream" );
331+ URL slotUrl = slot .getGetUrl ();
332+
333+ // fresh AES key + iv
334+ byte [] key = OmemoMediaSharingUtils .generateRandomKey ();
335+ byte [] iv = OmemoMediaSharingUtils .generateRandomIV ();
336+ Cipher cipher = OmemoMediaSharingUtils .encryptionCipherFrom (key , iv );
337+
338+ FileInputStream fis = new FileInputStream (file );
339+ // encrypt the file on the fly - encryption actually happens below in uploadFile()
340+ CipherInputStream cis = new CipherInputStream (fis , cipher );
341+
342+ uploadFile (cis , cipherFileLength , slot , listener );
343+
344+ return new AesgcmUrl (slotUrl , key , iv );
345+ }
268346
269347 /**
270348 * Request a new upload slot from default upload service (if discovered). When you get slot you should upload file
@@ -391,10 +469,13 @@ private void uploadFile(final File file, final Slot slot, UploadProgressListener
391469 if (fileSize >= Integer .MAX_VALUE ) {
392470 throw new IllegalArgumentException ("File size " + fileSize + " must be less than " + Integer .MAX_VALUE );
393471 }
394- final int fileSizeInt = (int ) fileSize ;
395472
396473 // Construct the FileInputStream first to make sure we can actually read the file.
397474 final FileInputStream fis = new FileInputStream (file );
475+ uploadFile (fis , fileSize , slot , listener );
476+ }
477+
478+ private void uploadFile (final InputStream fis , long fileSize , final Slot slot , UploadProgressListener listener ) throws IOException {
398479
399480 final URL putUrl = slot .getPutUrl ();
400481
@@ -404,7 +485,7 @@ private void uploadFile(final File file, final Slot slot, UploadProgressListener
404485 urlConnection .setUseCaches (false );
405486 urlConnection .setDoOutput (true );
406487 // TODO Change to using fileSize once Smack's minimum Android API level is 19 or higher.
407- urlConnection .setFixedLengthStreamingMode (fileSizeInt );
488+ urlConnection .setFixedLengthStreamingMode (( int ) fileSize );
408489 urlConnection .setRequestProperty ("Content-Type" , "application/octet-stream;" );
409490 for (Entry <String , String > header : slot .getHeaders ().entrySet ()) {
410491 urlConnection .setRequestProperty (header .getKey (), header .getValue ());
0 commit comments