|
24 | 24 | import java.io.InputStream; |
25 | 25 | import java.io.OutputStream; |
26 | 26 | import java.net.HttpURLConnection; |
27 | | -import java.net.MalformedURLException; |
28 | 27 | import java.net.URL; |
29 | 28 | import java.security.InvalidAlgorithmParameterException; |
30 | 29 | import java.security.InvalidKeyException; |
31 | 30 | import java.security.NoSuchAlgorithmException; |
32 | | -import java.security.SecureRandom; |
33 | 31 | import java.util.List; |
34 | 32 | import java.util.Map; |
35 | 33 | import java.util.Map.Entry; |
|
38 | 36 | import java.util.logging.Logger; |
39 | 37 | import javax.crypto.Cipher; |
40 | 38 | import javax.crypto.CipherInputStream; |
41 | | -import javax.crypto.KeyGenerator; |
42 | 39 | import javax.crypto.NoSuchPaddingException; |
43 | | -import javax.crypto.SecretKey; |
44 | | -import javax.crypto.spec.IvParameterSpec; |
45 | | -import javax.crypto.spec.SecretKeySpec; |
46 | 40 | import javax.net.ssl.HttpsURLConnection; |
47 | 41 | import javax.net.ssl.SSLContext; |
48 | 42 | import javax.net.ssl.SSLSocketFactory; |
|
55 | 49 | import org.jivesoftware.smack.XMPPConnection; |
56 | 50 | import org.jivesoftware.smack.XMPPConnectionRegistry; |
57 | 51 | import org.jivesoftware.smack.XMPPException; |
58 | | -import org.jivesoftware.smack.util.Objects; |
59 | | -import org.jivesoftware.smack.util.StringUtils; |
60 | 52 | import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; |
61 | 53 | import org.jivesoftware.smackx.disco.packet.DiscoverInfo; |
62 | 54 | import org.jivesoftware.smackx.httpfileupload.UploadService.Version; |
63 | 55 | import org.jivesoftware.smackx.httpfileupload.element.Slot; |
64 | 56 | import org.jivesoftware.smackx.httpfileupload.element.SlotRequest; |
65 | 57 | 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; |
66 | 60 | import org.jivesoftware.smackx.xdata.FormField; |
67 | 61 | import org.jivesoftware.smackx.xdata.packet.DataForm; |
68 | 62 |
|
@@ -95,7 +89,6 @@ public final class HttpFileUploadManager extends Manager { |
95 | 89 | public static final String NAMESPACE_0_2 = "urn:xmpp:http:upload"; |
96 | 90 |
|
97 | 91 | private static final Logger LOGGER = Logger.getLogger(HttpFileUploadManager.class.getName()); |
98 | | - private static final SecureRandom SECURE_RANDOM = new SecureRandom(); |
99 | 92 |
|
100 | 93 | static { |
101 | 94 | XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() { |
@@ -579,234 +572,4 @@ public static UploadService.Version namespaceToVersion(String namespace) { |
579 | 572 | private static boolean containsHttpFileUploadNamespace(DiscoverInfo discoverInfo) { |
580 | 573 | return discoverInfo.containsFeature(NAMESPACE) || discoverInfo.containsFeature(NAMESPACE_0_2); |
581 | 574 | } |
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 | | - } |
812 | 575 | } |
0 commit comments