Guidelines
- I have searched searched open and closed issues for duplicates
- I am submitting a bug report for existing functionality that does not work as intended
- This isn't a feature request or a discussion topic
Bug description
Reviewing the source code, I noticed the following:
public Single<WebsocketResponse> request(WebSocketRequestMessage requestMessage) { | |
try { | |
return getWebSocket().sendRequest(requestMessage); | |
} catch (IOException e) { | |
return Single.error(e); | |
} | |
} | |
public Single<WebsocketResponse> request(WebSocketRequestMessage requestMessage, @Nullable SealedSenderAccess sealedSenderAccess) { | |
if (sealedSenderAccess != null) { | |
List<String> headers = new ArrayList<>(requestMessage.headers); | |
headers.add(sealedSenderAccess.getHeader()); | |
WebSocketRequestMessage message = requestMessage.newBuilder() | |
.headers(headers) | |
.build(); | |
try { | |
return getUnidentifiedWebSocket().sendRequest(message) | |
.flatMap(r -> { | |
if (r.getStatus() == 401) { | |
return request(requestMessage, sealedSenderAccess.switchToFallback()); | |
} | |
return Single.just(r); | |
}); | |
} catch (IOException e) { | |
return Single.error(e); | |
} | |
} else { | |
return request(requestMessage); | |
} | |
} |
If a message with SealedSenderAccess is sent, it uses the unidentified web socket (as expected). If the server returns status 401, the client will attempt to send the message again using the SealedSenderAccess returned by SealedSenderAccess.switchToFallback().
For a chat with an individual that you don't share a group with, there is no fallback:
@JvmStatic | |
fun forIndividual(unidentifiedAccess: UnidentifiedAccess?): SealedSenderAccess? { | |
return unidentifiedAccess?.let { IndividualUnidentifiedAccessFirst(it) } | |
} |
For other chats there may be one or multiple fallbacks in place. In any case, once there is no fallback left, it will return the null fallback:
override fun switchToFallback(): SealedSenderAccess? { | |
val groupSendToken = createGroupSendToken?.create() | |
return if (groupSendToken != null) { | |
fallbackListener?.onAccessToTokenFallback() | |
IndividualGroupSendTokenFirst(groupSendToken, senderCertificate) | |
} else { | |
null | |
} | |
} |
If the null fallback is returned, request is invoked using null as a SealedSenderAccess, which results in it redirecting the message to the sending flow on the authenticated web socket without sealed sender access.
This effectively allows the server - if turned malicious - to turn off sealed sender selectively by returning a 401 for a given message they receive from a sender (by IP) or for a recipient.
Screenshots
No response
Device
No response
Android version
No response
Signal version
No response
Link to debug log
No response