download: remove file length checks and always check plaintext hash
This commit is contained in:
+16
-19
@@ -49,7 +49,10 @@ func (cli *Client) DownloadToFile(ctx context.Context, msg DownloadableMessage,
|
||||
if len(msg.GetDirectPath()) == 0 {
|
||||
return ErrNoURLPresent
|
||||
}
|
||||
return cli.DownloadMediaWithPathToFile(ctx, msg.GetDirectPath(), msg.GetFileEncSHA256(), msg.GetFileSHA256(), msg.GetMediaKey(), getSize(msg), mediaType, mediaTypeToMMSType[mediaType], false, file)
|
||||
return cli.DownloadMediaWithPathToFile(
|
||||
ctx, msg.GetDirectPath(), msg.GetFileEncSHA256(), msg.GetFileSHA256(), msg.GetMediaKey(),
|
||||
mediaType, mediaTypeToMMSType[mediaType], false, file,
|
||||
)
|
||||
}
|
||||
|
||||
func (cli *Client) DownloadFBToFile(
|
||||
@@ -58,18 +61,20 @@ func (cli *Client) DownloadFBToFile(
|
||||
mediaType MediaType,
|
||||
file File,
|
||||
) error {
|
||||
return cli.DownloadMediaWithPathToFile(ctx, transport.GetDirectPath(), transport.GetFileEncSHA256(), transport.GetFileSHA256(), transport.GetMediaKey(), -1, mediaType, mediaTypeToMMSType[mediaType], false, file)
|
||||
return cli.DownloadMediaWithPathToFile(
|
||||
ctx, transport.GetDirectPath(), transport.GetFileEncSHA256(), transport.GetFileSHA256(), transport.GetMediaKey(),
|
||||
mediaType, mediaTypeToMMSType[mediaType], false, file,
|
||||
)
|
||||
}
|
||||
|
||||
func (cli *Client) DownloadMediaWithOnlyPathToFile(ctx context.Context, directPath string, file File) error {
|
||||
return cli.DownloadMediaWithPathToFile(ctx, directPath, nil, nil, nil, -1, "", "", true, file)
|
||||
return cli.DownloadMediaWithPathToFile(ctx, directPath, nil, nil, nil, "", "", true, file)
|
||||
}
|
||||
|
||||
func (cli *Client) DownloadMediaWithPathToFile(
|
||||
ctx context.Context,
|
||||
directPath string,
|
||||
encFileHash, fileHash, mediaKey []byte,
|
||||
fileLength int,
|
||||
mediaType MediaType,
|
||||
mmsType string,
|
||||
allowNoHash bool,
|
||||
@@ -91,9 +96,8 @@ func (cli *Client) DownloadMediaWithPathToFile(
|
||||
for i, host := range mediaConn.Hosts {
|
||||
// TODO omit hash for unencrypted media?
|
||||
mediaURL := fmt.Sprintf("https://%s%s&hash=%s&mms-type=%s&__wa-mms=", host.Hostname, directPath, base64.URLEncoding.EncodeToString(encFileHash), mmsType)
|
||||
err = cli.downloadAndDecryptToFile(ctx, mediaURL, mediaKey, mediaType, fileLength, encFileHash, fileHash, file)
|
||||
err = cli.downloadAndDecryptToFile(ctx, mediaURL, mediaKey, mediaType, encFileHash, fileHash, file)
|
||||
if err == nil ||
|
||||
errors.Is(err, ErrFileLengthMismatch) ||
|
||||
errors.Is(err, ErrInvalidMediaSHA256) ||
|
||||
errors.Is(err, ErrMediaDownloadFailedWith403) ||
|
||||
errors.Is(err, ErrMediaDownloadFailedWith404) ||
|
||||
@@ -113,7 +117,6 @@ func (cli *Client) downloadAndDecryptToFile(
|
||||
url string,
|
||||
mediaKey []byte,
|
||||
appInfo MediaType,
|
||||
fileLength int,
|
||||
fileEncSHA256, fileSHA256 []byte,
|
||||
file File,
|
||||
) error {
|
||||
@@ -141,18 +144,12 @@ func (cli *Client) downloadAndDecryptToFile(
|
||||
return fmt.Errorf("failed to seek to start of file after validating mac: %w", err)
|
||||
} else if err = cbcutil.DecryptFile(cipherKey, iv, file); err != nil {
|
||||
return fmt.Errorf("failed to decrypt file: %w", err)
|
||||
} else if ReturnDownloadWarnings {
|
||||
if info, err := file.Stat(); err != nil {
|
||||
return fmt.Errorf("failed to stat file: %w", err)
|
||||
} else if fileLength >= 0 && info.Size() != int64(fileLength) {
|
||||
return fmt.Errorf("%w: expected %d, got %d", ErrFileLengthMismatch, fileLength, info.Size())
|
||||
} else if _, err = file.Seek(0, io.SeekStart); err != nil {
|
||||
return fmt.Errorf("failed to seek to start of file after decrypting: %w", err)
|
||||
} else if _, err = io.Copy(hasher, file); err != nil {
|
||||
return fmt.Errorf("failed to hash file: %w", err)
|
||||
} else if !hmac.Equal(fileSHA256, hasher.Sum(nil)) {
|
||||
return ErrInvalidMediaSHA256
|
||||
}
|
||||
} else if _, err = file.Seek(0, io.SeekStart); err != nil {
|
||||
return fmt.Errorf("failed to seek to start of file after decrypting: %w", err)
|
||||
} else if _, err = io.Copy(hasher, file); err != nil {
|
||||
return fmt.Errorf("failed to hash file: %w", err)
|
||||
} else if !hmac.Equal(fileSHA256, hasher.Sum(nil)) {
|
||||
return ErrInvalidMediaSHA256
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+7
-40
@@ -92,16 +92,6 @@ var (
|
||||
_ DownloadableMessage = (*types.StickerPackItem)(nil)
|
||||
)
|
||||
|
||||
type downloadableMessageWithLength interface {
|
||||
DownloadableMessage
|
||||
GetFileLength() uint64
|
||||
}
|
||||
|
||||
type downloadableMessageWithSizeBytes interface {
|
||||
DownloadableMessage
|
||||
GetFileSizeBytes() uint64
|
||||
}
|
||||
|
||||
var classToMediaType = map[protoreflect.Name]MediaType{
|
||||
"ImageMessage": MediaImage,
|
||||
"AudioMessage": MediaAudio,
|
||||
@@ -154,21 +144,6 @@ func (cli *Client) DownloadAny(ctx context.Context, msg *waE2E.Message) (data []
|
||||
}
|
||||
}
|
||||
|
||||
func getSize(msg DownloadableMessage) int {
|
||||
switch sized := msg.(type) {
|
||||
case downloadableMessageWithLength:
|
||||
return int(sized.GetFileLength())
|
||||
case downloadableMessageWithSizeBytes:
|
||||
return int(sized.GetFileSizeBytes())
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// ReturnDownloadWarnings controls whether the Download function returns non-fatal validation warnings.
|
||||
// Currently, these include [ErrFileLengthMismatch] and [ErrInvalidMediaSHA256].
|
||||
var ReturnDownloadWarnings = true
|
||||
|
||||
// DownloadThumbnail downloads a thumbnail from a message.
|
||||
//
|
||||
// This is primarily intended for downloading link preview thumbnails, which are in ExtendedTextMessage:
|
||||
@@ -183,7 +158,7 @@ func (cli *Client) DownloadThumbnail(ctx context.Context, msg DownloadableThumbn
|
||||
} else if len(msg.GetThumbnailDirectPath()) > 0 {
|
||||
return cli.DownloadMediaWithPath(
|
||||
ctx, msg.GetThumbnailDirectPath(), msg.GetThumbnailEncSHA256(), msg.GetThumbnailSHA256(), msg.GetMediaKey(),
|
||||
-1, mediaType, mediaTypeToMMSType[mediaType], false,
|
||||
mediaType, mediaTypeToMMSType[mediaType], false,
|
||||
)
|
||||
} else {
|
||||
return nil, ErrNoURLPresent
|
||||
@@ -243,7 +218,7 @@ func (cli *Client) Download(ctx context.Context, msg DownloadableMessage) ([]byt
|
||||
}
|
||||
return cli.DownloadMediaWithPath(
|
||||
ctx, msg.GetDirectPath(), msg.GetFileEncSHA256(), msg.GetFileSHA256(), msg.GetMediaKey(),
|
||||
getSize(msg), mediaType, mediaTypeToMMSType[mediaType], false,
|
||||
mediaType, mediaTypeToMMSType[mediaType], false,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -254,12 +229,12 @@ func (cli *Client) DownloadFB(
|
||||
) ([]byte, error) {
|
||||
return cli.DownloadMediaWithPath(
|
||||
ctx, transport.GetDirectPath(), transport.GetFileEncSHA256(), transport.GetFileSHA256(), transport.GetMediaKey(),
|
||||
-1, mediaType, mediaTypeToMMSType[mediaType], false,
|
||||
mediaType, mediaTypeToMMSType[mediaType], false,
|
||||
)
|
||||
}
|
||||
|
||||
func (cli *Client) DownloadMediaWithOnlyPath(ctx context.Context, directPath string) ([]byte, error) {
|
||||
return cli.DownloadMediaWithPath(ctx, directPath, nil, nil, nil, -1, "", "", true)
|
||||
return cli.DownloadMediaWithPath(ctx, directPath, nil, nil, nil, "", "", true)
|
||||
}
|
||||
|
||||
// DownloadMediaWithPath downloads an attachment by manually specifying the path and encryption details.
|
||||
@@ -267,7 +242,6 @@ func (cli *Client) DownloadMediaWithPath(
|
||||
ctx context.Context,
|
||||
directPath string,
|
||||
encFileHash, fileHash, mediaKey []byte,
|
||||
fileLength int,
|
||||
mediaType MediaType,
|
||||
mmsType string,
|
||||
allowNoHash bool,
|
||||
@@ -289,9 +263,8 @@ func (cli *Client) DownloadMediaWithPath(
|
||||
for i, host := range mediaConn.Hosts {
|
||||
// TODO omit hash for unencrypted media?
|
||||
mediaURL := fmt.Sprintf("https://%s%s&hash=%s&mms-type=%s&__wa-mms=", host.Hostname, directPath, base64.URLEncoding.EncodeToString(encFileHash), mmsType)
|
||||
data, err = cli.downloadAndDecrypt(ctx, mediaURL, mediaKey, mediaType, fileLength, encFileHash, fileHash)
|
||||
data, err = cli.downloadAndDecrypt(ctx, mediaURL, mediaKey, mediaType, encFileHash, fileHash)
|
||||
if err == nil ||
|
||||
errors.Is(err, ErrFileLengthMismatch) ||
|
||||
errors.Is(err, ErrInvalidMediaSHA256) ||
|
||||
errors.Is(err, ErrMediaDownloadFailedWith403) ||
|
||||
errors.Is(err, ErrMediaDownloadFailedWith404) ||
|
||||
@@ -311,7 +284,6 @@ func (cli *Client) downloadAndDecrypt(
|
||||
url string,
|
||||
mediaKey []byte,
|
||||
appInfo MediaType,
|
||||
fileLength int,
|
||||
fileEncSHA256,
|
||||
fileSHA256 []byte,
|
||||
) (data []byte, err error) {
|
||||
@@ -329,13 +301,8 @@ func (cli *Client) downloadAndDecrypt(
|
||||
|
||||
} else if data, err = cbcutil.Decrypt(cipherKey, iv, ciphertext); err != nil {
|
||||
err = fmt.Errorf("failed to decrypt file: %w", err)
|
||||
} else if ReturnDownloadWarnings {
|
||||
if fileLength >= 0 && len(data) != fileLength {
|
||||
err = fmt.Errorf("%w: expected %d, got %d", ErrFileLengthMismatch, fileLength, len(data))
|
||||
} else if len(fileSHA256) != 32 || sha256.Sum256(data) != *(*[32]byte)(fileSHA256) {
|
||||
// TODO maybe move this out of ReturnDownloadWarnings if whatsapp has started enforcing it in official clients
|
||||
err = ErrInvalidMediaSHA256
|
||||
}
|
||||
} else if len(fileSHA256) != 32 || sha256.Sum256(data) != *(*[32]byte)(fileSHA256) {
|
||||
err = ErrInvalidMediaSHA256
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -132,7 +132,6 @@ var (
|
||||
ErrMediaDownloadFailedWith404 = DownloadHTTPError{Response: &http.Response{StatusCode: 404}}
|
||||
ErrMediaDownloadFailedWith410 = DownloadHTTPError{Response: &http.Response{StatusCode: 410}}
|
||||
ErrNoURLPresent = errors.New("no url present")
|
||||
ErrFileLengthMismatch = errors.New("file length does not match")
|
||||
ErrTooShortFile = errors.New("file too short")
|
||||
ErrInvalidMediaHMAC = errors.New("invalid media hmac")
|
||||
ErrInvalidMediaEncSHA256 = errors.New("hash of media ciphertext doesn't match")
|
||||
@@ -140,6 +139,9 @@ var (
|
||||
ErrInvalidUnencryptedMediaSHA256 = errors.New("hash of unencrypted media doesn't match")
|
||||
ErrUnknownMediaType = errors.New("unknown media type")
|
||||
ErrNothingDownloadableFound = errors.New("didn't find any attachments in message")
|
||||
|
||||
// Deprecated: this is no longer returned anywhere
|
||||
ErrFileLengthMismatch = errors.New("file length does not match")
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
Generated
+4
-4
@@ -199,8 +199,8 @@ func (int *DangerousInternalClient) HandleConnectSuccess(ctx context.Context, no
|
||||
int.c.handleConnectSuccess(ctx, node)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) DownloadAndDecrypt(ctx context.Context, url string, mediaKey []byte, appInfo MediaType, fileLength int, fileEncSHA256, fileSHA256 []byte) (data []byte, err error) {
|
||||
return int.c.downloadAndDecrypt(ctx, url, mediaKey, appInfo, fileLength, fileEncSHA256, fileSHA256)
|
||||
func (int *DangerousInternalClient) DownloadAndDecrypt(ctx context.Context, url string, mediaKey []byte, appInfo MediaType, fileEncSHA256, fileSHA256 []byte) (data []byte, err error) {
|
||||
return int.c.downloadAndDecrypt(ctx, url, mediaKey, appInfo, fileEncSHA256, fileSHA256)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) DownloadPossiblyEncryptedMediaWithRetries(ctx context.Context, url string, checksum []byte) (file, mac []byte, err error) {
|
||||
@@ -219,8 +219,8 @@ func (int *DangerousInternalClient) DownloadEncryptedMedia(ctx context.Context,
|
||||
return int.c.downloadEncryptedMedia(ctx, url, checksum)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) DownloadAndDecryptToFile(ctx context.Context, url string, mediaKey []byte, appInfo MediaType, fileLength int, fileEncSHA256, fileSHA256 []byte, file File) error {
|
||||
return int.c.downloadAndDecryptToFile(ctx, url, mediaKey, appInfo, fileLength, fileEncSHA256, fileSHA256, file)
|
||||
func (int *DangerousInternalClient) DownloadAndDecryptToFile(ctx context.Context, url string, mediaKey []byte, appInfo MediaType, fileEncSHA256, fileSHA256 []byte, file File) error {
|
||||
return int.c.downloadAndDecryptToFile(ctx, url, mediaKey, appInfo, fileEncSHA256, fileSHA256, file)
|
||||
}
|
||||
|
||||
func (int *DangerousInternalClient) DownloadPossiblyEncryptedMediaWithRetriesToFile(ctx context.Context, url string, checksum []byte, file File) (mac []byte, err error) {
|
||||
|
||||
Reference in New Issue
Block a user