mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-26 11:51:10 +01:00
Do most of the proto and database groundwork for the new mediaName.
This commit is contained in:
committed by
Cody Henthorne
parent
e705495638
commit
38c8f852bf
@@ -7,10 +7,8 @@
|
||||
package org.whispersystems.signalservice.api;
|
||||
|
||||
import org.signal.core.util.StreamUtil;
|
||||
import org.signal.core.util.stream.LimitedInputStream;
|
||||
import org.signal.libsignal.protocol.InvalidMessageException;
|
||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||
import org.whispersystems.signalservice.api.attachment.AttachmentDownloadResult;
|
||||
import org.whispersystems.signalservice.api.backup.MediaRootBackupKey;
|
||||
import org.whispersystems.signalservice.api.crypto.AttachmentCipherInputStream;
|
||||
import org.whispersystems.signalservice.api.crypto.AttachmentCipherStreamUtil;
|
||||
@@ -27,7 +25,6 @@ import org.whispersystems.signalservice.internal.util.Util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.time.ZonedDateTime;
|
||||
@@ -68,7 +65,7 @@ public class SignalServiceMessageReceiver {
|
||||
*/
|
||||
public InputStream retrieveAttachment(SignalServiceAttachmentPointer pointer, File destination, long maxSizeBytes)
|
||||
throws IOException, InvalidMessageException, MissingConfigurationException {
|
||||
return retrieveAttachment(pointer, destination, maxSizeBytes, null).getDataStream();
|
||||
return retrieveAttachment(pointer, destination, maxSizeBytes, null);
|
||||
}
|
||||
|
||||
public InputStream retrieveProfileAvatar(String path, File destination, ProfileKey profileKey, long maxSizeBytes)
|
||||
@@ -99,9 +96,9 @@ public class SignalServiceMessageReceiver {
|
||||
* @throws IOException
|
||||
* @throws InvalidMessageException
|
||||
*/
|
||||
public AttachmentDownloadResult retrieveAttachment(SignalServiceAttachmentPointer pointer, File destination, long maxSizeBytes, ProgressListener listener)
|
||||
public InputStream retrieveAttachment(SignalServiceAttachmentPointer pointer, File destination, long maxSizeBytes, ProgressListener listener)
|
||||
throws IOException, InvalidMessageException, MissingConfigurationException {
|
||||
if (!pointer.getDigest().isPresent()) throw new InvalidMessageException("No attachment digest!");
|
||||
if (pointer.getDigest().isEmpty()) throw new InvalidMessageException("No attachment digest!");
|
||||
if (pointer.getKey() == null) throw new InvalidMessageException("No key!");
|
||||
|
||||
socket.retrieveAttachment(pointer.getCdnNumber(), Collections.emptyMap(), pointer.getRemoteId(), destination, maxSizeBytes, listener);
|
||||
@@ -111,16 +108,13 @@ public class SignalServiceMessageReceiver {
|
||||
StreamUtil.readFully(tempStream, iv);
|
||||
}
|
||||
|
||||
return new AttachmentDownloadResult(
|
||||
AttachmentCipherInputStream.createForAttachment(
|
||||
destination,
|
||||
pointer.getSize().orElse(0),
|
||||
pointer.getKey(),
|
||||
pointer.getDigest().get(),
|
||||
null,
|
||||
0
|
||||
),
|
||||
iv
|
||||
return AttachmentCipherInputStream.createForAttachment(
|
||||
destination,
|
||||
pointer.getSize().orElse(0),
|
||||
pointer.getKey(),
|
||||
pointer.getDigest().get(),
|
||||
null,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
@@ -136,13 +130,13 @@ public class SignalServiceMessageReceiver {
|
||||
*
|
||||
* @return An InputStream that streams the plaintext attachment contents.
|
||||
*/
|
||||
public AttachmentDownloadResult retrieveArchivedAttachment(@Nonnull MediaRootBackupKey.MediaKeyMaterial archivedMediaKeyMaterial,
|
||||
@Nonnull Map<String, String> readCredentialHeaders,
|
||||
@Nonnull File archiveDestination,
|
||||
@Nonnull SignalServiceAttachmentPointer pointer,
|
||||
@Nonnull File attachmentDestination,
|
||||
long maxSizeBytes,
|
||||
@Nullable ProgressListener listener)
|
||||
public InputStream retrieveArchivedAttachment(@Nonnull MediaRootBackupKey.MediaKeyMaterial archivedMediaKeyMaterial,
|
||||
@Nonnull Map<String, String> readCredentialHeaders,
|
||||
@Nonnull File archiveDestination,
|
||||
@Nonnull SignalServiceAttachmentPointer pointer,
|
||||
@Nonnull File attachmentDestination,
|
||||
long maxSizeBytes,
|
||||
@Nullable ProgressListener listener)
|
||||
throws IOException, InvalidMessageException, MissingConfigurationException
|
||||
{
|
||||
if (pointer.getDigest().isEmpty()) {
|
||||
@@ -160,29 +154,16 @@ public class SignalServiceMessageReceiver {
|
||||
.map(s -> AttachmentCipherStreamUtil.getCiphertextLength(PaddingInputStream.getPaddedSize(s)))
|
||||
.orElse(0L);
|
||||
|
||||
// There's two layers of encryption -- one from the backup, and one from the attachment. This only strips the outermost backup encryption layer.
|
||||
try (InputStream backupDecrypted = AttachmentCipherInputStream.createForArchivedMediaOuterLayer(archivedMediaKeyMaterial, archiveDestination, originalCipherLength)) {
|
||||
try (FileOutputStream fos = new FileOutputStream(attachmentDestination)) {
|
||||
// TODO [backup] I don't think we should be doing the full copy here. This is basically doing the entire download inline in this single line.
|
||||
StreamUtil.copy(backupDecrypted, fos);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] iv = new byte[16];
|
||||
try (InputStream tempStream = new FileInputStream(attachmentDestination)) {
|
||||
StreamUtil.readFully(tempStream, iv);
|
||||
}
|
||||
|
||||
LimitedInputStream dataStream = AttachmentCipherInputStream.createForAttachment(
|
||||
return AttachmentCipherInputStream.createForArchivedMedia(
|
||||
archivedMediaKeyMaterial,
|
||||
attachmentDestination,
|
||||
originalCipherLength,
|
||||
pointer.getSize().orElse(0),
|
||||
pointer.getKey(),
|
||||
pointer.getDigest().get(),
|
||||
null,
|
||||
0
|
||||
);
|
||||
|
||||
return new AttachmentDownloadResult(dataStream, iv);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,13 +178,13 @@ public class SignalServiceMessageReceiver {
|
||||
*
|
||||
* @return An InputStream that streams the plaintext attachment contents.
|
||||
*/
|
||||
public AttachmentDownloadResult retrieveArchivedThumbnail(@Nonnull MediaRootBackupKey.MediaKeyMaterial archivedMediaKeyMaterial,
|
||||
@Nonnull Map<String, String> readCredentialHeaders,
|
||||
@Nonnull File archiveDestination,
|
||||
@Nonnull SignalServiceAttachmentPointer pointer,
|
||||
@Nonnull File attachmentDestination,
|
||||
long maxSizeBytes,
|
||||
@Nullable ProgressListener listener)
|
||||
public InputStream retrieveArchivedThumbnail(@Nonnull MediaRootBackupKey.MediaKeyMaterial archivedMediaKeyMaterial,
|
||||
@Nonnull Map<String, String> readCredentialHeaders,
|
||||
@Nonnull File archiveDestination,
|
||||
@Nonnull SignalServiceAttachmentPointer pointer,
|
||||
@Nonnull File attachmentDestination,
|
||||
long maxSizeBytes,
|
||||
@Nullable ProgressListener listener)
|
||||
throws IOException, InvalidMessageException, MissingConfigurationException
|
||||
{
|
||||
if (pointer.getKey() == null) {
|
||||
@@ -217,26 +198,13 @@ public class SignalServiceMessageReceiver {
|
||||
.map(s -> AttachmentCipherStreamUtil.getCiphertextLength(PaddingInputStream.getPaddedSize(s)))
|
||||
.orElse(0L);
|
||||
|
||||
// There's two layers of encryption -- one from the backup, and one from the attachment. This only strips the outermost backup encryption layer.
|
||||
try (InputStream backupDecrypted = AttachmentCipherInputStream.createForArchivedMediaOuterLayer(archivedMediaKeyMaterial, archiveDestination, originalCipherLength)) {
|
||||
try (FileOutputStream fos = new FileOutputStream(attachmentDestination)) {
|
||||
// TODO [backup] I don't think we should be doing the full copy here. This is basically doing the entire download inline in this single line.
|
||||
StreamUtil.copy(backupDecrypted, fos);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] iv = new byte[16];
|
||||
try (InputStream tempStream = new FileInputStream(attachmentDestination)) {
|
||||
StreamUtil.readFully(tempStream, iv);
|
||||
}
|
||||
|
||||
LimitedInputStream dataStream = AttachmentCipherInputStream.createForArchiveThumbnailInnerLayer(
|
||||
return AttachmentCipherInputStream.createForArchivedThumbnail(
|
||||
archivedMediaKeyMaterial,
|
||||
attachmentDestination,
|
||||
originalCipherLength,
|
||||
pointer.getSize().orElse(0),
|
||||
pointer.getKey()
|
||||
);
|
||||
|
||||
return new AttachmentDownloadResult(dataStream, iv);
|
||||
}
|
||||
|
||||
public void retrieveBackup(int cdnNumber, Map<String, String> headers, String cdnPath, File destination, ProgressListener listener) throws MissingConfigurationException, IOException {
|
||||
|
||||
@@ -91,7 +91,6 @@ class AttachmentApi(
|
||||
remoteId = SignalServiceAttachmentRemoteId.V4(attachmentData.resumableUploadSpec.cdnKey),
|
||||
cdnNumber = attachmentData.resumableUploadSpec.cdnNumber,
|
||||
key = resumableUploadSpec.attachmentKey,
|
||||
iv = resumableUploadSpec.attachmentIv,
|
||||
digest = digestInfo.digest,
|
||||
incrementalDigest = digestInfo.incrementalDigest,
|
||||
incrementalDigestChunkSize = digestInfo.incrementalMacChunkSize,
|
||||
|
||||
@@ -14,7 +14,6 @@ class AttachmentUploadResult(
|
||||
val remoteId: SignalServiceAttachmentRemoteId,
|
||||
val cdnNumber: Int,
|
||||
val key: ByteArray,
|
||||
val iv: ByteArray,
|
||||
val digest: ByteArray,
|
||||
val incrementalDigest: ByteArray?,
|
||||
val incrementalDigestChunkSize: Int,
|
||||
|
||||
@@ -14,8 +14,8 @@ import org.signal.core.util.Hex
|
||||
value class MediaName(val name: String) {
|
||||
|
||||
companion object {
|
||||
fun fromDigest(digest: ByteArray) = MediaName(Hex.toStringCondensed(digest))
|
||||
fun fromDigestForThumbnail(digest: ByteArray) = MediaName("${Hex.toStringCondensed(digest)}_thumbnail")
|
||||
fun fromPlaintextHashAndRemoteKey(plaintextHash: ByteArray, remoteKey: ByteArray) = MediaName(Hex.toStringCondensed(plaintextHash + remoteKey))
|
||||
fun fromPlaintextHashAndRemoteKeyForThumbnail(plaintextHash: ByteArray, remoteKey: ByteArray) = MediaName(Hex.toStringCondensed(plaintextHash + remoteKey) + "_thumbnail")
|
||||
fun forThumbnailFromMediaName(mediaName: String) = MediaName("${mediaName}_thumbnail")
|
||||
|
||||
/**
|
||||
|
||||
@@ -96,56 +96,6 @@ object AttachmentCipherInputStream {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* After removing the server layer of encryption using [createForArchivedMediaOuterLayer], use this to decrypt the inner layer of the attachment.
|
||||
* Thumbnails have a special path because we don't do any additional digest/hash validation on them.
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(InvalidMessageException::class, IOException::class)
|
||||
fun createForArchiveThumbnailInnerLayer(
|
||||
file: File,
|
||||
plaintextLength: Long,
|
||||
combinedKeyMaterial: ByteArray
|
||||
): LimitedInputStream {
|
||||
return create(
|
||||
streamSupplier = { FileInputStream(file) },
|
||||
streamLength = file.length(),
|
||||
plaintextLength = plaintextLength,
|
||||
combinedKeyMaterial = combinedKeyMaterial,
|
||||
digest = null,
|
||||
incrementalDigest = null,
|
||||
incrementalMacChunkSize = 0,
|
||||
ignoreDigest = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* When you archive an attachment, you give the server an encrypted attachment, and the server wraps it in *another* layer of encryption.
|
||||
* This will return a stream that unwraps the server's layer of encryption, giving you a stream that contains a "normally-encrypted" attachment.
|
||||
*
|
||||
* Because we're validating the encryptedDigest/plaintextHash of the inner layer, there's no additional out-of-band validation of this outer layer.
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(InvalidMessageException::class, IOException::class)
|
||||
fun createForArchivedMediaOuterLayer(archivedMediaKeyMaterial: MediaKeyMaterial, file: File, originalCipherTextLength: Long): LimitedInputStream {
|
||||
val mac = initMac(archivedMediaKeyMaterial.macKey)
|
||||
|
||||
if (file.length() <= BLOCK_SIZE + mac.macLength) {
|
||||
throw InvalidMessageException("Message shorter than crypto overhead!")
|
||||
}
|
||||
|
||||
FileInputStream(file).use { macVerificationStream ->
|
||||
verifyMac(macVerificationStream, file.length(), mac, null)
|
||||
}
|
||||
|
||||
val encryptedStream = FileInputStream(file)
|
||||
val encryptedStreamExcludingMac = LimitedInputStream(encryptedStream, file.length() - mac.macLength)
|
||||
val cipher = createCipher(encryptedStreamExcludingMac, archivedMediaKeyMaterial.aesKey)
|
||||
val inputStream: InputStream = BetterCipherInputStream(encryptedStreamExcludingMac, cipher)
|
||||
|
||||
return LimitedInputStream(inputStream, originalCipherTextLength)
|
||||
}
|
||||
|
||||
/**
|
||||
* When you archive an attachment, you give the server an encrypted attachment, and the server wraps it in *another* layer of encryption.
|
||||
*
|
||||
@@ -156,7 +106,7 @@ object AttachmentCipherInputStream {
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(InvalidMessageException::class, IOException::class)
|
||||
fun createForArchivedMediaOuterAndInnerLayers(
|
||||
fun createForArchivedMedia(
|
||||
archivedMediaKeyMaterial: MediaKeyMaterial,
|
||||
file: File,
|
||||
originalCipherTextLength: Long,
|
||||
@@ -185,6 +135,42 @@ object AttachmentCipherInputStream {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* When you archive an attachment, you give the server an encrypted attachment, and the server wraps it in *another* layer of encryption.
|
||||
*
|
||||
* This creates a stream decrypt both the inner and outer layers of an archived attachment at the same time by basically double-decrypting it.
|
||||
*
|
||||
* @param incrementalDigest If null, incremental mac validation is disabled.
|
||||
* @param incrementalMacChunkSize If 0, incremental mac validation is disabled.
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(InvalidMessageException::class, IOException::class)
|
||||
fun createForArchivedThumbnail(
|
||||
archivedMediaKeyMaterial: MediaKeyMaterial,
|
||||
file: File,
|
||||
originalCipherTextLength: Long,
|
||||
plaintextLength: Long,
|
||||
combinedKeyMaterial: ByteArray
|
||||
): LimitedInputStream {
|
||||
val keyMaterial = CombinedKeyMaterial.from(combinedKeyMaterial)
|
||||
val mac = initMac(keyMaterial.macKey)
|
||||
|
||||
if (originalCipherTextLength <= BLOCK_SIZE + mac.macLength) {
|
||||
throw InvalidMessageException("Message shorter than crypto overhead!")
|
||||
}
|
||||
|
||||
return create(
|
||||
streamSupplier = { createForArchivedMediaOuterLayer(archivedMediaKeyMaterial, file, originalCipherTextLength) },
|
||||
streamLength = originalCipherTextLength,
|
||||
plaintextLength = plaintextLength,
|
||||
combinedKeyMaterial = combinedKeyMaterial,
|
||||
digest = null,
|
||||
incrementalDigest = null,
|
||||
incrementalMacChunkSize = 0,
|
||||
ignoreDigest = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a stream to decrypt sticker data. Stickers have a special path because the key material is derived from the pack key.
|
||||
*/
|
||||
@@ -209,6 +195,33 @@ object AttachmentCipherInputStream {
|
||||
return BetterCipherInputStream(encryptedStreamExcludingMac, cipher)
|
||||
}
|
||||
|
||||
/**
|
||||
* When you archive an attachment, you give the server an encrypted attachment, and the server wraps it in *another* layer of encryption.
|
||||
* This will return a stream that unwraps the server's layer of encryption, giving you a stream that contains a "normally-encrypted" attachment.
|
||||
*
|
||||
* Because we're validating the encryptedDigest/plaintextHash of the inner layer, there's no additional out-of-band validation of this outer layer.
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(InvalidMessageException::class, IOException::class)
|
||||
private fun createForArchivedMediaOuterLayer(archivedMediaKeyMaterial: MediaKeyMaterial, file: File, originalCipherTextLength: Long): LimitedInputStream {
|
||||
val mac = initMac(archivedMediaKeyMaterial.macKey)
|
||||
|
||||
if (file.length() <= BLOCK_SIZE + mac.macLength) {
|
||||
throw InvalidMessageException("Message shorter than crypto overhead!")
|
||||
}
|
||||
|
||||
FileInputStream(file).use { macVerificationStream ->
|
||||
verifyMac(macVerificationStream, file.length(), mac, null)
|
||||
}
|
||||
|
||||
val encryptedStream = FileInputStream(file)
|
||||
val encryptedStreamExcludingMac = LimitedInputStream(encryptedStream, file.length() - mac.macLength)
|
||||
val cipher = createCipher(encryptedStreamExcludingMac, archivedMediaKeyMaterial.aesKey)
|
||||
val inputStream: InputStream = BetterCipherInputStream(encryptedStreamExcludingMac, cipher)
|
||||
|
||||
return LimitedInputStream(inputStream, originalCipherTextLength)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Throws(InvalidMessageException::class, IOException::class)
|
||||
private fun create(
|
||||
|
||||
Reference in New Issue
Block a user