Files
Signal-Server/src/main/java/org/whispersystems/textsecuregcm/entities/EncryptedOutgoingMessage.java
Moxie Marlinspike 41d15b738b Refactor direct connect delivery pipeline and message store.
1) Make message store contents more memory efficient.

2) Make notification pipeline simpler and more memory efficient.

3) Don't b64 encode websocket message bodies.

// FREEBIE
2014-12-06 20:00:39 -08:00

128 lines
4.5 KiB
Java

/**
* Copyright (C) 2013 Open WhisperSystems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.textsecuregcm.entities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.textsecuregcm.entities.MessageProtos.OutgoingMessageSignal;
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;
private final String serializedAndEncoded;
public EncryptedOutgoingMessage(OutgoingMessageSignal outgoingMessage,
String signalingKey)
throws CryptoEncodingException
{
byte[] plaintext = outgoingMessage.toByteArray();
SecretKeySpec cipherKey = getCipherKey (signalingKey);
SecretKeySpec macKey = getMacKey(signalingKey);
this.serialized = getCiphertext(plaintext, cipherKey, macKey);
this.serializedAndEncoded = Base64.encodeBytes(this.serialized);
}
public String toEncodedString() {
return serializedAndEncoded;
}
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);
}
}
}