diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/CryptoEncodingException.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/CryptoEncodingException.java deleted file mode 100644 index 346092a13..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/CryptoEncodingException.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2013-2020 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.whispersystems.textsecuregcm.entities; - -public class CryptoEncodingException extends Exception { - - public CryptoEncodingException(String s) { - super(s); - } - - public CryptoEncodingException(Exception e) { - super(e); - } - -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/entities/EncryptedOutgoingMessage.java b/service/src/main/java/org/whispersystems/textsecuregcm/entities/EncryptedOutgoingMessage.java deleted file mode 100644 index e6ee47398..000000000 --- a/service/src/main/java/org/whispersystems/textsecuregcm/entities/EncryptedOutgoingMessage.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2013-2020 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ -package org.whispersystems.textsecuregcm.entities; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope; -import org.whispersystems.textsecuregcm.util.Base64; -import org.whispersystems.textsecuregcm.util.Util; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.Mac; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.spec.SecretKeySpec; -import java.io.IOException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; - -public class EncryptedOutgoingMessage { - - private final Logger logger = LoggerFactory.getLogger(EncryptedOutgoingMessage.class); - - private static final byte[] VERSION = new byte[]{0x01}; - private static final int CIPHER_KEY_SIZE = 32; - private static final int MAC_KEY_SIZE = 20; - private static final int MAC_SIZE = 10; - - private final byte[] serialized; - - public EncryptedOutgoingMessage(Envelope outgoingMessage, String signalingKey) - throws CryptoEncodingException - { - byte[] plaintext = outgoingMessage.toByteArray(); - SecretKeySpec cipherKey = getCipherKey (signalingKey); - SecretKeySpec macKey = getMacKey(signalingKey); - - this.serialized = getCiphertext(plaintext, cipherKey, macKey); - } - - public byte[] toByteArray() { - return serialized; - } - - private byte[] getCiphertext(byte[] plaintext, SecretKeySpec cipherKey, SecretKeySpec macKey) - throws CryptoEncodingException - { - try { - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - cipher.init(Cipher.ENCRYPT_MODE, cipherKey); - - Mac hmac = Mac.getInstance("HmacSHA256"); - hmac.init(macKey); - - hmac.update(VERSION); - - byte[] ivBytes = cipher.getIV(); - hmac.update(ivBytes); - - byte[] ciphertext = cipher.doFinal(plaintext); - byte[] mac = hmac.doFinal(ciphertext); - byte[] truncatedMac = new byte[MAC_SIZE]; - System.arraycopy(mac, 0, truncatedMac, 0, truncatedMac.length); - - return Util.combine(VERSION, ivBytes, ciphertext, truncatedMac); - } catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) { - throw new AssertionError(e); - } catch (InvalidKeyException e) { - logger.warn("Invalid Key", e); - throw new CryptoEncodingException("Invalid key!"); - } - } - - private SecretKeySpec getCipherKey(String signalingKey) throws CryptoEncodingException { - try { - byte[] signalingKeyBytes = Base64.decode(signalingKey); - byte[] cipherKey = new byte[CIPHER_KEY_SIZE]; - - if (signalingKeyBytes.length < CIPHER_KEY_SIZE) - throw new CryptoEncodingException("Signaling key too short!"); - - System.arraycopy(signalingKeyBytes, 0, cipherKey, 0, cipherKey.length); - return new SecretKeySpec(cipherKey, "AES"); - } catch (IOException e) { - throw new CryptoEncodingException(e); - } - } - - private SecretKeySpec getMacKey(String signalingKey) throws CryptoEncodingException { - try { - byte[] signalingKeyBytes = Base64.decode(signalingKey); - byte[] macKey = new byte[MAC_KEY_SIZE]; - - if (signalingKeyBytes.length < CIPHER_KEY_SIZE + MAC_KEY_SIZE) - throw new CryptoEncodingException("Signaling key too short!"); - - System.arraycopy(signalingKeyBytes, CIPHER_KEY_SIZE, macKey, 0, macKey.length); - - return new SecretKeySpec(macKey, "HmacSHA256"); - } catch (IOException e) { - throw new CryptoEncodingException(e); - } - } - -} diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnection.java b/service/src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnection.java index 1d13c1196..a48016454 100644 --- a/service/src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnection.java +++ b/service/src/main/java/org/whispersystems/textsecuregcm/websocket/WebSocketConnection.java @@ -18,8 +18,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.whispersystems.textsecuregcm.controllers.MessageController; import org.whispersystems.textsecuregcm.controllers.NoSuchUserException; -import org.whispersystems.textsecuregcm.entities.CryptoEncodingException; -import org.whispersystems.textsecuregcm.entities.EncryptedOutgoingMessage; import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntity; import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntityList; import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil; @@ -127,51 +125,38 @@ public class WebSocketConnection implements MessageAvailabilityListener, Displac } private CompletableFuture sendMessage(final Envelope message, final Optional storedMessageInfo) { - try { - String header; - Optional body; + final Optional body = Optional.ofNullable(message.toByteArray()); - if (Util.isEmpty(device.getSignalingKey())) { - header = "X-Signal-Key: false"; - body = Optional.ofNullable(message.toByteArray()); - } else { - header = "X-Signal-Key: true"; - body = Optional.ofNullable(new EncryptedOutgoingMessage(message, device.getSignalingKey()).toByteArray()); - } + sendMessageMeter.mark(); + bytesSentMeter.mark(body.map(bytes -> bytes.length).orElse(0)); - sendMessageMeter.mark(); - bytesSentMeter.mark(body.map(bytes -> bytes.length).orElse(0)); + // X-Signal-Key: false must be sent until Android stops assuming it missing means true + return client.sendRequest("PUT", "/api/v1/message", List.of("X-Signal-Key: false", TimestampHeaderUtil.getTimestampHeader()), body).whenComplete((response, throwable) -> { + if (throwable == null) { + if (isSuccessResponse(response)) { + if (storedMessageInfo.isPresent()) { + messagesManager.delete(account.getNumber(), account.getUuid(), device.getId(), storedMessageInfo.get().getGuid()); + } - return client.sendRequest("PUT", "/api/v1/message", List.of(header, TimestampHeaderUtil.getTimestampHeader()), body).whenComplete((response, throwable) -> { - if (throwable == null) { - if (isSuccessResponse(response)) { - if (storedMessageInfo.isPresent()) { - messagesManager.delete(account.getNumber(), account.getUuid(), device.getId(), storedMessageInfo.get().getGuid()); - } - - if (message.getType() != Envelope.Type.RECEIPT) { - recordMessageDeliveryDuration(message.getTimestamp(), device); - sendDeliveryReceiptFor(message); - } - } else { - final List tags = new ArrayList<>(List.of(Tag.of(STATUS_CODE_TAG, String.valueOf(response.getStatus())), - UserAgentTagUtil.getPlatformTag(client.getUserAgent()))); - - // TODO Remove this once we've identified the cause of message rejections from desktop clients - if (StringUtils.isNotBlank(response.getMessage())) { - tags.add(Tag.of(STATUS_MESSAGE_TAG, response.getMessage())); - } - - Metrics.counter(NON_SUCCESS_RESPONSE_COUNTER_NAME, tags).increment(); + if (message.getType() != Envelope.Type.RECEIPT) { + recordMessageDeliveryDuration(message.getTimestamp(), device); + sendDeliveryReceiptFor(message); } } else { - sendFailuresMeter.mark(); + final List tags = new ArrayList<>(List.of(Tag.of(STATUS_CODE_TAG, String.valueOf(response.getStatus())), + UserAgentTagUtil.getPlatformTag(client.getUserAgent()))); + + // TODO Remove this once we've identified the cause of message rejections from desktop clients + if (StringUtils.isNotBlank(response.getMessage())) { + tags.add(Tag.of(STATUS_MESSAGE_TAG, response.getMessage())); + } + + Metrics.counter(NON_SUCCESS_RESPONSE_COUNTER_NAME, tags).increment(); } - }); - } catch (CryptoEncodingException e) { - logger.warn("Bad signaling key", e); - return CompletableFuture.failedFuture(e); - } + } else { + sendFailuresMeter.mark(); + } + }); } public static void recordMessageDeliveryDuration(long timestamp, Device messageDestinationDevice) {