Bug Report: inbound E2EE media HMAC is not verified before decrypting
Describe the bug
Inbound E2EE media decryption strips the final 32-byte HMAC tag but does not verify it before decrypting the media bytes.
The decrypt helper derives keys from keyMaterial, but the derived MAC key is not used:
|
func (lc *LineClient) decryptImageData(encryptedData []byte, keyMaterialB64 string) ([]byte, error) { |
|
// macKey := derived[32:64] // for HMAC verification |
It then removes the last 32 bytes from the input and decrypts the remaining AES-CTR ciphertext directly:
|
encryptedData = encryptedData[:len(encryptedData)-32] |
Outbound media encryption does append HMACs, so the integrity tag exists on the wire:
|
h := hmac.New(sha256.New, macKey) |
|
// For videos: compute HMAC on chunk hashes |
|
h := hmac.New(sha256.New, macKey) |
The receive path wires all media handlers through this decrypt helper:
|
DecryptMedia: lc.decryptImageData, |
Example inbound media calls:
|
decryptedImg, err := h.DecryptMedia(imgData, decryptInfo.KeyMaterial) |
|
decryptedFile, err := h.DecryptMedia(fileData, decryptInfo.KeyMaterial) |
|
decryptedAudio, err := h.DecryptMedia(audioData, decryptInfo.KeyMaterial) |
|
decryptedVideo, err := h.DecryptMedia(videoData, decryptInfo.KeyMaterial) |
To Reproduce
This can be reproduced with a small unit test using the existing helpers:
- Encrypt a byte slice with
encryptFileData.
- Flip one byte in the ciphertext.
- Call
decryptImageData.
- Observe that decryption succeeds and returns altered plaintext instead of rejecting the media.
The same also happens if only the final HMAC tag byte is modified: decryptImageData still returns plaintext with no error.
Expected behavior
Inbound E2EE media should verify the appended HMAC before decrypting or uploading bytes to Matrix.
If the HMAC does not match, the bridge should fail closed, for example by returning an error or sending an unavailable-media placeholder.
Screenshots or logs
No UI screenshot needed. This is in the E2EE media receive path.
Additional context
AES-CTR does not provide integrity by itself. Without the HMAC check, any actor able to modify encrypted OBS media bytes after upload can alter the plaintext that the bridge uploads to Matrix, while the media still appears to come from the original LINE sender.
This is mainly relevant to the Letter Sealing / E2EE media trust boundary: LINE OBS/CDN/storage should not be able to silently modify E2EE media accepted by the bridge.
Possible implementation direction:
- Split incoming media into
ciphertext and receivedMAC.
- Derive and use
macKey.
- Verify with
hmac.Equal before AES-CTR decryption.
- For image/file/audio/thumbnail media, verify
HMAC-SHA256(macKey, ciphertext), matching the file and thumbnail send paths.
- For video media, match the existing send-side convention in
encryptVideoData, which computes the HMAC over generateChunkHashes(ciphertext).
One implementation detail: the current receive-side API is generic:
DecryptMedia func(data []byte, keyMaterial string) ([]byte, error)
but video uses a different HMAC input than image/file/audio. The fix may need either a media-type-aware decrypt helper or separate helpers for file-like media and video media.
Suggested test coverage:
- tampered ciphertext is rejected
- tampered HMAC tag is rejected
- valid encrypted image/file/audio media still decrypts
- valid encrypted video media still decrypts using the chunk-hash HMAC mode
Bug Report: inbound E2EE media HMAC is not verified before decrypting
Describe the bug
Inbound E2EE media decryption strips the final 32-byte HMAC tag but does not verify it before decrypting the media bytes.
The decrypt helper derives keys from
keyMaterial, but the derived MAC key is not used:line/pkg/connector/media.go
Line 27 in 593cd2d
line/pkg/connector/media.go
Line 42 in 593cd2d
It then removes the last 32 bytes from the input and decrypts the remaining AES-CTR ciphertext directly:
line/pkg/connector/media.go
Line 53 in 593cd2d
Outbound media encryption does append HMACs, so the integrity tag exists on the wire:
line/pkg/connector/media.go
Line 101 in 593cd2d
line/pkg/connector/media.go
Line 143 in 593cd2d
line/pkg/connector/media.go
Line 225 in 593cd2d
The receive path wires all media handlers through this decrypt helper:
line/pkg/connector/handle_message.go
Line 34 in 593cd2d
Example inbound media calls:
line/pkg/connector/handlers/image.go
Line 93 in 593cd2d
line/pkg/connector/handlers/file.go
Line 81 in 593cd2d
line/pkg/connector/handlers/audio.go
Line 84 in 593cd2d
line/pkg/connector/handlers/video.go
Line 92 in 593cd2d
To Reproduce
This can be reproduced with a small unit test using the existing helpers:
encryptFileData.decryptImageData.The same also happens if only the final HMAC tag byte is modified:
decryptImageDatastill returns plaintext with no error.Expected behavior
Inbound E2EE media should verify the appended HMAC before decrypting or uploading bytes to Matrix.
If the HMAC does not match, the bridge should fail closed, for example by returning an error or sending an unavailable-media placeholder.
Screenshots or logs
No UI screenshot needed. This is in the E2EE media receive path.
Additional context
AES-CTR does not provide integrity by itself. Without the HMAC check, any actor able to modify encrypted OBS media bytes after upload can alter the plaintext that the bridge uploads to Matrix, while the media still appears to come from the original LINE sender.
This is mainly relevant to the Letter Sealing / E2EE media trust boundary: LINE OBS/CDN/storage should not be able to silently modify E2EE media accepted by the bridge.
Possible implementation direction:
ciphertextandreceivedMAC.macKey.hmac.Equalbefore AES-CTR decryption.HMAC-SHA256(macKey, ciphertext), matching the file and thumbnail send paths.encryptVideoData, which computes the HMAC overgenerateChunkHashes(ciphertext).One implementation detail: the current receive-side API is generic:
DecryptMedia func(data []byte, keyMaterial string) ([]byte, error)but video uses a different HMAC input than image/file/audio. The fix may need either a media-type-aware decrypt helper or separate helpers for file-like media and video media.
Suggested test coverage: