I have noticed a problem in encrypted Gab chat rooms with lots of messages. This happens for me in Dissenter, Safari, and Vivaldi on MacOS, but does NOT happen in Google Chrome. All the messages get decrypted, for half a second, then all but the first few are replaced by "Message failed to decrypt. The author may have invalidated or re-generated their security keys."
In the browser DevTools page, pretty-printing the
decryptHtmlChatMessages
function from chat.gab.com/dist/js/hydra-app.min.js yields:
decryptHtmlChatMessages() { var e = this , t = e.chatMessageList.querySelectorAll(".message__body[data-iv]") , o = [] , r = t.length , a = 0; # Breakpoint here return t.forEach(t=>{ if (++a, "true" !== t.getAttribute("data-is-processed")) return t.getAttribute("data-recipient-id") !== hydra.data.user._id ? (t.parentElement.removeChild(t), void e.setLoadingProgress(a / r)) : void o.push(hydra.e2e.decryptMessage(e.currentChatRoom, t).then(()=>{ t.setAttribute("data-is-processed", "true") } ).catch(()=>(t.setAttribute("data-is-processed", "true"), Promise.resolve())).finally(()=>(e.setLoadingProgress(a / r), Promise.resolve()))); e.setLoadingProgress(a / r) } ), o }
decryptHtmlChatMessages
gets called three times during
the loading of an encrypted chat group. The first time does all the
work, but the second and third times
encounter "message__body"
elements that are queued for
decryption, but do NOT yet have their
"data-is-processed"
attribute set. So they attempt to decrypt the already-decrypted
plaintext, and fail.
I discovered this by putting a breakpoint at "Breakpoint here", and setting t to [] on the second and third times I got there. The messages were all properly decrypted.
The fix, I believe, is to set the "data-is-processed"
attribute BEFORE queueing for decryption (warning: untested code
below). That ensures that the second and third passes find nothing to
do:
decryptHtmlChatMessages() { var e = this , t = e.chatMessageList.querySelectorAll(".message__body[data-iv]") , o = [] , r = t.length , a = 0; return t.forEach(t=>{ if (++a, "true" !== t.getAttribute("data-is-processed")) { if (t.getAttribute("data-recipient-id") !== hydra.data.user._id) { t.parentElement.removeChild(t); void e.setLoadingProgress(a / r)); } else { t.setAttribute("data-is-processed", "true"); void o.push(hydra.e2e.decryptMessage(e.currentChatRoom, t) .catch(()=>(t.setAttribute("data-is-processed", "true"), Promise.resolve())) .finally(()=>(e.setLoadingProgress(a / r), Promise.resolve()))); } e.setLoadingProgress(a / r) } ), o }
I do not know why Google Chrome is successful where the other browsers are not. Likely something about timing of the processing of enqueued promised.
=====
One other thing I noticed. This is not a bug, per se, in that it doesn't cause any incorrect output, but when an encrypted group page is loaded, it currently gets encrypted content for each group member. In groups with a handful of members this isn't too bad. For groups with 50 members, it's 50 times the data, which is an unnecessary bandwidth load on Gab's server, and may make it take noticeably longer to load the page into the user's browser.
I think it would be better to send the messages over the WebSocket connection, encrypted to only the recipient.
The HTML below is the content for a single message, taken from the Network tab of the DevTools window, pretty-printed, and left-shifted to get rid of leading white space. Note the multiple "message__body" elements inside the "message__content" element. The group I took this from has 5 members, so there are five "message__body" elements per message.
<div class="message" data-author-id="5e35b4062a256d09a35c661f" data-author-username="found" data-message-id="5e360c9f2a256d09a35cafc4"> <a class="message__avatar" href="https://gab.com/Found"> <button class="avatar" title="Found"> <img class="avatar__img" src="https://gab.com/media/user/57ea428a3546e.jpg"/> </button> </a> <div class="message__content"> <div class="message__content__header"> <span class="message__sender"> <a class="message__sender__link" href="https://gab.com/Found">Found</a> </span> <a class="message__timestamp" data-msg-created="2020-02-01T23:41:19.375Z">11:41 PM on Feb 1, 2020</a> </div> <div class="message__body" data-sender-id="5e35b4062a256d09a35c661f" data-recipient-id="5e350100f778f5099e290f4f" data-iv="FZ76UJjPPn9QfIWt">WLip7m2+SqT63ErW2GDIXIuuEqb7t/6Ebe1nLMVDZJiA/s8=</div> <div class="message__body" data-sender-id="5e35b4062a256d09a35c661f" data-recipient-id="5e3502732a256d09a35bfc5f" data-iv="RLyPzBf97gKOtgQX">XcOreTpUi0L3jcVKnj21Otb5aiBdEDEH2zT8JnKggXirJtU=</div> <div class="message__body" data-sender-id="5e35b4062a256d09a35c661f" data-recipient-id="5e35ac50a3b5190604085890" data-iv="gUdnM79KhwTuYNUG">UnmsLN4QnltGz77cwhgO9G8IYXYB2jrc8E9kmaNyf6ai/mI=</div> <div class="message__body" data-sender-id="5e35b4062a256d09a35c661f" data-recipient-id="5e35b4062a256d09a35c661f" data-iv="E1hd+T+7urBebg/P">pvoiSSteLeCGrlORdmSD/MAiNpjtdjX1vFtfbOcYSQoZW+k=</div> <div class="message__body" data-sender-id="5e35b4062a256d09a35c661f" data-recipient-id="5e35e91b2a256d09a35c917f" data-iv="3xxWERVyppN8KtLg">G4HUj65c+2Vlp3Qyaanszaip1vANF8RSHYvl0DdBh7rCiEo=</div> </div> <div class="message__actions"> <div class="message-actions"> <div class="message-actions__container"> <form method="POST" action="/private-message"> <input type="hidden" name="username" value="Found"/> <button class="message-actions__btn" type="submit" title="Start DM with Found"> <i class="fa fa-envelope message-actions__btn__icon"></i> </button> </form> <button class="message-actions__btn" title="Block this user" data-user-id="5e35b4062a256d09a35c661f" data-username="Found" data-message-id="5e360c9f2a256d09a35cafc4" onclick="return hydra.chat.blockUser(event);"> <i class="fa fa-times-circle message-actions__btn__icon"></i> </button> </div> </div> </div> </div>